Switch to unified view

a b/bottle.py
1
#!/usr/bin/env python
2
# -*- coding: utf-8 -*-
3
"""
4
Bottle is a fast and simple micro-framework for small web applications. It
5
offers request dispatching (Routes) with url parameter support, templates,
6
a built-in HTTP Server and adapters for many third party WSGI/HTTP-server and
7
template engines - all in a single file and with no dependencies other than the
8
Python Standard Library.
9
10
Homepage and documentation: http://bottlepy.org/
11
12
Copyright (c) 2011, Marcel Hellkamp.
13
License: MIT (see LICENSE.txt for details)
14
"""
15
16
from __future__ import with_statement
17
18
__author__ = 'Marcel Hellkamp'
19
__version__ = '0.10.11'
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
24
if __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
38
import sys
39
import base64
40
import cgi
41
import email.utils
42
import functools
43
import hmac
44
import httplib
45
import imp
46
import itertools
47
import mimetypes
48
import os
49
import re
50
import subprocess
51
import tempfile
52
import thread
53
import threading
54
import time
55
import warnings
56
57
from Cookie import SimpleCookie
58
from datetime import date as datedate, datetime, timedelta
59
from tempfile import TemporaryFile
60
from traceback import format_exc, print_exc
61
from urlparse import urljoin, SplitResult as UrlSplitResult
62
63
# Workaround for a bug in some versions of lib2to3 (fixed on CPython 2.7 and 3.2)
64
import urllib
65
urlencode = urllib.urlencode
66
urlquote = urllib.quote
67
urlunquote = urllib.unquote
68
69
try: from collections import MutableMapping as DictMixin
70
except ImportError: # pragma: no cover
71
    from UserDict import DictMixin
72
73
try: import cPickle as pickle
74
except ImportError: # pragma: no cover
75
    import pickle
76
77
try: from json import dumps as json_dumps, loads as json_lds
78
except ImportError: # pragma: no cover
79
    try: from simplejson import dumps as json_dumps, loads as json_lds
80
    except ImportError: # pragma: no cover
81
        try: from django.utils.simplejson import dumps as json_dumps, loads as json_lds
82
        except ImportError: # pragma: no cover
83
            def json_dumps(data):
84
                raise ImportError("JSON support requires Python 2.6 or simplejson.")
85
            json_lds = json_dumps
86
87
py = sys.version_info
88
py3k = py >= (3,0,0)
89
NCTextIOWrapper = None
90
91
if sys.version_info < (2,6,0):
92
    msg = "Python 2.5 support may be dropped in future versions of Bottle."
93
    warnings.warn(msg, DeprecationWarning)
94
95
if py3k: # pragma: no cover
96
    json_loads = lambda s: json_lds(touni(s))
97
    urlunquote = functools.partial(urlunquote, encoding='latin1')
98
    # See Request.POST
99
    from io import BytesIO
100
    def touni(x, enc='utf8', err='strict'):
101
        """ Convert anything to unicode """
102
        return str(x, enc, err) if isinstance(x, bytes) else str(x)
103
    if sys.version_info < (3,2,0):
104
        from io import TextIOWrapper
105
        class NCTextIOWrapper(TextIOWrapper):
106
            ''' Garbage collecting an io.TextIOWrapper(buffer) instance closes
107
                the wrapped buffer. This subclass keeps it open. '''
108
            def close(self): pass
109
else:
110
    json_loads = json_lds
111
    from StringIO import StringIO as BytesIO
112
    bytes = str
113
    def touni(x, enc='utf8', err='strict'):
114
        """ Convert anything to unicode """
115
        return x if isinstance(x, unicode) else unicode(str(x), enc, err)
116
117
def tob(data, enc='utf8'):
118
    """ Convert anything to bytes """
119
    return data.encode(enc) if isinstance(data, unicode) else bytes(data)
120
121
tonat = touni if py3k else tob
122
tonat.__doc__ = """ Convert anything to native strings """
123
124
def try_update_wrapper(wrapper, wrapped, *a, **ka):
125
    try: # Bug: functools breaks if wrapper is an instane method
126
        functools.update_wrapper(wrapper, wrapped, *a, **ka)
127
    except AttributeError: pass
128
129
# 3.2 fixes cgi.FieldStorage to accept bytes (which makes a lot of sense).
130
#     but defaults to utf-8 (which is not always true)
131
# 3.1 needs a workaround.
132
NCTextIOWrapper = None
133
if (3,0,0) < py < (3,2,0):
134
    from io import TextIOWrapper
135
    class NCTextIOWrapper(TextIOWrapper):
136
        def close(self): pass # Keep wrapped buffer open.
137
138
# Backward compatibility
139
def depr(message):
140
    warnings.warn(message, DeprecationWarning, stacklevel=3)
141
142
143
# Small helpers
144
def makelist(data):
145
    if isinstance(data, (tuple, list, set, dict)): return list(data)
146
    elif data: return [data]
147
    else: return []
148
149
150
class DictProperty(object):
151
    ''' Property that maps to a key in a local dict-like attribute. '''
152
    def __init__(self, attr, key=None, read_only=False):
153
        self.attr, self.key, self.read_only = attr, key, read_only
154
155
    def __call__(self, func):
156
        functools.update_wrapper(self, func, updated=[])
157
        self.getter, self.key = func, self.key or func.__name__
158
        return self
159
160
    def __get__(self, obj, cls):
161
        if obj is None: return self
162
        key, storage = self.key, getattr(obj, self.attr)
163
        if key not in storage: storage[key] = self.getter(obj)
164
        return storage[key]
165
166
    def __set__(self, obj, value):
167
        if self.read_only: raise AttributeError("Read-Only property.")
168
        getattr(obj, self.attr)[self.key] = value
169
170
    def __delete__(self, obj):
171
        if self.read_only: raise AttributeError("Read-Only property.")
172
        del getattr(obj, self.attr)[self.key]
173
174
175
class CachedProperty(object):
176
    ''' A property that is only computed once per instance and then replaces
177
        itself with an ordinary attribute. Deleting the attribute resets the
178
        property. '''
179
180
    def __init__(self, func):
181
        self.func = func
182
183
    def __get__(self, obj, cls):
184
        if obj is None: return self
185
        value = obj.__dict__[self.func.__name__] = self.func(obj)
186
        return value
187
188
cached_property = CachedProperty
189
190
191
class lazy_attribute(object): # Does not need configuration -> lower-case name
192
    ''' A property that caches itself to the class object. '''
193
    def __init__(self, func):
194
        functools.update_wrapper(self, func, updated=[])
195
        self.getter = func
196
197
    def __get__(self, obj, cls):
198
        value = self.getter(cls)
199
        setattr(cls, self.__name__, value)
200
        return value
201
202
203
204
205
206
207
###############################################################################
208
# Exceptions and Events ########################################################
209
###############################################################################
210
211
212
class BottleException(Exception):
213
    """ A base class for exceptions used by bottle. """
214
    pass
215
216
217
#TODO: These should subclass BaseRequest
218
219
class HTTPResponse(BottleException):
220
    """ Used to break execution and immediately finish the response """
221
    def __init__(self, output='', status=200, header=None):
222
        super(BottleException, self).__init__("HTTP Response %d" % status)
223
        self.status = int(status)
224
        self.output = output
225
        self.headers = HeaderDict(header) if header else None
226
227
    def apply(self, response):
228
        if self.headers:
229
            for key, value in self.headers.iterallitems():
230
                response.headers[key] = value
231
        response.status = self.status
232
233
234
class HTTPError(HTTPResponse):
235
    """ Used to generate an error page """
236
    def __init__(self, code=500, output='Unknown Error', exception=None,
237
                 traceback=None, header=None):
238
        super(HTTPError, self).__init__(output, code, header)
239
        self.exception = exception
240
        self.traceback = traceback
241
242
    def __repr__(self):
243
        return tonat(template(ERROR_PAGE_TEMPLATE, e=self))
244
245
246
247
248
249
250
###############################################################################
251
# Routing ######################################################################
252
###############################################################################
253
254
255
class RouteError(BottleException):
256
    """ This is a base class for all routing related exceptions """
257
258
259
class RouteReset(BottleException):
260
    """ If raised by a plugin or request handler, the route is reset and all
261
        plugins are re-applied. """
262
263
class RouterUnknownModeError(RouteError): pass
264
265
class RouteSyntaxError(RouteError):
266
    """ The route parser found something not supported by this router """
267
268
class RouteBuildError(RouteError):
269
    """ The route could not been built """
270
271
class Router(object):
272
    ''' A Router is an ordered collection of route->target pairs. It is used to
273
        efficiently match WSGI requests against a number of routes and return
274
        the first target that satisfies the request. The target may be anything,
275
        usually a string, ID or callable object. A route consists of a path-rule
276
        and a HTTP method.
277
278
        The path-rule is either a static path (e.g. `/contact`) or a dynamic
279
        path that contains wildcards (e.g. `/wiki/<page>`). The wildcard syntax
280
        and details on the matching order are described in docs:`routing`.
281
    '''
282
283
    default_pattern = '[^/]+'
284
    default_filter   = 're'
285
    #: Sorry for the mess. It works. Trust me.
286
    rule_syntax = re.compile('(\\\\*)'\
287
        '(?:(?::([a-zA-Z_][a-zA-Z_0-9]*)?()(?:#(.*?)#)?)'\
288
          '|(?:<([a-zA-Z_][a-zA-Z_0-9]*)?(?::([a-zA-Z_]*)'\
289
            '(?::((?:\\\\.|[^\\\\>]+)+)?)?)?>))')
290
291
    def __init__(self, strict=False):
292
        self.rules    = {} # A {rule: Rule} mapping
293
        self.builder  = {} # A rule/name->build_info mapping
294
        self.static   = {} # Cache for static routes: {path: {method: target}}
295
        self.dynamic  = [] # Cache for dynamic routes. See _compile()
296
        #: If true, static routes are no longer checked first.
297
        self.strict_order = strict
298
        self.filters = {'re': self.re_filter, 'int': self.int_filter,
299
                        'float': self.float_filter, 'path': self.path_filter}
300
301
    def re_filter(self, conf):
302
        return conf or self.default_pattern, None, None
303
304
    def int_filter(self, conf):
305
        return r'-?\d+', int, lambda x: str(int(x))
306
307
    def float_filter(self, conf):
308
        return r'-?[\d.]+', float, lambda x: str(float(x))
309
310
    def path_filter(self, conf):
311
        return r'.*?', None, None
312
    
313
    def add_filter(self, name, func):
314
        ''' Add a filter. The provided function is called with the configuration
315
        string as parameter and must return a (regexp, to_python, to_url) tuple.
316
        The first element is a string, the last two are callables or None. '''
317
        self.filters[name] = func
318
    
319
    def parse_rule(self, rule):
320
        ''' Parses a rule into a (name, filter, conf) token stream. If mode is
321
            None, name contains a static rule part. '''
322
        offset, prefix = 0, ''
323
        for match in self.rule_syntax.finditer(rule):
324
            prefix += rule[offset:match.start()]
325
            g = match.groups()
326
            if len(g[0])%2: # Escaped wildcard
327
                prefix += match.group(0)[len(g[0]):]
328
                offset = match.end()
329
                continue
330
            if prefix: yield prefix, None, None
331
            name, filtr, conf = g[1:4] if not g[2] is None else g[4:7]
332
            if not filtr: filtr = self.default_filter
333
            yield name, filtr, conf or None
334
            offset, prefix = match.end(), ''
335
        if offset <= len(rule) or prefix:
336
            yield prefix+rule[offset:], None, None
337
338
    def add(self, rule, method, target, name=None):
339
        ''' Add a new route or replace the target for an existing route. '''
340
        if rule in self.rules:
341
            self.rules[rule][method] = target
342
            if name: self.builder[name] = self.builder[rule]
343
            return
344
345
        target = self.rules[rule] = {method: target}
346
347
        # Build pattern and other structures for dynamic routes
348
        anons = 0      # Number of anonymous wildcards
349
        pattern = ''   # Regular expression  pattern
350
        filters = []   # Lists of wildcard input filters
351
        builder = []   # Data structure for the URL builder
352
        is_static = True
353
        for key, mode, conf in self.parse_rule(rule):
354
            if mode:
355
                is_static = False
356
                mask, in_filter, out_filter = self.filters[mode](conf)
357
                if key:
358
                    pattern += '(?P<%s>%s)' % (key, mask)
359
                else:
360
                    pattern += '(?:%s)' % mask
361
                    key = 'anon%d' % anons; anons += 1
362
                if in_filter: filters.append((key, in_filter))
363
                builder.append((key, out_filter or str))
364
            elif key:
365
                pattern += re.escape(key)
366
                builder.append((None, key))
367
        self.builder[rule] = builder
368
        if name: self.builder[name] = builder
369
370
        if is_static and not self.strict_order:
371
            self.static[self.build(rule)] = target
372
            return
373
374
        def fpat_sub(m):
375
            return m.group(0) if len(m.group(1)) % 2 else m.group(1) + '(?:'
376
        flat_pattern = re.sub(r'(\\*)(\(\?P<[^>]*>|\((?!\?))', fpat_sub, pattern)
377
378
        try:
379
            re_match = re.compile('^(%s)$' % pattern).match
380
        except re.error, e:
381
            raise RouteSyntaxError("Could not add Route: %s (%s)" % (rule, e))
382
383
        def match(path):
384
            """ Return an url-argument dictionary. """
385
            url_args = re_match(path).groupdict()
386
            for name, wildcard_filter in filters:
387
                try:
388
                    url_args[name] = wildcard_filter(url_args[name])
389
                except ValueError:
390
                    raise HTTPError(400, 'Path has wrong format.')
391
            return url_args
392
393
        try:
394
            combined = '%s|(^%s$)' % (self.dynamic[-1][0].pattern, flat_pattern)
395
            self.dynamic[-1] = (re.compile(combined), self.dynamic[-1][1])
396
            self.dynamic[-1][1].append((match, target))
397
        except (AssertionError, IndexError), e: # AssertionError: Too many groups
398
            self.dynamic.append((re.compile('(^%s$)' % flat_pattern),
399
                                [(match, target)]))
400
        return match
401
402
    def build(self, _name, *anons, **query):
403
        ''' Build an URL by filling the wildcards in a rule. '''
404
        builder = self.builder.get(_name)
405
        if not builder: raise RouteBuildError("No route with that name.", _name)
406
        try:
407
            for i, value in enumerate(anons): query['anon%d'%i] = value
408
            url = ''.join([f(query.pop(n)) if n else f for (n,f) in builder])
409
            return url if not query else url+'?'+urlencode(query)
410
        except KeyError, e:
411
            raise RouteBuildError('Missing URL argument: %r' % e.args[0])
412
413
    def match(self, environ):
414
        ''' Return a (target, url_agrs) tuple or raise HTTPError(400/404/405). '''
415
        path, targets, urlargs = environ['PATH_INFO'] or '/', None, {}
416
        if path in self.static:
417
            targets = self.static[path]
418
        else:
419
            for combined, rules in self.dynamic:
420
                match = combined.match(path)
421
                if not match: continue
422
                getargs, targets = rules[match.lastindex - 1]
