Switch to unified view

a b/web/bottle.py
1
#!/usr/bin/env python
2
# -*- coding: utf-8 -*-
3
"""
4
Bottle is a fast and simple micro-framework for small web applications. It
5
offers request dispatching (Routes) with url parameter support, templates,
6
a built-in HTTP Server and adapters for many third party WSGI/HTTP-server and
7
template engines - all in a single file and with no dependencies other than the
8
Python Standard Library.
9
10
Homepage and documentation: http://bottlepy.org/
11
12
Copyright (c) 2014, Marcel Hellkamp.
13
License: MIT (see LICENSE for details)
14
"""
15
16
from __future__ import with_statement
17
18
__author__ = 'Marcel Hellkamp'
19
__version__ = '0.13-dev'
20
__license__ = 'MIT'
21
22
# The gevent and eventlet server adapters need to patch some modules before
23
# they are imported. This is why we parse the commandline parameters here but
24
# handle them later
25
if __name__ == '__main__':
26
    from optparse import OptionParser
27
    _cmd_parser = OptionParser(
28
        usage="usage: %prog [options] package.module:app")
29
    _opt = _cmd_parser.add_option
30
    _opt("--version", action="store_true", help="show version number.")
31
    _opt("-b", "--bind", metavar="ADDRESS", help="bind socket to ADDRESS.")
32
    _opt("-s", "--server", default='wsgiref', help="use SERVER as backend.")
33
    _opt("-p", "--plugin",
34
         action="append",
35
         help="install additional plugin/s.")
36
    _opt("--debug", action="store_true", help="start server in debug mode.")
37
    _opt("--reload", action="store_true", help="auto-reload on file changes.")
38
    _cmd_options, _cmd_args = _cmd_parser.parse_args()
39
    if _cmd_options.server:
40
        if _cmd_options.server.startswith('gevent'):
41
            import gevent.monkey
42
            gevent.monkey.patch_all()
43
        elif _cmd_options.server.startswith('eventlet'):
44
            import eventlet
45
            eventlet.monkey_patch()
46
47
import base64, cgi, email.utils, functools, hmac, imp, itertools, mimetypes,\
48
        os, re, sys, tempfile, threading, time, warnings
49
50
from types import FunctionType
51
from datetime import date as datedate, datetime, timedelta
52
from tempfile import TemporaryFile
53
from traceback import format_exc, print_exc
54
from inspect import getargspec
55
from unicodedata import normalize
56
57
try:
58
    from simplejson import dumps as json_dumps, loads as json_lds
59
except ImportError:  # pragma: no cover
60
    try:
61
        from json import dumps as json_dumps, loads as json_lds
62
    except ImportError:
63
        try:
64
            from django.utils.simplejson import dumps as json_dumps, loads as json_lds
65
        except ImportError:
66
67
            def json_dumps(data):
68
                raise ImportError(
69
                    "JSON support requires Python 2.6 or simplejson.")
70
71
            json_lds = json_dumps
72
73
# We now try to fix 2.5/2.6/3.1/3.2 incompatibilities.
74
# It ain't pretty but it works... Sorry for the mess.
75
76
py = sys.version_info
77
py3k = py >= (3, 0, 0)
78
py25 = py <  (2, 6, 0)
79
py31 = (3, 1, 0) <= py < (3, 2, 0)
80
81
# Workaround for the missing "as" keyword in py3k.
82
def _e():
83
    return sys.exc_info()[1]
84
85
# Workaround for the "print is a keyword/function" Python 2/3 dilemma
86
# and a fallback for mod_wsgi (resticts stdout/err attribute access)
87
try:
88
    _stdout, _stderr = sys.stdout.write, sys.stderr.write
89
except IOError:
90
    _stdout = lambda x: sys.stdout.write(x)
91
    _stderr = lambda x: sys.stderr.write(x)
92
93
# Lots of stdlib and builtin differences.
94
if py3k:
95
    import http.client as httplib
96
    import _thread as thread
97
    from urllib.parse import urljoin, SplitResult as UrlSplitResult
98
    from urllib.parse import urlencode, quote as urlquote, unquote as urlunquote
99
    urlunquote = functools.partial(urlunquote, encoding='latin1')
100
    from http.cookies import SimpleCookie
101
    from collections import MutableMapping as DictMixin
102
    import pickle
103
    from io import BytesIO
104
    from configparser import ConfigParser
105
    basestring = str
106
    unicode = str
107
    json_loads = lambda s: json_lds(touni(s))
108
    callable = lambda x: hasattr(x, '__call__')
109
    imap = map
110
111
    def _raise(*a):
112
        raise a[0](a[1]).with_traceback(a[2])
113
else:  # 2.x
114
    import httplib
115
    import thread
116
    from urlparse import urljoin, SplitResult as UrlSplitResult
117
    from urllib import urlencode, quote as urlquote, unquote as urlunquote
118
    from Cookie import SimpleCookie
119
    from itertools import imap
120
    import cPickle as pickle
121
    from StringIO import StringIO as BytesIO
122
    from ConfigParser import SafeConfigParser as ConfigParser
123
    if py25:
124
        msg = "Python 2.5 support may be dropped in future versions of Bottle."
125
        warnings.warn(msg, DeprecationWarning)
126
        from UserDict import DictMixin
127
128
        def next(it):
129
            return it.next()
130
131
        bytes = str
132
    else:  # 2.6, 2.7
133
        from collections import MutableMapping as DictMixin
134
    unicode = unicode
135
    json_loads = json_lds
136
    eval(compile('def _raise(*a): raise a[0], a[1], a[2]', '<py3fix>', 'exec'))
137
138
139
# Some helpers for string/byte handling
140
def tob(s, enc='utf8'):
141
    return s.encode(enc) if isinstance(s, unicode) else bytes(s)
142
143
144
def touni(s, enc='utf8', err='strict'):
145
    if isinstance(s, bytes):
146
        return s.decode(enc, err)
147
    else:
148
        return unicode(s or ("" if s is None else s))
149
150
151
tonat = touni if py3k else tob
152
153
# 3.2 fixes cgi.FieldStorage to accept bytes (which makes a lot of sense).
154
# 3.1 needs a workaround.
155
if py31:
156
    from io import TextIOWrapper
157
158
    class NCTextIOWrapper(TextIOWrapper):
159
        def close(self):
160
            pass  # Keep wrapped buffer open.
161
162
163
# A bug in functools causes it to break if the wrapper is an instance method
164
def update_wrapper(wrapper, wrapped, *a, **ka):
165
    try:
166
        functools.update_wrapper(wrapper, wrapped, *a, **ka)
167
    except AttributeError:
168
        pass
169
170
# These helpers are used at module level and need to be defined first.
171
# And yes, I know PEP-8, but sometimes a lower-case classname makes more sense.
172
173
174
def depr(message, strict=False):
175
    warnings.warn(message, DeprecationWarning, stacklevel=3)
176
177
178
def makelist(data):  # This is just too handy
179
    if isinstance(data, (tuple, list, set, dict)):
180
        return list(data)
181
    elif data:
182
        return [data]
183
    else:
184
        return []
185
186
187
class DictProperty(object):
188
    """ Property that maps to a key in a local dict-like attribute. """
189
190
    def __init__(self, attr, key=None, read_only=False):
191
        self.attr, self.key, self.read_only = attr, key, read_only
192
193
    def __call__(self, func):
194
        functools.update_wrapper(self, func, updated=[])
195
        self.getter, self.key = func, self.key or func.__name__
196
        return self
197
198
    def __get__(self, obj, cls):
199
        if obj is None: return self
200
        key, storage = self.key, getattr(obj, self.attr)
201
        if key not in storage: storage[key] = self.getter(obj)
202
        return storage[key]
203
204
    def __set__(self, obj, value):
205
        if self.read_only: raise AttributeError("Read-Only property.")
206
        getattr(obj, self.attr)[self.key] = value
207
208
    def __delete__(self, obj):
209
        if self.read_only: raise AttributeError("Read-Only property.")
210
        del getattr(obj, self.attr)[self.key]
211
212
213
class cached_property(object):
214
    """ A property that is only computed once per instance and then replaces
215
        itself with an ordinary attribute. Deleting the attribute resets the
216
        property. """
217
218
    def __init__(self, func):
219
        self.__doc__ = getattr(func, '__doc__')
220
        self.func = func
221
222
    def __get__(self, obj, cls):
223
        if obj is None: return self
224
        value = obj.__dict__[self.func.__name__] = self.func(obj)
225
        return value
226
227
228
class lazy_attribute(object):
229
    """ A property that caches itself to the class object. """
230
231
    def __init__(self, func):
232
        functools.update_wrapper(self, func, updated=[])
233
        self.getter = func
234
235
    def __get__(self, obj, cls):
236
        value = self.getter(cls)
237
        setattr(cls, self.__name__, value)
238
        return value
239
240
###############################################################################
241
# Exceptions and Events ########################################################
242
###############################################################################
243
244
245
class BottleException(Exception):
246
    """ A base class for exceptions used by bottle. """
247
    pass
248
249
###############################################################################
250
# Routing ######################################################################
251
###############################################################################
252
253
254
class RouteError(BottleException):
255
    """ This is a base class for all routing related exceptions """
256
257
258
class RouteReset(BottleException):
259
    """ If raised by a plugin or request handler, the route is reset and all
260
        plugins are re-applied. """
261
262
263
class RouterUnknownModeError(RouteError):
264
265
    pass
266
267
268
class RouteSyntaxError(RouteError):
269
    """ The route parser found something not supported by this router. """
270
271
272
class RouteBuildError(RouteError):
273
    """ The route could not be built. """
274
275
276
def _re_flatten(p):
277
    """ Turn all capturing groups in a regular expression pattern into
278
        non-capturing groups. """
279
    if '(' not in p:
280
        return p
281
    return re.sub(r'(\\*)(\(\?P<[^>]+>|\((?!\?))', lambda m: m.group(0) if
282
                  len(m.group(1)) % 2 else m.group(1) + '(?:', p)
283
284
285
class Router(object):
286
    """ A Router is an ordered collection of route->target pairs. It is used to
287
        efficiently match WSGI requests against a number of routes and return
288
        the first target that satisfies the request. The target may be anything,
289
        usually a string, ID or callable object. A route consists of a path-rule
290
        and a HTTP method.
291
292
        The path-rule is either a static path (e.g. `/contact`) or a dynamic
293
        path that contains wildcards (e.g. `/wiki/<page>`). The wildcard syntax
294
        and details on the matching order are described in docs:`routing`.
295
    """
296
297
    default_pattern = '[^/]+'
298
    default_filter = 're'
299
300
    #: The current CPython regexp implementation does not allow more
301
    #: than 99 matching groups per regular expression.
302
    _MAX_GROUPS_PER_PATTERN = 99
303
304
    def __init__(self, strict=False):
305
        self.rules = []  # All rules in order
306
        self._groups = {}  # index of regexes to find them in dyna_routes
307
        self.builder = {}  # Data structure for the url builder
308
        self.static = {}  # Search structure for static routes
309
        self.dyna_routes = {}
310
        self.dyna_regexes = {}  # Search structure for dynamic routes
311
        #: If true, static routes are no longer checked first.
312
        self.strict_order = strict
313
        self.filters = {
314
            're': lambda conf: (_re_flatten(conf or self.default_pattern),
315
                                None, None),
316
            'int': lambda conf: (r'-?\d+', int, lambda x: str(int(x))),
317
            'float': lambda conf: (r'-?[\d.]+', float, lambda x: str(float(x))),
318
            'path': lambda conf: (r'.+?', None, None)
319
        }
320
321
    def add_filter(self, name, func):
322
        """ Add a filter. The provided function is called with the configuration
323
        string as parameter and must return a (regexp, to_python, to_url) tuple.
324
        The first element is a string, the last two are callables or None. """
325
        self.filters[name] = func
326
327
    rule_syntax = re.compile('(\\\\*)'
328
        '(?:(?::([a-zA-Z_][a-zA-Z_0-9]*)?()(?:#(.*?)#)?)'
329
          '|(?:<([a-zA-Z_][a-zA-Z_0-9]*)?(?::([a-zA-Z_]*)'
330
            '(?::((?:\\\\.|[^\\\\>]+)+)?)?)?>))')
331
332
    def _itertokens(self, rule):
333
        offset, prefix = 0, ''
334
        for match in self.rule_syntax.finditer(rule):
335
            prefix += rule[offset:match.start()]
336
            g = match.groups()
337
            if len(g[0]) % 2:  # Escaped wildcard
338
                prefix += match.group(0)[len(g[0]):]
339
                offset = match.end()
340
                continue
341
            if prefix:
342
                yield prefix, None, None
343
            name, filtr, conf = g[4:7] if g[2] is None else g[1:4]
344
            yield name, filtr or 'default', conf or None
345
            offset, prefix = match.end(), ''
346
        if offset <= len(rule) or prefix:
347
            yield prefix + rule[offset:], None, None
348
349
    def add(self, rule, method, target, name=None):
350
        """ Add a new rule or replace the target for an existing rule. """
351
        anons = 0  # Number of anonymous wildcards found
352
        keys = []  # Names of keys
353
        pattern = ''  # Regular expression pattern with named groups
354
        filters = []  # Lists of wildcard input filters
355
        builder = []  # Data structure for the URL builder
356
        is_static = True
357
358
        for key, mode, conf in self._itertokens(rule):
359
            if mode:
360
                is_static = False
361
                if mode == 'default': mode = self.default_filter
362
                mask, in_filter, out_filter = self.filters[mode](conf)
363
                if not key:
364
                    pattern += '(?:%s)' % mask
365
                    key = 'anon%d' % anons
366
                    anons += 1
367
                else:
368
                    pattern += '(?P<%s>%s)' % (key, mask)
369
                    keys.append(key)
370
                if in_filter: filters.append((key, in_filter))
371
                builder.append((key, out_filter or str))
372
            elif key:
373
                pattern += re.escape(key)
374
                builder.append((None, key))
375
376
        self.builder[rule] = builder
377
        if name: self.builder[name] = builder
378
379
        if is_static and not self.strict_order:
380
            self.static.setdefault(method, {})
381
            self.static[method][self.build(rule)] = (target, None)
382
            return
383
384
        try:
385
            re_pattern = re.compile('^(%s)$' % pattern)
386
            re_match = re_pattern.match
387
        except re.error:
388
            raise RouteSyntaxError("Could not add Route: %s (%s)" %
389
                                   (rule, _e()))
390
391
        if filters:
392
393
            def getargs(path):
394
                url_args = re_match(path).groupdict()
395
                for name, wildcard_filter in filters:
396
                    try:
397
                        url_args[name] = wildcard_filter(url_args[name])
398
                    except ValueError:
399
                        raise HTTPError(400, 'Path has wrong format.')
400
                return url_args
401
        elif re_pattern.groupindex:
402
403
            def getargs(path):
404
                return re_match(path).groupdict()
405
        else:
406
            getargs = None
407
408
        flatpat = _re_flatten(pattern)
409
        whole_rule = (rule, flatpat, target, getargs)
410
411
        if (flatpat, method) in self._groups:
412
            if DEBUG:
413
                msg = 'Route <%s %s> overwrites a previously defined route'
414
                warnings.warn(msg % (method, rule), RuntimeWarning)
415
            self.dyna_routes[method][
416
                self._groups[flatpat, method]] = whole_rule
417
        else:
418
            self.dyna_routes.setdefault(method, []).append(whole_rule)
419
            self._groups[flatpat, method] = len(self.dyna_routes[method]) - 1
420
421
        self._compile(method)
422
423
    def _compile(self, method):
424
        all_rules = self.dyna_routes[method]
425
        comborules = self.dyna_regexes[method] = []
426
        maxgroups = self._MAX_GROUPS_PER_PATTERN
427
        for x in range(0, len(all_rules), maxgroups):
428
            some = all_rules[x:x + maxgroups]
429
            combined = (flatpat for (_, flatpat, _, _) in some)
430
            combined = '|'.join('(^%s$)' % flatpat for flatpat in combined)
431
            combined = re.compile(combined).match
432
            rules = [(target, getargs) for (_, _, target, getargs) in some]
433
            comborules.append((combined, rules))
434
435
    def build(self, _name, *anons, **query):
436
        """ Build an URL by filling the wildcards in a rule. """
437
        builder = self.builder.get(_name)
438
        if not builder:
439
            raise RouteBuildError("No route with that name.", _name)
440
        try:
441
            for i, value in enumerate(anons):
442
                query['anon%d' % i] = value
443
            url = ''.join([f(query.pop(n)) if n else f for (n, f) in builder])
444
            return url if not query else url + '?' + urlencode(query)
445
        except KeyError:
446
            raise RouteBuildError('Missing URL argument: %r' % _e().args[0])
447
448
    def match(self, environ):
449
        """ Return a (target, url_args) tuple or raise HTTPError(400/404/405). """
450
        verb = environ['REQUEST_METHOD'].upper()
451
        path = environ['PATH_INFO'] or '/'
452
453
        if verb == 'HEAD':
454
            methods = ['PROXY', verb, 'GET', 'ANY']
455
        else:
456
            methods = ['PROXY', verb, 'ANY']
457
458
        for method in methods:
459
            if method in self.static and path in self.static[method]:
460
                target, getargs = self.static[method][path]
461
                return target, getargs(path) if getargs else {}
462
            elif method in self.dyna_regexes:
463
                for combined, rules in self.dyna_regexes[method]:
464
                    match = combined(path)
465
                    if match:
466
                        target, getargs = rules[match.lastindex - 1]
467
                        return target, getargs(path) if getargs else {}
468
469
        # No matching route found. Collect alternative methods for 405 response
470
        allowed = set([])
471
        nocheck = set(methods)
472
        for method in set(self.static) - nocheck:
473
            if path in self.static[method]:
474
                allowed.add(verb)
475
        for method in set(self.dyna_regexes) - allowed - nocheck:
476
            for combined, rules in self.dyna_regexes[method]:
477
                match = combined(path)
478
                if match:
479
                    allowed.add(method)
480
        if allowed:
481
            allow_header = ",".join(sorted(allowed))
482
            raise HTTPError(405, "Method not allowed.", Allow=allow_header)
483
484
        # No matching route and no alternative method found. We give up
485
        raise HTTPError(404, "Not found: " + repr(path))
486
487
488
class Route(object):
489
    """ This class wraps a route callback along with route specific metadata and
490
        configuration and applies Plugins on demand. It is also responsible for
491
        turing an URL path rule into a regular expression usable by the Router.
492
    """
493
494
    def __init__(self, app, rule, method, callback,
495
                 name=None,
496
                 plugins=None,
497
                 skiplist=None, **config):
498
        #: The application this route is installed to.
499
        self.app = app
500
        #: The path-rule string (e.g. ``/wiki/<page>``).
501
        self.rule = rule
502
        #: The HTTP method as a string (e.g. ``GET``).
503
        self.method = method
504
        #: The original callback with no plugins applied. Useful for introspection.
505
        self.callback = callback
506
        #: The name of the route (if specified) or ``None``.
507
        self.name = name or None
508
        #: A list of route-specific plugins (see :meth:`Bottle.route`).
509
        self.plugins = plugins or []
