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