423
                urlargs = getargs(path) if getargs else {}
424
                break
425
426
        if not targets:
427
            raise HTTPError(404, "Not found: " + repr(environ['PATH_INFO']))
428
        method = environ['REQUEST_METHOD'].upper()
429
        if method in targets:
430
            return targets[method], urlargs
431
        if method == 'HEAD' and 'GET' in targets:
432
            return targets['GET'], urlargs
433
        if 'ANY' in targets:
434
            return targets['ANY'], urlargs
435
        allowed = [verb for verb in targets if verb != 'ANY']
436
        if 'GET' in allowed and 'HEAD' not in allowed:
437
            allowed.append('HEAD')
438
        raise HTTPError(405, "Method not allowed.",
439
                        header=[('Allow',",".join(allowed))])
440
441
442
443
class Route(object):
444
    ''' This class wraps a route callback along with route specific metadata and
445
        configuration and applies Plugins on demand. It is also responsible for
446
        turing an URL path rule into a regular expression usable by the Router.
447
    '''
448
449
450
    def __init__(self, app, rule, method, callback, name=None,
451
                 plugins=None, skiplist=None, **config):
452
        #: The application this route is installed to.
453
        self.app = app
454
        #: The path-rule string (e.g. ``/wiki/:page``).
455
        self.rule = rule
456
        #: The HTTP method as a string (e.g. ``GET``).
457
        self.method = method
458
        #: The original callback with no plugins applied. Useful for introspection.
459
        self.callback = callback
460
        #: The name of the route (if specified) or ``None``.
461
        self.name = name or None
462
        #: A list of route-specific plugins (see :meth:`Bottle.route`).
463
        self.plugins = plugins or []
464
        #: A list of plugins to not apply to this route (see :meth:`Bottle.route`).
465
        self.skiplist = skiplist or []
466
        #: Additional keyword arguments passed to the :meth:`Bottle.route`
467
        #: decorator are stored in this dictionary. Used for route-specific
468
        #: plugin configuration and meta-data.
469
        self.config = ConfigDict(config)
470
471
    def __call__(self, *a, **ka):
472
        depr("Some APIs changed to return Route() instances instead of"\
473
             " callables. Make sure to use the Route.call method and not to"\
474
             " call Route instances directly.")
475
        return self.call(*a, **ka)
476
477
    @cached_property
478
    def call(self):
479
        ''' The route callback with all plugins applied. This property is
480
            created on demand and then cached to speed up subsequent requests.'''
481
        return self._make_callback()
482
483
    def reset(self):
484
        ''' Forget any cached values. The next time :attr:`call` is accessed,
485
            all plugins are re-applied. '''
486
        self.__dict__.pop('call', None)
487
488
    def prepare(self):
489
        ''' Do all on-demand work immediately (useful for debugging).'''
490
        self.call
491
492
    @property
493
    def _context(self):
494
        depr('Switch to Plugin API v2 and access the Route object directly.')
495
        return dict(rule=self.rule, method=self.method, callback=self.callback,
496
                    name=self.name, app=self.app, config=self.config,
497
                    apply=self.plugins, skip=self.skiplist)
498
499
    def all_plugins(self):
500
        ''' Yield all Plugins affecting this route. '''
501
        unique = set()
502
        for p in reversed(self.app.plugins + self.plugins):
503
            if True in self.skiplist: break
504
            name = getattr(p, 'name', False)
505
            if name and (name in self.skiplist or name in unique): continue
506
            if p in self.skiplist or type(p) in self.skiplist: continue
507
            if name: unique.add(name)
508
            yield p
509
510
    def _make_callback(self):
511
        callback = self.callback
512
        for plugin in self.all_plugins():
513
            try:
514
                if hasattr(plugin, 'apply'):
515
                    api = getattr(plugin, 'api', 1)
516
                    context = self if api > 1 else self._context
517
                    callback = plugin.apply(callback, context)
518
                else:
519
                    callback = plugin(callback)
520
            except RouteReset: # Try again with changed configuration.
521
                return self._make_callback()
522
            if not callback is self.callback:
523
                try_update_wrapper(callback, self.callback)
524
        return callback
525
526
527
528
529
530
531
###############################################################################
532
# Application Object ###########################################################
533
###############################################################################
534
535
536
class Bottle(object):
537
    """ WSGI application """
538
539
    def __init__(self, catchall=True, autojson=True, config=None):
540
        """ Create a new bottle instance.
541
            You usually don't do that. Use `bottle.app.push()` instead.
542
        """
543
        self.routes = [] # List of installed :class:`Route` instances.
544
        self.router = Router() # Maps requests to :class:`Route` instances.
545
        self.plugins = [] # List of installed plugins.
546
547
        self.error_handler = {}
548
        #: If true, most exceptions are catched and returned as :exc:`HTTPError`
549
        self.config = ConfigDict(config or {})
550
        self.catchall = catchall
551
        #: An instance of :class:`HooksPlugin`. Empty by default.
552
        self.hooks = HooksPlugin()
553
        self.install(self.hooks)
554
        if autojson:
555
            self.install(JSONPlugin())
556
        self.install(TemplatePlugin())
557
558
    def mount(self, prefix, app, **options):
559
        ''' Mount an application (:class:`Bottle` or plain WSGI) to a specific
560
            URL prefix. Example::
561
562
                root_app.mount('/admin/', admin_app)
563
564
            :param prefix: path prefix or `mount-point`. If it ends in a slash,
565
                that slash is mandatory.
566
            :param app: an instance of :class:`Bottle` or a WSGI application.
567
568
            All other parameters are passed to the underlying :meth:`route` call.
569
        '''
570
        if isinstance(app, basestring):
571
            prefix, app = app, prefix
572
            depr('Parameter order of Bottle.mount() changed.') # 0.10
573
574
        parts = filter(None, prefix.split('/'))
575
        if not parts: raise ValueError('Empty path prefix.')
576
        path_depth = len(parts)
577
        options.setdefault('skip', True)
578
        options.setdefault('method', 'ANY')
579
580
        @self.route('/%s/:#.*#' % '/'.join(parts), **options)
581
        def mountpoint():
582
            try:
583
                request.path_shift(path_depth)
584
                rs = BaseResponse([], 200)
585
                def start_response(status, header):
586
                    rs.status = status
587
                    for name, value in header: rs.add_header(name, value)
588
                    return rs.body.append
589
                rs.body = itertools.chain(rs.body, app(request.environ, start_response))
590
                return HTTPResponse(rs.body, rs.status_code, rs.headers)
591
            finally:
592
                request.path_shift(-path_depth)
593
594
        if not prefix.endswith('/'):
595
            self.route('/' + '/'.join(parts), callback=mountpoint, **options)
596
597
    def install(self, plugin):
598
        ''' Add a plugin to the list of plugins and prepare it for being
599
            applied to all routes of this application. A plugin may be a simple
600
            decorator or an object that implements the :class:`Plugin` API.
601
        '''
602
        if hasattr(plugin, 'setup'): plugin.setup(self)
603
        if not callable(plugin) and not hasattr(plugin, 'apply'):
604
            raise TypeError("Plugins must be callable or implement .apply()")
605
        self.plugins.append(plugin)
606
        self.reset()
607
        return plugin
608
609
    def uninstall(self, plugin):
610
        ''' Uninstall plugins. Pass an instance to remove a specific plugin, a type
611
            object to remove all plugins that match that type, a string to remove
612
            all plugins with a matching ``name`` attribute or ``True`` to remove all
613
            plugins. Return the list of removed plugins. '''
614
        removed, remove = [], plugin
615
        for i, plugin in list(enumerate(self.plugins))[::-1]:
616
            if remove is True or remove is plugin or remove is type(plugin) \
617
            or getattr(plugin, 'name', True) == remove:
618
                removed.append(plugin)
619
                del self.plugins[i]
620
                if hasattr(plugin, 'close'): plugin.close()
621
        if removed: self.reset()
622
        return removed
623
624
    def reset(self, route=None):
625
        ''' Reset all routes (force plugins to be re-applied) and clear all
626
            caches. If an ID or route object is given, only that specific route
627
            is affected. '''
628
        if route is None: routes = self.routes
629
        elif isinstance(route, Route): routes = [route]
630
        else: routes = [self.routes[route]]
631
        for route in routes: route.reset()
632
        if DEBUG:
633
            for route in routes: route.prepare()
634
        self.hooks.trigger('app_reset')
635
636
    def close(self):
637
        ''' Close the application and all installed plugins. '''
638
        for plugin in self.plugins:
639
            if hasattr(plugin, 'close'): plugin.close()
640
        self.stopped = True
641
642
    def match(self, environ):
643
        """ Search for a matching route and return a (:class:`Route` , urlargs)
644
            tuple. The second value is a dictionary with parameters extracted
645
            from the URL. Raise :exc:`HTTPError` (404/405) on a non-match."""
646
        return self.router.match(environ)
647
648
    def get_url(self, routename, **kargs):
649
        """ Return a string that matches a named route """
650
        scriptname = request.environ.get('SCRIPT_NAME', '').strip('/') + '/'
651
        location = self.router.build(routename, **kargs).lstrip('/')
652
        return urljoin(urljoin('/', scriptname), location)
653
654
    def route(self, path=None, method='GET', callback=None, name=None,
655
              apply=None, skip=None, **config):
656
        """ A decorator to bind a function to a request URL. Example::
657
658
                @app.route('/hello/:name')
659
                def hello(name):
660
                    return 'Hello %s' % name
661
662
            The ``:name`` part is a wildcard. See :class:`Router` for syntax
663
            details.
664
665
            :param path: Request path or a list of paths to listen to. If no
666
              path is specified, it is automatically generated from the
667
              signature of the function.
668
            :param method: HTTP method (`GET`, `POST`, `PUT`, ...) or a list of
669
              methods to listen to. (default: `GET`)
670
            :param callback: An optional shortcut to avoid the decorator
671
              syntax. ``route(..., callback=func)`` equals ``route(...)(func)``
672
            :param name: The name for this route. (default: None)
673
            :param apply: A decorator or plugin or a list of plugins. These are
674
              applied to the route callback in addition to installed plugins.
675
            :param skip: A list of plugins, plugin classes or names. Matching
676
              plugins are not installed to this route. ``True`` skips all.
677
678
            Any additional keyword arguments are stored as route-specific
679
            configuration and passed to plugins (see :meth:`Plugin.apply`).
680
        """
681
        if callable(path): path, callback = None, path
682
        plugins = makelist(apply)
683
        skiplist = makelist(skip)
684
        def decorator(callback):
685
            # TODO: Documentation and tests
686
            if isinstance(callback, basestring): callback = load(callback)
687
            for rule in makelist(path) or yieldroutes(callback):
688
                for verb in makelist(method):
689
                    verb = verb.upper()
690
                    route = Route(self, rule, verb, callback, name=name,
691
                                  plugins=plugins, skiplist=skiplist, **config)
692
                    self.routes.append(route)
693
                    self.router.add(rule, verb, route, name=name)
694
                    if DEBUG: route.prepare()
695
            return callback
696
        return decorator(callback) if callback else decorator
697
698
    def get(self, path=None, method='GET', **options):
699
        """ Equals :meth:`route`. """
700
        return self.route(path, method, **options)
701
702
    def post(self, path=None, method='POST', **options):
703
        """ Equals :meth:`route` with a ``POST`` method parameter. """
704
        return self.route(path, method, **options)
705
706
    def put(self, path=None, method='PUT', **options):
707
        """ Equals :meth:`route` with a ``PUT`` method parameter. """
708
        return self.route(path, method, **options)
709
710
    def delete(self, path=None, method='DELETE', **options):
711
        """ Equals :meth:`route` with a ``DELETE`` method parameter. """
712
        return self.route(path, method, **options)
713
714
    def error(self, code=500):
715
        """ Decorator: Register an output handler for a HTTP error code"""
716
        def wrapper(handler):
717
            self.error_handler[int(code)] = handler
718
            return handler
719
        return wrapper
720
721
    def hook(self, name):
722
        """ Return a decorator that attaches a callback to a hook. """
723
        def wrapper(func):
724
            self.hooks.add(name, func)
725
            return func
726
        return wrapper
727
728
    def handle(self, path, method='GET'):
729
        """ (deprecated) Execute the first matching route callback and return
730
            the result. :exc:`HTTPResponse` exceptions are catched and returned.
731
            If :attr:`Bottle.catchall` is true, other exceptions are catched as
732
            well and returned as :exc:`HTTPError` instances (500).
733
        """
734
        depr("This method will change semantics in 0.10. Try to avoid it.")
735
        if isinstance(path, dict):
736
            return self._handle(path)
737
        return self._handle({'PATH_INFO': path, 'REQUEST_METHOD': method.upper()})
738
739
    def _handle(self, environ):
740
        try:
741
            route, args = self.router.match(environ)
742
            environ['route.handle'] = environ['bottle.route'] = route
743
            environ['route.url_args'] = args
744
            return route.call(**args)
745
        except HTTPResponse, r:
746
            return r
747
        except RouteReset:
748
            route.reset()
749
            return self._handle(environ)
750
        except (KeyboardInterrupt, SystemExit, MemoryError):
751
            raise
752
        except Exception, e:
753
            if not self.catchall: raise
754
            stacktrace = format_exc(10)
755
            environ['wsgi.errors'].write(stacktrace)
756
            return HTTPError(500, "Internal Server Error", e, stacktrace)
757
758
    def _cast(self, out, request, response, peek=None):
759
        """ Try to convert the parameter into something WSGI compatible and set
760
        correct HTTP headers when possible.
761
        Support: False, str, unicode, dict, HTTPResponse, HTTPError, file-like,
762
        iterable of strings and iterable of unicodes
763
        """
764
765
        # Empty output is done here
766
        if not out:
767
            response['Content-Length'] = 0
768
            return []
769
        # Join lists of byte or unicode strings. Mixed lists are NOT supported
770
        if isinstance(out, (tuple, list))\
771
        and isinstance(out[0], (bytes, unicode)):
772
            out = out[0][0:0].join(out) # b'abc'[0:0] -> b''
773
        # Encode unicode strings
774
        if isinstance(out, unicode):
775
            out = out.encode(response.charset)
776
        # Byte Strings are just returned
777
        if isinstance(out, bytes):
778
            response['Content-Length'] = len(out)
779
            return [out]
780
        # HTTPError or HTTPException (recursive, because they may wrap anything)
781
        # TODO: Handle these explicitly in handle() or make them iterable.
782
        if isinstance(out, HTTPError):
783
            out.apply(response)
784
            out = self.error_handler.get(out.status, repr)(out)
785
            if isinstance(out, HTTPResponse):
786
                depr('Error handlers must not return :exc:`HTTPResponse`.') #0.9
787
            return self._cast(out, request, response)
788
        if isinstance(out, HTTPResponse):
789
            out.apply(response)
790
            return self._cast(out.output, request, response)
791
792
        # File-like objects.
793
        if hasattr(out, 'read'):
794
            if 'wsgi.file_wrapper' in request.environ:
795
                return request.environ['wsgi.file_wrapper'](out)
796
            elif hasattr(out, 'close') or not hasattr(out, '__iter__'):