510
        #: A list of plugins to not apply to this route (see :meth:`Bottle.route`).
511
        self.skiplist = skiplist or []
512
        #: Additional keyword arguments passed to the :meth:`Bottle.route`
513
        #: decorator are stored in this dictionary. Used for route-specific
514
        #: plugin configuration and meta-data.
515
        self.config = ConfigDict().load_dict(config)
516
517
    @cached_property
518
    def call(self):
519
        """ The route callback with all plugins applied. This property is
520
            created on demand and then cached to speed up subsequent requests."""
521
        return self._make_callback()
522
523
    def reset(self):
524
        """ Forget any cached values. The next time :attr:`call` is accessed,
525
            all plugins are re-applied. """
526
        self.__dict__.pop('call', None)
527
528
    def prepare(self):
529
        """ Do all on-demand work immediately (useful for debugging)."""
530
        self.call
531
532
    def all_plugins(self):
533
        """ Yield all Plugins affecting this route. """
534
        unique = set()
535
        for p in reversed(self.app.plugins + self.plugins):
536
            if True in self.skiplist: break
537
            name = getattr(p, 'name', False)
538
            if name and (name in self.skiplist or name in unique): continue
539
            if p in self.skiplist or type(p) in self.skiplist: continue
540
            if name: unique.add(name)
541
            yield p
542
543
    def _make_callback(self):
544
        callback = self.callback
545
        for plugin in self.all_plugins():
546
            try:
547
                if hasattr(plugin, 'apply'):
548
                    callback = plugin.apply(callback, self)
549
                else:
550
                    callback = plugin(callback)
551
            except RouteReset:  # Try again with changed configuration.
552
                return self._make_callback()
553
            if not callback is self.callback:
554
                update_wrapper(callback, self.callback)
555
        return callback
556
557
    def get_undecorated_callback(self):
558
        """ Return the callback. If the callback is a decorated function, try to
559
            recover the original function. """
560
        func = self.callback
561
        func = getattr(func, '__func__' if py3k else 'im_func', func)
562
        closure_attr = '__closure__' if py3k else 'func_closure'
563
        while hasattr(func, closure_attr) and getattr(func, closure_attr):
564
            attributes = getattr(func, closure_attr)
565
            func = attributes[0].cell_contents
566
567
            # in case of decorators with multiple arguments
568
            if not isinstance(func, FunctionType):
569
                # pick first FunctionType instance from multiple arguments
570
                func = filter(lambda x: isinstance(x, FunctionType),
571
                              map(lambda x: x.cell_contents, attributes))
572
                func = list(func)[0]  # py3 support
573
        return func
574
575
    def get_callback_args(self):
576
        """ Return a list of argument names the callback (most likely) accepts
577
            as keyword arguments. If the callback is a decorated function, try
578
            to recover the original function before inspection. """
579
        return getargspec(self.get_undecorated_callback())[0]
580
581
    def get_config(self, key, default=None):
582
        """ Lookup a config field and return its value, first checking the
583
            route.config, then route.app.config."""
584
        for conf in (self.config, self.app.config):
585
            if key in conf: return conf[key]
586
        return default
587
588
    def __repr__(self):
589
        cb = self.get_undecorated_callback()
590
        return '<%s %r %r>' % (self.method, self.rule, cb)
591
592
###############################################################################
593
# Application Object ###########################################################
594
###############################################################################
595
596
597
class Bottle(object):
598
    """ Each Bottle object represents a single, distinct web application and
599
        consists of routes, callbacks, plugins, resources and configuration.
600
        Instances are callable WSGI applications.
601
602
        :param catchall: If true (default), handle all exceptions. Turn off to
603
                         let debugging middleware handle exceptions.
604
    """
605
606
    def __init__(self, catchall=True, autojson=True):
607
608
        #: A :class:`ConfigDict` for app specific configuration.
609
        self.config = ConfigDict()
610
        self.config._on_change = functools.partial(self.trigger_hook, 'config')
611
        self.config.meta_set('autojson', 'validate', bool)
612
        self.config.meta_set('catchall', 'validate', bool)
613
        self.config['catchall'] = catchall
614
        self.config['autojson'] = autojson
615
616
        #: A :class:`ResourceManager` for application files
617
        self.resources = ResourceManager()
618
619
        self.routes = []  # List of installed :class:`Route` instances.
620
        self.router = Router()  # Maps requests to :class:`Route` instances.
621
        self.error_handler = {}
622
623
        # Core plugins
624
        self.plugins = []  # List of installed plugins.
625
        if self.config['autojson']:
626
            self.install(JSONPlugin())
627
        self.install(TemplatePlugin())
628
629
    #: If true, most exceptions are caught and returned as :exc:`HTTPError`
630
    catchall = DictProperty('config', 'catchall')
631
632
    __hook_names = 'before_request', 'after_request', 'app_reset', 'config'
633
    __hook_reversed = 'after_request'
634
635
    @cached_property
636
    def _hooks(self):
637
        return dict((name, []) for name in self.__hook_names)
638
639
    def add_hook(self, name, func):
640
        """ Attach a callback to a hook. Three hooks are currently implemented:
641
642
            before_request
643
                Executed once before each request. The request context is
644
                available, but no routing has happened yet.
645
            after_request
646
                Executed once after each request regardless of its outcome.
647
            app_reset
648
                Called whenever :meth:`Bottle.reset` is called.
649
        """
650
        if name in self.__hook_reversed:
651
            self._hooks[name].insert(0, func)
652
        else:
653
            self._hooks[name].append(func)
654
655
    def remove_hook(self, name, func):
656
        """ Remove a callback from a hook. """
657
        if name in self._hooks and func in self._hooks[name]:
658
            self._hooks[name].remove(func)
659
            return True
660
661
    def trigger_hook(self, __name, *args, **kwargs):
662
        """ Trigger a hook and return a list of results. """
663
        return [hook(*args, **kwargs) for hook in self._hooks[__name][:]]
664
665
    def hook(self, name):
666
        """ Return a decorator that attaches a callback to a hook. See
667
            :meth:`add_hook` for details."""
668
669
        def decorator(func):
670
            self.add_hook(name, func)
671
            return func
672
673
        return decorator
674
675
    def mount(self, prefix, app, **options):
676
        """ Mount an application (:class:`Bottle` or plain WSGI) to a specific
677
            URL prefix. Example::
678
679
                root_app.mount('/admin/', admin_app)
680
681
            :param prefix: path prefix or `mount-point`. If it ends in a slash,
682
                that slash is mandatory.
683
            :param app: an instance of :class:`Bottle` or a WSGI application.
684
685
            All other parameters are passed to the underlying :meth:`route` call.
686
        """
687
688
        segments = [p for p in prefix.split('/') if p]
689
        if not segments: raise ValueError('Empty path prefix.')
690
        path_depth = len(segments)
691
692
        def mountpoint_wrapper():
693
            try:
694
                request.path_shift(path_depth)
695
                rs = HTTPResponse([])
696
697
                def start_response(status, headerlist, exc_info=None):
698
                    if exc_info:
699
                        _raise(*exc_info)
700
                    rs.status = status
701
                    for name, value in headerlist:
702
                        rs.add_header(name, value)
703
                    return rs.body.append
704
705
                body = app(request.environ, start_response)
706
                if body and rs.body: body = itertools.chain(rs.body, body)
707
                rs.body = body or rs.body
708
                return rs
709
            finally:
710
                request.path_shift(-path_depth)
711
712
        options.setdefault('skip', True)
713
        options.setdefault('method', 'PROXY')
714
        options.setdefault('mountpoint', {'prefix': prefix, 'target': app})
715
        options['callback'] = mountpoint_wrapper
716
717
        self.route('/%s/<:re:.*>' % '/'.join(segments), **options)
718
        if not prefix.endswith('/'):
719
            self.route('/' + '/'.join(segments), **options)
720
721
    def merge(self, routes):
722
        """ Merge the routes of another :class:`Bottle` application or a list of
723
            :class:`Route` objects into this application. The routes keep their
724
            'owner', meaning that the :data:`Route.app` attribute is not
725
            changed. """
726
        if isinstance(routes, Bottle):
727
            routes = routes.routes
728
        for route in routes:
729
            self.add_route(route)
730
731
    def install(self, plugin):
732
        """ Add a plugin to the list of plugins and prepare it for being
733
            applied to all routes of this application. A plugin may be a simple
734
            decorator or an object that implements the :class:`Plugin` API.
735
        """
736
        if hasattr(plugin, 'setup'): plugin.setup(self)
737
        if not callable(plugin) and not hasattr(plugin, 'apply'):
738
            raise TypeError("Plugins must be callable or implement .apply()")
739
        self.plugins.append(plugin)
740
        self.reset()
741
        return plugin
742
743
    def uninstall(self, plugin):
744
        """ Uninstall plugins. Pass an instance to remove a specific plugin, a type
745
            object to remove all plugins that match that type, a string to remove
746
            all plugins with a matching ``name`` attribute or ``True`` to remove all
747
            plugins. Return the list of removed plugins. """
748
        removed, remove = [], plugin
749
        for i, plugin in list(enumerate(self.plugins))[::-1]:
750
            if remove is True or remove is plugin or remove is type(plugin) \
751
            or getattr(plugin, 'name', True) == remove:
752
                removed.append(plugin)
753
                del self.plugins[i]
754
                if hasattr(plugin, 'close'): plugin.close()
755
        if removed: self.reset()
756
        return removed
757
758
    def reset(self, route=None):
759
        """ Reset all routes (force plugins to be re-applied) and clear all
760
            caches. If an ID or route object is given, only that specific route
761
            is affected. """
762
        if route is None: routes = self.routes
763
        elif isinstance(route, Route): routes = [route]
764
        else: routes = [self.routes[route]]
765
        for route in routes:
766
            route.reset()
767
        if DEBUG:
768
            for route in routes:
769
                route.prepare()
770
        self.trigger_hook('app_reset')
771
772
    def close(self):
773
        """ Close the application and all installed plugins. """
774
        for plugin in self.plugins:
775
            if hasattr(plugin, 'close'): plugin.close()
776
777
    def run(self, **kwargs):
778
        """ Calls :func:`run` with the same parameters. """
779
        run(self, **kwargs)
780
781
    def match(self, environ):
782
        """ Search for a matching route and return a (:class:`Route` , urlargs)
783
            tuple. The second value is a dictionary with parameters extracted
784
            from the URL. Raise :exc:`HTTPError` (404/405) on a non-match."""
785
        return self.router.match(environ)
786
787
    def get_url(self, routename, **kargs):
788
        """ Return a string that matches a named route """
789
        scriptname = request.environ.get('SCRIPT_NAME', '').strip('/') + '/'
790
        location = self.router.build(routename, **kargs).lstrip('/')
791
        return urljoin(urljoin('/', scriptname), location)
792
793
    def add_route(self, route):
794
        """ Add a route object, but do not change the :data:`Route.app`
795
            attribute."""
796
        self.routes.append(route)
797
        self.router.add(route.rule, route.method, route, name=route.name)
798
        if DEBUG: route.prepare()
799
800
    def route(self,
801
              path=None,
802
              method='GET',
803
              callback=None,
804
              name=None,
805
              apply=None,
806
              skip=None, **config):
807
        """ A decorator to bind a function to a request URL. Example::
808
809
                @app.route('/hello/<name>')
810
                def hello(name):
811
                    return 'Hello %s' % name
812
813
            The ``:name`` part is a wildcard. See :class:`Router` for syntax
814
            details.
815
816
            :param path: Request path or a list of paths to listen to. If no
817
              path is specified, it is automatically generated from the
818
              signature of the function.
819
            :param method: HTTP method (`GET`, `POST`, `PUT`, ...) or a list of
820
              methods to listen to. (default: `GET`)
821
            :param callback: An optional shortcut to avoid the decorator
822
              syntax. ``route(..., callback=func)`` equals ``route(...)(func)``
823
            :param name: The name for this route. (default: None)
824
            :param apply: A decorator or plugin or a list of plugins. These are
825
              applied to the route callback in addition to installed plugins.
826
            :param skip: A list of plugins, plugin classes or names. Matching
827
              plugins are not installed to this route. ``True`` skips all.
828
829
            Any additional keyword arguments are stored as route-specific
830
            configuration and passed to plugins (see :meth:`Plugin.apply`).
831
        """
832
        if callable(path): path, callback = None, path
833
        plugins = makelist(apply)
834
        skiplist = makelist(skip)
835
836
        def decorator(callback):
837
            if isinstance(callback, basestring): callback = load(callback)
838
            for rule in makelist(path) or yieldroutes(callback):
839
                for verb in makelist(method):
840
                    verb = verb.upper()
841
                    route = Route(self, rule, verb, callback,
842
                                  name=name,
843
                                  plugins=plugins,
844
                                  skiplist=skiplist, **config)
845
                    self.add_route(route)
846
            return callback
847
848
        return decorator(callback) if callback else decorator
849
850
    def get(self, path=None, method='GET', **options):
851
        """ Equals :meth:`route`. """
852
        return self.route(path, method, **options)
853
854
    def post(self, path=None, method='POST', **options):
855
        """ Equals :meth:`route` with a ``POST`` method parameter. """
856
        return self.route(path, method, **options)
857
858
    def put(self, path=None, method='PUT', **options):
859
        """ Equals :meth:`route` with a ``PUT`` method parameter. """
860
        return self.route(path, method, **options)
861
862
    def delete(self, path=None, method='DELETE', **options):
863
        """ Equals :meth:`route` with a ``DELETE`` method parameter. """
864
        return self.route(path, method, **options)
865
866
    def patch(self, path=None, method='PATCH', **options):
867
        """ Equals :meth:`route` with a ``PATCH`` method parameter. """
868
        return self.route(path, method, **options)
869
870
    def error(self, code=500):
871
        """ Decorator: Register an output handler for a HTTP error code"""
872
873
        def wrapper(handler):
874
            self.error_handler[int(code)] = handler
875
            return handler
876
877
        return wrapper
878
879
    def default_error_handler(self, res):
880
        return tob(template(ERROR_PAGE_TEMPLATE, e=res))
881
882
    def _handle(self, environ):
883
        path = environ['bottle.raw_path'] = environ['PATH_INFO']
884
        if py3k:
885
            try:
886
                environ['PATH_INFO'] = path.encode('latin1').decode('utf8')
887
            except UnicodeError:
888
                return HTTPError(400, 'Invalid path string. Expected UTF-8')
889
890
        try:
891
            environ['bottle.app'] = self
892
            request.bind(environ)
893
            response.bind()
894
            try:
895
                self.trigger_hook('before_request')
896
                route, args = self.router.match(environ)
897
                environ['route.handle'] = route
898
                environ['bottle.route'] = route
899
                environ['route.url_args'] = args
900
                return route.call(**args)
901
            finally:
902
                self.trigger_hook('after_request')
903
        except HTTPResponse:
904
            return _e()
905
        except RouteReset:
906
            route.reset()
907
            return self._handle(environ)
908
        except (KeyboardInterrupt, SystemExit, MemoryError):
909
            raise
910
        except Exception:
911
            if not self.catchall: raise
912
            stacktrace = format_exc()
913
            environ['wsgi.errors'].write(stacktrace)
914
            return HTTPError(500, "Internal Server Error", _e(), stacktrace)
915
916
    def _cast(self, out, peek=None):
917
        """ Try to convert the parameter into something WSGI compatible and set
918
        correct HTTP headers when possible.
919
        Support: False, str, unicode, dict, HTTPResponse, HTTPError, file-like,
920
        iterable of strings and iterable of unicodes
921
        """
922
923
        # Empty output is done here
924
        if not out:
925
            if 'Content-Length' not in response:
926
                response['Content-Length'] = 0
927
            return []
928
        # Join lists of byte or unicode strings. Mixed lists are NOT supported
929
        if isinstance(out, (tuple, list))\
930
        and isinstance(out[0], (bytes, unicode)):
931
            out = out[0][0:0].join(out)  # b'abc'[0:0] -> b''
932
        # Encode unicode strings
933
        if isinstance(out, unicode):
934
            out = out.encode(response.charset)
935
        # Byte Strings are just returned
936
        if isinstance(out, bytes):
937
            if 'Content-Length' not in response:
938
                response['Content-Length'] = len(out)
939
            return [out]
940
        # HTTPError or HTTPException (recursive, because they may wrap anything)
941
        # TODO: Handle these explicitly in handle() or make them iterable.
942
        if isinstance(out, HTTPError):
943
            out.apply(response)
944
            out = self.error_handler.get(out.status_code,
945
                                         self.default_error_handler)(out)
946
            return self._cast(out)
947
        if isinstance(out, HTTPResponse):
948
            out.apply(response)
949
            return self._cast(out.body)
950
951
        # File-like objects.
952
        if hasattr(out, 'read'):
953
            if 'wsgi.file_wrapper' in request.environ:
954
                return request.environ['wsgi.file_wrapper'](out)
955
            elif hasattr(out, 'close') or not hasattr(out, '__iter__'):
956
                return WSGIFileWrapper(out)
957
958
        # Handle Iterables. We peek into them to detect their inner type.
959
        try:
960
            iout = iter(out)
961
            first = next(iout)
962
            while not first:
963
                first = next(iout)
964
        except StopIteration:
965
            return self._cast('')
966
        except HTTPResponse:
967
            first = _e()
968
        except (KeyboardInterrupt, SystemExit, MemoryError):
969
            raise
970
        except:
971
            if not self.catchall: raise
972
            first = HTTPError(500, 'Unhandled exception', _e(), format_exc())
973
974
        # These are the inner types allowed in iterator or generator objects.
975
        if isinstance(first, HTTPResponse):
976
            return self._cast(first)
977
        elif isinstance(first, bytes):
978
            new_iter = itertools.chain([first], iout)
979
        elif isinstance(first, unicode):
980
            encoder = lambda x: x.encode(response.charset)
981
            new_iter = imap(encoder, itertools.chain([first], iout))
982
        else:
983
            msg = 'Unsupported response type: %s' % type(first)
984
            return self._cast(HTTPError(500, msg))
985
        if hasattr(out, 'close'):
986
            new_iter = _closeiter(new_iter, out.close)
987
        return new_iter
988
989
    def wsgi(self, environ, start_response):
990
        """ The bottle WSGI-interface. """
991
        try:
992
            out = self._cast(self._handle(environ))
993
            # rfc2616 section 4.3
994
            if response._status_code in (100, 101, 204, 304)\
995
            or environ['REQUEST_METHOD'] == 'HEAD':
996
                if hasattr(out, 'close'): out.close()
997
                out = []
998
            start_response(response._status_line, response.headerlist)
999
            return out
1000
        except (KeyboardInterrupt, SystemExit, MemoryError):
1001
            raise
1002
        except:
1003
            if not self.catchall: raise
1004
            err = '<h1>Critical error while processing request: %s</h1>' \
1005
                  % html_escape(environ.get('PATH_INFO', '/'))
1006
            if DEBUG:
1007
                err += '<h2>Error:</h2>\n<pre>\n%s\n</pre>\n' \
1008
                       '<h2>Traceback:</h2>\n<pre>\n%s\n</pre>\n' \
