source: osmose-frontend/bottle.py

Last change on this file was beced85, checked in by Jocelyn Jaubert <jocelyn.jaubert@…>, 5 years ago

Upgrade bottle to version 0.12.7

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