797
                return WSGIFileWrapper(out)
798
799
        # Handle Iterables. We peek into them to detect their inner type.
800
        try:
801
            out = iter(out)
802
            first = out.next()
803
            while not first:
804
                first = out.next()
805
        except StopIteration:
806
            return self._cast('', request, response)
807
        except HTTPResponse, e:
808
            first = e
809
        except Exception, e:
810
            first = HTTPError(500, 'Unhandled exception', e, format_exc(10))
811
            if isinstance(e, (KeyboardInterrupt, SystemExit, MemoryError))\
812
            or not self.catchall:
813
                raise
814
        # These are the inner types allowed in iterator or generator objects.
815
        if isinstance(first, HTTPResponse):
816
            return self._cast(first, request, response)
817
        if isinstance(first, bytes):
818
            return itertools.chain([first], out)
819
        if isinstance(first, unicode):
820
            return itertools.imap(lambda x: x.encode(response.charset),
821
                                  itertools.chain([first], out))
822
        return self._cast(HTTPError(500, 'Unsupported response type: %s'\
823
                                         % type(first)), request, response)
824
825
    def wsgi(self, environ, start_response):
826
        """ The bottle WSGI-interface. """
827
        try:
828
            environ['bottle.app'] = self
829
            request.bind(environ)
830
            response.bind()
831
            out = self._cast(self._handle(environ), request, response)
832
            # rfc2616 section 4.3
833
            if response._status_code in (100, 101, 204, 304)\
834
            or request.method == 'HEAD':
835
                if hasattr(out, 'close'): out.close()
836
                out = []
837
            start_response(response._status_line, list(response.iter_headers()))
838
            return out
839
        except (KeyboardInterrupt, SystemExit, MemoryError):
840
            raise
841
        except Exception, e:
842
            if not self.catchall: raise
843
            err = '<h1>Critical error while processing request: %s</h1>' \
844
                  % html_escape(environ.get('PATH_INFO', '/'))
845
            if DEBUG:
846
                err += '<h2>Error:</h2>\n<pre>\n%s\n</pre>\n' \
847
                       '<h2>Traceback:</h2>\n<pre>\n%s\n</pre>\n' \
848
                       % (html_escape(repr(e)), html_escape(format_exc(10)))
849
            environ['wsgi.errors'].write(err)
850
            headers = [('Content-Type', 'text/html; charset=UTF-8')]
851
            start_response('500 INTERNAL SERVER ERROR', headers)
852
            return [tob(err)]
853
854
    def __call__(self, environ, start_response):
855
        ''' Each instance of :class:'Bottle' is a WSGI application. '''
856
        return self.wsgi(environ, start_response)
857
858
859
860
861
862
863
###############################################################################
864
# HTTP and WSGI Tools ##########################################################
865
###############################################################################
866
867
868
class BaseRequest(DictMixin):
869
    """ A wrapper for WSGI environment dictionaries that adds a lot of
870
        convenient access methods and properties. Most of them are read-only."""
871
872
    #: Maximum size of memory buffer for :attr:`body` in bytes.
873
    MEMFILE_MAX = 102400
874
    #: Maximum number pr GET or POST parameters per request
875
    MAX_PARAMS  = 100
876
877
    def __init__(self, environ):
878
        """ Wrap a WSGI environ dictionary. """
879
        #: The wrapped WSGI environ dictionary. This is the only real attribute.
880
        #: All other attributes actually are read-only properties.
881
        self.environ = environ
882
        environ['bottle.request'] = self
883
884
    @property
885
    def path(self):
886
        ''' The value of ``PATH_INFO`` with exactly one prefixed slash (to fix
887
            broken clients and avoid the "empty path" edge case). '''
888
        return '/' + self.environ.get('PATH_INFO','').lstrip('/')
889
890
    @property
891
    def method(self):
892
        ''' The ``REQUEST_METHOD`` value as an uppercase string. '''
893
        return self.environ.get('REQUEST_METHOD', 'GET').upper()
894
895
    @DictProperty('environ', 'bottle.request.headers', read_only=True)
896
    def headers(self):
897
        ''' A :class:`WSGIHeaderDict` that provides case-insensitive access to
898
            HTTP request headers. '''
899
        return WSGIHeaderDict(self.environ)
900
901
    def get_header(self, name, default=None):
902
        ''' Return the value of a request header, or a given default value. '''
903
        return self.headers.get(name, default)
904
905
    @DictProperty('environ', 'bottle.request.cookies', read_only=True)
906
    def cookies(self):
907
        """ Cookies parsed into a :class:`FormsDict`. Signed cookies are NOT
908
            decoded. Use :meth:`get_cookie` if you expect signed cookies. """
909
        cookies = SimpleCookie(self.environ.get('HTTP_COOKIE',''))
910
        cookies = list(cookies.values())[:self.MAX_PARAMS]
911
        return FormsDict((c.key, c.value) for c in cookies)
912
913
    def get_cookie(self, key, default=None, secret=None):
914
        """ Return the content of a cookie. To read a `Signed Cookie`, the
915
            `secret` must match the one used to create the cookie (see
916
            :meth:`BaseResponse.set_cookie`). If anything goes wrong (missing
917
            cookie or wrong signature), return a default value. """
918
        value = self.cookies.get(key)
919
        if secret and value:
920
            dec = cookie_decode(value, secret) # (key, value) tuple or None
921
            return dec[1] if dec and dec[0] == key else default
922
        return value or default
923
924
    @DictProperty('environ', 'bottle.request.query', read_only=True)
925
    def query(self):
926
        ''' The :attr:`query_string` parsed into a :class:`FormsDict`. These
927
            values are sometimes called "URL arguments" or "GET parameters", but
928
            not to be confused with "URL wildcards" as they are provided by the
929
            :class:`Router`. '''
930
        get = self.environ['bottle.get'] = FormsDict()
931
        pairs = _parse_qsl(self.environ.get('QUERY_STRING', ''))
932
        for key, value in pairs[:self.MAX_PARAMS]:
933
            get[key] = value
934
        return get
935
936
    @DictProperty('environ', 'bottle.request.forms', read_only=True)
937
    def forms(self):
938
        """ Form values parsed from an `url-encoded` or `multipart/form-data`
939
            encoded POST or PUT request body. The result is retuned as a
940
            :class:`FormsDict`. All keys and values are strings. File uploads
941
            are stored separately in :attr:`files`. """
942
        forms = FormsDict()
943
        for name, item in self.POST.iterallitems():
944
            if not hasattr(item, 'filename'):
945
                forms[name] = item
946
        return forms
947
948
    @DictProperty('environ', 'bottle.request.params', read_only=True)
949
    def params(self):
950
        """ A :class:`FormsDict` with the combined values of :attr:`query` and
951
            :attr:`forms`. File uploads are stored in :attr:`files`. """
952
        params = FormsDict()
953
        for key, value in self.query.iterallitems():
954
            params[key] = value
955
        for key, value in self.forms.iterallitems():
956
            params[key] = value
957
        return params
958
959
    @DictProperty('environ', 'bottle.request.files', read_only=True)
960
    def files(self):
961
        """ File uploads parsed from an `url-encoded` or `multipart/form-data`
962
            encoded POST or PUT request body. The values are instances of
963
            :class:`cgi.FieldStorage`. The most important attributes are:
964
965
            filename
966
                The filename, if specified; otherwise None; this is the client
967
                side filename, *not* the file name on which it is stored (that's
968
                a temporary file you don't deal with)
969
            file
970
                The file(-like) object from which you can read the data.
971
            value
972
                The value as a *string*; for file uploads, this transparently
973
                reads the file every time you request the value. Do not do this
974
                on big files.
975
        """
976
        files = FormsDict()
977
        for name, item in self.POST.iterallitems():
978
            if hasattr(item, 'filename'):
979
                files[name] = item
980
        return files
981
982
    @DictProperty('environ', 'bottle.request.json', read_only=True)
983
    def json(self):
984
        ''' If the ``Content-Type`` header is ``application/json``, this
985
            property holds the parsed content of the request body. Only requests
986
            smaller than :attr:`MEMFILE_MAX` are processed to avoid memory
987
            exhaustion. '''
988
        if 'application/json' in self.environ.get('CONTENT_TYPE', '') \
989
        and 0 < self.content_length < self.MEMFILE_MAX:
990
            return json_loads(self.body.read(self.MEMFILE_MAX))
991
        return None
992
993
    @DictProperty('environ', 'bottle.request.body', read_only=True)
994
    def _body(self):
995
        maxread = max(0, self.content_length)
996
        stream = self.environ['wsgi.input']
997
        body = BytesIO() if maxread < self.MEMFILE_MAX else TemporaryFile(mode='w+b')
998
        while maxread > 0:
999
            part = stream.read(min(maxread, self.MEMFILE_MAX))
1000
            if not part: break
1001
            body.write(part)
1002
            maxread -= len(part)
1003
        self.environ['wsgi.input'] = body
1004
        body.seek(0)
1005
        return body
1006
1007
    @property
1008
    def body(self):
1009
        """ The HTTP request body as a seek-able file-like object. Depending on
1010
            :attr:`MEMFILE_MAX`, this is either a temporary file or a
1011
            :class:`io.BytesIO` instance. Accessing this property for the first
1012
            time reads and replaces the ``wsgi.input`` environ variable.
1013
            Subsequent accesses just do a `seek(0)` on the file object. """
1014
        self._body.seek(0)
1015
        return self._body
1016
1017
    #: An alias for :attr:`query`.
1018
    GET = query
1019
1020
    @DictProperty('environ', 'bottle.request.post', read_only=True)
1021
    def POST(self):
1022
        """ The values of :attr:`forms` and :attr:`files` combined into a single
1023
            :class:`FormsDict`. Values are either strings (form values) or
1024
            instances of :class:`cgi.FieldStorage` (file uploads).
1025
        """
1026
        post = FormsDict()
1027
        # We default to application/x-www-form-urlencoded for everything that
1028
        # is not multipart and take the fast path (also: 3.1 workaround)
1029
        if not self.content_type.startswith('multipart/'):
1030
            maxlen = max(0, min(self.content_length, self.MEMFILE_MAX))
1031
            pairs = _parse_qsl(tonat(self.body.read(maxlen), 'latin1'))
1032
            for key, value in pairs[:self.MAX_PARAMS]:
1033
                post[key] = value
1034
            return post
1035
1036
        safe_env = {'QUERY_STRING':''} # Build a safe environment for cgi
1037
        for key in ('REQUEST_METHOD', 'CONTENT_TYPE', 'CONTENT_LENGTH'):
1038
            if key in self.environ: safe_env[key] = self.environ[key]
1039
        args = dict(fp=self.body, environ=safe_env, keep_blank_values=True)
1040
        if py >= (3,2,0):
1041
            args['encoding'] = 'ISO-8859-1'
1042
        if NCTextIOWrapper:
1043
            args['fp'] = NCTextIOWrapper(args['fp'], encoding='ISO-8859-1',
1044
                                         newline='\n')
1045
        data = cgi.FieldStorage(**args)
1046
        for item in (data.list or [])[:self.MAX_PARAMS]:
1047
            post[item.name] = item if item.filename else item.value
1048
        return post
1049
1050
    @property
1051
    def COOKIES(self):
1052
        ''' Alias for :attr:`cookies` (deprecated). '''
1053
        depr('BaseRequest.COOKIES was renamed to BaseRequest.cookies (lowercase).')
1054
        return self.cookies
1055
1056
    @property
1057
    def url(self):
1058
        """ The full request URI including hostname and scheme. If your app
1059
            lives behind a reverse proxy or load balancer and you get confusing
1060
            results, make sure that the ``X-Forwarded-Host`` header is set
1061
            correctly. """
1062
        return self.urlparts.geturl()
1063
1064
    @DictProperty('environ', 'bottle.request.urlparts', read_only=True)
1065
    def urlparts(self):
1066
        ''' The :attr:`url` string as an :class:`urlparse.SplitResult` tuple.
1067
            The tuple contains (scheme, host, path, query_string and fragment),
1068
            but the fragment is always empty because it is not visible to the
1069
            server. '''
1070
        env = self.environ
1071
        http = env.get('wsgi.url_scheme', 'http')
1072
        host = env.get('HTTP_X_FORWARDED_HOST') or env.get('HTTP_HOST')
1073
        if not host:
1074
            # HTTP 1.1 requires a Host-header. This is for HTTP/1.0 clients.
1075
            host = env.get('SERVER_NAME', '127.0.0.1')
1076
            port = env.get('SERVER_PORT')
1077
            if port and port != ('80' if http == 'http' else '443'):
1078
                host += ':' + port
1079
        path = urlquote(self.fullpath)
1080
        return UrlSplitResult(http, host, path, env.get('QUERY_STRING'), '')
1081
1082
    @property
1083
    def fullpath(self):
1084
        """ Request path including :attr:`script_name` (if present). """
1085
        return urljoin(self.script_name, self.path.lstrip('/'))
1086
1087
    @property
1088
    def query_string(self):
1089
        """ The raw :attr:`query` part of the URL (everything in between ``?``
1090
            and ``#``) as a string. """
1091
        return self.environ.get('QUERY_STRING', '')
1092
1093
    @property
1094
    def script_name(self):
1095
        ''' The initial portion of the URL's `path` that was removed by a higher
1096
            level (server or routing middleware) before the application was
1097
            called. This script path is returned with leading and tailing
1098
            slashes. '''
1099
        script_name = self.environ.get('SCRIPT_NAME', '').strip('/')
1100
        return '/' + script_name + '/' if script_name else '/'
1101
1102
    def path_shift(self, shift=1):
1103
        ''' Shift path segments from :attr:`path` to :attr:`script_name` and
1104
            vice versa.
1105
1106
           :param shift: The number of path segments to shift. May be negative
1107
                         to change the shift direction. (default: 1)
1108
        '''
1109
        script = self.environ.get('SCRIPT_NAME','/')
1110
        self['SCRIPT_NAME'], self['PATH_INFO'] = path_shift(script, self.path, shift)
1111
1112
    @property
1113
    def content_length(self):
1114
        ''' The request body length as an integer. The client is responsible to
1115
            set this header. Otherwise, the real length of the body is unknown
1116
            and -1 is returned. In this case, :attr:`body` will be empty. '''
1117
        return int(self.environ.get('CONTENT_LENGTH') or -1)
1118
1119
    @property
1120
    def content_type(self):
1121
        ''' The Content-Type header as a lowercase-string (default: empty). '''
1122
        return self.environ.get('CONTENT_TYPE', '').lower()
1123
1124
    @property
1125
    def is_xhr(self):
1126
        ''' True if the request was triggered by a XMLHttpRequest. This only
1127
            works with JavaScript libraries that support the `X-Requested-With`
1128
            header (most of the popular libraries do). '''
1129
        requested_with = self.environ.get('HTTP_X_REQUESTED_WITH','')
1130
        return requested_with.lower() == 'xmlhttprequest'
1131
1132
    @property
1133
    def is_ajax(self):
1134
        ''' Alias for :attr:`is_xhr`. "Ajax" is not the right term. '''
1135
        return self.is_xhr