1009
                       % (html_escape(repr(_e())), html_escape(format_exc()))
1010
            environ['wsgi.errors'].write(err)
1011
            headers = [('Content-Type', 'text/html; charset=UTF-8')]
1012
            start_response('500 INTERNAL SERVER ERROR', headers, sys.exc_info())
1013
            return [tob(err)]
1014
1015
    def __call__(self, environ, start_response):
1016
        """ Each instance of :class:'Bottle' is a WSGI application. """
1017
        return self.wsgi(environ, start_response)
1018
1019
    def __enter__(self):
1020
        """ Use this application as default for all module-level shortcuts. """
1021
        default_app.push(self)
1022
        return self
1023
1024
    def __exit__(self, exc_type, exc_value, traceback):
1025
        default_app.pop()
1026
1027
###############################################################################
1028
# HTTP and WSGI Tools ##########################################################
1029
###############################################################################
1030
1031
1032
class BaseRequest(object):
1033
    """ A wrapper for WSGI environment dictionaries that adds a lot of
1034
        convenient access methods and properties. Most of them are read-only.
1035
1036
        Adding new attributes to a request actually adds them to the environ
1037
        dictionary (as 'bottle.request.ext.<name>'). This is the recommended
1038
        way to store and access request-specific data.
1039
    """
1040
1041
    __slots__ = ('environ', )
1042
1043
    #: Maximum size of memory buffer for :attr:`body` in bytes.
1044
    MEMFILE_MAX = 102400
1045
1046
    def __init__(self, environ=None):
1047
        """ Wrap a WSGI environ dictionary. """
1048
        #: The wrapped WSGI environ dictionary. This is the only real attribute.
1049
        #: All other attributes actually are read-only properties.
1050
        self.environ = {} if environ is None else environ
1051
        self.environ['bottle.request'] = self
1052
1053
    @DictProperty('environ', 'bottle.app', read_only=True)
1054
    def app(self):
1055
        """ Bottle application handling this request. """
1056
        raise RuntimeError('This request is not connected to an application.')
1057
1058
    @DictProperty('environ', 'bottle.route', read_only=True)
1059
    def route(self):
1060
        """ The bottle :class:`Route` object that matches this request. """
1061
        raise RuntimeError('This request is not connected to a route.')
1062
1063
    @DictProperty('environ', 'route.url_args', read_only=True)
1064
    def url_args(self):
1065
        """ The arguments extracted from the URL. """
1066
        raise RuntimeError('This request is not connected to a route.')
1067
1068
    @property
1069
    def path(self):
1070
        """ The value of ``PATH_INFO`` with exactly one prefixed slash (to fix
1071
            broken clients and avoid the "empty path" edge case). """
1072
        return '/' + self.environ.get('PATH_INFO', '').lstrip('/')
1073
1074
    @property
1075
    def method(self):
1076
        """ The ``REQUEST_METHOD`` value as an uppercase string. """
1077
        return self.environ.get('REQUEST_METHOD', 'GET').upper()
1078
1079
    @DictProperty('environ', 'bottle.request.headers', read_only=True)
1080
    def headers(self):
1081
        """ A :class:`WSGIHeaderDict` that provides case-insensitive access to
1082
            HTTP request headers. """
1083
        return WSGIHeaderDict(self.environ)
1084
1085
    def get_header(self, name, default=None):
1086
        """ Return the value of a request header, or a given default value. """
1087
        return self.headers.get(name, default)
1088
1089
    @DictProperty('environ', 'bottle.request.cookies', read_only=True)
1090
    def cookies(self):
1091
        """ Cookies parsed into a :class:`FormsDict`. Signed cookies are NOT
1092
            decoded. Use :meth:`get_cookie` if you expect signed cookies. """
1093
        cookies = SimpleCookie(self.environ.get('HTTP_COOKIE', '')).values()
1094
        return FormsDict((c.key, c.value) for c in cookies)
1095
1096
    def get_cookie(self, key, default=None, secret=None):
1097
        """ Return the content of a cookie. To read a `Signed Cookie`, the
1098
            `secret` must match the one used to create the cookie (see
1099
            :meth:`BaseResponse.set_cookie`). If anything goes wrong (missing
1100
            cookie or wrong signature), return a default value. """
1101
        value = self.cookies.get(key)
1102
        if secret and value:
1103
            dec = cookie_decode(value, secret)  # (key, value) tuple or None
1104
            return dec[1] if dec and dec[0] == key else default
1105
        return value or default
1106
1107
    @DictProperty('environ', 'bottle.request.query', read_only=True)
1108
    def query(self):
1109
        """ The :attr:`query_string` parsed into a :class:`FormsDict`. These
1110
            values are sometimes called "URL arguments" or "GET parameters", but
1111
            not to be confused with "URL wildcards" as they are provided by the
1112
            :class:`Router`. """
1113
        get = self.environ['bottle.get'] = FormsDict()
1114
        pairs = _parse_qsl(self.environ.get('QUERY_STRING', ''))
1115
        for key, value in pairs:
1116
            get[key] = value
1117
        return get
1118
1119
    @DictProperty('environ', 'bottle.request.forms', read_only=True)
1120
    def forms(self):
1121
        """ Form values parsed from an `url-encoded` or `multipart/form-data`
1122
            encoded POST or PUT request body. The result is returned as a
1123
            :class:`FormsDict`. All keys and values are strings. File uploads
1124
            are stored separately in :attr:`files`. """
1125
        forms = FormsDict()
1126
        for name, item in self.POST.allitems():
1127
            if not isinstance(item, FileUpload):
1128
                forms[name] = item
1129
        return forms
1130
1131
    @DictProperty('environ', 'bottle.request.params', read_only=True)
1132
    def params(self):
1133
        """ A :class:`FormsDict` with the combined values of :attr:`query` and
1134
            :attr:`forms`. File uploads are stored in :attr:`files`. """
1135
        params = FormsDict()
1136
        for key, value in self.query.allitems():
1137
            params[key] = value
1138
        for key, value in self.forms.allitems():
1139
            params[key] = value
1140
        return params
1141
1142
    @DictProperty('environ', 'bottle.request.files', read_only=True)
1143
    def files(self):
1144
        """ File uploads parsed from `multipart/form-data` encoded POST or PUT
1145
            request body. The values are instances of :class:`FileUpload`.
1146
1147
        """
1148
        files = FormsDict()
1149
        for name, item in self.POST.allitems():
1150
            if isinstance(item, FileUpload):
1151
                files[name] = item
1152
        return files
1153
1154
    @DictProperty('environ', 'bottle.request.json', read_only=True)
1155
    def json(self):
1156
        """ If the ``Content-Type`` header is ``application/json``, this
1157
            property holds the parsed content of the request body. Only requests
1158
            smaller than :attr:`MEMFILE_MAX` are processed to avoid memory
1159
            exhaustion. """
1160
        ctype = self.environ.get('CONTENT_TYPE', '').lower().split(';')[0]
1161
        if ctype == 'application/json':
1162
            b = self._get_body_string()
1163
            if not b:
1164
                return None
1165
            return json_loads(b)
1166
        return None
1167
1168
    def _iter_body(self, read, bufsize):
1169
        maxread = max(0, self.content_length)
1170
        while maxread:
1171
            part = read(min(maxread, bufsize))
1172
            if not part: break
1173
            yield part
1174
            maxread -= len(part)
1175
1176
    @staticmethod
1177
    def _iter_chunked(read, bufsize):
1178
        err = HTTPError(400, 'Error while parsing chunked transfer body.')
1179
        rn, sem, bs = tob('\r\n'), tob(';'), tob('')
1180
        while True:
1181
            header = read(1)
1182
            while header[-2:] != rn:
1183
                c = read(1)
1184
                header += c
1185
                if not c: raise err
1186
                if len(header) > bufsize: raise err
1187
            size, _, _ = header.partition(sem)
1188
            try:
1189
                maxread = int(tonat(size.strip()), 16)
1190
            except ValueError:
1191
                raise err
1192
            if maxread == 0: break
1193
            buff = bs
1194
            while maxread > 0:
1195
                if not buff:
1196
                    buff = read(min(maxread, bufsize))
1197
                part, buff = buff[:maxread], buff[maxread:]
1198
                if not part: raise err
1199
                yield part
1200
                maxread -= len(part)
1201
            if read(2) != rn:
1202
                raise err
1203
1204
    @DictProperty('environ', 'bottle.request.body', read_only=True)
1205
    def _body(self):
1206
        try:
1207
            read_func = self.environ['wsgi.input'].read
1208
        except KeyError:
1209
            self.environ['wsgi.input'] = BytesIO()
1210
            return self.environ['wsgi.input']
1211
        body_iter = self._iter_chunked if self.chunked else self._iter_body
1212
        body, body_size, is_temp_file = BytesIO(), 0, False
1213
        for part in body_iter(read_func, self.MEMFILE_MAX):
1214
            body.write(part)
1215
            body_size += len(part)
1216
            if not is_temp_file and body_size > self.MEMFILE_MAX:
1217
                body, tmp = TemporaryFile(mode='w+b'), body
1218
                body.write(tmp.getvalue())
1219
                del tmp
1220
                is_temp_file = True
1221
        self.environ['wsgi.input'] = body
1222
        body.seek(0)
1223
        return body
1224
1225
    def _get_body_string(self):
1226
        """ read body until content-length or MEMFILE_MAX into a string. Raise
1227
            HTTPError(413) on requests that are to large. """
1228
        clen = self.content_length
1229
        if clen > self.MEMFILE_MAX:
1230
            raise HTTPError(413, 'Request entity too large')
1231
        if clen < 0: clen = self.MEMFILE_MAX + 1
1232
        data = self.body.read(clen)
1233
        if len(data) > self.MEMFILE_MAX:  # Fail fast
1234
            raise HTTPError(413, 'Request entity too large')
1235
        return data
1236
1237
    @property
1238
    def body(self):
1239
        """ The HTTP request body as a seek-able file-like object. Depending on
1240
            :attr:`MEMFILE_MAX`, this is either a temporary file or a
1241
            :class:`io.BytesIO` instance. Accessing this property for the first
1242
            time reads and replaces the ``wsgi.input`` environ variable.
1243
            Subsequent accesses just do a `seek(0)` on the file object. """
1244
        self._body.seek(0)
1245
        return self._body
1246
1247
    @property
1248
    def chunked(self):
1249
        """ True if Chunked transfer encoding was. """
1250
        return 'chunked' in self.environ.get(
1251
            'HTTP_TRANSFER_ENCODING', '').lower()
1252
1253
    #: An alias for :attr:`query`.
1254
    GET = query
1255
1256
    @DictProperty('environ', 'bottle.request.post', read_only=True)
1257
    def POST(self):
1258
        """ The values of :attr:`forms` and :attr:`files` combined into a single
1259
            :class:`FormsDict`. Values are either strings (form values) or
1260
            instances of :class:`cgi.FieldStorage` (file uploads).
1261
        """
1262
        post = FormsDict()
1263
        # We default to application/x-www-form-urlencoded for everything that
1264
        # is not multipart and take the fast path (also: 3.1 workaround)
1265
        if not self.content_type.startswith('multipart/'):
1266
            pairs = _parse_qsl(tonat(self._get_body_string(), 'latin1'))
1267
            for key, value in pairs:
1268
                post[key] = value
1269
            return post
1270
1271
        safe_env = {'QUERY_STRING': ''}  # Build a safe environment for cgi
1272
        for key in ('REQUEST_METHOD', 'CONTENT_TYPE', 'CONTENT_LENGTH'):
1273
            if key in self.environ: safe_env[key] = self.environ[key]
1274
        args = dict(fp=self.body, environ=safe_env, keep_blank_values=True)
1275
        if py31:
1276
            args['fp'] = NCTextIOWrapper(args['fp'],
1277
                                         encoding='utf8',
1278
                                         newline='\n')
1279
        elif py3k:
1280
            args['encoding'] = 'utf8'
1281
        data = cgi.FieldStorage(**args)
1282
        self['_cgi.FieldStorage'] = data  #http://bugs.python.org/issue18394
1283
        data = data.list or []
1284
        for item in data:
1285
            if item.filename:
1286
                post[item.name] = FileUpload(item.file, item.name,
1287
                                             item.filename, item.headers)
1288
            else:
1289
                post[item.name] = item.value
1290
        return post
1291
1292
    @property
1293
    def url(self):
1294
        """ The full request URI including hostname and scheme. If your app
1295
            lives behind a reverse proxy or load balancer and you get confusing
1296
            results, make sure that the ``X-Forwarded-Host`` header is set
1297
            correctly. """
1298
        return self.urlparts.geturl()
1299
1300
    @DictProperty('environ', 'bottle.request.urlparts', read_only=True)
1301
    def urlparts(self):
1302
        """ The :attr:`url` string as an :class:`urlparse.SplitResult` tuple.
1303
            The tuple contains (scheme, host, path, query_string and fragment),
1304
            but the fragment is always empty because it is not visible to the
1305
            server. """
1306
        env = self.environ
1307
        http = env.get('HTTP_X_FORWARDED_PROTO') \
1308
             or env.get('wsgi.url_scheme', 'http')
1309
        host = env.get('HTTP_X_FORWARDED_HOST') or env.get('HTTP_HOST')
1310
        if not host:
1311
            # HTTP 1.1 requires a Host-header. This is for HTTP/1.0 clients.
1312
            host = env.get('SERVER_NAME', '127.0.0.1')
1313
            port = env.get('SERVER_PORT')
1314
            if port and port != ('80' if http == 'http' else '443'):
1315
                host += ':' + port
1316
        path = urlquote(self.fullpath)
1317
        return UrlSplitResult(http, host, path, env.get('QUERY_STRING'), '')
1318
1319
    @property
1320
    def fullpath(self):
1321
        """ Request path including :attr:`script_name` (if present). """
1322
        return urljoin(self.script_name, self.path.lstrip('/'))
1323
1324
    @property
1325
    def query_string(self):
1326
        """ The raw :attr:`query` part of the URL (everything in between ``?``
1327
            and ``#``) as a string. """
1328
        return self.environ.get('QUERY_STRING', '')
1329
1330
    @property
1331
    def script_name(self):
1332
        """ The initial portion of the URL's `path` that was removed by a higher
1333
            level (server or routing middleware) before the application was
1334
            called. This script path is returned with leading and tailing
1335
            slashes. """
1336
        script_name = self.environ.get('SCRIPT_NAME', '').strip('/')
1337
        return '/' + script_name + '/' if script_name else '/'
1338
1339
    def path_shift(self, shift=1):
1340
        """ Shift path segments from :attr:`path` to :attr:`script_name` and
1341
            vice versa.
1342
1343
           :param shift: The number of path segments to shift. May be negative
1344
                         to change the shift direction. (default: 1)
1345
        """
1346
        script, path = path_shift(self.environ.get('SCRIPT_NAME', '/'), self.path, shift)
1347
        self['SCRIPT_NAME'], self['PATH_INFO'] = script, path
1348
1349
    @property
1350
    def content_length(self):
1351
        """ The request body length as an integer. The client is responsible to
1352
            set this header. Otherwise, the real length of the body is unknown
1353
            and -1 is returned. In this case, :attr:`body` will be empty. """
1354
        return int(self.environ.get('CONTENT_LENGTH') or -1)
1355
1356
    @property
1357
    def content_type(self):
1358
        """ The Content-Type header as a lowercase-string (default: empty). """
1359
        return self.environ.get('CONTENT_TYPE', '').lower()
1360
1361
    @property
1362
    def is_xhr(self):
1363
        """ True if the request was triggered by a XMLHttpRequest. This only
1364
            works with JavaScript libraries that support the `X-Requested-With`
1365
            header (most of the popular libraries do). """
1366
        requested_with = self.environ.get('HTTP_X_REQUESTED_WITH', '')
1367
        return requested_with.lower() == 'xmlhttprequest'
1368
1369
    @property
1370
    def is_ajax(self):
1371
        """ Alias for :attr:`is_xhr`. "Ajax" is not the right term. """
1372
        return self.is_xhr
1373
1374
    @property
1375
    def auth(self):
1376
        """ HTTP authentication data as a (user, password) tuple. This
1377
            implementation currently supports basic (not digest) authentication
1378
            only. If the authentication happened at a higher level (e.g. in the
1379
            front web-server or a middleware), the password field is None, but
1380
            the user field is looked up from the ``REMOTE_USER`` environ
1381
            variable. On any errors, None is returned. """
1382
        basic = parse_auth(self.environ.get('HTTP_AUTHORIZATION', ''))
1383
        if basic: return basic
1384
        ruser = self.environ.get('REMOTE_USER')
1385
        if ruser: return (ruser, None)
1386
        return None
1387
1388
    @property
1389
    def remote_route(self):
1390
        """ A list of all IPs that were involved in this request, starting with
1391
            the client IP and followed by zero or more proxies. This does only
1392
            work if all proxies support the ```X-Forwarded-For`` header. Note
1393
            that this information can be forged by malicious clients. """
1394
        proxy = self.environ.get('HTTP_X_FORWARDED_FOR')
1395
        if proxy: return [ip.strip() for ip in proxy.split(',')]
1396
        remote = self.environ.get('REMOTE_ADDR')
1397
        return [remote] if remote else []
1398
1399
    @property
1400
    def remote_addr(self):
1401
        """ The client IP as a string. Note that this information can be forged
1402
            by malicious clients. """
1403
        route = self.remote_route
1404
        return route[0] if route else None
1405
1406
    def copy(self):
1407
        """ Return a new :class:`Request` with a shallow :attr:`environ` copy. """
1408
        return Request(self.environ.copy())
1409
1410
    def get(self, value, default=None):
1411
        return self.environ.get(value, default)
1412
1413
    def __getitem__(self, key):
1414
        return self.environ[key]
1415
1416
    def __delitem__(self, key):
1417
        self[key] = ""
1418
        del (self.environ[key])
1419
1420
    def __iter__(self):
1421
        return iter(self.environ)
1422
1423
    def __len__(self):
1424
        return len(self.environ)
1425
1426
    def keys(self):
1427
        return self.environ.keys()
1428
1429
    def __setitem__(self, key, value):
1430
        """ Change an environ value and clear all caches that depend on it. """
1431
1432
        if self.environ.get('bottle.request.readonly'):
1433
            raise KeyError('The environ dictionary is read-only.')
1434
1435
        self.environ[key] = value
1436
        todelete = ()
1437
1438
        if key == 'wsgi.input':
1439
            todelete = ('body', 'forms', 'files', 'params', 'post', 'json')
1440
        elif key == 'QUERY_STRING':
1441
            todelete = ('query', 'params')
1442
        elif key.startswith('HTTP_'):
1443
            todelete = ('headers', 'cookies')
1444
1445
        for key in todelete:
1446
            self.environ.pop('bottle.request.' + key, None)
1447
1448
    def __repr__(self):
1449
        return '<%s: %s %s>' % (self.__class__.__name__, self.method, self.url)
1450
1451
    def __getattr__(self, name):
1452
        """ Search in self.environ for additional user defined attributes. """
1453
        try:
1454
            var = self.environ['bottle.request.ext.%s' % name]
1455
            return var.__get__(self) if hasattr(var, '__get__') else var
