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