1136
1137
    @property
1138
    def auth(self):
1139
        """ HTTP authentication data as a (user, password) tuple. This
1140
            implementation currently supports basic (not digest) authentication
1141
            only. If the authentication happened at a higher level (e.g. in the
1142
            front web-server or a middleware), the password field is None, but
1143
            the user field is looked up from the ``REMOTE_USER`` environ
1144
            variable. On any errors, None is returned. """
1145
        basic = parse_auth(self.environ.get('HTTP_AUTHORIZATION',''))
1146
        if basic: return basic
1147
        ruser = self.environ.get('REMOTE_USER')
1148
        if ruser: return (ruser, None)
1149
        return None
1150
1151
    @property
1152
    def remote_route(self):
1153
        """ A list of all IPs that were involved in this request, starting with
1154
            the client IP and followed by zero or more proxies. This does only
1155
            work if all proxies support the ```X-Forwarded-For`` header. Note
1156
            that this information can be forged by malicious clients. """
1157
        proxy = self.environ.get('HTTP_X_FORWARDED_FOR')
1158
        if proxy: return [ip.strip() for ip in proxy.split(',')]
1159
        remote = self.environ.get('REMOTE_ADDR')
1160
        return [remote] if remote else []
1161
1162
    @property
1163
    def remote_addr(self):
1164
        """ The client IP as a string. Note that this information can be forged
1165
            by malicious clients. """
1166
        route = self.remote_route
1167
        return route[0] if route else None
1168
1169
    def copy(self):
1170
        """ Return a new :class:`Request` with a shallow :attr:`environ` copy. """
1171
        return Request(self.environ.copy())
1172
1173
    def __getitem__(self, key): return self.environ[key]
1174
    def __delitem__(self, key): self[key] = ""; del(self.environ[key])
1175
    def __iter__(self): return iter(self.environ)
1176
    def __len__(self): return len(self.environ)
1177
    def keys(self): return self.environ.keys()
1178
    def __setitem__(self, key, value):
1179
        """ Change an environ value and clear all caches that depend on it. """
1180
1181
        if self.environ.get('bottle.request.readonly'):
1182
            raise KeyError('The environ dictionary is read-only.')
1183
1184
        self.environ[key] = value
1185
        todelete = ()
1186
1187
        if key == 'wsgi.input':
1188
            todelete = ('body', 'forms', 'files', 'params', 'post', 'json')
1189
        elif key == 'QUERY_STRING':
1190
            todelete = ('query', 'params')
1191
        elif key.startswith('HTTP_'):
1192
            todelete = ('headers', 'cookies')
1193
1194
        for key in todelete:
1195
            self.environ.pop('bottle.request.'+key, None)
1196
1197
    def __repr__(self):
1198
        return '<%s: %s %s>' % (self.__class__.__name__, self.method, self.url)
1199
1200
def _hkey(s):
1201
    return s.title().replace('_','-')
1202
1203
1204
class HeaderProperty(object):
1205
    def __init__(self, name, reader=None, writer=str, default=''):
1206
        self.name, self.reader, self.writer, self.default = name, reader, writer, default
1207
        self.__doc__ = 'Current value of the %r header.' % name.title()
1208
1209
    def __get__(self, obj, cls):
1210
        if obj is None: return self
1211
        value = obj.headers.get(self.name)
1212
        return self.reader(value) if (value and self.reader) else (value or self.default)
1213
1214
    def __set__(self, obj, value):
1215
        if self.writer: value = self.writer(value)
1216
        obj.headers[self.name] = value
1217
1218
    def __delete__(self, obj):
1219
        if self.name in obj.headers:
1220
            del obj.headers[self.name]
1221
1222
1223
class BaseResponse(object):
1224
    """ Storage class for a response body as well as headers and cookies.
1225
1226
        This class does support dict-like case-insensitive item-access to
1227
        headers, but is NOT a dict. Most notably, iterating over a response
1228
        yields parts of the body and not the headers.
1229
    """
1230
1231
    default_status = 200
1232
    default_content_type = 'text/html; charset=UTF-8'
1233
1234
    # Header blacklist for specific response codes
1235
    # (rfc2616 section 10.2.3 and 10.3.5)
1236
    bad_headers = {
1237
        204: set(('Content-Type',)),
1238
        304: set(('Allow', 'Content-Encoding', 'Content-Language',
1239
                  'Content-Length', 'Content-Range', 'Content-Type',
1240
                  'Content-Md5', 'Last-Modified'))}
1241
1242
    def __init__(self, body='', status=None, **headers):
1243
        self._status_line = None
1244
        self._status_code = None
1245
        self.body = body
1246
        self._cookies = None
1247
        self._headers = {'Content-Type': [self.default_content_type]}
1248
        self.status = status or self.default_status
1249
        if headers:
1250
            for name, value in headers.items():
1251
                self[name] = value
1252
1253
    def copy(self):
1254
        ''' Returns a copy of self. '''
1255
        copy = Response()
1256
        copy.status = self.status
1257
        copy._headers = dict((k, v[:]) for (k, v) in self._headers.items())
1258
        return copy
1259
1260
    def __iter__(self):
1261
        return iter(self.body)
1262
1263
    def close(self):
1264
        if hasattr(self.body, 'close'):
1265
            self.body.close()
1266
1267
    @property
1268
    def status_line(self):
1269
        ''' The HTTP status line as a string (e.g. ``404 Not Found``).'''
1270
        return self._status_line
1271
1272
    @property
1273
    def status_code(self):
1274
        ''' The HTTP status code as an integer (e.g. 404).'''
1275
        return self._status_code
1276
1277
    def _set_status(self, status):
1278
        if isinstance(status, int):
1279
            code, status = status, _HTTP_STATUS_LINES.get(status)
1280
        elif ' ' in status:
1281
            status = status.strip()
1282
            code   = int(status.split()[0])
1283
        else:
1284
            raise ValueError('String status line without a reason phrase.')
1285
        if not 100 <= code <= 999: raise ValueError('Status code out of range.')
1286
        self._status_code = code
1287
        self._status_line = status or ('%d Unknown' % code)
1288
1289
    def _get_status(self):
1290
        depr('BaseRequest.status will change to return a string in 0.11. Use'\
1291
             ' status_line and status_code to make sure.') #0.10
1292
        return self._status_code
1293
1294
    status = property(_get_status, _set_status, None,
1295
        ''' A writeable property to change the HTTP response status. It accepts
1296
            either a numeric code (100-999) or a string with a custom reason
1297
            phrase (e.g. "404 Brain not found"). Both :data:`status_line` and
1298
            :data:`status_code` are updates accordingly. The return value is
1299
            always a numeric code. ''')
1300
    del _get_status, _set_status
1301
1302
    @property
1303
    def headers(self):
1304
        ''' An instance of :class:`HeaderDict`, a case-insensitive dict-like
1305
            view on the response headers. '''
1306
        self.__dict__['headers'] = hdict = HeaderDict()
1307
        hdict.dict = self._headers
1308
        return hdict
1309
1310
    def __contains__(self, name): return _hkey(name) in self._headers
1311
    def __delitem__(self, name):  del self._headers[_hkey(name)]
1312
    def __getitem__(self, name):  return self._headers[_hkey(name)][-1]
1313
    def __setitem__(self, name, value): self._headers[_hkey(name)] = [str(value)]
1314
1315
    def get_header(self, name, default=None):
1316
        ''' Return the value of a previously defined header. If there is no
1317
            header with that name, return a default value. '''
1318
        return self._headers.get(_hkey(name), [default])[-1]
1319
1320
    def set_header(self, name, value, append=False):
1321
        ''' Create a new response header, replacing any previously defined
1322
            headers with the same name. '''
1323
        if append:
1324
            self.add_header(name, value)
1325
        else:
1326
            self._headers[_hkey(name)] = [str(value)]
1327
1328
    def add_header(self, name, value):
1329
        ''' Add an additional response header, not removing duplicates. '''
1330
        self._headers.setdefault(_hkey(name), []).append(str(value))
1331
1332
    def iter_headers(self):
1333
        ''' Yield (header, value) tuples, skipping headers that are not
1334
            allowed with the current response status code. '''
1335
        headers = self._headers.iteritems()
1336
        bad_headers = self.bad_headers.get(self.status_code)
1337
        if bad_headers:
1338
            headers = [h for h in headers if h[0] not in bad_headers]
1339
        for name, values in headers:
1340
            for value in values:
1341
                yield name, value
1342
        if self._cookies:
1343
            for c in self._cookies.values():
1344
                yield 'Set-Cookie', c.OutputString()
1345
1346
    def wsgiheader(self):
1347
        depr('The wsgiheader method is deprecated. See headerlist.') #0.10
1348
        return self.headerlist
1349
1350
    @property
1351
    def headerlist(self):
1352
        ''' WSGI conform list of (header, value) tuples. '''
1353
        return list(self.iter_headers())
1354
1355
    content_type = HeaderProperty('Content-Type')
1356
    content_length = HeaderProperty('Content-Length', reader=int)
1357
1358
    @property
1359
    def charset(self):
1360
        """ Return the charset specified in the content-type header (default: utf8). """
1361
        if 'charset=' in self.content_type:
1362
            return self.content_type.split('charset=')[-1].split(';')[0].strip()
1363
        return 'UTF-8'
1364
1365
    @property
1366
    def COOKIES(self):
1367
        """ A dict-like SimpleCookie instance. This should not be used directly.
1368
            See :meth:`set_cookie`. """
1369
        depr('The COOKIES dict is deprecated. Use `set_cookie()` instead.') # 0.10
1370
        if not self._cookies:
1371
            self._cookies = SimpleCookie()
1372
        return self._cookies
1373
1374
    def set_cookie(self, name, value, secret=None, **options):
1375
        ''' Create a new cookie or replace an old one. If the `secret` parameter is
1376
            set, create a `Signed Cookie` (described below).
1377
1378
            :param name: the name of the cookie.
1379
            :param value: the value of the cookie.
1380
            :param secret: a signature key required for signed cookies.
1381
1382
            Additionally, this method accepts all RFC 2109 attributes that are
1383
            supported by :class:`cookie.Morsel`, including:
1384
1385
            :param max_age: maximum age in seconds. (default: None)
1386
            :param expires: a datetime object or UNIX timestamp. (default: None)
1387
            :param domain: the domain that is allowed to read the cookie.
1388
              (default: current domain)
1389
            :param path: limits the cookie to a given path (default: current path)
1390
            :param secure: limit the cookie to HTTPS connections (default: off).
1391
            :param httponly: prevents client-side javascript to read this cookie
1392
              (default: off, requires Python 2.6 or newer).
1393
1394
            If neither `expires` nor `max_age` is set (default), the cookie will
1395
            expire at the end of the browser session (as soon as the browser
1396
            window is closed).
1397
1398
            Signed cookies may store any pickle-able object and are
1399
            cryptographically signed to prevent manipulation. Keep in mind that
1400
            cookies are limited to 4kb in most browsers.
1401
1402
            Warning: Signed cookies are not encrypted (the client can still see
1403
            the content) and not copy-protected (the client can restore an old
1404
            cookie). The main intention is to make pickling and unpickling
1405
            save, not to store secret information at client side.
1406
        '''
1407
        if not self._cookies:
1408
            self._cookies = SimpleCookie()
1409
1410
        if secret:
1411
            value = touni(cookie_encode((name, value), secret))
1412
        elif not isinstance(value, basestring):
1413
            raise TypeError('Secret key missing for non-string Cookie.')
1414
1415
        if len(value) > 4096: raise ValueError('Cookie value to long.')
1416
        self._cookies[name] = value
1417
1418
        for key, value in options.iteritems():
1419
            if key == 'max_age':
1420
                if isinstance(value, timedelta):
1421
                    value = value.seconds + value.days * 24 * 3600
1422
            if key == 'expires':
1423
                if isinstance(value, (datedate, datetime)):
1424
                    value = value.timetuple()
1425
                elif isinstance(value, (int, float)):
1426
                    value = time.gmtime(value)
1427
                value = time.strftime("%a, %d %b %Y %H:%M:%S GMT", value)
1428
            self._cookies[name][key.replace('_', '-')] = value
1429
1430
    def delete_cookie(self, key, **kwargs):
1431
        ''' Delete a cookie. Be sure to use the same `domain` and `path`
1432
            settings as used to create the cookie. '''
1433
        kwargs['max_age'] = -1
1434
        kwargs['expires'] = 0
1435
        self.set_cookie(key, '', **kwargs)
1436
1437
    def __repr__(self):
1438
        out = ''
1439
        for name, value in self.headerlist:
1440
            out += '%s: %s\n' % (name.title(), value.strip())
1441
        return out
1442
1443
1444
class LocalRequest(BaseRequest, threading.local):
1445
    ''' A thread-local subclass of :class:`BaseRequest`. '''
1446
    def __init__(self): pass
1447
    bind = BaseRequest.__init__
1448
1449
1450
class LocalResponse(BaseResponse, threading.local):
1451
    ''' A thread-local subclass of :class:`BaseResponse`. '''
1452
    bind = BaseResponse.__init__
1453
1454
Response = LocalResponse # BC 0.9
1455
Request  = LocalRequest  # BC 0.9
1456
1457
1458
1459
1460
1461
1462
###############################################################################
1463
# Plugins ######################################################################
1464
###############################################################################
1465
1466
class PluginError(BottleException): pass
1467
1468
class JSONPlugin(object):
1469
    name = 'json'
1470
    api  = 2
1471
1472
    def __init__(self, json_dumps=json_dumps):
1473
        self.json_dumps = json_dumps
1474
1475
    def apply(self, callback, context):
1476
        dumps = self.json_dumps
1477
        if not dumps: return callback
1478
        def wrapper(*a, **ka):
1479
            rv = callback(*a, **ka)
1480
            if isinstance(rv, dict):
1481
                #Attempt to serialize, raises exception on failure
1482
                json_response = dumps(rv)
1483
                #Set content type only if serialization succesful
1484
                response.content_type = 'application/json'
1485
                return json_response
1486
            return rv
1487
        return wrapper
1488
1489
1490
class HooksPlugin(object):
1491
    name = 'hooks'
1492
    api  = 2
1493
1494
    _names = 'before_request', 'after_request', 'app_reset'
1495
1496
    def __init__(self):
1497
        self.hooks = dict((name, []) for name in self._names)
1498
        self.app = None
1499
1500
    def _empty(self):
1501
        return not (self.hooks['before_request'] or self.hooks['after_request'])
1502
1503
    def setup(self, app):
1504
        self.app = app
1505
1506
    def add(self, name, func):
1507
        ''' Attach a callback to a hook. '''
1508
        was_empty = self._empty()
1509
        self.hooks.setdefault(name, []).append(func)
1510
        if self.app and was_empty and not self._empty(): self.app.reset()
1511
1512
    def remove(self, name, func):
1513
        ''' Remove a callback from a hook. '''
1514
        was_empty = self._empty()
1515
        if name in self.hooks and func in self.hooks[name]:
1516
            self.hooks[name].remove(func)
1517
        if self.app and not was_empty and self._empty(): self.app.reset()
1518
1519
    def trigger(self, name, *a, **ka):