1456
        except KeyError:
1457
            raise AttributeError('Attribute %r not defined.' % name)
1458
1459
    def __setattr__(self, name, value):
1460
        if name == 'environ': return object.__setattr__(self, name, value)
1461
        self.environ['bottle.request.ext.%s' % name] = value
1462
1463
1464
def _hkey(s):
1465
    return s.title().replace('_', '-')
1466
1467
1468
class HeaderProperty(object):
1469
    def __init__(self, name, reader=None, writer=str, default=''):
1470
        self.name, self.default = name, default
1471
        self.reader, self.writer = reader, writer
1472
        self.__doc__ = 'Current value of the %r header.' % name.title()
1473
1474
    def __get__(self, obj, _):
1475
        if obj is None: return self
1476
        value = obj.headers.get(self.name, self.default)
1477
        return self.reader(value) if self.reader else value
1478
1479
    def __set__(self, obj, value):
1480
        obj.headers[self.name] = self.writer(value)
1481
1482
    def __delete__(self, obj):
1483
        del obj.headers[self.name]
1484
1485
1486
class BaseResponse(object):
1487
    """ Storage class for a response body as well as headers and cookies.
1488
1489
        This class does support dict-like case-insensitive item-access to
1490
        headers, but is NOT a dict. Most notably, iterating over a response
1491
        yields parts of the body and not the headers.
1492
1493
        :param body: The response body as one of the supported types.
1494
        :param status: Either an HTTP status code (e.g. 200) or a status line
1495
                       including the reason phrase (e.g. '200 OK').
1496
        :param headers: A dictionary or a list of name-value pairs.
1497
1498
        Additional keyword arguments are added to the list of headers.
1499
        Underscores in the header name are replaced with dashes.
1500
    """
1501
1502
    default_status = 200
1503
    default_content_type = 'text/html; charset=UTF-8'
1504
1505
    # Header blacklist for specific response codes
1506
    # (rfc2616 section 10.2.3 and 10.3.5)
1507
    bad_headers = {
1508
        204: set(('Content-Type', )),
1509
        304: set(('Allow', 'Content-Encoding', 'Content-Language',
1510
                  'Content-Length', 'Content-Range', 'Content-Type',
1511
                  'Content-Md5', 'Last-Modified'))
1512
    }
1513
1514
    def __init__(self, body='', status=None, headers=None, **more_headers):
1515
        self._cookies = None
1516
        self._headers = {}
1517
        self.body = body
1518
        self.status = status or self.default_status
1519
        if headers:
1520
            if isinstance(headers, dict):
1521
                headers = headers.items()
1522
            for name, value in headers:
1523
                self.add_header(name, value)
1524
        if more_headers:
1525
            for name, value in more_headers.items():
1526
                self.add_header(name, value)
1527
1528
    def copy(self, cls=None):
1529
        """ Returns a copy of self. """
1530
        cls = cls or BaseResponse
1531
        assert issubclass(cls, BaseResponse)
1532
        copy = cls()
1533
        copy.status = self.status
1534
        copy._headers = dict((k, v[:]) for (k, v) in self._headers.items())
1535
        if self._cookies:
1536
            copy._cookies = SimpleCookie()
1537
            copy._cookies.load(self._cookies.output(header=''))
1538
        return copy
1539
1540
    def __iter__(self):
1541
        return iter(self.body)
1542
1543
    def close(self):
1544
        if hasattr(self.body, 'close'):
1545
            self.body.close()
1546
1547
    @property
1548
    def status_line(self):
1549
        """ The HTTP status line as a string (e.g. ``404 Not Found``)."""
1550
        return self._status_line
1551
1552
    @property
1553
    def status_code(self):
1554
        """ The HTTP status code as an integer (e.g. 404)."""
1555
        return self._status_code
1556
1557
    def _set_status(self, status):
1558
        if isinstance(status, int):
1559
            code, status = status, _HTTP_STATUS_LINES.get(status)
1560
        elif ' ' in status:
1561
            status = status.strip()
1562
            code = int(status.split()[0])
1563
        else:
1564
            raise ValueError('String status line without a reason phrase.')
1565
        if not 100 <= code <= 999:
1566
            raise ValueError('Status code out of range.')
1567
        self._status_code = code
1568
        self._status_line = str(status or ('%d Unknown' % code))
1569
1570
    def _get_status(self):
1571
        return self._status_line
1572
1573
    status = property(
1574
        _get_status, _set_status, None,
1575
        ''' A writeable property to change the HTTP response status. It accepts
1576
            either a numeric code (100-999) or a string with a custom reason
1577
            phrase (e.g. "404 Brain not found"). Both :data:`status_line` and
1578
            :data:`status_code` are updated accordingly. The return value is
1579
            always a status string. ''')
1580
    del _get_status, _set_status
1581
1582
    @property
1583
    def headers(self):
1584
        """ An instance of :class:`HeaderDict`, a case-insensitive dict-like
1585
            view on the response headers. """
1586
        hdict = HeaderDict()
1587
        hdict.dict = self._headers
1588
        return hdict
1589
1590
    def __contains__(self, name):
1591
        return _hkey(name) in self._headers
1592
1593
    def __delitem__(self, name):
1594
        del self._headers[_hkey(name)]
1595
1596
    def __getitem__(self, name):
1597
        return self._headers[_hkey(name)][-1]
1598
1599
    def __setitem__(self, name, value):
1600
        self._headers[_hkey(name)] = [value if isinstance(value, unicode) else
1601
                                      str(value)]
1602
1603
    def get_header(self, name, default=None):
1604
        """ Return the value of a previously defined header. If there is no
1605
            header with that name, return a default value. """
1606
        return self._headers.get(_hkey(name), [default])[-1]
1607
1608
    def set_header(self, name, value):
1609
        """ Create a new response header, replacing any previously defined
1610
            headers with the same name. """
1611
        self._headers[_hkey(name)] = [value if isinstance(value, unicode)
1612
                                            else str(value)]
1613
1614
    def add_header(self, name, value):
1615
        """ Add an additional response header, not removing duplicates. """
1616
        self._headers.setdefault(_hkey(name), []).append(
1617
            value if isinstance(value, unicode) else str(value))
1618
1619
    def iter_headers(self):
1620
        """ Yield (header, value) tuples, skipping headers that are not
1621
            allowed with the current response status code. """
1622
        return self.headerlist
1623
1624
    @property
1625
    def headerlist(self):
1626
        """ WSGI conform list of (header, value) tuples. """
1627
        out = []
1628
        headers = list(self._headers.items())
1629
        if 'Content-Type' not in self._headers:
1630
            headers.append(('Content-Type', [self.default_content_type]))
1631
        if self._status_code in self.bad_headers:
1632
            bad_headers = self.bad_headers[self._status_code]
1633
            headers = [h for h in headers if h[0] not in bad_headers]
1634
        out += [(name, val) for (name, vals) in headers for val in vals]
1635
        if self._cookies:
1636
            for c in self._cookies.values():
1637
                out.append(('Set-Cookie', c.OutputString()))
1638
        if py3k:
1639
            return [(k, v.encode('utf8').decode('latin1')) for (k, v) in out]
1640
        else:
1641
            return [(k, v.encode('utf8') if isinstance(v, unicode) else v)
1642
                    for (k, v) in out]
1643
1644
    content_type = HeaderProperty('Content-Type')
1645
    content_length = HeaderProperty('Content-Length', reader=int)
1646
    expires = HeaderProperty(
1647
        'Expires',
1648
        reader=lambda x: datetime.utcfromtimestamp(parse_date(x)),
1649
        writer=lambda x: http_date(x))
1650
1651
    @property
1652
    def charset(self, default='UTF-8'):
1653
        """ Return the charset specified in the content-type header (default: utf8). """
1654
        if 'charset=' in self.content_type:
1655
            return self.content_type.split('charset=')[-1].split(';')[0].strip()
1656
        return default
1657
1658
    def set_cookie(self, name, value, secret=None, **options):
1659
        """ Create a new cookie or replace an old one. If the `secret` parameter is
1660
            set, create a `Signed Cookie` (described below).
1661
1662
            :param name: the name of the cookie.
1663
            :param value: the value of the cookie.
1664
            :param secret: a signature key required for signed cookies.
1665
1666
            Additionally, this method accepts all RFC 2109 attributes that are
1667
            supported by :class:`cookie.Morsel`, including:
1668
1669
            :param max_age: maximum age in seconds. (default: None)
1670
            :param expires: a datetime object or UNIX timestamp. (default: None)
1671
            :param domain: the domain that is allowed to read the cookie.
1672
              (default: current domain)
1673
            :param path: limits the cookie to a given path (default: current path)
1674
            :param secure: limit the cookie to HTTPS connections (default: off).
1675
            :param httponly: prevents client-side javascript to read this cookie
1676
              (default: off, requires Python 2.6 or newer).
1677
1678
            If neither `expires` nor `max_age` is set (default), the cookie will
1679
            expire at the end of the browser session (as soon as the browser
1680
            window is closed).
1681
1682
            Signed cookies may store any pickle-able object and are
1683
            cryptographically signed to prevent manipulation. Keep in mind that
1684
            cookies are limited to 4kb in most browsers.
1685
1686
            Warning: Signed cookies are not encrypted (the client can still see
1687
            the content) and not copy-protected (the client can restore an old
1688
            cookie). The main intention is to make pickling and unpickling
1689
            save, not to store secret information at client side.
1690
        """
1691
        if not self._cookies:
1692
            self._cookies = SimpleCookie()
1693
1694
        if secret:
1695
            value = touni(cookie_encode((name, value), secret))
1696
        elif not isinstance(value, basestring):
1697
            raise TypeError('Secret key missing for non-string Cookie.')
1698
1699
        if len(value) > 4096: raise ValueError('Cookie value to long.')
1700
        self._cookies[name] = value
1701
1702
        for key, value in options.items():
1703
            if key == 'max_age':
1704
                if isinstance(value, timedelta):
1705
                    value = value.seconds + value.days * 24 * 3600
1706
            if key == 'expires':
1707
                if isinstance(value, (datedate, datetime)):
1708
                    value = value.timetuple()
1709
                elif isinstance(value, (int, float)):
1710
                    value = time.gmtime(value)
1711
                value = time.strftime("%a, %d %b %Y %H:%M:%S GMT", value)
1712
            self._cookies[name][key.replace('_', '-')] = value
1713
1714
    def delete_cookie(self, key, **kwargs):
1715
        """ Delete a cookie. Be sure to use the same `domain` and `path`
1716
            settings as used to create the cookie. """
1717
        kwargs['max_age'] = -1
1718
        kwargs['expires'] = 0
1719
        self.set_cookie(key, '', **kwargs)
1720
1721
    def __repr__(self):
1722
        out = ''
1723
        for name, value in self.headerlist:
1724
            out += '%s: %s\n' % (name.title(), value.strip())
1725
        return out
1726
1727
1728
def _local_property():
1729
    ls = threading.local()
1730
1731
    def fget(_):
1732
        try:
1733
            return ls.var
1734
        except AttributeError:
1735
            raise RuntimeError("Request context not initialized.")
1736
1737
    def fset(_, value):
1738
        ls.var = value
1739
1740
    def fdel(_):
1741
        del ls.var
1742
1743
    return property(fget, fset, fdel, 'Thread-local property')
1744
1745
1746
class LocalRequest(BaseRequest):
1747
    """ A thread-local subclass of :class:`BaseRequest` with a different
1748
        set of attributes for each thread. There is usually only one global
1749
        instance of this class (:data:`request`). If accessed during a
1750
        request/response cycle, this instance always refers to the *current*
1751
        request (even on a multithreaded server). """
1752
    bind = BaseRequest.__init__
1753
    environ = _local_property()
1754
1755
1756
class LocalResponse(BaseResponse):
1757
    """ A thread-local subclass of :class:`BaseResponse` with a different
1758
        set of attributes for each thread. There is usually only one global
1759
        instance of this class (:data:`response`). Its attributes are used
1760
        to build the HTTP response at the end of the request/response cycle.
1761
    """
1762
    bind = BaseResponse.__init__
1763
    _status_line = _local_property()
1764
    _status_code = _local_property()
1765
    _cookies = _local_property()
1766
    _headers = _local_property()
1767
    body = _local_property()
1768
1769
1770
Request = BaseRequest
1771
Response = BaseResponse
1772
1773
1774
class HTTPResponse(Response, BottleException):
1775
    def __init__(self, body='', status=None, headers=None, **more_headers):
1776
        super(HTTPResponse, self).__init__(body, status, headers, **more_headers)
1777
1778
    def apply(self, other):
1779
        other._status_code = self._status_code
1780
        other._status_line = self._status_line
1781
        other._headers = self._headers
1782
        other._cookies = self._cookies
1783
        other.body = self.body
1784
1785
1786
class HTTPError(HTTPResponse):
1787
    default_status = 500
1788
1789
    def __init__(self,
1790
                 status=None,
1791
                 body=None,
1792
                 exception=None,
1793
                 traceback=None, **options):
1794
        self.exception = exception
1795
        self.traceback = traceback
1796
        super(HTTPError, self).__init__(body, status, **options)
1797
1798
###############################################################################
1799
# Plugins ######################################################################
1800
###############################################################################
1801
1802
1803
class PluginError(BottleException):
1804
    pass
1805
1806
1807
class JSONPlugin(object):
1808
    name = 'json'
1809
    api = 2
1810
1811
    def __init__(self, json_dumps=json_dumps):
1812
        self.json_dumps = json_dumps
1813
1814
    def apply(self, callback, _):
1815
        dumps = self.json_dumps
1816
        if not dumps: return callback
1817
1818
        def wrapper(*a, **ka):
1819
            try:
1820
                rv = callback(*a, **ka)
1821
            except HTTPError:
1822
                rv = _e()
1823
1824
            if isinstance(rv, dict):
1825
                #Attempt to serialize, raises exception on failure
1826
                json_response = dumps(rv)
1827
                #Set content type only if serialization successful
1828
                response.content_type = 'application/json'
1829
                return json_response
1830
            elif isinstance(rv, HTTPResponse) and isinstance(rv.body, dict):
1831
                rv.body = dumps(rv.body)
1832
                rv.content_type = 'application/json'
1833
            return rv
1834
1835
        return wrapper
1836
1837
1838
class TemplatePlugin(object):
1839
    """ This plugin applies the :func:`view` decorator to all routes with a
1840
        `template` config parameter. If the parameter is a tuple, the second
1841
        element must be a dict with additional options (e.g. `template_engine`)
1842
        or default variables for the template. """
1843
    name = 'template'
1844
    api = 2
1845
1846
    def apply(self, callback, route):
1847
        conf = route.config.get('template')
1848
        if isinstance(conf, (tuple, list)) and len(conf) == 2:
1849
            return view(conf[0], **conf[1])(callback)
1850
        elif isinstance(conf, str):
1851
            return view(conf)(callback)
1852
        else:
1853
            return callback
1854
1855
1856
#: Not a plugin, but part of the plugin API. TODO: Find a better place.
1857
class _ImportRedirect(object):
1858
    def __init__(self, name, impmask):
1859
        """ Create a virtual package that redirects imports (see PEP 302). """
1860
        self.name = name
1861
        self.impmask = impmask
1862
        self.module = sys.modules.setdefault(name, imp.new_module(name))
1863
        self.module.__dict__.update({
1864
            '__file__': __file__,
1865
            '__path__': [],
1866
            '__all__': [],
1867
            '__loader__': self
1868
        })
1869
        sys.meta_path.append(self)
1870
1871
    def find_module(self, fullname, path=None):
1872
        if '.' not in fullname: return
1873
        packname = fullname.rsplit('.', 1)[0]
1874
        if packname != self.name: return
1875
        return self
1876
1877
    def load_module(self, fullname):
1878
        if fullname in sys.modules: return sys.modules[fullname]
1879
        modname = fullname.rsplit('.', 1)[1]
1880
        realname = self.impmask % modname
1881
        __import__(realname)
1882
        module = sys.modules[fullname] = sys.modules[realname]
1883
        setattr(self.module, modname, module)
1884
        module.__loader__ = self
1885
        return module
1886
1887
###############################################################################
1888
# Common Utilities #############################################################
1889
###############################################################################
1890
1891
1892
class MultiDict(DictMixin):
1893
    """ This dict stores multiple values per key, but behaves exactly like a
1894
        normal dict in that it returns only the newest value for any given key.
1895
        There are special methods available to access the full list of values.
1896
    """
1897
1898
    def __init__(self, *a, **k):
1899
        self.dict = dict((k, [v]) for (k, v) in dict(*a, **k).items())
1900
1901
    def __len__(self):
1902
        return len(self.dict)
1903
1904
    def __iter__(self):
1905
        return iter(self.dict)
1906
1907
    def __contains__(self, key):
1908
        return key in self.dict
1909
1910
    def __delitem__(self, key):
1911
        del self.dict[key]
1912
1913
    def __getitem__(self, key):
1914
        return self.dict[key][-1]
1915
1916
    def __setitem__(self, key, value):
1917
        self.append(key, value)
1918
1919
    def keys(self):
1920
        return self.dict.keys()
1921
1922
    if py3k:
1923
1924
        def values(self):
1925
            return (v[-1] for v in self.dict.values())
1926
1927
        def items(self):
1928
            return ((k, v[-1]) for k, v in self.dict.items())
1929
1930
        def allitems(self):
1931
            return ((k, v) for k, vl in self.dict.items() for v in vl)
1932
1933
        iterkeys = keys
1934
        itervalues = values
1935
        iteritems = items
1936
        iterallitems = allitems
1937
1938
    else:
1939
1940
        def values(self):
1941
            return [v[-1] for v in self.dict.values()]
1942
1943
        def items(self):
1944
            return [(k, v[-1]) for k, v in self.dict.items()]
1945
1946
        def iterkeys(self):
1947
            return self.dict.iterkeys()
1948
1949
        def itervalues(self):
1950
            return (v[-1] for v in self.dict.itervalues())
1951
1952
        def iteritems(self):
1953
            return ((k, v[-1]) for k, v in self.dict.iteritems())
1954
1955
        def iterallitems(self):
1956
            return ((k, v) for k, vl in self.dict.iteritems() for v in vl)
1957
1958
        def allitems(self):
1959
            return [(k, v) for k, vl in self.dict.iteritems() for v in vl]
1960
1961
    def get(self, key, default=None, index=-1, type=None):
1962
        """ Return the most recent value for a key.
1963
1964
            :param default: The default value to be returned if the key is not
1965
                   present or the type conversion fails.
1966
            :param index: An index for the list of available values.
1967
            :param type: If defined, this callable is used to cast the value
1968
                    into a specific type. Exception are suppressed and result in
1969
                    the default value to be returned.
1970
        """
1971
        try:
1972
            val = self.dict[key][index]
1973
            return type(val) if type else val
1974
        except Exception:
1975
            pass
1976
        return default
1977
1978
    def append(self, key, value):
1979
        """ Add a new value to the list of values for this key. """
1980
        self.dict.setdefault(key, []).append(value)
1981
1982
    def replace(self, key, value):
1983
        """ Replace the list of values with a single value. """
