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