1520
        ''' Trigger a hook and return a list of results. '''
1521
        hooks = self.hooks[name]
1522
        if ka.pop('reversed', False): hooks = hooks[::-1]
1523
        return [hook(*a, **ka) for hook in hooks]
1524
1525
    def apply(self, callback, context):
1526
        if self._empty(): return callback
1527
        def wrapper(*a, **ka):
1528
            self.trigger('before_request')
1529
            rv = callback(*a, **ka)
1530
            self.trigger('after_request', reversed=True)
1531
            return rv
1532
        return wrapper
1533
1534
1535
class TemplatePlugin(object):
1536
    ''' This plugin applies the :func:`view` decorator to all routes with a
1537
        `template` config parameter. If the parameter is a tuple, the second
1538
        element must be a dict with additional options (e.g. `template_engine`)
1539
        or default variables for the template. '''
1540
    name = 'template'
1541
    api  = 2
1542
1543
    def apply(self, callback, route):
1544
        conf = route.config.get('template')
1545
        if isinstance(conf, (tuple, list)) and len(conf) == 2:
1546
            return view(conf[0], **conf[1])(callback)
1547
        elif isinstance(conf, str) and 'template_opts' in route.config:
1548
            depr('The `template_opts` parameter is deprecated.') #0.9
1549
            return view(conf, **route.config['template_opts'])(callback)
1550
        elif isinstance(conf, str):
1551
            return view(conf)(callback)
1552
        else:
1553
            return callback
1554
1555
1556
#: Not a plugin, but part of the plugin API. TODO: Find a better place.
1557
class _ImportRedirect(object):
1558
    def __init__(self, name, impmask):
1559
        ''' Create a virtual package that redirects imports (see PEP 302). '''
1560
        self.name = name
1561
        self.impmask = impmask
1562
        self.module = sys.modules.setdefault(name, imp.new_module(name))
1563
        self.module.__dict__.update({'__file__': __file__, '__path__': [],
1564
                                    '__all__': [], '__loader__': self})
1565
        sys.meta_path.append(self)
1566
1567
    def find_module(self, fullname, path=None):
1568
        if '.' not in fullname: return
1569
        packname, modname = fullname.rsplit('.', 1)
1570
        if packname != self.name: return
1571
        return self
1572
1573
    def load_module(self, fullname):
1574
        if fullname in sys.modules: return sys.modules[fullname]
1575
        packname, modname = fullname.rsplit('.', 1)
1576
        realname = self.impmask % modname
1577
        __import__(realname)
1578
        module = sys.modules[fullname] = sys.modules[realname]
1579
        setattr(self.module, modname, module)
1580
        module.__loader__ = self
1581
        return module
1582
1583
1584
1585
1586
1587
1588
###############################################################################
1589
# Common Utilities #############################################################
1590
###############################################################################
1591
1592
1593
class MultiDict(DictMixin):
1594
    """ This dict stores multiple values per key, but behaves exactly like a
1595
        normal dict in that it returns only the newest value for any given key.
1596
        There are special methods available to access the full list of values.
1597
    """
1598
1599
    def __init__(self, *a, **k):
1600
        self.dict = dict((k, [v]) for k, v in dict(*a, **k).iteritems())
1601
    def __len__(self): return len(self.dict)
1602
    def __iter__(self): return iter(self.dict)
1603
    def __contains__(self, key): return key in self.dict
1604
    def __delitem__(self, key): del self.dict[key]
1605
    def __getitem__(self, key): return self.dict[key][-1]
1606
    def __setitem__(self, key, value): self.append(key, value)
1607
    def iterkeys(self): return self.dict.iterkeys()
1608
    def itervalues(self): return (v[-1] for v in self.dict.itervalues())
1609
    def iteritems(self): return ((k, v[-1]) for (k, v) in self.dict.iteritems())
1610
    def iterallitems(self):
1611
        for key, values in self.dict.iteritems():
1612
            for value in values:
1613
                yield key, value
1614
1615
    # 2to3 is not able to fix these automatically.
1616
    keys     = iterkeys     if py3k else lambda self: list(self.iterkeys())
1617
    values   = itervalues   if py3k else lambda self: list(self.itervalues())
1618
    items    = iteritems    if py3k else lambda self: list(self.iteritems())
1619
    allitems = iterallitems if py3k else lambda self: list(self.iterallitems())
1620
1621
    def get(self, key, default=None, index=-1, type=None):
1622
        ''' Return the most recent value for a key.
1623
1624
            :param default: The default value to be returned if the key is not
1625
                   present or the type conversion fails.
1626
            :param index: An index for the list of available values.
1627
            :param type: If defined, this callable is used to cast the value
1628
                    into a specific type. Exception are suppressed and result in
1629
                    the default value to be returned.
1630
        '''
1631
        try:
1632
            val = self.dict[key][index]
1633
            return type(val) if type else val
1634
        except Exception, e:
1635
            pass
1636
        return default
1637
1638
    def append(self, key, value):
1639
        ''' Add a new value to the list of values for this key. '''
1640
        self.dict.setdefault(key, []).append(value)
1641
1642
    def replace(self, key, value):
1643
        ''' Replace the list of values with a single value. '''
1644
        self.dict[key] = [value]
1645
1646
    def getall(self, key):
1647
        ''' Return a (possibly empty) list of values for a key. '''
1648
        return self.dict.get(key) or []
1649
1650
    #: Aliases for WTForms to mimic other multi-dict APIs (Django)
1651
    getone = get
1652
    getlist = getall
1653
1654
1655
1656
class FormsDict(MultiDict):
1657
    ''' This :class:`MultiDict` subclass is used to store request form data.
1658
        Additionally to the normal dict-like item access methods (which return
1659
        unmodified data as native strings), this container also supports
1660
        attribute-like access to its values. Attribues are automatiically de- or
1661
        recoded to match :attr:`input_encoding` (default: 'utf8'). Missing
1662
        attributes default to an empty string. '''
1663
1664
    #: Encoding used for attribute values.
1665
    input_encoding = 'utf8'
1666
1667
    def getunicode(self, name, default=None, encoding=None):
1668
        value, enc = self.get(name, default), encoding or self.input_encoding
1669
        try:
1670
            if isinstance(value, bytes): # Python 2 WSGI
1671
                return value.decode(enc)
1672
            elif isinstance(value, unicode): # Python 3 WSGI
1673
                return value.encode('latin1').decode(enc)
1674
            return value
1675
        except UnicodeError, e:
1676
            return default
1677
1678
    def __getattr__(self, name): return self.getunicode(name, default=u'')
1679
1680
1681
class HeaderDict(MultiDict):
1682
    """ A case-insensitive version of :class:`MultiDict` that defaults to
1683
        replace the old value instead of appending it. """
1684
1685
    def __init__(self, *a, **ka):
1686
        self.dict = {}
1687
        if a or ka: self.update(*a, **ka)
1688
1689
    def __contains__(self, key): return _hkey(key) in self.dict
1690
    def __delitem__(self, key): del self.dict[_hkey(key)]
1691
    def __getitem__(self, key): return self.dict[_hkey(key)][-1]
1692
    def __setitem__(self, key, value): self.dict[_hkey(key)] = [str(value)]
1693
    def append(self, key, value):
1694
        self.dict.setdefault(_hkey(key), []).append(str(value))
1695
    def replace(self, key, value): self.dict[_hkey(key)] = [str(value)]
1696
    def getall(self, key): return self.dict.get(_hkey(key)) or []
1697
    def get(self, key, default=None, index=-1):
1698
        return MultiDict.get(self, _hkey(key), default, index)
1699
    def filter(self, names):
1700
        for name in map(_hkey, names):
1701
            if name in self.dict:
1702
                del self.dict[name]
1703
1704
1705
class WSGIHeaderDict(DictMixin):
1706
    ''' This dict-like class wraps a WSGI environ dict and provides convenient
1707
        access to HTTP_* fields. Keys and values are native strings
1708
        (2.x bytes or 3.x unicode) and keys are case-insensitive. If the WSGI
1709
        environment contains non-native string values, these are de- or encoded
1710
        using a lossless 'latin1' character set.
1711
1712
        The API will remain stable even on changes to the relevant PEPs.
1713
        Currently PEP 333, 444 and 3333 are supported. (PEP 444 is the only one
1714
        that uses non-native strings.)
1715
    '''
1716
    #: List of keys that do not have a 'HTTP_' prefix.
1717
    cgikeys = ('CONTENT_TYPE', 'CONTENT_LENGTH')
1718
1719
    def __init__(self, environ):
1720
        self.environ = environ
1721
1722
    def _ekey(self, key):
1723
        ''' Translate header field name to CGI/WSGI environ key. '''
1724
        key = key.replace('-','_').upper()
1725
        if key in self.cgikeys:
1726
            return key
1727
        return 'HTTP_' + key
1728
1729
    def raw(self, key, default=None):
1730
        ''' Return the header value as is (may be bytes or unicode). '''
1731
        return self.environ.get(self._ekey(key), default)
1732
1733
    def __getitem__(self, key):
1734
        return tonat(self.environ[self._ekey(key)], 'latin1')
1735
1736
    def __setitem__(self, key, value):
1737
        raise TypeError("%s is read-only." % self.__class__)
1738
1739
    def __delitem__(self, key):
1740
        raise TypeError("%s is read-only." % self.__class__)
1741
1742
    def __iter__(self):
1743
        for key in self.environ:
1744
            if key[:5] == 'HTTP_':
1745
                yield key[5:].replace('_', '-').title()
1746
            elif key in self.cgikeys:
1747
                yield key.replace('_', '-').title()
1748
1749
    def keys(self): return [x for x in self]
1750
    def __len__(self): return len(self.keys())
1751
    def __contains__(self, key): return self._ekey(key) in self.environ
1752
1753
1754
class ConfigDict(dict):
1755
    ''' A dict-subclass with some extras: You can access keys like attributes.
1756
        Uppercase attributes create new ConfigDicts and act as name-spaces.
1757
        Other missing attributes return None. Calling a ConfigDict updates its
1758
        values and returns itself.
1759
1760
        >>> cfg = ConfigDict()
1761
        >>> cfg.Namespace.value = 5
1762
        >>> cfg.OtherNamespace(a=1, b=2)
1763
        >>> cfg
1764
        {'Namespace': {'value': 5}, 'OtherNamespace': {'a': 1, 'b': 2}}
1765
    '''
1766
1767
    def __getattr__(self, key):
1768
        if key not in self and key[0].isupper():
1769
            self[key] = ConfigDict()
1770
        return self.get(key)
1771
1772
    def __setattr__(self, key, value):
1773
        if hasattr(dict, key):
1774
            raise AttributeError('Read-only attribute.')
1775
        if key in self and self[key] and isinstance(self[key], ConfigDict):
1776
            raise AttributeError('Non-empty namespace attribute.')
1777
        self[key] = value
1778
1779
    def __delattr__(self, key):
1780
        if key in self: del self[key]
1781
1782
    def __call__(self, *a, **ka):
1783
        for key, value in dict(*a, **ka).iteritems(): setattr(self, key, value)
1784
        return self
1785
1786
1787
class AppStack(list):
1788
    """ A stack-like list. Calling it returns the head of the stack. """
1789
1790
    def __call__(self):
1791
        """ Return the current default application. """
1792
        return self[-1]
1793
1794
    def push(self, value=None):
1795
        """ Add a new :class:`Bottle` instance to the stack """
1796
        if not isinstance(value, Bottle):
1797
            value = Bottle()
1798
        self.append(value)
1799
        return value
1800
1801
1802
class WSGIFileWrapper(object):
1803
1804
   def __init__(self, fp, buffer_size=1024*64):
1805
       self.fp, self.buffer_size = fp, buffer_size
1806
       for attr in ('fileno', 'close', 'read', 'readlines'):
1807
           if hasattr(fp, attr): setattr(self, attr, getattr(fp, attr))
1808
1809
   def __iter__(self):
1810
       read, buff = self.fp.read, self.buffer_size
1811
       while True:
1812
           part = read(buff)
1813
           if not part: break
1814
           yield part
1815
1816
1817
1818
1819
1820
1821
###############################################################################
1822
# Application Helper ###########################################################
1823
###############################################################################
1824
1825
1826
def abort(code=500, text='Unknown Error: Application stopped.'):
1827
    """ Aborts execution and causes a HTTP error. """
1828
    raise HTTPError(code, text)
1829
1830
1831
def redirect(url, code=None):
1832
    """ Aborts execution and causes a 303 or 302 redirect, depending on
1833
        the HTTP protocol version. """
1834
    if code is None:
1835
        code = 303 if request.get('SERVER_PROTOCOL') == "HTTP/1.1" else 302
1836
    location = urljoin(request.url, url)
1837
    raise HTTPResponse("", status=code, header=dict(Location=location))
1838
1839
1840
def static_file(filename, root, mimetype='auto', download=False):
1841
    """ Open a file in a safe way and return :exc:`HTTPResponse` with status
1842
        code 200, 305, 401 or 404. Set Content-Type, Content-Encoding,
1843
        Content-Length and Last-Modified header. Obey If-Modified-Since header
1844
        and HEAD requests.
1845
    """
1846
    root = os.path.abspath(root) + os.sep
1847
    filename = os.path.abspath(os.path.join(root, filename.strip('/\\')))
1848
    header = dict()
1849
1850
    if not filename.startswith(root):
1851
        return HTTPError(403, "Access denied.")
1852
    if not os.path.exists(filename) or not os.path.isfile(filename):
1853
        return HTTPError(404, "File does not exist.")
1854
    if not os.access(filename, os.R_OK):
1855
        return HTTPError(403, "You do not have permission to access this file.")
1856
1857
    if mimetype == 'auto':
1858
        mimetype, encoding = mimetypes.guess_type(filename)
1859
        if mimetype: header['Content-Type'] = mimetype
1860
        if encoding: header['Content-Encoding'] = encoding
1861
    elif mimetype:
1862
        header['Content-Type'] = mimetype
1863
1864
    if download:
1865
        download = os.path.basename(filename if download == True else download)
1866
        header['Content-Disposition'] = 'attachment; filename="%s"' % download
1867
1868
    stats = os.stat(filename)
1869
    header['Content-Length'] = stats.st_size
1870
    lm = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(stats.st_mtime))
1871
    header['Last-Modified'] = lm
1872
1873
    ims = request.environ.get('HTTP_IF_MODIFIED_SINCE')
1874
    if ims:
1875
        ims = parse_date(ims.split(";")[0].strip())
1876
    if ims is not None and ims >= int(stats.st_mtime):