1984
        self.dict[key] = [value]
1985
1986
    def getall(self, key):
1987
        """ Return a (possibly empty) list of values for a key. """
1988
        return self.dict.get(key) or []
1989
1990
    #: Aliases for WTForms to mimic other multi-dict APIs (Django)
1991
    getone = get
1992
    getlist = getall
1993
1994
1995
class FormsDict(MultiDict):
1996
    """ This :class:`MultiDict` subclass is used to store request form data.
1997
        Additionally to the normal dict-like item access methods (which return
1998
        unmodified data as native strings), this container also supports
1999
        attribute-like access to its values. Attributes are automatically de-
2000
        or recoded to match :attr:`input_encoding` (default: 'utf8'). Missing
2001
        attributes default to an empty string. """
2002
2003
    #: Encoding used for attribute values.
2004
    input_encoding = 'utf8'
2005
    #: If true (default), unicode strings are first encoded with `latin1`
2006
    #: and then decoded to match :attr:`input_encoding`.
2007
    recode_unicode = True
2008
2009
    def _fix(self, s, encoding=None):
2010
        if isinstance(s, unicode) and self.recode_unicode:  # Python 3 WSGI
2011
            return s.encode('latin1').decode(encoding or self.input_encoding)
2012
        elif isinstance(s, bytes):  # Python 2 WSGI
2013
            return s.decode(encoding or self.input_encoding)
2014
        else:
2015
            return s
2016
2017
    def decode(self, encoding=None):
2018
        """ Returns a copy with all keys and values de- or recoded to match
2019
            :attr:`input_encoding`. Some libraries (e.g. WTForms) want a
2020
            unicode dictionary. """
2021
        copy = FormsDict()
2022
        enc = copy.input_encoding = encoding or self.input_encoding
2023
        copy.recode_unicode = False
2024
        for key, value in self.allitems():
2025
            copy.append(self._fix(key, enc), self._fix(value, enc))
2026
        return copy
2027
2028
    def getunicode(self, name, default=None, encoding=None):
2029
        """ Return the value as a unicode string, or the default. """
2030
        try:
2031
            return self._fix(self[name], encoding)
2032
        except (UnicodeError, KeyError):
2033
            return default
2034
2035
    def __getattr__(self, name, default=unicode()):
2036
        # Without this guard, pickle generates a cryptic TypeError:
2037
        if name.startswith('__') and name.endswith('__'):
2038
            return super(FormsDict, self).__getattr__(name)
2039
        return self.getunicode(name, default=default)
2040
2041
2042
class HeaderDict(MultiDict):
2043
    """ A case-insensitive version of :class:`MultiDict` that defaults to
2044
        replace the old value instead of appending it. """
2045
2046
    def __init__(self, *a, **ka):
2047
        self.dict = {}
2048
        if a or ka: self.update(*a, **ka)
2049
2050
    def __contains__(self, key):
2051
        return _hkey(key) in self.dict
2052
2053
    def __delitem__(self, key):
2054
        del self.dict[_hkey(key)]
2055
2056
    def __getitem__(self, key):
2057
        return self.dict[_hkey(key)][-1]
2058
2059
    def __setitem__(self, key, value):
2060
        self.dict[_hkey(key)] = [value if isinstance(value, unicode) else
2061
                                 str(value)]
2062
2063
    def append(self, key, value):
2064
        self.dict.setdefault(_hkey(key), []).append(
2065
            value if isinstance(value, unicode) else str(value))
2066
2067
    def replace(self, key, value):
2068
        self.dict[_hkey(key)] = [value if isinstance(value, unicode) else
2069
                                 str(value)]
2070
2071
    def getall(self, key):
2072
        return self.dict.get(_hkey(key)) or []
2073
2074
    def get(self, key, default=None, index=-1):
2075
        return MultiDict.get(self, _hkey(key), default, index)
2076
2077
    def filter(self, names):
2078
        for name in [_hkey(n) for n in names]:
2079
            if name in self.dict:
2080
                del self.dict[name]
2081
2082
2083
class WSGIHeaderDict(DictMixin):
2084
    """ This dict-like class wraps a WSGI environ dict and provides convenient
2085
        access to HTTP_* fields. Keys and values are native strings
2086
        (2.x bytes or 3.x unicode) and keys are case-insensitive. If the WSGI
2087
        environment contains non-native string values, these are de- or encoded
2088
        using a lossless 'latin1' character set.
2089
2090
        The API will remain stable even on changes to the relevant PEPs.
2091
        Currently PEP 333, 444 and 3333 are supported. (PEP 444 is the only one
2092
        that uses non-native strings.)
2093
    """
2094
    #: List of keys that do not have a ``HTTP_`` prefix.
2095
    cgikeys = ('CONTENT_TYPE', 'CONTENT_LENGTH')
2096
2097
    def __init__(self, environ):
2098
        self.environ = environ
2099
2100
    def _ekey(self, key):
2101
        """ Translate header field name to CGI/WSGI environ key. """
2102
        key = key.replace('-', '_').upper()
2103
        if key in self.cgikeys:
2104
            return key
2105
        return 'HTTP_' + key
2106
2107
    def raw(self, key, default=None):
2108
        """ Return the header value as is (may be bytes or unicode). """
2109
        return self.environ.get(self._ekey(key), default)
2110
2111
    def __getitem__(self, key):
2112
        val = self.environ[self._ekey(key)]
2113
        if py3k:
2114
            if isinstance(val, unicode):
2115
                val = val.encode('latin1').decode('utf8')
2116
            else:
2117
                val = val.decode('utf8')
2118
        return val
2119
2120
    def __setitem__(self, key, value):
2121
        raise TypeError("%s is read-only." % self.__class__)
2122
2123
    def __delitem__(self, key):
2124
        raise TypeError("%s is read-only." % self.__class__)
2125
2126
    def __iter__(self):
2127
        for key in self.environ:
2128
            if key[:5] == 'HTTP_':
2129
                yield _hkey(key[5:])
2130
            elif key in self.cgikeys:
2131
                yield _hkey(key)
2132
2133
    def keys(self):
2134
        return [x for x in self]
2135
2136
    def __len__(self):
2137
        return len(self.keys())
2138
2139
    def __contains__(self, key):
2140
        return self._ekey(key) in self.environ
2141
2142
2143
class ConfigDict(dict):
2144
    """ A dict-like configuration storage with additional support for
2145
        namespaces, validators, meta-data, on_change listeners and more.
2146
    """
2147
2148
    __slots__ = ('_meta', '_on_change')
2149
2150
    def __init__(self):
2151
        self._meta = {}
2152
        self._on_change = lambda name, value: None
2153
2154
    def load_config(self, filename):
2155
        """ Load values from an ``*.ini`` style config file.
2156
2157
            If the config file contains sections, their names are used as
2158
            namespaces for the values within. The two special sections
2159
            ``DEFAULT`` and ``bottle`` refer to the root namespace (no prefix).
2160
        """
2161
        conf = ConfigParser()
2162
        conf.read(filename)
2163
        for section in conf.sections():
2164
            for key, value in conf.items(section):
2165
                if section not in ('DEFAULT', 'bottle'):
2166
                    key = section + '.' + key
2167
                self[key] = value
2168
        return self
2169
2170
    def load_dict(self, source, namespace=''):
2171
        """ Load values from a dictionary structure. Nesting can be used to
2172
            represent namespaces.
2173
2174
            >>> c = ConfigDict()
2175
            >>> c.load_dict({'some': {'namespace': {'key': 'value'} } })
2176
            {'some.namespace.key': 'value'}
2177
        """
2178
        for key, value in source.items():
2179
            if isinstance(key, str):
2180
                nskey = (namespace + '.' + key).strip('.')
2181
                if isinstance(value, dict):
2182
                    self.load_dict(value, namespace=nskey)
2183
                else:
2184
                    self[nskey] = value
2185
            else:
2186
                raise TypeError('Key has type %r (not a string)' % type(key))
2187
        return self
2188
2189
    def update(self, *a, **ka):
2190
        """ If the first parameter is a string, all keys are prefixed with this
2191
            namespace. Apart from that it works just as the usual dict.update().
2192
            Example: ``update('some.namespace', key='value')`` """
2193
        prefix = ''
2194
        if a and isinstance(a[0], str):
2195
            prefix = a[0].strip('.') + '.'
2196
            a = a[1:]
2197
        for key, value in dict(*a, **ka).items():
2198
            self[prefix + key] = value
2199
2200
    def setdefault(self, key, value):
2201
        if key not in self:
2202
            self[key] = value
2203
        return self[key]
2204
2205
    def __setitem__(self, key, value):
2206
        if not isinstance(key, str):
2207
            raise TypeError('Key has type %r (not a string)' % type(key))
2208
        value = self.meta_get(key, 'filter', lambda x: x)(value)
2209
        if key in self and self[key] is value:
2210
            return
2211
        self._on_change(key, value)
2212
        dict.__setitem__(self, key, value)
2213
2214
    def __delitem__(self, key):
2215
        self._on_change(key, None)
2216
        dict.__delitem__(self, key)
2217
2218
    def meta_get(self, key, metafield, default=None):
2219
        """ Return the value of a meta field for a key. """
2220
        return self._meta.get(key, {}).get(metafield, default)
2221
2222
    def meta_set(self, key, metafield, value):
2223
        """ Set the meta field for a key to a new value. This triggers the
2224
            on-change handler for existing keys. """
2225
        self._meta.setdefault(key, {})[metafield] = value
2226
        if key in self:
2227
            self[key] = self[key]
2228
2229
    def meta_list(self, key):
2230
        """ Return an iterable of meta field names defined for a key. """
2231
        return self._meta.get(key, {}).keys()
2232
2233
2234
class AppStack(list):
2235
    """ A stack-like list. Calling it returns the head of the stack. """
2236
2237
    def __call__(self):
2238
        """ Return the current default application. """
2239
        return self[-1]
2240
2241
    def push(self, value=None):
2242
        """ Add a new :class:`Bottle` instance to the stack """
2243
        if not isinstance(value, Bottle):
2244
            value = Bottle()
2245
        self.append(value)
2246
        return value
2247
2248
2249
class WSGIFileWrapper(object):
2250
    def __init__(self, fp, buffer_size=1024 * 64):
2251
        self.fp, self.buffer_size = fp, buffer_size
2252
        for attr in ('fileno', 'close', 'read', 'readlines', 'tell', 'seek'):
2253
            if hasattr(fp, attr): setattr(self, attr, getattr(fp, attr))
2254
2255
    def __iter__(self):
2256
        buff, read = self.buffer_size, self.read
2257
        while True:
2258
            part = read(buff)
2259
            if not part: return
2260
            yield part
2261
2262
2263
class _closeiter(object):
2264
    """ This only exists to be able to attach a .close method to iterators that
2265
        do not support attribute assignment (most of itertools). """
2266
2267
    def __init__(self, iterator, close=None):
2268
        self.iterator = iterator
2269
        self.close_callbacks = makelist(close)
2270
2271
    def __iter__(self):
2272
        return iter(self.iterator)
2273
2274
    def close(self):
2275
        for func in self.close_callbacks:
2276
            func()
2277
2278
2279
class ResourceManager(object):
2280
    """ This class manages a list of search paths and helps to find and open
2281
        application-bound resources (files).
2282
2283
        :param base: default value for :meth:`add_path` calls.
2284
        :param opener: callable used to open resources.
2285
        :param cachemode: controls which lookups are cached. One of 'all',
2286
                         'found' or 'none'.
2287
    """
2288
2289
    def __init__(self, base='./', opener=open, cachemode='all'):
2290
        self.opener = opener
2291
        self.base = base
2292
        self.cachemode = cachemode
2293
2294
        #: A list of search paths. See :meth:`add_path` for details.
2295
        self.path = []
2296
        #: A cache for resolved paths. ``res.cache.clear()`` clears the cache.
2297
        self.cache = {}
2298
2299
    def add_path(self, path, base=None, index=None, create=False):
2300
        """ Add a new path to the list of search paths. Return False if the
2301
            path does not exist.
2302
2303
            :param path: The new search path. Relative paths are turned into
2304
                an absolute and normalized form. If the path looks like a file
2305
                (not ending in `/`), the filename is stripped off.
2306
            :param base: Path used to absolutize relative search paths.
2307
                Defaults to :attr:`base` which defaults to ``os.getcwd()``.
2308
            :param index: Position within the list of search paths. Defaults
2309
                to last index (appends to the list).
2310
2311
            The `base` parameter makes it easy to reference files installed
2312
            along with a python module or package::
2313
2314
                res.add_path('./resources/', __file__)
2315
        """
2316
        base = os.path.abspath(os.path.dirname(base or self.base))
2317
        path = os.path.abspath(os.path.join(base, os.path.dirname(path)))
2318
        path += os.sep
2319
        if path in self.path:
2320
            self.path.remove(path)
2321
        if create and not os.path.isdir(path):
2322
            os.makedirs(path)
2323
        if index is None:
2324
            self.path.append(path)
2325
        else:
2326
            self.path.insert(index, path)
2327
        self.cache.clear()
2328
        return os.path.exists(path)
2329
2330
    def __iter__(self):
2331
        """ Iterate over all existing files in all registered paths. """
2332
        search = self.path[:]
2333
        while search:
2334
            path = search.pop()
2335
            if not os.path.isdir(path): continue
2336
            for name in os.listdir(path):
2337
                full = os.path.join(path, name)
2338
                if os.path.isdir(full): search.append(full)
2339
                else: yield full
2340
2341
    def lookup(self, name):
2342
        """ Search for a resource and return an absolute file path, or `None`.
2343
2344
            The :attr:`path` list is searched in order. The first match is
2345
            returend. Symlinks are followed. The result is cached to speed up
2346
            future lookups. """
2347
        if name not in self.cache or DEBUG:
2348
            for path in self.path:
2349
                fpath = os.path.join(path, name)
2350
                if os.path.isfile(fpath):
2351
                    if self.cachemode in ('all', 'found'):
2352
                        self.cache[name] = fpath
2353
                    return fpath
2354
            if self.cachemode == 'all':
2355
                self.cache[name] = None
2356
        return self.cache[name]
2357
2358
    def open(self, name, mode='r', *args, **kwargs):
2359
        """ Find a resource and return a file object, or raise IOError. """
2360
        fname = self.lookup(name)
2361
        if not fname: raise IOError("Resource %r not found." % name)
2362
        return self.opener(fname, mode=mode, *args, **kwargs)
2363
2364
2365
class FileUpload(object):
2366
    def __init__(self, fileobj, name, filename, headers=None):
2367
        """ Wrapper for file uploads. """
2368
        #: Open file(-like) object (BytesIO buffer or temporary file)
2369
        self.file = fileobj
2370
        #: Name of the upload form field
2371
        self.name = name
2372
        #: Raw filename as sent by the client (may contain unsafe characters)
2373
        self.raw_filename = filename
2374
        #: A :class:`HeaderDict` with additional headers (e.g. content-type)
2375
        self.headers = HeaderDict(headers) if headers else HeaderDict()
2376
2377
    content_type = HeaderProperty('Content-Type')
2378
    content_length = HeaderProperty('Content-Length', reader=int, default=-1)
2379
2380
    @cached_property
2381
    def filename(self):
2382
        """ Name of the file on the client file system, but normalized to ensure
2383
            file system compatibility. An empty filename is returned as 'empty'.
2384
2385
            Only ASCII letters, digits, dashes, underscores and dots are
2386
            allowed in the final filename. Accents are removed, if possible.
2387
            Whitespace is replaced by a single dash. Leading or tailing dots
2388
            or dashes are removed. The filename is limited to 255 characters.
2389
        """
2390
        fname = self.raw_filename
2391
        if not isinstance(fname, unicode):
2392
            fname = fname.decode('utf8', 'ignore')
2393
        fname = normalize('NFKD', fname)
2394
        fname = fname.encode('ASCII', 'ignore').decode('ASCII')
2395
        fname = os.path.basename(fname.replace('\\', os.path.sep))
2396
        fname = re.sub(r'[^a-zA-Z0-9-_.\s]', '', fname).strip()
2397
        fname = re.sub(r'[-\s]+', '-', fname).strip('.-')
2398
        return fname[:255] or 'empty'
2399
2400
    def _copy_file(self, fp, chunk_size=2 ** 16):
2401
        read, write, offset = self.file.read, fp.write, self.file.tell()
2402
        while 1:
2403
            buf = read(chunk_size)
2404
            if not buf: break
2405
            write(buf)
2406
        self.file.seek(offset)
2407
2408
    def save(self, destination, overwrite=False, chunk_size=2 ** 16):
2409
        """ Save file to disk or copy its content to an open file(-like) object.
2410
            If *destination* is a directory, :attr:`filename` is added to the
2411
            path. Existing files are not overwritten by default (IOError).
2412
2413
            :param destination: File path, directory or file(-like) object.
2414
            :param overwrite: If True, replace existing files. (default: False)
2415
            :param chunk_size: Bytes to read at a time. (default: 64kb)
2416
        """
2417
        if isinstance(destination, basestring):  # Except file-likes here
2418
            if os.path.isdir(destination):
2419
                destination = os.path.join(destination, self.filename)
2420
            if not overwrite and os.path.exists(destination):
2421
                raise IOError('File exists.')
2422
            with open(destination, 'wb') as fp:
2423
                self._copy_file(fp, chunk_size)
2424
        else:
2425
            self._copy_file(destination, chunk_size)
2426
2427
###############################################################################
2428
# Application Helper ###########################################################
2429
###############################################################################
2430
2431
2432
def abort(code=500, text='Unknown Error.'):
2433
    """ Aborts execution and causes a HTTP error. """
2434
    raise HTTPError(code, text)
2435
2436
2437
def redirect(url, code=None):
2438
    """ Aborts execution and causes a 303 or 302 redirect, depending on
2439
        the HTTP protocol version. """
2440
    if not code:
2441
        code = 303 if request.get('SERVER_PROTOCOL') == "HTTP/1.1" else 302
2442
    res = response.copy(cls=HTTPResponse)
2443
    res.status = code
2444
    res.body = ""
2445
    res.set_header('Location', urljoin(request.url, url))
2446
    raise res
2447
2448
2449
def _file_iter_range(fp, offset, bytes, maxread=1024 * 1024):
2450
    """ Yield chunks from a range in a file. No chunk is bigger than maxread."""
2451
    fp.seek(offset)
2452
    while bytes > 0:
2453
        part = fp.read(min(bytes, maxread))
2454
        if not part: break
2455
        bytes -= len(part)
2456
        yield part
2457
2458
2459
def static_file(filename, root,
2460
                mimetype='auto',
2461
                download=False,
2462
                charset='UTF-8'):
2463
    """ Open a file in a safe way and return :exc:`HTTPResponse` with status
2464
        code 200, 305, 403 or 404. The ``Content-Type``, ``Content-Encoding``,
2465
        ``Content-Length`` and ``Last-Modified`` headers are set if possible.
2466
        Special support for ``If-Modified-Since``, ``Range`` and ``HEAD``
2467
        requests.
2468
2469
        :param filename: Name or path of the file to send.
2470
        :param root: Root path for file lookups. Should be an absolute directory
2471
            path.
2472
        :param mimetype: Defines the content-type header (default: guess from
2473
            file extension)
2474
        :param download: If True, ask the browser to open a `Save as...` dialog
2475
            instead of opening the file with the associated program. You can
2476
            specify a custom filename as a string. If not specified, the
2477
            original filename is used (default: False).
2478
        :param charset: The charset to use for files with a ``text/*``
2479
            mime-type. (default: UTF-8)
2480
    """
2481
2482
    root = os.path.abspath(root) + os.sep
2483
    filename = os.path.abspath(os.path.join(root, filename.strip('/\\')))
2484
    headers = dict()
2485
2486
    if not filename.startswith(root):
2487
        return HTTPError(403, "Access denied.")
2488
    if not os.path.exists(filename) or not os.path.isfile(filename):
2489
        return HTTPError(404, "File does not exist.")
2490
    if not os.access(filename, os.R_OK):
2491
        return HTTPError(403, "You do not have permission to access this file.")
2492
2493
    if mimetype == 'auto':
2494
        if download and download != True:
2495
            mimetype, encoding = mimetypes.guess_type(download)
2496
        else:
2497
            mimetype, encoding = mimetypes.guess_type(filename)
2498
        if encoding: headers['Content-Encoding'] = encoding
2499
2500
    if mimetype:
2501
        if mimetype[:5] == 'text/' and charset and 'charset' not in mimetype:
2502
            mimetype += '; charset=%s' % charset
2503
        headers['Content-Type'] = mimetype
2504
2505
    if download:
2506
        download = os.path.basename(filename if download == True else download)
2507
        headers['Content-Disposition'] = 'attachment; filename="%s"' % download
2508
2509
    stats = os.stat(filename)
2510
    headers['Content-Length'] = clen = stats.st_size
2511
    lm = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(stats.st_mtime))
2512
    headers['Last-Modified'] = lm
2513
2514
    ims = request.environ.get('HTTP_IF_MODIFIED_SINCE')
2515
    if ims:
2516
        ims = parse_date(ims.split(";")[0].strip())
2517
    if ims is not None and ims >= int(stats.st_mtime):
2518
        headers['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT",
2519
                                        time.gmtime())
2520
        return HTTPResponse(status=304, **headers)
2521
2522
    body = '' if request.method == 'HEAD' else open(filename, 'rb')
2523
2524
    headers["Accept-Ranges"] = "bytes"
2525
    ranges = request.environ.get('HTTP_RANGE')
2526
    if 'HTTP_RANGE' in request.environ:
2527
        ranges = list(parse_range_header(request.environ['HTTP_RANGE'], clen))
2528
        if not ranges:
2529
            return HTTPError(416, "Requested Range Not Satisfiable")
2530
        offset, end = ranges[0]
2531
        headers["Content-Range"] = "bytes %d-%d/%d" % (offset, end - 1, clen)
2532
        headers["Content-Length"] = str(end - offset)
2533
        if body: body = _file_iter_range(body, offset, end - offset)
2534
        return HTTPResponse(body, status=206, **headers)
2535
    return HTTPResponse(body, **headers)
2536
2537
###############################################################################
2538
# HTTP Utilities and MISC (TODO) ###############################################
2539
###############################################################################
2540
2541
2542
def debug(mode=True):
2543
    """ Change the debug level.
2544
    There is only one debug level supported at the moment."""
2545
    global DEBUG
2546
    if mode: warnings.simplefilter('default')
2547
    DEBUG = bool(mode)
2548
2549
2550
def http_date(value):
2551
    if isinstance(value, (datedate, datetime)):
2552
        value = value.utctimetuple()
2553
    elif isinstance(value, (int, float)):
2554
        value = time.gmtime(value)
2555
    if not isinstance(value, basestring):
2556
        value = time.strftime("%a, %d %b %Y %H:%M:%S GMT", value)
2557
    return value
2558
2559
2560
def parse_date(ims):
2561
    """ Parse rfc1123, rfc850 and asctime timestamps and return UTC epoch. """
2562
    try:
2563
        ts = email.utils.parsedate_tz(ims)
2564
        return time.mktime(ts[:8] + (0, )) - (ts[9] or 0) - time.timezone
2565
    except (TypeError, ValueError, IndexError, OverflowError):
2566
        return None
2567
2568
2569
def parse_auth(header):
2570
    """ Parse rfc2617 HTTP authentication header string (basic) and return (user,pass) tuple or None"""
2571
    try:
2572
        method, data = header.split(None, 1)
2573
        if method.lower() == 'basic':
2574
            user, pwd = touni(base64.b64decode(tob(data))).split(':', 1)
2575
            return user, pwd
2576
    except (KeyError, ValueError):
2577
        return None
2578
2579
2580
def parse_range_header(header, maxlen=0):
2581
    """ Yield (start, end) ranges parsed from a HTTP Range header. Skip
2582
        unsatisfiable ranges. The end index is non-inclusive."""
2583
    if not header or header[:6] != 'bytes=': return
2584
    ranges = [r.split('-', 1) for r in header[6:].split(',') if '-' in r]
2585
    for start, end in ranges:
2586
        try:
2587
            if not start:  # bytes=-100    -> last 100 bytes
2588
                start, end = max(0, maxlen - int(end)), maxlen
2589
            elif not end:  # bytes=100-    -> all but the first 99 bytes
2590
                start, end = int(start), maxlen
2591
            else:  # bytes=100-200 -> bytes 100-200 (inclusive)
2592
                start, end = int(start), min(int(end) + 1, maxlen)
2593
            if 0 <= start < end <= maxlen:
2594
                yield start, end
2595
        except ValueError:
2596
            pass
2597
2598
2599
def _parse_qsl(qs):
2600
    r = []
2601
    for pair in qs.replace(';', '&').split('&'):
2602
        if not pair: continue
2603
        nv = pair.split('=', 1)
2604
        if len(nv) != 2: nv.append('')
2605
        key = urlunquote(nv[0].replace('+', ' '))
2606
        value = urlunquote(nv[1].replace('+', ' '))
2607
        r.append((key, value))
2608
    return r
2609
2610
2611
def _lscmp(a, b):
2612
    """ Compares two strings in a cryptographically safe way:
2613
        Runtime is not affected by length of common prefix. """
2614
    return not sum(0 if x == y else 1
2615
                   for x, y in zip(a, b)) and len(a) == len(b)
2616
2617
2618
def cookie_encode(data, key):
2619
    """ Encode and sign a pickle-able object. Return a (byte) string """
2620
    msg = base64.b64encode(pickle.dumps(data, -1))
2621
    sig = base64.b64encode(hmac.new(tob(key), msg).digest())
2622
    return tob('!') + sig + tob('?') + msg
2623
2624
2625
def cookie_decode(data, key):
2626
    """ Verify and decode an encoded string. Return an object or None."""
2627
    data = tob(data)
2628
    if cookie_is_encoded(data):
2629
        sig, msg = data.split(tob('?'), 1)
2630
        if _lscmp(sig[1:], base64.b64encode(hmac.new(tob(key), msg).digest())):
2631
            return pickle.loads(base64.b64decode(msg))
2632
    return None
2633
2634
2635
def cookie_is_encoded(data):
2636
    """ Return True if the argument looks like a encoded cookie."""
2637
    return bool(data.startswith(tob('!')) and tob('?') in data)
2638
2639
2640
def html_escape(string):
2641
    """ Escape HTML special characters ``&<>`` and quotes ``'"``. """
2642
    return string.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')\
2643
                 .replace('"', '&quot;').replace("'", '&#039;')
2644
2645
2646
def html_quote(string):
2647
    """ Escape and quote a string to be used as an HTTP attribute."""
2648
    return '"%s"' % html_escape(string).replace('\n', '&#10;')\
2649
                    .replace('\r', '&#13;').replace('\t', '&#9;')
2650
2651
2652
def yieldroutes(func):
2653
    """ Return a generator for routes that match the signature (name, args)
2654
    of the func parameter. This may yield more than one route if the function
2655
    takes optional keyword arguments. The output is best described by example::
2656
2657
        a()         -> '/a'
2658
        b(x, y)     -> '/b/<x>/<y>'
2659
        c(x, y=5)   -> '/c/<x>' and '/c/<x>/<y>'
2660
        d(x=5, y=6) -> '/d' and '/d/<x>' and '/d/<x>/<y>'
2661
    """
2662
    path = '/' + func.__name__.replace('__', '/').lstrip('/')
2663
    spec = getargspec(func)
2664
    argc = len(spec[0]) - len(spec[3] or [])
2665
    path += ('/<%s>' * argc) % tuple(spec[0][:argc])
2666
    yield path
2667
    for arg in spec[0][argc:]:
2668
        path += '/<%s>' % arg
2669
        yield path
2670
2671
2672
def path_shift(script_name, path_info, shift=1):
2673
    """ Shift path fragments from PATH_INFO to SCRIPT_NAME and vice versa.
2674
2675
        :return: The modified paths.
2676
        :param script_name: The SCRIPT_NAME path.
2677
        :param script_name: The PATH_INFO path.
2678
        :param shift: The number of path fragments to shift. May be negative to
2679
          change the shift direction. (default: 1)
2680
    """
2681
    if shift == 0: return script_name, path_info
2682
    pathlist = path_info.strip('/').split('/')
2683
    scriptlist = script_name.strip('/').split('/')
2684
    if pathlist and pathlist[0] == '': pathlist = []
2685
    if scriptlist and scriptlist[0] == '': scriptlist = []
2686
    if 0 < shift <= len(pathlist):
2687
        moved = pathlist[:shift]
2688
        scriptlist = scriptlist + moved
2689
        pathlist = pathlist[shift:]
2690
    elif 0 > shift >= -len(scriptlist):
2691
        moved = scriptlist[shift:]
2692
        pathlist = moved + pathlist
2693
        scriptlist = scriptlist[:shift]
2694
    else:
2695
        empty = 'SCRIPT_NAME' if shift < 0 else 'PATH_INFO'
2696
        raise AssertionError("Cannot shift. Nothing left from %s" % empty)
2697
    new_script_name = '/' + '/'.join(scriptlist)
2698
    new_path_info = '/' + '/'.join(pathlist)
2699
    if path_info.endswith('/') and pathlist: new_path_info += '/'
2700
    return new_script_name, new_path_info
2701
2702
2703
def auth_basic(check, realm="private", text="Access denied"):
2704
    """ Callback decorator to require HTTP auth (basic).
2705
        TODO: Add route(check_auth=...) parameter. """
2706
2707
    def decorator(func):
2708
2709
        @functools.wraps(func)
2710
        def wrapper(*a, **ka):
2711
            user, password = request.auth or (None, None)
2712
            if user is None or not check(user, password):
2713
                err = HTTPError(401, text)
2714
                err.add_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
2715
                return err
2716
            return func(*a, **ka)
2717
2718
        return wrapper
2719
2720
    return decorator
2721
2722
# Shortcuts for common Bottle methods.
2723
# They all refer to the current default application.
2724
2725
2726
def make_default_app_wrapper(name):
2727
    """ Return a callable that relays calls to the current default app. """
2728
2729
    @functools.wraps(getattr(Bottle, name))
2730
    def wrapper(*a, **ka):
2731
        return getattr(app(), name)(*a, **ka)
2732
2733
    return wrapper
2734
2735
2736
route     = make_default_app_wrapper('route')
2737
get       = make_default_app_wrapper('get')
2738
post      = make_default_app_wrapper('post')
2739
put       = make_default_app_wrapper('put')
2740
delete    = make_default_app_wrapper('delete')
2741
patch     = make_default_app_wrapper('patch')
2742
error     = make_default_app_wrapper('error')
2743
mount     = make_default_app_wrapper('mount')
2744
hook      = make_default_app_wrapper('hook')
2745
install   = make_default_app_wrapper('install')
2746
uninstall = make_default_app_wrapper('uninstall')
2747
url       = make_default_app_wrapper('get_url')
2748
2749
###############################################################################
2750
# Server Adapter ###############################################################
2751
###############################################################################
2752
2753
2754
class ServerAdapter(object):
2755
    quiet = False
2756
2757
    def __init__(self, host='127.0.0.1', port=8080, **options):
2758
        self.options = options
2759
        self.host = host
2760
        self.port = int(port)
2761
2762
    def run(self, handler):  # pragma: no cover
2763
        pass
2764
2765
    def __repr__(self):
2766
        args = ', '.join(['%s=%s' % (k, repr(v))
2767
                          for k, v in self.options.items()])
2768
        return "%s(%s)" % (self.__class__.__name__, args)
2769
2770
2771
class CGIServer(ServerAdapter):
2772
    quiet = True
2773
2774
    def run(self, handler):  # pragma: no cover
2775
        from wsgiref.handlers import CGIHandler
2776
2777
        def fixed_environ(environ, start_response):
2778
            environ.setdefault('PATH_INFO', '')
2779
            return handler(environ, start_response)
2780
2781
        CGIHandler().run(fixed_environ)
2782
2783
2784
class FlupFCGIServer(ServerAdapter):
2785
    def run(self, handler):  # pragma: no cover
2786
        import flup.server.fcgi
2787
        self.options.setdefault('bindAddress', (self.host, self.port))
2788
        flup.server.fcgi.WSGIServer(handler, **self.options).run()
2789
2790
2791
class WSGIRefServer(ServerAdapter):
2792
    def run(self, app):  # pragma: no cover
2793
        from wsgiref.simple_server import make_server
2794
        from wsgiref.simple_server import WSGIRequestHandler, WSGIServer
2795
        import socket
2796
2797
        class FixedHandler(WSGIRequestHandler):
2798
            def address_string(self):  # Prevent reverse DNS lookups please.
2799
                return self.client_address[0]
2800
2801
            def log_request(*args, **kw):
2802
                if not self.quiet:
2803
                    return WSGIRequestHandler.log_request(*args, **kw)
2804
2805
        handler_cls = self.options.get('handler_class', FixedHandler)
2806
        server_cls = self.options.get('server_class', WSGIServer)
2807
2808
        if ':' in self.host:  # Fix wsgiref for IPv6 addresses.
2809
            if getattr(server_cls, 'address_family') == socket.AF_INET:
2810
2811
                class server_cls(server_cls):
2812
                    address_family = socket.AF_INET6
2813
2814
        self.srv = make_server(self.host, self.port, app, server_cls,
2815
                               handler_cls)
2816
        self.port = self.srv.server_port  # update port actual port (0 means random)
2817
        try:
2818
            self.srv.serve_forever()
2819
        except KeyboardInterrupt:
2820
            self.srv.server_close()  # Prevent ResourceWarning: unclosed socket
2821
            raise
2822
2823
2824
class CherryPyServer(ServerAdapter):
2825
    def run(self, handler):  # pragma: no cover
2826
        from cherrypy import wsgiserver
2827
        self.options['bind_addr'] = (self.host, self.port)
2828
        self.options['wsgi_app'] = handler
2829
2830
        certfile = self.options.get('certfile')
2831
        if certfile:
2832
            del self.options['certfile']
2833
        keyfile = self.options.get('keyfile')
2834
        if keyfile:
2835
            del self.options['keyfile']
2836
2837
        server = wsgiserver.CherryPyWSGIServer(**self.options)
2838
        if certfile:
2839
            server.ssl_certificate = certfile
2840
        if keyfile:
2841
            server.ssl_private_key = keyfile
2842
2843
        try:
2844
            server.start()
2845
        finally:
2846
            server.stop()
2847
2848
2849
class WaitressServer(ServerAdapter):
2850
    def run(self, handler):
2851
        from waitress import serve
2852
        serve(handler, host=self.host, port=self.port, _quiet=self.quiet)
2853
2854
2855
class PasteServer(ServerAdapter):
2856
    def run(self, handler):  # pragma: no cover
2857
        from paste import httpserver
2858
        from paste.translogger import TransLogger
2859
        handler = TransLogger(handler, setup_console_handler=(not self.quiet))
2860
        httpserver.serve(handler,
2861
                         host=self.host,
2862
                         port=str(self.port), **self.options)
2863
2864
2865
class MeinheldServer(ServerAdapter):
2866
    def run(self, handler):
2867
        from meinheld import server
2868
        server.listen((self.host, self.port))
2869
        server.run(handler)
2870
2871
2872
class FapwsServer(ServerAdapter):
2873
    """ Extremely fast webserver using libev. See http://www.fapws.org/ """
2874
2875
    def run(self, handler):  # pragma: no cover
2876
        import fapws._evwsgi as evwsgi
2877
        from fapws import base, config
2878
        port = self.port
2879
        if float(config.SERVER_IDENT[-2:]) > 0.4:
2880
            # fapws3 silently changed its API in 0.5
2881
            port = str(port)
2882
        evwsgi.start(self.host, port)
2883
        # fapws3 never releases the GIL. Complain upstream. I tried. No luck.
2884
        if 'BOTTLE_CHILD' in os.environ and not self.quiet:
2885
            _stderr("WARNING: Auto-reloading does not work with Fapws3.\n")
2886
            _stderr("         (Fapws3 breaks python thread support)\n")
2887
        evwsgi.set_base_module(base)
2888
2889
        def app(environ, start_response):
2890
            environ['wsgi.multiprocess'] = False
2891
            return handler(environ, start_response)
2892
2893
        evwsgi.wsgi_cb(('', app))
2894
        evwsgi.run()
2895
2896
2897
class TornadoServer(ServerAdapter):
2898
    """ The super hyped asynchronous server by facebook. Untested. """
2899
2900
    def run(self, handler):  # pragma: no cover
2901
        import tornado.wsgi, tornado.httpserver, tornado.ioloop
2902
        container = tornado.wsgi.WSGIContainer(handler)
2903
        server = tornado.httpserver.HTTPServer(container)
2904
        server.listen(port=self.port, address=self.host)
2905
        tornado.ioloop.IOLoop.instance().start()
2906
2907
2908
class AppEngineServer(ServerAdapter):
2909
    """ Adapter for Google App Engine. """
2910
    quiet = True
2911
2912
    def run(self, handler):
2913
        from google.appengine.ext.webapp import util
2914
        # A main() function in the handler script enables 'App Caching'.
2915
        # Lets makes sure it is there. This _really_ improves performance.
2916
        module = sys.modules.get('__main__')
2917
        if module and not hasattr(module, 'main'):
2918
            module.main = lambda: util.run_wsgi_app(handler)
2919
        util.run_wsgi_app(handler)
2920
2921
2922
class TwistedServer(ServerAdapter):
2923
    """ Untested. """
2924
2925
    def run(self, handler):
2926
        from twisted.web import server, wsgi
2927
        from twisted.python.threadpool import ThreadPool
2928
        from twisted.internet import reactor
2929
        thread_pool = ThreadPool()
2930
        thread_pool.start()
2931
        reactor.addSystemEventTrigger('after', 'shutdown', thread_pool.stop)
2932
        factory = server.Site(wsgi.WSGIResource(reactor, thread_pool, handler))
2933
        reactor.listenTCP(self.port, factory, interface=self.host)
2934
        if not reactor.running:
2935
            reactor.run()
2936
2937
2938
class DieselServer(ServerAdapter):
2939
    """ Untested. """
2940
2941
    def run(self, handler):