1877
        header['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime())
1878
        return HTTPResponse(status=304, header=header)
1879
1880
    body = '' if request.method == 'HEAD' else open(filename, 'rb')
1881
    return HTTPResponse(body, header=header)
1882
1883
1884
1885
1886
1887
1888
###############################################################################
1889
# HTTP Utilities and MISC (TODO) ###############################################
1890
###############################################################################
1891
1892
1893
def debug(mode=True):
1894
    """ Change the debug level.
1895
    There is only one debug level supported at the moment."""
1896
    global DEBUG
1897
    DEBUG = bool(mode)
1898
1899
1900
def parse_date(ims):
1901
    """ Parse rfc1123, rfc850 and asctime timestamps and return UTC epoch. """
1902
    try:
1903
        ts = email.utils.parsedate_tz(ims)
1904
        return time.mktime(ts[:8] + (0,)) - (ts[9] or 0) - time.timezone
1905
    except (TypeError, ValueError, IndexError, OverflowError):
1906
        return None
1907
1908
1909
def parse_auth(header):
1910
    """ Parse rfc2617 HTTP authentication header string (basic) and return (user,pass) tuple or None"""
1911
    try:
1912
        method, data = header.split(None, 1)
1913
        if method.lower() == 'basic':
1914
            #TODO: Add 2to3 save base64[encode/decode] functions.
1915
            user, pwd = touni(base64.b64decode(tob(data))).split(':',1)
1916
            return user, pwd
1917
    except (KeyError, ValueError):
1918
        return None
1919
1920
1921
def _parse_qsl(qs):
1922
    r = []
1923
    for pair in qs.replace(';','&').split('&'):
1924
        if not pair: continue
1925
        nv = pair.split('=', 1)
1926
        if len(nv) != 2: nv.append('')
1927
        key = urlunquote(nv[0].replace('+', ' '))
1928
        value = urlunquote(nv[1].replace('+', ' '))
1929
        r.append((key, value))
1930
    return r
1931
1932
1933
def _lscmp(a, b):
1934
    ''' Compares two strings in a cryptographically save way:
1935
        Runtime is not affected by length of common prefix. '''
1936
    return not sum(0 if x==y else 1 for x, y in zip(a, b)) and len(a) == len(b)
1937
1938
1939
def cookie_encode(data, key):
1940
    ''' Encode and sign a pickle-able object. Return a (byte) string '''
1941
    msg = base64.b64encode(pickle.dumps(data, -1))
1942
    sig = base64.b64encode(hmac.new(tob(key), msg).digest())
1943
    return tob('!') + sig + tob('?') + msg
1944
1945
1946
def cookie_decode(data, key):
1947
    ''' Verify and decode an encoded string. Return an object or None.'''
1948
    data = tob(data)
1949
    if cookie_is_encoded(data):
1950
        sig, msg = data.split(tob('?'), 1)
1951
        if _lscmp(sig[1:], base64.b64encode(hmac.new(tob(key), msg).digest())):
1952
            return pickle.loads(base64.b64decode(msg))
1953
    return None
1954
1955
1956
def cookie_is_encoded(data):
1957
    ''' Return True if the argument looks like a encoded cookie.'''
1958
    return bool(data.startswith(tob('!')) and tob('?') in data)
1959
1960
1961
def html_escape(string):
1962
    ''' Escape HTML special characters ``&<>`` and quotes ``'"``. '''
1963
    return string.replace('&','&amp;').replace('<','&lt;').replace('>','&gt;')\
1964
                 .replace('"','&quot;').replace("'",'&#039;')
1965
1966
1967
def html_quote(string):
1968
    ''' Escape and quote a string to be used as an HTTP attribute.'''
1969
    return '"%s"' % html_escape(string).replace('\n','%#10;')\
1970
                    .replace('\r','&#13;').replace('\t','&#9;')
1971
1972
1973
def yieldroutes(func):
1974
    """ Return a generator for routes that match the signature (name, args)
1975
    of the func parameter. This may yield more than one route if the function
1976
    takes optional keyword arguments. The output is best described by example::
1977
1978
        a()         -> '/a'
1979
        b(x, y)     -> '/b/:x/:y'
1980
        c(x, y=5)   -> '/c/:x' and '/c/:x/:y'
1981
        d(x=5, y=6) -> '/d' and '/d/:x' and '/d/:x/:y'
1982
    """
1983
    import inspect # Expensive module. Only import if necessary.
1984
    path = '/' + func.__name__.replace('__','/').lstrip('/')
1985
    spec = inspect.getargspec(func)
1986
    argc = len(spec[0]) - len(spec[3] or [])
1987
    path += ('/:%s' * argc) % tuple(spec[0][:argc])
1988
    yield path
1989
    for arg in spec[0][argc:]:
1990
        path += '/:%s' % arg
1991
        yield path
1992
1993
1994
def path_shift(script_name, path_info, shift=1):
1995
    ''' Shift path fragments from PATH_INFO to SCRIPT_NAME and vice versa.
1996
1997
        :return: The modified paths.
1998
        :param script_name: The SCRIPT_NAME path.
1999
        :param script_name: The PATH_INFO path.
2000
        :param shift: The number of path fragments to shift. May be negative to
2001
          change the shift direction. (default: 1)
2002
    '''
2003
    if shift == 0: return script_name, path_info
2004
    pathlist = path_info.strip('/').split('/')
2005
    scriptlist = script_name.strip('/').split('/')
2006
    if pathlist and pathlist[0] == '': pathlist = []
2007
    if scriptlist and scriptlist[0] == '': scriptlist = []
2008
    if shift > 0 and shift <= len(pathlist):
2009
        moved = pathlist[:shift]
2010
        scriptlist = scriptlist + moved
2011
        pathlist = pathlist[shift:]
2012
    elif shift < 0 and shift >= -len(scriptlist):
2013
        moved = scriptlist[shift:]
2014
        pathlist = moved + pathlist
2015
        scriptlist = scriptlist[:shift]
2016
    else:
2017
        empty = 'SCRIPT_NAME' if shift < 0 else 'PATH_INFO'
2018
        raise AssertionError("Cannot shift. Nothing left from %s" % empty)
2019
    new_script_name = '/' + '/'.join(scriptlist)
2020
    new_path_info = '/' + '/'.join(pathlist)
2021
    if path_info.endswith('/') and pathlist: new_path_info += '/'
2022
    return new_script_name, new_path_info
2023
2024
2025
def validate(**vkargs):
2026
    """
2027
    Validates and manipulates keyword arguments by user defined callables.
2028
    Handles ValueError and missing arguments by raising HTTPError(403).
2029
    """
2030
    depr('Use route wildcard filters instead.')
2031
    def decorator(func):
2032
        @functools.wraps(func)
2033
        def wrapper(*args, **kargs):
2034
            for key, value in vkargs.iteritems():
2035
                if key not in kargs:
2036
                    abort(403, 'Missing parameter: %s' % key)
2037
                try:
2038
                    kargs[key] = value(kargs[key])
2039
                except ValueError:
2040
                    abort(403, 'Wrong parameter format for: %s' % key)
2041
            return func(*args, **kargs)
2042
        return wrapper
2043
    return decorator
2044
2045
2046
def auth_basic(check, realm="private", text="Access denied"):
2047
    ''' Callback decorator to require HTTP auth (basic).
2048
        TODO: Add route(check_auth=...) parameter. '''
2049
    def decorator(func):
2050
      def wrapper(*a, **ka):
2051
        user, password = request.auth or (None, None)
2052
        if user is None or not check(user, password):
2053
          response.headers['WWW-Authenticate'] = 'Basic realm="%s"' % realm
2054
          return HTTPError(401, text)
2055
        return func(*a, **ka)
2056
      return wrapper
2057
    return decorator
2058
2059
2060
def make_default_app_wrapper(name):
2061
    ''' Return a callable that relays calls to the current default app. '''
2062
    @functools.wraps(getattr(Bottle, name))
2063
    def wrapper(*a, **ka):
2064
        return getattr(app(), name)(*a, **ka)
2065
    return wrapper
2066
2067
2068
for name in '''route get post put delete error mount
2069
               hook install uninstall'''.split():
2070
    globals()[name] = make_default_app_wrapper(name)
2071
url = make_default_app_wrapper('get_url')
2072
del name
2073
2074
2075
2076
2077
2078
2079
###############################################################################
2080
# Server Adapter ###############################################################
2081
###############################################################################
2082
2083
2084
class ServerAdapter(object):
2085
    quiet = False
2086
    def __init__(self, host='127.0.0.1', port=8080, **config):
2087
        self.options = config
2088
        self.host = host
2089
        self.port = int(port)
2090
2091
    def run(self, handler): # pragma: no cover
2092
        pass
2093
2094
    def __repr__(self):
2095
        args = ', '.join(['%s=%s'%(k,repr(v)) for k, v in self.options.items()])
2096
        return "%s(%s)" % (self.__class__.__name__, args)
2097
2098
2099
class CGIServer(ServerAdapter):
2100
    quiet = True
2101
    def run(self, handler): # pragma: no cover
2102
        from wsgiref.handlers import CGIHandler
2103
        def fixed_environ(environ, start_response):
2104
            environ.setdefault('PATH_INFO', '')
2105
            return handler(environ, start_response)
2106
        CGIHandler().run(fixed_environ)
2107
2108
2109
class FlupFCGIServer(ServerAdapter):
2110
    def run(self, handler): # pragma: no cover
2111
        import flup.server.fcgi
2112
        self.options.setdefault('bindAddress', (self.host, self.port))
2113
        flup.server.fcgi.WSGIServer(handler, **self.options).run()
2114
2115
2116
class WSGIRefServer(ServerAdapter):
2117
    def run(self, handler): # pragma: no cover
2118
        from wsgiref.simple_server import make_server, WSGIRequestHandler
2119
        if self.quiet:
2120
            class QuietHandler(WSGIRequestHandler):
2121
                def log_request(*args, **kw): pass
2122
            self.options['handler_class'] = QuietHandler
2123
        srv = make_server(self.host, self.port, handler, **self.options)
2124
        srv.serve_forever()
2125
2126
2127
class CherryPyServer(ServerAdapter):
2128
    def run(self, handler): # pragma: no cover
2129
        from cherrypy import wsgiserver
2130
        server = wsgiserver.CherryPyWSGIServer((self.host, self.port), handler)
2131
        try:
2132
            server.start()
2133
        finally:
2134
            server.stop()
2135
2136
2137
class PasteServer(ServerAdapter):
2138
    def run(self, handler): # pragma: no cover
2139
        from paste import httpserver
2140
        if not self.quiet:
2141
            from paste.translogger import TransLogger
2142
            handler = TransLogger(handler)
2143
        httpserver.serve(handler, host=self.host, port=str(self.port),
2144
                         **self.options)
2145
2146
2147
class MeinheldServer(ServerAdapter):
2148
    def run(self, handler):
2149
        from meinheld import server
2150
        server.listen((self.host, self.port))
2151
        server.run(handler)
2152
2153
2154
class FapwsServer(ServerAdapter):
2155
    """ Extremely fast webserver using libev. See http://www.fapws.org/ """
2156
    def run(self, handler): # pragma: no cover
2157
        import fapws._evwsgi as evwsgi
2158
        from fapws import base, config
2159
        port = self.port
2160
        if float(config.SERVER_IDENT[-2:]) > 0.4:
2161
            # fapws3 silently changed its API in 0.5
2162
            port = str(port)
2163
        evwsgi.start(self.host, port)
2164
        # fapws3 never releases the GIL. Complain upstream. I tried. No luck.
2165
        if 'BOTTLE_CHILD' in os.environ and not self.quiet:
2166
            print "WARNING: Auto-reloading does not work with Fapws3."
2167
            print "         (Fapws3 breaks python thread support)"
2168
        evwsgi.set_base_module(base)
2169
        def app(environ, start_response):
2170
            environ['wsgi.multiprocess'] = False
2171
            return handler(environ, start_response)
2172
        evwsgi.wsgi_cb(('', app))
2173
        evwsgi.run()
2174
2175
2176
class TornadoServer(ServerAdapter):
2177
    """ The super hyped asynchronous server by facebook. Untested. """
2178
    def run(self, handler): # pragma: no cover
2179
        import tornado.wsgi, tornado.httpserver, tornado.ioloop
2180
        container = tornado.wsgi.WSGIContainer(handler)
2181
        server = tornado.httpserver.HTTPServer(container)
2182
        server.listen(port=self.port)
2183
        tornado.ioloop.IOLoop.instance().start()
2184
2185
2186
class AppEngineServer(ServerAdapter):
2187
    """ Adapter for Google App Engine. """
2188
    quiet = True
2189
    def run(self, handler):
2190
        from google.appengine.ext.webapp import util
2191
        # A main() function in the handler script enables 'App Caching'.
2192
        # Lets makes sure it is there. This _really_ improves performance.
2193
        module = sys.modules.get('__main__')
2194
        if module and not hasattr(module, 'main'):
2195
            module.main = lambda: util.run_wsgi_app(handler)
2196
        util.run_wsgi_app(handler)
2197
2198
2199
class TwistedServer(ServerAdapter):
2200
    """ Untested. """
2201
    def run(self, handler):
2202
        from twisted.web import server, wsgi
2203
        from twisted.python.threadpool import ThreadPool
2204
        from twisted.internet import reactor
2205
        thread_pool = ThreadPool()
2206
        thread_pool.start()
2207
        reactor.addSystemEventTrigger('after', 'shutdown', thread_pool.stop)
2208
        factory = server.Site(wsgi.WSGIResource(reactor, thread_pool, handler))
2209
        reactor.listenTCP(self.port, factory, interface=self.host)
2210
        reactor.run()
2211
2212
2213
class DieselServer(ServerAdapter):
2214
    """ Untested. """
2215
    def run(self, handler):
2216
        from diesel.protocols.wsgi import WSGIApplication
2217
        app = WSGIApplication(handler, port=self.port)
2218
        app.run()
2219
2220
2221
class GeventServer(ServerAdapter):
2222
    """ Untested. Options:
2223
2224
        * `monkey` (default: True) fixes the stdlib to use greenthreads.
2225
        * `fast` (default: False) uses libevent's http server, but has some
2226
          issues: No streaming, no pipelining, no SSL.
2227
    """
2228
    def run(self, handler):
2229
        from gevent import wsgi as wsgi_fast, pywsgi, monkey, local
2230
        if self.options.get('monkey', True):
2231
            if not threading.local is local.local: monkey.patch_all()
2232
        wsgi = wsgi_fast if self.options.get('fast') else pywsgi
2233
        wsgi.WSGIServer((self.host, self.port), handler).serve_forever()
2234
2235
2236
class GunicornServer(ServerAdapter):
2237
    """ Untested. See http://gunicorn.org/configure.html for options. """
2238
    def run(self, handler):
2239
        from gunicorn.app.base import Application
2240
2241
        config = {'bind': "%s:%d" % (self.host, int(self.port))}
2242
        config.update(self.options)
2243
2244
        class GunicornApplication(Application):
2245
            def init(self, parser, opts, args):
2246
                return config
2247
2248
            def load(self):
2249
                return handler
2250
2251
        GunicornApplication().run()
2252
2253
2254
class EventletServer(ServerAdapter):
2255
    """ Untested """
2256
    def run(self, handler):
2257
        from eventlet import wsgi, listen
2258
        wsgi.server(listen((self.host, self.port)), handler)
2259
2260
2261
class RocketServer(ServerAdapter):
2262
    """ Untested. """
2263
    def run(self, handler):
2264
        from rocket import Rocket
2265
        server = Rocket((self.host, self.port), 'wsgi', { 'wsgi_app' : handler })
2266
        server.start()
2267
2268
2269
class BjoernServer(ServerAdapter):
2270
    """ Fast server written in C: https://github.com/jonashaag/bjoern """
2271
    def run(self, handler):
2272
        from bjoern import run
2273
        run(handler, self.host, self.port)
2274
2275
2276
class AutoServer(ServerAdapter):
2277
    """ Untested. """
2278
    adapters = [PasteServer, TwistedServer, CherryPyServer, WSGIRefServer]
2279
    def run(self, handler):
2280
        for sa in self.adapters:
2281
            try:
2282
                return sa(self.host, self.port, **self.options).run(handler)
2283
            except ImportError:
2284
                pass
2285
2286
server_names = {
2287
    'cgi': CGIServer,
2288
    'flup': FlupFCGIServer,
2289
    'wsgiref': WSGIRefServer,
2290
    'cherrypy': CherryPyServer,
2291
    'paste': PasteServer,
2292
    'fapws3': FapwsServer,
2293
    'tornado': TornadoServer,
2294
    'gae': AppEngineServer,
2295
    'twisted': TwistedServer,
2296
    'diesel': DieselServer,
2297
    'meinheld': MeinheldServer,
2298
    'gunicorn': GunicornServer,
2299
    'eventlet': EventletServer,
2300
    'gevent': GeventServer,
2301
    'rocket': RocketServer,
2302
    'bjoern' : BjoernServer,
2303
    'auto': AutoServer,
2304
}
2305
2306
2307
2308
2309
2310
2311
###############################################################################
2312
# Application Control ##########################################################
2313
###############################################################################
2314
2315
2316
def load(target, **namespace):
2317
    """ Import a module or fetch an object from a module.
2318
2319
        * ``package.module`` returns `module` as a module object.
2320
        * ``pack.mod:name`` returns the module variable `name` from `pack.mod`.
2321
        * ``pack.mod:func()`` calls `pack.mod.func()` and returns the result.
2322
2323
        The last form accepts not only function calls, but any type of
2324
        expression. Keyword arguments passed to this function are available as
2325
        local variables. Example: ``import_string('re:compile(x)', x='[a-z]')``
2326
    """
2327
    module, target = target.split(":", 1) if ':' in target else (target, None)
2328
    if module not in sys.modules: __import__(module)
2329
    if not target: return sys.modules[module]
2330
    if target.isalnum(): return getattr(sys.modules[module], target)
2331
    package_name = module.split('.')[0]
2332
    namespace[package_name] = sys.modules[package_name]
2333
    return eval('%s.%s' % (module, target), namespace)
2334
2335
2336
def load_app(target):
2337
    """ Load a bottle application from a module and make sure that the import
2338
        does not affect the current default application, but returns a separate
2339
        application object. See :func:`load` for the target parameter. """
2340
    global NORUN; NORUN, nr_old = True, NORUN
2341
    try:
2342
        tmp = default_app.push() # Create a new "default application"
2343
        rv = load(target) # Import the target module
2344
        return rv if callable(rv) else tmp
2345
    finally:
2346
        default_app.remove(tmp) # Remove the temporary added default application
2347
        NORUN = nr_old
2348
2349
def run(app=None, server='wsgiref', host='127.0.0.1', port=8080,
2350
        interval=1, reloader=False, quiet=False, plugins=None, **kargs):
2351
    """ Start a server instance. This method blocks until the server terminates.
2352
2353
        :param app: WSGI application or target string supported by
2354
               :func:`load_app`. (default: :func:`default_app`)
2355
        :param server: Server adapter to use. See :data:`server_names` keys
2356
               for valid names or pass a :class:`ServerAdapter` subclass.
2357
               (default: `wsgiref`)
2358
        :param host: Server address to bind to. Pass ``0.0.0.0`` to listens on
2359
               all interfaces including the external one. (default: 127.0.0.1)
2360
        :param port: Server port to bind to. Values below 1024 require root
2361
               privileges. (default: 8080)
2362
        :param reloader: Start auto-reloading server? (default: False)
2363
        :param interval: Auto-reloader interval in seconds (default: 1)
2364
        :param quiet: Suppress output to stdout and stderr? (default: False)
2365
        :param options: Options passed to the server adapter.
2366
     """
2367
    if NORUN: return
2368
    if reloader and not os.environ.get('BOTTLE_CHILD'):
2369
        try:
2370
            fd, lockfile = tempfile.mkstemp(prefix='bottle.', suffix='.lock')
2371
            os.close(fd) # We only need this file to exist. We never write to it
2372
            while os.path.exists(lockfile):
2373
                args = [sys.executable] + sys.argv
2374
                environ = os.environ.copy()
2375
                environ['BOTTLE_CHILD'] = 'true'
2376
                environ['BOTTLE_LOCKFILE'] = lockfile
2377
                p = subprocess.Popen(args, env=environ)
2378
                while p.poll() is None: # Busy wait...
2379
                    os.utime(lockfile, None) # I am alive!
2380
                    time.sleep(interval)
2381
                if p.poll() != 3:
2382
                    if os.path.exists(lockfile): os.unlink(lockfile)
2383
                    sys.exit(p.poll())
2384
        except KeyboardInterrupt:
2385
            pass
2386
        finally:
2387
            if os.path.exists(lockfile):
2388
                os.unlink(lockfile)
2389
        return
2390
2391
    stderr = sys.stderr.write
2392
2393
    try:
2394
        app = app or default_app()
2395
        if isinstance(app, basestring):
2396
            app = load_app(app)
2397
        if not callable(app):
2398
            raise ValueError("Application is not callable: %r" % app)
2399
2400
        for plugin in plugins or []:
2401
            app.install(plugin)
2402
2403
        if server in server_names:
2404
            server = server_names.get(server)
2405
        if isinstance(server, basestring):
2406
            server = load(server)
2407
        if isinstance(server, type):
2408
            server = server(host=host, port=port, **kargs)
2409
        if not isinstance(server, ServerAdapter):
2410
            raise ValueError("Unknown or unsupported server: %r" % server)
2411
2412
        server.quiet = server.quiet or quiet
2413
        if not server.quiet:
2414
            stderr("Bottle server starting up (using %s)...\n" % repr(server))
2415
            stderr("Listening on http://%s:%d/\n" % (server.host, server.port))
2416
            stderr("Hit Ctrl-C to quit.\n\n")
2417
2418
        if reloader:
2419
            lockfile = os.environ.get('BOTTLE_LOCKFILE')
2420
            bgcheck = FileCheckerThread(lockfile, interval)
2421
            with bgcheck:
2422
                server.run(app)
2423
            if bgcheck.status == 'reload':
2424
                sys.exit(3)
2425
        else:
2426
            server.run(app)
2427
    except KeyboardInterrupt:
2428
        pass
2429
    except (SyntaxError, ImportError):
2430
        if not reloader: raise
2431
        if not getattr(server, 'quiet', False): print_exc()
2432
        sys.exit(3)
2433
    finally:
2434
        if not getattr(server, 'quiet', False): stderr('Shutdown...\n')
2435
2436
2437
class FileCheckerThread(threading.Thread):
2438
    ''' Interrupt main-thread as soon as a changed module file is detected,
2439
        the lockfile gets deleted or gets to old. '''
2440
2441
    def __init__(self, lockfile, interval):
2442
        threading.Thread.__init__(self)
2443
        self.lockfile, self.interval = lockfile, interval
2444
        #: Is one of 'reload', 'error' or 'exit'
2445
        self.status = None
2446
2447
    def run(self):
2448
        exists = os.path.exists
2449
        mtime = lambda path: os.stat(path).st_mtime
2450
        files = dict()
2451
2452
        for module in sys.modules.values():
2453
            path = getattr(module, '__file__', '')
2454
            if path[-4:] in ('.pyo', '.pyc'): path = path[:-1]
2455
            if path and exists(path): files[path] = mtime(path)
2456
2457
        while not self.status:
2458
            if not exists(self.lockfile)\
2459
            or mtime(self.lockfile) < time.time() - self.interval - 5:
2460
                self.status = 'error'
2461
                thread.interrupt_main()
2462
            for path, lmtime in files.iteritems():
2463
                if not exists(path) or mtime(path) > lmtime:
2464
                    self.status = 'reload'
2465
                    thread.interrupt_main()
2466
                    break
2467
            time.sleep(self.interval)
2468
    
2469
    def __enter__(self):
2470
        self.start()
2471
    
2472
    def __exit__(self, exc_type, exc_val, exc_tb):
2473
        if not self.status: self.status = 'exit' # silent exit
2474
        self.join()
2475
        return issubclass(exc_type, KeyboardInterrupt)
2476
2477
2478
2479
2480
2481
###############################################################################
2482
# Template Adapters ############################################################
2483
###############################################################################
2484
2485
2486
class TemplateError(HTTPError):
2487
    def __init__(self, message):
2488
        HTTPError.__init__(self, 500, message)
2489
2490
2491
class BaseTemplate(object):
2492
    """ Base class and minimal API for template adapters """
2493
    extensions = ['tpl','html','thtml','stpl']
2494
    settings = {} #used in prepare()
2495
    defaults = {} #used in render()
2496
2497
    def __init__(self, source=None, name=None, lookup=[], encoding='utf8', **settings):
2498
        """ Create a new template.
2499
        If the source parameter (str or buffer) is missing, the name argument
2500
        is used to guess a template filename. Subclasses can assume that
2501
        self.source and/or self.filename are set. Both are strings.
2502
        The lookup, encoding and settings parameters are stored as instance
2503
        variables.
2504
        The lookup parameter stores a list containing directory paths.
2505
        The encoding parameter should be used to decode byte strings or files.
2506
        The settings parameter contains a dict for engine-specific settings.
2507
        """
2508
        self.name = name
2509
        self.source = source.read() if hasattr(source, 'read') else source
2510
        self.filename = source.filename if hasattr(source, 'filename') else None
2511
        self.lookup = map(os.path.abspath, lookup)
2512
        self.encoding = encoding
2513
        self.settings = self.settings.copy() # Copy from class variable
2514
        self.settings.update(settings) # Apply
2515
        if not self.source and self.name:
2516
            self.filename = self.search(self.name, self.lookup)
2517
            if not self.filename:
2518
                raise TemplateError('Template %s not found.' % repr(name))
2519
        if not self.source and not self.filename:
2520
            raise TemplateError('No template specified.')
2521
        self.prepare(**self.settings)
2522
2523
    @classmethod
2524
    def search(cls, name, lookup=[]):
2525
        """ Search name in all directories specified in lookup.
2526
        First without, then with common extensions. Return first hit. """
2527
        if os.path.isfile(name): return name
2528
        for spath in lookup:
2529
            fname = os.path.join(spath, name)
2530
            if os.path.isfile(fname):
2531
                return fname
2532
            for ext in cls.extensions:
2533
                if os.path.isfile('%s.%s' % (fname, ext)):
2534
                    return '%s.%s' % (fname, ext)
2535
2536
    @classmethod
2537
    def global_config(cls, key, *args):
2538
        ''' This reads or sets the global settings stored in class.settings. '''
2539
        if args:
2540
            cls.settings = cls.settings.copy() # Make settings local to class
2541
            cls.settings[key] = args[0]
2542
        else:
2543
            return cls.settings[key]
2544
2545
    def prepare(self, **options):
2546
        """ Run preparations (parsing, caching, ...).
2547
        It should be possible to call this again to refresh a template or to
2548
        update settings.
2549
        """
2550
        raise NotImplementedError
2551
2552
    def render(self, *args, **kwargs):
2553
        """ Render the template with the specified local variables and return
2554
        a single byte or unicode string. If it is a byte string, the encoding
2555
        must match self.encoding. This method must be thread-safe!
2556
        Local variables may be provided in dictionaries (*args)
2557
        or directly, as keywords (**kwargs).
2558
        """
2559
        raise NotImplementedError
2560
2561
2562
class MakoTemplate(BaseTemplate):
2563
    def prepare(self, **options):
2564
        from mako.template import Template
2565
        from mako.lookup import TemplateLookup
2566
        options.update({'input_encoding':self.encoding})
2567
        options.setdefault('format_exceptions', bool(DEBUG))
2568
        lookup = TemplateLookup(directories=self.lookup, **options)
2569
        if self.source:
2570
            self.tpl = Template(self.source, lookup=lookup, **options)
2571
        else:
2572
            self.tpl = Template(uri=self.name, filename=self.filename, lookup=lookup, **options)
2573
2574
    def render(self, *args, **kwargs):
2575
        for dictarg in args: kwargs.update(dictarg)
2576
        _defaults = self.defaults.copy()
2577
        _defaults.update(kwargs)
2578
        return self.tpl.render(**_defaults)
2579
2580
2581
class CheetahTemplate(BaseTemplate):
2582
    def prepare(self, **options):
2583
        from Cheetah.Template import Template
2584
        self.context = threading.local()
2585
        self.context.vars = {}
2586
        options['searchList'] = [self.context.vars]
2587
        if self.source:
2588
            self.tpl = Template(source=self.source, **options)
2589
        else:
2590
            self.tpl = Template(file=self.filename, **options)
2591
2592
    def render(self, *args, **kwargs):
2593
        for dictarg in args: kwargs.update(dictarg)
2594
        self.context.vars.update(self.defaults)
2595
        self.context.vars.update(kwargs)
2596
        out = str(self.tpl)
2597
        self.context.vars.clear()
2598
        return out
2599
2600
2601
class Jinja2Template(BaseTemplate):
2602
    def prepare(self, filters=None, tests=None, **kwargs):
2603
        from jinja2 import Environment, FunctionLoader
2604
        if 'prefix' in kwargs: # TODO: to be removed after a while
2605
            raise RuntimeError('The keyword argument `prefix` has been removed. '
2606
                'Use the full jinja2 environment name line_statement_prefix instead.')
2607
        self.env = Environment(loader=FunctionLoader(self.loader), **kwargs)
2608
        if filters: self.env.filters.update(filters)
2609
        if tests: self.env.tests.update(tests)
2610
        if self.source:
2611
            self.tpl = self.env.from_string(self.source)
2612
        else:
2613
            self.tpl = self.env.get_template(self.filename)
2614
2615
    def render(self, *args, **kwargs):
2616
        for dictarg in args: kwargs.update(dictarg)
2617
        _defaults = self.defaults.copy()
2618
        _defaults.update(kwargs)
2619
        return self.tpl.render(**_defaults)
2620
2621
    def loader(self, name):
2622
        fname = self.search(name, self.lookup)
2623
        if fname:
2624
            with open(fname, "rb") as f:
2625
                return f.read().decode(self.encoding)
2626
2627
2628
class SimpleTALTemplate(BaseTemplate):
2629
    ''' Untested! '''
2630
    def prepare(self, **options):
2631
        from simpletal import simpleTAL
2632
        # TODO: add option to load METAL files during render
2633
        if self.source:
2634
            self.tpl = simpleTAL.compileHTMLTemplate(self.source)
2635
        else:
2636
            with open(self.filename, 'rb') as fp:
2637
                self.tpl = simpleTAL.compileHTMLTemplate(tonat(fp.read()))
2638
2639
    def render(self, *args, **kwargs):
2640
        from simpletal import simpleTALES
2641
        for dictarg in args: kwargs.update(dictarg)
2642
        # TODO: maybe reuse a context instead of always creating one
2643
        context = simpleTALES.Context()
2644
        for k,v in self.defaults.items():
2645
            context.addGlobal(k, v)
2646
        for k,v in kwargs.items():
2647
            context.addGlobal(k, v)
2648
        output = StringIO()
2649
        self.tpl.expand(context, output)
2650
        return output.getvalue()
2651
2652
2653
class SimpleTemplate(BaseTemplate):
2654
    blocks = ('if', 'elif', 'else', 'try', 'except', 'finally', 'for', 'while',
2655
              'with', 'def', 'class')
2656
    dedent_blocks = ('elif', 'else', 'except', 'finally')
2657
2658
    @lazy_attribute
2659
    def re_pytokens(cls):
2660
        ''' This matches comments and all kinds of quoted strings but does
2661
            NOT match comments (#...) within quoted strings. (trust me) '''
2662
        return re.compile(r'''
2663
            (''(?!')|""(?!")|'{6}|"{6}    # Empty strings (all 4 types)
2664
             |'(?:[^\\']|\\.)+?'          # Single quotes (')
2665
             |"(?:[^\\"]|\\.)+?"          # Double quotes (")
2666
             |'{3}(?:[^\\]|\\.|\n)+?'{3}  # Triple-quoted strings (')
2667
             |"{3}(?:[^\\]|\\.|\n)+?"{3}  # Triple-quoted strings (")
2668
             |\#.*                        # Comments
2669
            )''', re.VERBOSE)
2670
2671
    def prepare(self, escape_func=html_escape, noescape=False, **kwargs):
2672
        self.cache = {}
2673
        enc = self.encoding
2674
        self._str = lambda x: touni(x, enc)
2675
        self._escape = lambda x: escape_func(touni(x, enc))
2676
        if noescape:
2677
            self._str, self._escape = self._escape, self._str
2678
2679
    @classmethod
2680
    def split_comment(cls, code):
2681
        """ Removes comments (#...) from python code. """
2682
        if '#' not in code: return code
2683
        #: Remove comments only (leave quoted strings as they are)
2684
        subf = lambda m: '' if m.group(0)[0]=='#' else m.group(0)
2685
        return re.sub(cls.re_pytokens, subf, code)
2686
2687
    @cached_property
2688
    def co(self):
2689
        return compile(self.code, self.filename or '<string>', 'exec')
2690
2691
    @cached_property
2692
    def code(self):
2693
        stack = [] # Current Code indentation
2694
        lineno = 0 # Current line of code
2695
        ptrbuffer = [] # Buffer for printable strings and token tuple instances
2696
        codebuffer = [] # Buffer for generated python code
2697
        multiline = dedent = oneline = False
2698
        template = self.source or open(self.filename, 'rb').read()
2699
2700
        def yield_tokens(line):
2701
            for i, part in enumerate(re.split(r'\{\{(.*?)\}\}', line)):
2702
                if i % 2:
2703
                    if part.startswith('!'): yield 'RAW', part[1:]
2704
                    else: yield 'CMD', part
2705
                else: yield 'TXT', part
2706
2707
        def flush(): # Flush the ptrbuffer
2708
            if not ptrbuffer: return
2709
            cline = ''
2710
            for line in ptrbuffer:
2711
                for token, value in line:
2712
                    if token == 'TXT': cline += repr(value)
2713
                    elif token == 'RAW': cline += '_str(%s)' % value
2714
                    elif token == 'CMD': cline += '_escape(%s)' % value
2715
                    cline +=  ', '
2716
                cline = cline[:-2] + '\\\n'
2717
            cline = cline[:-2]
2718
            if cline[:-1].endswith('\\\\\\\\\\n'):
2719
                cline = cline[:-7] + cline[-1] # 'nobr\\\\\n' --> 'nobr'
2720
            cline = '_printlist([' + cline + '])'
2721
            del ptrbuffer[:] # Do this before calling code() again
2722
            code(cline)
2723
2724
        def code(stmt):
2725
            for line in stmt.splitlines():
2726
                codebuffer.append('  ' * len(stack) + line.strip())
2727
2728
        for line in template.splitlines(True):
2729
            lineno += 1
2730
            line = line if isinstance(line, unicode)\
2731
                        else unicode(line, encoding=self.encoding)
2732
            sline = line.lstrip()
2733
            if lineno <= 2:
2734
                m = re.search(r"%.*coding[:=]\s*([-\w\.]+)", line)
2735
                if m: self.encoding = m.group(1)
2736
                if m: line = line.replace('coding','coding (removed)')
2737
            if sline and sline[0] == '%' and sline[:2] != '%%':
2738
                line = line.split('%',1)[1].lstrip() # Full line following the %
2739
                cline = self.split_comment(line).strip()
2740
                cmd = re.split(r'[^a-zA-Z0-9_]', cline)[0]
2741
                flush() # You are actually reading this? Good luck, it's a mess :)
2742
                if cmd in self.blocks or multiline:
2743
                    cmd = multiline or cmd
2744
                    dedent = cmd in self.dedent_blocks # "else:"
2745
                    if dedent and not oneline and not multiline:
2746
                        cmd = stack.pop()
2747
                    code(line)
2748
                    oneline = not cline.endswith(':') # "if 1: pass"
2749
                    multiline = cmd if cline.endswith('\\') else False
2750
                    if not oneline and not multiline:
2751
                        stack.append(cmd)
2752
                elif cmd == 'end' and stack:
2753
                    code('#end(%s) %s' % (stack.pop(), line.strip()[3:]))
2754
                elif cmd == 'include':
2755
                    p = cline.split(None, 2)[1:]
2756
                    if len(p) == 2:
2757
                        code("_=_include(%s, _stdout, %s)" % (repr(p[0]), p[1]))
2758
                    elif p:
2759
                        code("_=_include(%s, _stdout)" % repr(p[0]))
2760
                    else: # Empty %include -> reverse of %rebase
2761
                        code("_printlist(_base)")
2762
                elif cmd == 'rebase':
2763
                    p = cline.split(None, 2)[1:]
2764
                    if len(p) == 2:
2765
                        code("globals()['_rebase']=(%s, dict(%s))" % (repr(p[0]), p[1]))
2766
                    elif p:
2767
                        code("globals()['_rebase']=(%s, {})" % repr(p[0]))
2768
                else:
2769
                    code(line)
2770
            else: # Line starting with text (not '%') or '%%' (escaped)
2771
                if line.strip().startswith('%%'):
2772
                    line = line.replace('%%', '%', 1)
2773
                ptrbuffer.append(yield_tokens(line))
2774
        flush()
2775
        return '\n'.join(codebuffer) + '\n'
2776
2777
    def subtemplate(self, _name, _stdout, *args, **kwargs):
2778
        for dictarg in args: kwargs.update(dictarg)
2779
        if _name not in self.cache:
2780
            self.cache[_name] = self.__class__(name=_name, lookup=self.lookup)
2781
        return self.cache[_name].execute(_stdout, kwargs)
2782
2783
    def execute(self, _stdout, *args, **kwargs):
2784
        for dictarg in args: kwargs.update(dictarg)
2785
        env = self.defaults.copy()
2786
        env.update({'_stdout': _stdout, '_printlist': _stdout.extend,
2787
               '_include': self.subtemplate, '_str': self._str,
2788
               '_escape': self._escape, 'get': env.get,
2789
               'setdefault': env.setdefault, 'defined': env.__contains__})
2790
        env.update(kwargs)
2791
        eval(self.co, env)
2792
        if '_rebase' in env:
2793
            subtpl, rargs = env['_rebase']
2794
            rargs['_base'] = _stdout[:] #copy stdout
2795
            del _stdout[:] # clear stdout
2796
            return self.subtemplate(subtpl,_stdout,rargs)
2797
        return env
2798
2799
    def render(self, *args, **kwargs):
2800
        """ Render the template using keyword arguments as local variables. """
2801
        for dictarg in args: kwargs.update(dictarg)
2802
        stdout = []
2803
        self.execute(stdout, kwargs)
2804
        return ''.join(stdout)
2805
2806
2807
def template(*args, **kwargs):
2808
    '''
2809
    Get a rendered template as a string iterator.
2810
    You can use a name, a filename or a template string as first parameter.
2811
    Template rendering arguments can be passed as dictionaries
2812
    or directly (as keyword arguments).
2813
    '''
2814
    tpl = args[0] if args else None
2815
    template_adapter = kwargs.pop('template_adapter', SimpleTemplate)
2816
    if tpl not in TEMPLATES or DEBUG:
2817
        settings = kwargs.pop('template_settings', {})
2818
        lookup = kwargs.pop('template_lookup', TEMPLATE_PATH)
2819
        if isinstance(tpl, template_adapter):
2820
            TEMPLATES[tpl] = tpl
2821
            if settings: TEMPLATES[tpl].prepare(**settings)
2822
        elif "\n" in tpl or "{" in tpl or "%" in tpl or '$' in tpl:
2823
            TEMPLATES[tpl] = template_adapter(source=tpl, lookup=lookup, **settings)
2824
        else:
2825
            TEMPLATES[tpl] = template_adapter(name=tpl, lookup=lookup, **settings)
2826
    if not TEMPLATES[tpl]:
2827
        abort(500, 'Template (%s) not found' % tpl)
2828
    for dictarg in args[1:]: kwargs.update(dictarg)
2829
    return TEMPLATES[tpl].render(kwargs)
2830
2831
mako_template = functools.partial(template, template_adapter=MakoTemplate)
2832
cheetah_template = functools.partial(template, template_adapter=CheetahTemplate)
2833
jinja2_template = functools.partial(template, template_adapter=Jinja2Template)
2834
simpletal_template = functools.partial(template, template_adapter=SimpleTALTemplate)
2835
2836
2837
def view(tpl_name, **defaults):
2838
    ''' Decorator: renders a template for a handler.
2839
        The handler can control its behavior like that:
2840
2841
          - return a dict of template vars to fill out the template
2842
          - return something other than a dict and the view decorator will not
2843
            process the template, but return the handler result as is.
2844
            This includes returning a HTTPResponse(dict) to get,
2845
            for instance, JSON with autojson or other castfilters.
2846
    '''
2847
    def decorator(func):
2848
        @functools.wraps(func)
2849
        def wrapper(*args, **kwargs):
2850
            result = func(*args, **kwargs)
2851
            if isinstance(result, (dict, DictMixin)):
2852
                tplvars = defaults.copy()
2853
                tplvars.update(result)
2854
                return template(tpl_name, **tplvars)
2855
            return result
2856
        return wrapper
2857
    return decorator
2858
2859
mako_view = functools.partial(view, template_adapter=MakoTemplate)
2860
cheetah_view = functools.partial(view, template_adapter=CheetahTemplate)
2861
jinja2_view = functools.partial(view, template_adapter=Jinja2Template)
2862
simpletal_view = functools.partial(view, template_adapter=SimpleTALTemplate)
2863
2864
2865
2866
2867
2868
2869
###############################################################################
2870
# Constants and Globals ########################################################
2871
###############################################################################
2872
2873
2874
TEMPLATE_PATH = ['./', './views/']
2875
TEMPLATES = {}
2876
DEBUG = False
2877
NORUN = False # If set, run() does nothing. Used by load_app()
2878
2879
#: A dict to map HTTP status codes (e.g. 404) to phrases (e.g. 'Not Found')
2880
HTTP_CODES = httplib.responses
2881
HTTP_CODES[418] = "I'm a teapot" # RFC 2324
2882
HTTP_CODES[428] = "Precondition Required"
2883
HTTP_CODES[429] = "Too Many Requests"
2884
HTTP_CODES[431] = "Request Header Fields Too Large"
2885
HTTP_CODES[511] = "Network Authentication Required"
2886
_HTTP_STATUS_LINES = dict((k, '%d %s'%(k,v)) for (k,v) in HTTP_CODES.iteritems())
2887
2888
#: The default template used for error pages. Override with @error()
2889
ERROR_PAGE_TEMPLATE = """
2890
%try:
2891
    %from bottle import DEBUG, HTTP_CODES, request, touni
2892
    %status_name = HTTP_CODES.get(e.status, 'Unknown').title()
2893
    <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
2894
    <html>
2895
        <head>
2896
            <title>Error {{e.status}}: {{status_name}}</title>
2897
            <style type="text/css">
2898
              html {background-color: #eee; font-family: sans;}
2899
              body {background-color: #fff; border: 1px solid #ddd;
2900
                    padding: 15px; margin: 15px;}
2901
              pre {background-color: #eee; border: 1px solid #ddd; padding: 5px;}
2902
            </style>
2903
        </head>
2904
        <body>
2905
            <h1>Error {{e.status}}: {{status_name}}</h1>
2906
            <p>Sorry, the requested URL <tt>{{repr(request.url)}}</tt>
2907
               caused an error:</p>
2908
            <pre>{{e.output}}</pre>
2909
            %if DEBUG and e.exception:
2910
              <h2>Exception:</h2>
2911
              <pre>{{repr(e.exception)}}</pre>
2912
            %end
2913
            %if DEBUG and e.traceback:
2914
              <h2>Traceback:</h2>
2915
              <pre>{{e.traceback}}</pre>
2916
            %end
2917
        </body>
2918
    </html>
2919
%except ImportError:
2920
    <b>ImportError:</b> Could not generate the error page. Please add bottle to
2921
    the import path.
2922
%end
2923
"""
2924
2925
#: A thread-safe instance of :class:`Request` representing the `current` request.
2926
request = Request()
2927
2928
#: A thread-safe instance of :class:`Response` used to build the HTTP response.
2929
response = Response()
2930
2931
#: A thread-safe namespace. Not used by Bottle.
2932
local = threading.local()
2933
2934
# Initialize app stack (create first empty Bottle app)
2935
# BC: 0.6.4 and needed for run()
2936
app = default_app = AppStack()
2937
app.push()
2938
2939
#: A virtual package that redirects import statements.
2940
#: Example: ``import bottle.ext.sqlite`` actually imports `bottle_sqlite`.
2941
ext = _ImportRedirect(__name__+'.ext', 'bottle_%s').module
2942
2943
if __name__ == '__main__':
2944
    opt, args, parser = _cmd_options, _cmd_args, _cmd_parser
2945
    if opt.version:
2946
        print 'Bottle', __version__; sys.exit(0)
2947
    if not args:
2948
        parser.print_help()
2949
        print '\nError: No application specified.\n'
2950
        sys.exit(1)
2951
2952
    try:
2953
        sys.path.insert(0, '.')
2954
        sys.modules.setdefault('bottle', sys.modules['__main__'])
2955
    except (AttributeError, ImportError), e:
2956
        parser.error(e.args[0])
2957
2958
    if opt.bind and ':' in opt.bind:
2959
        host, port = opt.bind.rsplit(':', 1)
2960
    else:
2961
        host, port = (opt.bind or 'localhost'), 8080
2962
2963
    debug(opt.debug)
2964
    run(args[0], host=host, port=port, server=opt.server, reloader=opt.reload, plugins=opt.plugin)
2965
2966
# THE END