2942
        from diesel.protocols.wsgi import WSGIApplication
2943
        app = WSGIApplication(handler, port=self.port)
2944
        app.run()
2945
2946
2947
class GeventServer(ServerAdapter):
2948
    """ Untested. Options:
2949
2950
        * `fast` (default: False) uses libevent's http server, but has some
2951
          issues: No streaming, no pipelining, no SSL.
2952
        * See gevent.wsgi.WSGIServer() documentation for more options.
2953
    """
2954
2955
    def run(self, handler):
2956
        from gevent import wsgi, pywsgi, local
2957
        if not isinstance(threading.local(), local.local):
2958
            msg = "Bottle requires gevent.monkey.patch_all() (before import)"
2959
            raise RuntimeError(msg)
2960
        if not self.options.pop('fast', None): wsgi = pywsgi
2961
        self.options['log'] = None if self.quiet else 'default'
2962
        address = (self.host, self.port)
2963
        server = wsgi.WSGIServer(address, handler, **self.options)
2964
        if 'BOTTLE_CHILD' in os.environ:
2965
            import signal
2966
            signal.signal(signal.SIGINT, lambda s, f: server.stop())
2967
        server.serve_forever()
2968
2969
2970
class GeventSocketIOServer(ServerAdapter):
2971
    def run(self, handler):
2972
        from socketio import server
2973
        address = (self.host, self.port)
2974
        server.SocketIOServer(address, handler, **self.options).serve_forever()
2975
2976
2977
class GunicornServer(ServerAdapter):
2978
    """ Untested. See http://gunicorn.org/configure.html for options. """
2979
2980
    def run(self, handler):
2981
        from gunicorn.app.base import Application
2982
2983
        config = {'bind': "%s:%d" % (self.host, int(self.port))}
2984
        config.update(self.options)
2985
2986
        class GunicornApplication(Application):
2987
            def init(self, parser, opts, args):
2988
                return config
2989
2990
            def load(self):
2991
                return handler
2992
2993
        GunicornApplication().run()
2994
2995
2996
class EventletServer(ServerAdapter):
2997
    """ Untested. Options:
2998
2999
        * `backlog` adjust the eventlet backlog parameter which is the maximum
3000
          number of queued connections. Should be at least 1; the maximum
3001
          value is system-dependent.
3002
        * `family`: (default is 2) socket family, optional. See socket
3003
          documentation for available families.
3004
    """
3005
3006
    def run(self, handler):
3007
        from eventlet import wsgi, listen, patcher
3008
        if not patcher.is_monkey_patched(os):
3009
            msg = "Bottle requires eventlet.monkey_patch() (before import)"
3010
            raise RuntimeError(msg)
3011
        socket_args = {}
3012
        for arg in ('backlog', 'family'):
3013
            try:
3014
                socket_args[arg] = self.options.pop(arg)
3015
            except KeyError:
3016
                pass
3017
        address = (self.host, self.port)
3018
        try:
3019
            wsgi.server(listen(address, **socket_args), handler,
3020
                        log_output=(not self.quiet))
3021
        except TypeError:
3022
            # Fallback, if we have old version of eventlet
3023
            wsgi.server(listen(address), handler)
3024
3025
3026
class RocketServer(ServerAdapter):
3027
    """ Untested. """
3028
3029
    def run(self, handler):
3030
        from rocket import Rocket
3031
        server = Rocket((self.host, self.port), 'wsgi', {'wsgi_app': handler})
3032
        server.start()
3033
3034
3035
class BjoernServer(ServerAdapter):
3036
    """ Fast server written in C: https://github.com/jonashaag/bjoern """
3037
3038
    def run(self, handler):
3039
        from bjoern import run
3040
        run(handler, self.host, self.port)
3041
3042
3043
class AiohttpServer(ServerAdapter):
3044
    """ Untested. 
3045
        aiohttp
3046
        https://pypi.python.org/pypi/aiohttp/
3047
    """
3048
3049
    def run(self, handler):
3050
        import asyncio
3051
        from aiohttp.wsgi import WSGIServerHttpProtocol
3052
        self.loop = asyncio.new_event_loop()
3053
        asyncio.set_event_loop(self.loop)
3054
3055
        protocol_factory = lambda: WSGIServerHttpProtocol(
3056
            handler,
3057
            readpayload=True,
3058
            debug=(not self.quiet))
3059
        self.loop.run_until_complete(self.loop.create_server(protocol_factory,
3060
                                                             self.host,
3061
                                                             self.port))
3062
3063
        if 'BOTTLE_CHILD' in os.environ:
3064
            import signal
3065
            signal.signal(signal.SIGINT, lambda s, f: self.loop.stop())
3066
3067
        try:
3068
            self.loop.run_forever()
3069
        except KeyboardInterrupt:
3070
            self.loop.stop()
3071
3072
3073
class AutoServer(ServerAdapter):
3074
    """ Untested. """
3075
    adapters = [WaitressServer, PasteServer, TwistedServer, CherryPyServer,
3076
                WSGIRefServer]
3077
3078
    def run(self, handler):
3079
        for sa in self.adapters:
3080
            try:
3081
                return sa(self.host, self.port, **self.options).run(handler)
3082
            except ImportError:
3083
                pass
3084
3085
3086
server_names = {
3087
    'cgi': CGIServer,
3088
    'flup': FlupFCGIServer,
3089
    'wsgiref': WSGIRefServer,
3090
    'waitress': WaitressServer,
3091
    'cherrypy': CherryPyServer,
3092
    'paste': PasteServer,
3093
    'fapws3': FapwsServer,
3094
    'tornado': TornadoServer,
3095
    'gae': AppEngineServer,
3096
    'twisted': TwistedServer,
3097
    'diesel': DieselServer,
3098
    'meinheld': MeinheldServer,
3099
    'gunicorn': GunicornServer,
3100
    'eventlet': EventletServer,
3101
    'gevent': GeventServer,
3102
    'geventSocketIO': GeventSocketIOServer,
3103
    'rocket': RocketServer,
3104
    'bjoern': BjoernServer,
3105
    'aiohttp': AiohttpServer,
3106
    'auto': AutoServer,
3107
}
3108
3109
###############################################################################
3110
# Application Control ##########################################################
3111
###############################################################################
3112
3113
3114
def load(target, **namespace):
3115
    """ Import a module or fetch an object from a module.
3116
3117
        * ``package.module`` returns `module` as a module object.
3118
        * ``pack.mod:name`` returns the module variable `name` from `pack.mod`.
3119
        * ``pack.mod:func()`` calls `pack.mod.func()` and returns the result.
3120
3121
        The last form accepts not only function calls, but any type of
3122
        expression. Keyword arguments passed to this function are available as
3123
        local variables. Example: ``import_string('re:compile(x)', x='[a-z]')``
3124
    """
3125
    module, target = target.split(":", 1) if ':' in target else (target, None)
3126
    if module not in sys.modules: __import__(module)
3127
    if not target: return sys.modules[module]
3128
    if target.isalnum(): return getattr(sys.modules[module], target)
3129
    package_name = module.split('.')[0]
3130
    namespace[package_name] = sys.modules[package_name]
3131
    return eval('%s.%s' % (module, target), namespace)
3132
3133
3134
def load_app(target):
3135
    """ Load a bottle application from a module and make sure that the import
3136
        does not affect the current default application, but returns a separate
3137
        application object. See :func:`load` for the target parameter. """
3138
    global NORUN
3139
    NORUN, nr_old = True, NORUN
3140
    tmp = default_app.push()  # Create a new "default application"
3141
    try:
3142
        rv = load(target)  # Import the target module
3143
        return rv if callable(rv) else tmp
3144
    finally:
3145
        default_app.remove(tmp)  # Remove the temporary added default application
3146
        NORUN = nr_old
3147
3148
3149
_debug = debug
3150
3151
3152
def run(app=None,
3153
        server='wsgiref',
3154
        host='127.0.0.1',
3155
        port=8080,
3156
        interval=1,
3157
        reloader=False,
3158
        quiet=False,
3159
        plugins=None,
3160
        debug=None, **kargs):
3161
    """ Start a server instance. This method blocks until the server terminates.
3162
3163
        :param app: WSGI application or target string supported by
3164
               :func:`load_app`. (default: :func:`default_app`)
3165
        :param server: Server adapter to use. See :data:`server_names` keys
3166
               for valid names or pass a :class:`ServerAdapter` subclass.
3167
               (default: `wsgiref`)
3168
        :param host: Server address to bind to. Pass ``0.0.0.0`` to listens on
3169
               all interfaces including the external one. (default: 127.0.0.1)
3170
        :param port: Server port to bind to. Values below 1024 require root
3171
               privileges. (default: 8080)
3172
        :param reloader: Start auto-reloading server? (default: False)
3173
        :param interval: Auto-reloader interval in seconds (default: 1)
3174
        :param quiet: Suppress output to stdout and stderr? (default: False)
3175
        :param options: Options passed to the server adapter.
3176
     """
3177
    if NORUN: return
3178
    if reloader and not os.environ.get('BOTTLE_CHILD'):
3179
        import subprocess
3180
        lockfile = None
3181
        try:
3182
            fd, lockfile = tempfile.mkstemp(prefix='bottle.', suffix='.lock')
3183
            os.close(fd)  # We only need this file to exist. We never write to it
3184
            while os.path.exists(lockfile):
3185
                args = [sys.executable] + sys.argv
3186
                environ = os.environ.copy()
3187
                environ['BOTTLE_CHILD'] = 'true'
3188
                environ['BOTTLE_LOCKFILE'] = lockfile
3189
                p = subprocess.Popen(args, env=environ)
3190
                while p.poll() is None:  # Busy wait...
3191
                    os.utime(lockfile, None)  # I am alive!
3192
                    time.sleep(interval)
3193
                if p.poll() != 3:
3194
                    if os.path.exists(lockfile): os.unlink(lockfile)
3195
                    sys.exit(p.poll())
3196
        except KeyboardInterrupt:
3197
            pass
3198
        finally:
3199
            if os.path.exists(lockfile):
3200
                os.unlink(lockfile)
3201
        return
3202
3203
    try:
3204
        if debug is not None: _debug(debug)
3205
        app = app or default_app()
3206
        if isinstance(app, basestring):
3207
            app = load_app(app)
3208
        if not callable(app):
3209
            raise ValueError("Application is not callable: %r" % app)
3210
3211
        for plugin in plugins or []:
3212
            if isinstance(plugin, basestring):
3213
                plugin = load(plugin)
3214
            app.install(plugin)
3215
3216
        if server in server_names:
3217
            server = server_names.get(server)
3218
        if isinstance(server, basestring):
3219
            server = load(server)
3220
        if isinstance(server, type):
3221
            server = server(host=host, port=port, **kargs)
3222
        if not isinstance(server, ServerAdapter):
3223
            raise ValueError("Unknown or unsupported server: %r" % server)
3224
3225
        server.quiet = server.quiet or quiet
3226
        if not server.quiet:
3227
            _stderr("Bottle v%s server starting up (using %s)...\n" %
3228
                    (__version__, repr(server)))
3229
            _stderr("Listening on http://%s:%d/\n" %
3230
                    (server.host, server.port))
3231
            _stderr("Hit Ctrl-C to quit.\n\n")
3232
3233
        if reloader:
3234
            lockfile = os.environ.get('BOTTLE_LOCKFILE')
3235
            bgcheck = FileCheckerThread(lockfile, interval)
3236
            with bgcheck:
3237
                server.run(app)
3238
            if bgcheck.status == 'reload':
3239
                sys.exit(3)
3240
        else:
3241
            server.run(app)
3242
    except KeyboardInterrupt:
3243
        pass
3244
    except (SystemExit, MemoryError):
3245
        raise
3246
    except:
3247
        if not reloader: raise
3248
        if not getattr(server, 'quiet', quiet):
3249
            print_exc()
3250
        time.sleep(interval)
3251
        sys.exit(3)
3252
3253
3254
class FileCheckerThread(threading.Thread):
3255
    """ Interrupt main-thread as soon as a changed module file is detected,
3256
        the lockfile gets deleted or gets to old. """
3257
3258
    def __init__(self, lockfile, interval):
3259
        threading.Thread.__init__(self)
3260
        self.daemon = True
3261
        self.lockfile, self.interval = lockfile, interval
3262
        #: Is one of 'reload', 'error' or 'exit'
3263
        self.status = None
3264
3265
    def run(self):
3266
        exists = os.path.exists
3267
        mtime = lambda p: os.stat(p).st_mtime
3268
        files = dict()
3269
3270
        for module in list(sys.modules.values()):
3271
            path = getattr(module, '__file__', '')
3272
            if path[-4:] in ('.pyo', '.pyc'): path = path[:-1]
3273
            if path and exists(path): files[path] = mtime(path)
3274
3275
        while not self.status:
3276
            if not exists(self.lockfile)\
3277
            or mtime(self.lockfile) < time.time() - self.interval - 5:
3278
                self.status = 'error'
3279
                thread.interrupt_main()
3280
            for path, lmtime in list(files.items()):
3281
                if not exists(path) or mtime(path) > lmtime:
3282
                    self.status = 'reload'
3283
                    thread.interrupt_main()
3284
                    break
3285
            time.sleep(self.interval)
3286
3287
    def __enter__(self):
3288
        self.start()
3289
3290
    def __exit__(self, exc_type, *_):
3291
        if not self.status: self.status = 'exit'  # silent exit
3292
        self.join()
3293
        return exc_type is not None and issubclass(exc_type, KeyboardInterrupt)
3294
3295
###############################################################################
3296
# Template Adapters ############################################################
3297
###############################################################################
3298
3299
3300
class TemplateError(HTTPError):
3301
    def __init__(self, message):
3302
        HTTPError.__init__(self, 500, message)
3303
3304
3305
class BaseTemplate(object):
3306
    """ Base class and minimal API for template adapters """
3307
    extensions = ['tpl', 'html', 'thtml', 'stpl']
3308
    settings = {}  #used in prepare()
3309
    defaults = {}  #used in render()
3310
3311
    def __init__(self,
3312
                 source=None,
3313
                 name=None,
3314
                 lookup=None,
3315
                 encoding='utf8', **settings):
3316
        """ Create a new template.
3317
        If the source parameter (str or buffer) is missing, the name argument
3318
        is used to guess a template filename. Subclasses can assume that
3319
        self.source and/or self.filename are set. Both are strings.
3320
        The lookup, encoding and settings parameters are stored as instance
3321
        variables.
3322
        The lookup parameter stores a list containing directory paths.
3323
        The encoding parameter should be used to decode byte strings or files.
3324
        The settings parameter contains a dict for engine-specific settings.
3325
        """
3326
        self.name = name
3327
        self.source = source.read() if hasattr(source, 'read') else source
3328
        self.filename = source.filename if hasattr(source, 'filename') else None
3329
        self.lookup = [os.path.abspath(x) for x in lookup] if lookup else []
3330
        self.encoding = encoding
3331
        self.settings = self.settings.copy()  # Copy from class variable
3332
        self.settings.update(settings)  # Apply
3333
        if not self.source and self.name:
3334
            self.filename = self.search(self.name, self.lookup)
3335
            if not self.filename:
3336
                raise TemplateError('Template %s not found.' % repr(name))
3337
        if not self.source and not self.filename:
3338
            raise TemplateError('No template specified.')
3339
        self.prepare(**self.settings)
3340
3341
    @classmethod
3342
    def search(cls, name, lookup=None):
3343
        """ Search name in all directories specified in lookup.
3344
        First without, then with common extensions. Return first hit. """
3345
        if not lookup:
3346
            depr('The template lookup path list should not be empty.',
3347
                 True)  #0.12
3348
            lookup = ['.']
3349
3350
        if os.path.isabs(name) and os.path.isfile(name):
3351
            depr('Absolute template path names are deprecated.', True)  #0.12
3352
            return os.path.abspath(name)
3353
3354
        for spath in lookup:
3355
            spath = os.path.abspath(spath) + os.sep
3356
            fname = os.path.abspath(os.path.join(spath, name))
3357
            if not fname.startswith(spath): continue
3358
            if os.path.isfile(fname): return fname
3359
            for ext in cls.extensions:
3360
                if os.path.isfile('%s.%s' % (fname, ext)):
3361
                    return '%s.%s' % (fname, ext)
3362
3363
    @classmethod
3364
    def global_config(cls, key, *args):
3365
        """ This reads or sets the global settings stored in class.settings. """
3366
        if args:
3367
            cls.settings = cls.settings.copy()  # Make settings local to class
3368
            cls.settings[key] = args[0]
3369
        else:
3370
            return cls.settings[key]
3371
3372
    def prepare(self, **options):
3373
        """ Run preparations (parsing, caching, ...).
3374
        It should be possible to call this again to refresh a template or to
3375
        update settings.
3376
        """
3377
        raise NotImplementedError
3378
3379
    def render(self, *args, **kwargs):
3380
        """ Render the template with the specified local variables and return
3381
        a single byte or unicode string. If it is a byte string, the encoding
3382
        must match self.encoding. This method must be thread-safe!
3383
        Local variables may be provided in dictionaries (args)
3384
        or directly, as keywords (kwargs).
3385
        """
3386
        raise NotImplementedError
3387
3388
3389
class MakoTemplate(BaseTemplate):
3390
    def prepare(self, **options):
3391
        from mako.template import Template
3392
        from mako.lookup import TemplateLookup
3393
        options.update({'input_encoding': self.encoding})
3394
        options.setdefault('format_exceptions', bool(DEBUG))
3395
        lookup = TemplateLookup(directories=self.lookup, **options)
3396
        if self.source:
3397
            self.tpl = Template(self.source, lookup=lookup, **options)
3398
        else:
3399
            self.tpl = Template(uri=self.name,
3400
                                filename=self.filename,
3401
                                lookup=lookup, **options)
3402
3403
    def render(self, *args, **kwargs):
3404
        for dictarg in args:
3405
            kwargs.update(dictarg)
3406
        _defaults = self.defaults.copy()
3407
        _defaults.update(kwargs)
3408
        return self.tpl.render(**_defaults)
3409
3410
3411
class CheetahTemplate(BaseTemplate):
3412
    def prepare(self, **options):
3413
        from Cheetah.Template import Template
3414
        self.context = threading.local()
3415
        self.context.vars = {}
3416
        options['searchList'] = [self.context.vars]
3417
        if self.source:
3418
            self.tpl = Template(source=self.source, **options)
3419
        else:
3420
            self.tpl = Template(file=self.filename, **options)
3421
3422
    def render(self, *args, **kwargs):
3423
        for dictarg in args:
3424
            kwargs.update(dictarg)
3425
        self.context.vars.update(self.defaults)
3426
        self.context.vars.update(kwargs)
3427
        out = str(self.tpl)
3428
        self.context.vars.clear()
3429
        return out
3430
3431
3432
class Jinja2Template(BaseTemplate):
3433
    def prepare(self, filters=None, tests=None, globals={}, **kwargs):
3434
        from jinja2 import Environment, FunctionLoader
3435
        self.env = Environment(loader=FunctionLoader(self.loader), **kwargs)
3436
        if filters: self.env.filters.update(filters)
3437
        if tests: self.env.tests.update(tests)
3438
        if globals: self.env.globals.update(globals)
3439
        if self.source:
3440
            self.tpl = self.env.from_string(self.source)
3441
        else:
3442
            self.tpl = self.env.get_template(self.filename)
3443
3444
    def render(self, *args, **kwargs):
3445
        for dictarg in args:
3446
            kwargs.update(dictarg)
3447
        _defaults = self.defaults.copy()
3448
        _defaults.update(kwargs)
3449
        return self.tpl.render(**_defaults)
3450
3451
    def loader(self, name):
3452
        fname = self.search(name, self.lookup)
3453
        if not fname: return
3454
        with open(fname, "rb") as f:
3455
            return f.read().decode(self.encoding)
3456
3457
3458
class SimpleTemplate(BaseTemplate):
3459
    def prepare(self,
3460
                escape_func=html_escape,
3461
                noescape=False,
3462
                syntax=None, **ka):
3463
        self.cache = {}
3464
        enc = self.encoding
3465
        self._str = lambda x: touni(x, enc)
3466
        self._escape = lambda x: escape_func(touni(x, enc))
3467
        self.syntax = syntax
3468
        if noescape:
3469
            self._str, self._escape = self._escape, self._str
3470
3471
    @cached_property
3472
    def co(self):
3473
        return compile(self.code, self.filename or '<string>', 'exec')
3474
3475
    @cached_property
3476
    def code(self):
3477
        source = self.source
3478
        if not source:
3479
            with open(self.filename, 'rb') as f:
3480
                source = f.read()
3481
        try:
3482
            source, encoding = touni(source), 'utf8'
3483
        except UnicodeError:
3484
            depr('Template encodings other than utf8 are not supported.')  #0.11
3485
            source, encoding = touni(source, 'latin1'), 'latin1'
3486
        parser = StplParser(source, encoding=encoding, syntax=self.syntax)
3487
        code = parser.translate()
3488
        self.encoding = parser.encoding
3489
        return code
3490
3491
    def _rebase(self, _env, _name=None, **kwargs):
3492
        _env['_rebase'] = (_name, kwargs)
3493
3494
    def _include(self, _env, _name=None, **kwargs):
3495
        env = _env.copy()
3496
        env.update(kwargs)
3497
        if _name not in self.cache:
3498
            self.cache[_name] = self.__class__(name=_name, lookup=self.lookup)
3499
        return self.cache[_name].execute(env['_stdout'], env)
3500
3501
    def execute(self, _stdout, kwargs):
3502
        env = self.defaults.copy()
3503
        env.update(kwargs)
3504
        env.update({
3505
            '_stdout': _stdout,
3506
            '_printlist': _stdout.extend,
3507
            'include': functools.partial(self._include, env),
3508
            'rebase': functools.partial(self._rebase, env),
3509
            '_rebase': None,
3510
            '_str': self._str,
3511
            '_escape': self._escape,
3512
            'get': env.get,
3513
            'setdefault': env.setdefault,
3514
            'defined': env.__contains__
3515
        })
3516
        eval(self.co, env)
3517
        if env.get('_rebase'):
3518
            subtpl, rargs = env.pop('_rebase')
3519
            rargs['base'] = ''.join(_stdout)  #copy stdout
3520
            del _stdout[:]  # clear stdout
3521
            return self._include(env, subtpl, **rargs)
3522
        return env
3523
3524
    def render(self, *args, **kwargs):
3525
        """ Render the template using keyword arguments as local variables. """
3526
        env = {}
3527
        stdout = []
3528
        for dictarg in args:
3529
            env.update(dictarg)
3530
        env.update(kwargs)
3531
        self.execute(stdout, env)
3532
        return ''.join(stdout)
3533
3534
3535
class StplSyntaxError(TemplateError):
3536
3537
    pass
3538
3539
3540
class StplParser(object):
3541
    """ Parser for stpl templates. """
3542
    _re_cache = {}  #: Cache for compiled re patterns
3543
3544
    # This huge pile of voodoo magic splits python code into 8 different tokens.
3545
    # We use the verbose (?x) regex mode to make this more manageable
3546
3547
    _re_tok = _re_inl = r'''((?mx)         # verbose and dot-matches-newline mode
3548
        [urbURB]*
3549
        (?:  ''(?!')
3550
            |""(?!")
3551
            |'{6}
3552
            |"{6}
3553
            |'(?:[^\\']|\\.)+?'
3554
            |"(?:[^\\"]|\\.)+?"
3555
            |'{3}(?:[^\\]|\\.|\n)+?'{3}
3556
            |"{3}(?:[^\\]|\\.|\n)+?"{3}
3557
        )
3558
    )'''
3559
3560
    _re_inl = _re_tok.replace(r'|\n', '')  # We re-use this string pattern later
3561
3562
    _re_tok += r'''
3563
        # 2: Comments (until end of line, but not the newline itself)
3564
        |(\#.*)
3565
3566
        # 3: Open and close (4) grouping tokens
3567
        |([\[\{\(])
3568
        |([\]\}\)])
3569
3570
        # 5,6: Keywords that start or continue a python block (only start of line)
3571
        |^([\ \t]*(?:if|for|while|with|try|def|class)\b)
3572
        |^([\ \t]*(?:elif|else|except|finally)\b)
3573
3574
        # 7: Our special 'end' keyword (but only if it stands alone)
3575
        |((?:^|;)[\ \t]*end[\ \t]*(?=(?:%(block_close)s[\ \t]*)?\r?$|;|\#))
3576
3577
        # 8: A customizable end-of-code-block template token (only end of line)
3578
        |(%(block_close)s[\ \t]*(?=\r?$))
3579
3580
        # 9: And finally, a single newline. The 10th token is 'everything else'
3581
        |(\r?\n)
3582
    '''
3583
3584
    # Match the start tokens of code areas in a template
3585
    _re_split = r'''(?m)^[ \t]*(\\?)((%(line_start)s)|(%(block_start)s))'''
3586
    # Match inline statements (may contain python strings)
3587
    _re_inl = r'''%%(inline_start)s((?:%s|[^'"\n]+?)*?)%%(inline_end)s''' % _re_inl
3588
3589
    default_syntax = '<% %> % {{ }}'
3590
3591
    def __init__(self, source, syntax=None, encoding='utf8'):
3592
        self.source, self.encoding = touni(source, encoding), encoding
3593
        self.set_syntax(syntax or self.default_syntax)
3594
        self.code_buffer, self.text_buffer = [], []
3595
        self.lineno, self.offset = 1, 0
3596
        self.indent, self.indent_mod = 0, 0
3597
        self.paren_depth = 0
3598
3599
    def get_syntax(self):
3600
        """ Tokens as a space separated string (default: <% %> % {{ }}) """
3601
        return self._syntax
3602
3603
    def set_syntax(self, syntax):
3604
        self._syntax = syntax
3605
        self._tokens = syntax.split()
3606
        if not syntax in self._re_cache:
3607
            names = 'block_start block_close line_start inline_start inline_end'
3608
            etokens = map(re.escape, self._tokens)
3609
            pattern_vars = dict(zip(names.split(), etokens))
3610
            patterns = (self._re_split, self._re_tok, self._re_inl)
3611
            patterns = [re.compile(p % pattern_vars) for p in patterns]
3612
            self._re_cache[syntax] = patterns
3613
        self.re_split, self.re_tok, self.re_inl = self._re_cache[syntax]
3614
3615
    syntax = property(get_syntax, set_syntax)
3616
3617
    def translate(self):
3618
        if self.offset: raise RuntimeError('Parser is a one time instance.')
3619
        while True:
3620
            m = self.re_split.search(self.source, pos=self.offset)
3621
            if m:
3622
                text = self.source[self.offset:m.start()]
3623
                self.text_buffer.append(text)
3624
                self.offset = m.end()
3625
                if m.group(1):  # Escape syntax
3626
                    line, sep, _ = self.source[self.offset:].partition('\n')
3627
                    self.text_buffer.append(self.source[m.start():m.start(1)] +
3628
                                            m.group(2) + line + sep)
3629
                    self.offset += len(line + sep)
3630
                    continue
3631
                self.flush_text()
3632
                self.offset += self.read_code(self.source[self.offset:],
3633
                                              multiline=bool(m.group(4)))
3634
            else:
3635
                break
3636
        self.text_buffer.append(self.source[self.offset:])
3637
        self.flush_text()
3638
        return ''.join(self.code_buffer)
3639
3640
    def read_code(self, pysource, multiline):
3641
        code_line, comment = '', ''
3642
        offset = 0
3643
        while True:
3644
            m = self.re_tok.search(pysource, pos=offset)
3645
            if not m:
3646
                code_line += pysource[offset:]
3647
                offset = len(pysource)
3648
                self.write_code(code_line.strip(), comment)
3649
                break
3650
            code_line += pysource[offset:m.start()]
3651
            offset = m.end()
3652
            _str, _com, _po, _pc, _blk1, _blk2, _end, _cend, _nl = m.groups()
3653
            if self.paren_depth > 0 and (_blk1 or _blk2):  # a if b else c
3654
                code_line += _blk1 or _blk2
3655
                continue
3656
            if _str:  # Python string
3657
                code_line += _str
3658
            elif _com:  # Python comment (up to EOL)
3659
                comment = _com
3660
                if multiline and _com.strip().endswith(self._tokens[1]):
3661
                    multiline = False  # Allow end-of-block in comments
3662
            elif _po:  # open parenthesis
3663
                self.paren_depth += 1
3664
                code_line += _po
3665
            elif _pc:  # close parenthesis
3666
                if self.paren_depth > 0:
3667
                    # we could check for matching parentheses here, but it's
3668
                    # easier to leave that to python - just check counts
3669
                    self.paren_depth -= 1
3670
                code_line += _pc
3671
            elif _blk1:  # Start-block keyword (if/for/while/def/try/...)
3672
                code_line, self.indent_mod = _blk1, -1
3673
                self.indent += 1
3674
            elif _blk2:  # Continue-block keyword (else/elif/except/...)
3675
                code_line, self.indent_mod = _blk2, -1
3676
            elif _end:  # The non-standard 'end'-keyword (ends a block)
3677
                self.indent -= 1
3678
            elif _cend:  # The end-code-block template token (usually '%>')
3679
                if multiline: multiline = False
3680
                else: code_line += _cend
3681
            else:  # \n
3682
                self.write_code(code_line.strip(), comment)
3683
                self.lineno += 1
3684
                code_line, comment, self.indent_mod = '', '', 0
3685
                if not multiline:
3686
                    break
3687
3688
        return offset
3689
3690
    def flush_text(self):
3691
        text = ''.join(self.text_buffer)
3692
        del self.text_buffer[:]
3693
        if not text: return
3694
        parts, pos, nl = [], 0, '\\\n' + '  ' * self.indent
3695
        for m in self.re_inl.finditer(text):
3696
            prefix, pos = text[pos:m.start()], m.end()
3697
            if prefix:
3698
                parts.append(nl.join(map(repr, prefix.splitlines(True))))
3699
            if prefix.endswith('\n'): parts[-1] += nl
3700
            parts.append(self.process_inline(m.group(1).strip()))
3701
        if pos < len(text):
3702
            prefix = text[pos:]
3703
            lines = prefix.splitlines(True)
3704
            if lines[-1].endswith('\\\\\n'): lines[-1] = lines[-1][:-3]
3705
            elif lines[-1].endswith('\\\\\r\n'): lines[-1] = lines[-1][:-4]
3706
            parts.append(nl.join(map(repr, lines)))
3707
        code = '_printlist((%s,))' % ', '.join(parts)
3708
        self.lineno += code.count('\n') + 1
3709
        self.write_code(code)
3710
3711
    @staticmethod
3712
    def process_inline(chunk):
3713
        if chunk[0] == '!': return '_str(%s)' % chunk[1:]
3714
        return '_escape(%s)' % chunk
3715
3716
    def write_code(self, line, comment=''):
3717
        code = '  ' * (self.indent + self.indent_mod)
3718
        code += line.lstrip() + comment + '\n'
3719
        self.code_buffer.append(code)
3720
3721
3722
def template(*args, **kwargs):
3723
    """
3724
    Get a rendered template as a string iterator.
3725
    You can use a name, a filename or a template string as first parameter.
3726
    Template rendering arguments can be passed as dictionaries
3727
    or directly (as keyword arguments).
3728
    """
3729
    tpl = args[0] if args else None
3730
    adapter = kwargs.pop('template_adapter', SimpleTemplate)
3731
    lookup = kwargs.pop('template_lookup', TEMPLATE_PATH)
3732
    tplid = (id(lookup), tpl)
3733
    if tplid not in TEMPLATES or DEBUG:
3734
        settings = kwargs.pop('template_settings', {})
3735
        if isinstance(tpl, adapter):
3736
            TEMPLATES[tplid] = tpl
3737
            if settings: TEMPLATES[tplid].prepare(**settings)
3738
        elif "\n" in tpl or "{" in tpl or "%" in tpl or '$' in tpl:
3739
            TEMPLATES[tplid] = adapter(source=tpl, lookup=lookup, **settings)
3740
        else:
3741
            TEMPLATES[tplid] = adapter(name=tpl, lookup=lookup, **settings)
3742
    if not TEMPLATES[tplid]:
3743
        abort(500, 'Template (%s) not found' % tpl)
3744
    for dictarg in args[1:]:
3745
        kwargs.update(dictarg)
3746
    return TEMPLATES[tplid].render(kwargs)
3747
3748
3749
mako_template = functools.partial(template, template_adapter=MakoTemplate)
3750
cheetah_template = functools.partial(template,
3751
                                     template_adapter=CheetahTemplate)
3752
jinja2_template = functools.partial(template, template_adapter=Jinja2Template)
3753
3754
3755
def view(tpl_name, **defaults):
3756
    """ Decorator: renders a template for a handler.
3757
        The handler can control its behavior like that:
3758
3759
          - return a dict of template vars to fill out the template
3760
          - return something other than a dict and the view decorator will not
3761
            process the template, but return the handler result as is.
3762
            This includes returning a HTTPResponse(dict) to get,
3763
            for instance, JSON with autojson or other castfilters.
3764
    """
3765
3766
    def decorator(func):
3767
3768
        @functools.wraps(func)
3769
        def wrapper(*args, **kwargs):
3770
            result = func(*args, **kwargs)
3771
            if isinstance(result, (dict, DictMixin)):
3772
                tplvars = defaults.copy()
3773
                tplvars.update(result)
3774
                return template(tpl_name, **tplvars)
3775
            elif result is None:
3776
                return template(tpl_name, defaults)
3777
            return result
3778
3779
        return wrapper
3780
3781
    return decorator
3782
3783
3784
mako_view = functools.partial(view, template_adapter=MakoTemplate)
3785
cheetah_view = functools.partial(view, template_adapter=CheetahTemplate)
3786
jinja2_view = functools.partial(view, template_adapter=Jinja2Template)
3787
3788
###############################################################################
3789
# Constants and Globals ########################################################
3790
###############################################################################
3791
3792
TEMPLATE_PATH = ['./', './views/']
3793
TEMPLATES = {}
3794
DEBUG = False
3795
NORUN = False  # If set, run() does nothing. Used by load_app()
3796
3797
#: A dict to map HTTP status codes (e.g. 404) to phrases (e.g. 'Not Found')
3798
HTTP_CODES = httplib.responses
3799
HTTP_CODES[418] = "I'm a teapot"  # RFC 2324
3800
HTTP_CODES[428] = "Precondition Required"
3801
HTTP_CODES[429] = "Too Many Requests"
3802
HTTP_CODES[431] = "Request Header Fields Too Large"
3803
HTTP_CODES[511] = "Network Authentication Required"
3804
_HTTP_STATUS_LINES = dict((k, '%d %s' % (k, v))
3805
                          for (k, v) in HTTP_CODES.items())
3806
3807
#: The default template used for error pages. Override with @error()
3808
ERROR_PAGE_TEMPLATE = """
3809
%%try:
3810
    %%from %s import DEBUG, request
3811
    <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
3812
    <html>
3813
        <head>
3814
            <title>Error: {{e.status}}</title>
3815
            <style type="text/css">
3816
              html {background-color: #eee; font-family: sans-serif;}
3817
              body {background-color: #fff; border: 1px solid #ddd;
3818
                    padding: 15px; margin: 15px;}
3819
              pre {background-color: #eee; border: 1px solid #ddd; padding: 5px;}
3820
            </style>
3821
        </head>
3822
        <body>
3823
            <h1>Error: {{e.status}}</h1>
3824
            <p>Sorry, the requested URL <tt>{{repr(request.url)}}</tt>
3825
               caused an error:</p>
3826
            <pre>{{e.body}}</pre>
3827
            %%if DEBUG and e.exception:
3828
              <h2>Exception:</h2>
3829
              <pre>{{repr(e.exception)}}</pre>
3830
            %%end
3831
            %%if DEBUG and e.traceback:
3832
              <h2>Traceback:</h2>
3833
              <pre>{{e.traceback}}</pre>
3834
            %%end
3835
        </body>
3836
    </html>
3837
%%except ImportError:
3838
    <b>ImportError:</b> Could not generate the error page. Please add bottle to
3839
    the import path.
3840
%%end
3841
""" % __name__
3842
3843
#: A thread-safe instance of :class:`LocalRequest`. If accessed from within a
3844
#: request callback, this instance always refers to the *current* request
3845
#: (even on a multithreaded server).
3846
request = LocalRequest()
3847
3848
#: A thread-safe instance of :class:`LocalResponse`. It is used to change the
3849
#: HTTP response for the *current* request.
3850
response = LocalResponse()
3851
3852
#: A thread-safe namespace. Not used by Bottle.
3853
local = threading.local()
3854
3855
# Initialize app stack (create first empty Bottle app)
3856
# BC: 0.6.4 and needed for run()
3857
app = default_app = AppStack()
3858
app.push()
3859
3860
#: A virtual package that redirects import statements.
3861
#: Example: ``import bottle.ext.sqlite`` actually imports `bottle_sqlite`.
3862
ext = _ImportRedirect('bottle.ext' if __name__ == '__main__' else
3863
                      __name__ + ".ext", 'bottle_%s').module
3864
3865
if __name__ == '__main__':
3866
    opt, args, parser = _cmd_options, _cmd_args, _cmd_parser
3867
    if opt.version:
3868
        _stdout('Bottle %s\n' % __version__)
3869
        sys.exit(0)
3870
    if not args:
3871
        parser.print_help()
3872
        _stderr('\nError: No application entry point specified.\n')
3873
        sys.exit(1)
3874
3875
    sys.path.insert(0, '.')
3876
    sys.modules.setdefault('bottle', sys.modules['__main__'])
3877
3878
    host, port = (opt.bind or 'localhost'), 8080
3879
    if ':' in host and host.rfind(']') < host.rfind(':'):
3880
        host, port = host.rsplit(':', 1)
3881
    host = host.strip('[]')
3882
3883
    run(args[0],
3884
        host=host,
3885
        port=int(port),
3886
        server=opt.server,
3887
        reloader=opt.reload,
3888
        plugins=opt.plugin,
3889
        debug=opt.debug)
3890
3891
# THE END