1
2
3
4 """
5 This file is part of the web2py Web Framework
6 Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
7 License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
8
9 gluon.rewrite parses incoming URLs and formats outgoing URLs for gluon.html.URL.
10
11 In addition, it rewrites both incoming and outgoing URLs based on the (optional) user-supplied routes.py,
12 which also allows for rewriting of certain error messages.
13
14 routes.py supports two styles of URL rewriting, depending on whether 'routers' is defined.
15 Refer to router.example.py and routes.example.py for additional documentation.
16
17 """
18
19 import os
20 import re
21 import logging
22 import traceback
23 import threading
24 import urllib
25 from storage import Storage, List
26 from http import HTTP
27 from fileutils import abspath
28 from settings import global_settings
29
30 logger = logging.getLogger('web2py.rewrite')
31
32 thread = threading.local()
33
35 "return new copy of default base router"
36 router = Storage(
37 default_application = 'init',
38 applications = 'ALL',
39 default_controller = 'default',
40 controllers = 'DEFAULT',
41 default_function = 'index',
42 functions = None,
43 default_language = None,
44 languages = None,
45 root_static = ['favicon.ico', 'robots.txt'],
46 domains = None,
47 map_hyphen = True,
48 acfe_match = r'\w+$',
49 file_match = r'(\w+[-=./]?)+$',
50 args_match = r'([\w@ -]+[=.]?)*$',
51 )
52 return router
53
55 "return new copy of default parameters"
56 p = Storage()
57 p.name = app or "BASE"
58 p.default_application = app or "init"
59 p.default_controller = "default"
60 p.default_function = "index"
61 p.routes_app = []
62 p.routes_in = []
63 p.routes_out = []
64 p.routes_onerror = []
65 p.routes_apps_raw = []
66 p.error_handler = None
67 p.error_message = '<html><body><h1>%s</h1></body></html>'
68 p.error_message_ticket = \
69 '<html><body><h1>Internal error</h1>Ticket issued: <a href="/admin/default/ticket/%(ticket)s" target="_blank">%(ticket)s</a></body><!-- this is junk text else IE does not display the page: '+('x'*512)+' //--></html>'
70 p.routers = None
71 return p
72
73 params_apps = dict()
74 params = _params_default(app=None)
75 thread.routes = params
76 routers = None
77
78 ROUTER_KEYS = set(('default_application', 'applications', 'default_controller', 'controllers',
79 'default_function', 'functions', 'default_language', 'languages',
80 'domain', 'domains', 'root_static', 'path_prefix',
81 'map_hyphen', 'map_static',
82 'acfe_match', 'file_match', 'args_match'))
83
84 ROUTER_BASE_KEYS = set(('applications', 'default_application', 'domains', 'path_prefix'))
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
107
108 -def url_out(request, env, application, controller, function, args, other, scheme, host, port):
109 "assemble and rewrite outgoing URL"
110 if routers:
111 acf = map_url_out(request, application, controller, function, args)
112 url = '%s%s' % (acf, other)
113 else:
114 url = '/%s/%s/%s%s' % (application, controller, function, other)
115 url = regex_filter_out(url, env)
116
117
118
119
120 if scheme or port is not None:
121 if host is None:
122 host = True
123 if not scheme or scheme is True:
124 if request and request.env:
125 scheme = request.env.get('WSGI_URL_SCHEME', 'http').lower()
126 else:
127 scheme = 'http'
128 if host is not None:
129 if host is True:
130 host = request.env.http_host
131 if host:
132 if port is None:
133 port = ''
134 else:
135 port = ':%s' % port
136 url = '%s://%s%s%s' % (scheme, host, port, url)
137 return url
138
140 "called from main.wsgibase to rewrite the http response"
141 status = int(str(http_object.status).split()[0])
142 if status>399 and thread.routes.routes_onerror:
143 keys=set(('%s/%s' % (request.application, status),
144 '%s/*' % (request.application),
145 '*/%s' % (status),
146 '*/*'))
147 for (key,redir) in thread.routes.routes_onerror:
148 if key in keys:
149 if redir == '!':
150 break
151 elif '?' in redir:
152 url = '%s&code=%s&ticket=%s&requested_uri=%s&request_url=%s' % \
153 (redir,status,ticket,request.env.request_uri,request.url)
154 else:
155 url = '%s?code=%s&ticket=%s&requested_uri=%s&request_url=%s' % \
156 (redir,status,ticket,request.env.request_uri,request.url)
157 return HTTP(303,
158 'You are being redirected <a href="%s">here</a>' % url,
159 Location=url)
160 return http_object
161
162
163 -def load(routes='routes.py', app=None, data=None, rdict=None):
164 """
165 load: read (if file) and parse routes
166 store results in params
167 (called from main.py at web2py initialization time)
168 If data is present, it's used instead of the routes.py contents.
169 If rdict is present, it must be a dict to be used for routers (unit test)
170 """
171 global params
172 global routers
173 if app is None:
174
175 global params_apps
176 params_apps = dict()
177 params = _params_default(app=None)
178 thread.routes = params
179 routers = None
180
181 if isinstance(rdict, dict):
182 symbols = dict(routers=rdict)
183 path = 'rdict'
184 else:
185 if data is not None:
186 path = 'routes'
187 else:
188 if app is None:
189 path = abspath(routes)
190 else:
191 path = abspath('applications', app, routes)
192 if not os.path.exists(path):
193 return
194 routesfp = open(path, 'r')
195 data = routesfp.read().replace('\r\n','\n')
196 routesfp.close()
197
198 symbols = {}
199 try:
200 exec (data + '\n') in symbols
201 except SyntaxError, e:
202 logger.error(
203 '%s has a syntax error and will not be loaded\n' % path
204 + traceback.format_exc())
205 raise e
206
207 p = _params_default(app)
208
209 for sym in ('routes_app', 'routes_in', 'routes_out'):
210 if sym in symbols:
211 for (k, v) in symbols[sym]:
212 p[sym].append(compile_regex(k, v))
213 for sym in ('routes_onerror', 'routes_apps_raw',
214 'error_handler','error_message', 'error_message_ticket',
215 'default_application','default_controller', 'default_function'):
216 if sym in symbols:
217 p[sym] = symbols[sym]
218 if 'routers' in symbols:
219 p.routers = Storage(symbols['routers'])
220 for key in p.routers:
221 if isinstance(p.routers[key], dict):
222 p.routers[key] = Storage(p.routers[key])
223
224 if app is None:
225 params = p
226 thread.routes = params
227
228
229
230 routers = params.routers
231 if isinstance(routers, dict):
232 routers = Storage(routers)
233 if routers is not None:
234 router = _router_default()
235 if routers.BASE:
236 router.update(routers.BASE)
237 routers.BASE = router
238
239
240
241
242
243 all_apps = []
244 for appname in [app for app in os.listdir(abspath('applications')) if not app.startswith('.')]:
245 if os.path.isdir(abspath('applications', appname)) and \
246 os.path.isdir(abspath('applications', appname, 'controllers')):
247 all_apps.append(appname)
248 if routers:
249 router = Storage(routers.BASE)
250 if appname in routers:
251 for key in routers[appname].keys():
252 if key in ROUTER_BASE_KEYS:
253 raise SyntaxError, "BASE-only key '%s' in router '%s'" % (key, appname)
254 router.update(routers[appname])
255 routers[appname] = router
256 if os.path.exists(abspath('applications', appname, routes)):
257 load(routes, appname)
258
259 if routers:
260 load_routers(all_apps)
261
262 else:
263 params_apps[app] = p
264 if routers and p.routers:
265 if app in p.routers:
266 routers[app].update(p.routers[app])
267
268 logger.debug('URL rewrite is on. configuration in %s' % path)
269
270
271 regex_at = re.compile(r'(?<!\\)\$[a-zA-Z]\w*')
272 regex_anything = re.compile(r'(?<!\\)\$anything')
273
275 """
276 Preprocess and compile the regular expressions in routes_app/in/out
277
278 The resulting regex will match a pattern of the form:
279
280 [remote address]:[protocol]://[host]:[method] [path]
281
282 We allow abbreviated regexes on input; here we try to complete them.
283 """
284 k0 = k
285
286 if not k[0] == '^':
287 k = '^%s' % k
288 if not k[-1] == '$':
289 k = '%s$' % k
290
291 if k.find(':') < 0:
292
293 k = '^.*?:https?://[^:/]+:[a-z]+ %s' % k[1:]
294
295 if k.find('://') < 0:
296 i = k.find(':/')
297 if i < 0:
298 raise SyntaxError, "routes pattern syntax error: path needs leading '/' [%s]" % k0
299 k = r'%s:https?://[^:/]+:[a-z]+ %s' % (k[:i], k[i+1:])
300
301 for item in regex_anything.findall(k):
302 k = k.replace(item, '(?P<anything>.*)')
303
304 for item in regex_at.findall(k):
305 k = k.replace(item, r'(?P<%s>\w+)' % item[1:])
306
307 for item in regex_at.findall(v):
308 v = v.replace(item, r'\g<%s>' % item[1:])
309 return (re.compile(k, re.DOTALL), v)
310
312 "load-time post-processing of routers"
313
314 for app in routers.keys():
315
316 if app not in all_apps:
317 all_apps.append(app)
318 router = Storage(routers.BASE)
319 if app != 'BASE':
320 for key in routers[app].keys():
321 if key in ROUTER_BASE_KEYS:
322 raise SyntaxError, "BASE-only key '%s' in router '%s'" % (key, app)
323 router.update(routers[app])
324 routers[app] = router
325 router = routers[app]
326 for key in router.keys():
327 if key not in ROUTER_KEYS:
328 raise SyntaxError, "unknown key '%s' in router '%s'" % (key, app)
329 if not router.controllers:
330 router.controllers = set()
331 elif not isinstance(router.controllers, str):
332 router.controllers = set(router.controllers)
333 if router.functions:
334 router.functions = set(router.functions)
335 else:
336 router.functions = set()
337 if router.languages:
338 router.languages = set(router.languages)
339 else:
340 router.languages = set()
341 if app != 'BASE':
342 for base_only in ROUTER_BASE_KEYS:
343 router.pop(base_only, None)
344 if 'domain' in router:
345 routers.BASE.domains[router.domain] = app
346 if isinstance(router.controllers, str) and router.controllers == 'DEFAULT':
347 router.controllers = set()
348 if os.path.isdir(abspath('applications', app)):
349 cpath = abspath('applications', app, 'controllers')
350 for cname in os.listdir(cpath):
351 if os.path.isfile(abspath(cpath, cname)) and cname.endswith('.py'):
352 router.controllers.add(cname[:-3])
353 if router.controllers:
354 router.controllers.add('static')
355 router.controllers.add(router.default_controller)
356 if router.functions:
357 router.functions.add(router.default_function)
358
359 if isinstance(routers.BASE.applications, str) and routers.BASE.applications == 'ALL':
360 routers.BASE.applications = list(all_apps)
361 if routers.BASE.applications:
362 routers.BASE.applications = set(routers.BASE.applications)
363 else:
364 routers.BASE.applications = set()
365
366 for app in routers.keys():
367
368 router = routers[app]
369 router.name = app
370
371 router._acfe_match = re.compile(router.acfe_match)
372 router._file_match = re.compile(router.file_match)
373 if router.args_match:
374 router._args_match = re.compile(router.args_match)
375
376 if router.path_prefix:
377 if isinstance(router.path_prefix, str):
378 router.path_prefix = router.path_prefix.strip('/').split('/')
379
380
381
382
383
384
385
386 domains = dict()
387 if routers.BASE.domains:
388 for (domain, app) in [(d.strip(':'), a.strip('/')) for (d, a) in routers.BASE.domains.items()]:
389 port = None
390 if ':' in domain:
391 (domain, port) = domain.split(':')
392 ctlr = None
393 if '/' in app:
394 (app, ctlr) = app.split('/')
395 if app not in all_apps and app not in routers:
396 raise SyntaxError, "unknown app '%s' in domains" % app
397 domains[(domain, port)] = (app, ctlr)
398 routers.BASE.domains = domains
399
400 -def regex_uri(e, regexes, tag, default=None):
401 "filter incoming URI against a list of regexes"
402 path = e['PATH_INFO']
403 host = e.get('HTTP_HOST', 'localhost').lower()
404 i = host.find(':')
405 if i > 0:
406 host = host[:i]
407 key = '%s:%s://%s:%s %s' % \
408 (e.get('REMOTE_ADDR','localhost'),
409 e.get('WSGI_URL_SCHEME', 'http').lower(), host,
410 e.get('REQUEST_METHOD', 'get').lower(), path)
411 for (regex, value) in regexes:
412 if regex.match(key):
413 rewritten = regex.sub(value, key)
414 logger.debug('%s: [%s] [%s] -> %s' % (tag, key, value, rewritten))
415 return rewritten
416 logger.debug('%s: [%s] -> %s (not rewritten)' % (tag, key, default))
417 return default
418
435
437 "regex rewrite incoming URL"
438 query = e.get('QUERY_STRING', None)
439 e['WEB2PY_ORIGINAL_URI'] = e['PATH_INFO'] + (query and ('?' + query) or '')
440 if thread.routes.routes_in:
441 path = regex_uri(e, thread.routes.routes_in, "routes_in", e['PATH_INFO'])
442 items = path.split('?', 1)
443 e['PATH_INFO'] = items[0]
444 if len(items) > 1:
445 if query:
446 query = items[1] + '&' + query
447 else:
448 query = items[1]
449 e['QUERY_STRING'] = query
450 e['REQUEST_URI'] = e['PATH_INFO'] + (query and ('?' + query) or '')
451 return e
452
453
454
455
456 regex_space = re.compile('(\+|\s|%20)+')
457
458
459
460
461
462
463
464
465
466
467
468 regex_static = re.compile(r'''
469 (^ # static pages
470 /(?P<b> \w+) # b=app
471 /static # /b/static
472 /(?P<x> (\w[\-\=\./]?)* ) # x=file
473 $)
474 ''', re.X)
475
476 regex_url = re.compile(r'''
477 (^( # (/a/c/f.e/s)
478 /(?P<a> [\w\s+]+ ) # /a=app
479 ( # (/c.f.e/s)
480 /(?P<c> [\w\s+]+ ) # /a/c=controller
481 ( # (/f.e/s)
482 /(?P<f> [\w\s+]+ ) # /a/c/f=function
483 ( # (.e)
484 \.(?P<e> [\w\s+]+ ) # /a/c/f.e=extension
485 )?
486 ( # (/s)
487 /(?P<r> # /a/c/f.e/r=raw_args
488 .*
489 )
490 )?
491 )?
492 )?
493 )?
494 /?$)
495 ''', re.X)
496
497 regex_args = re.compile(r'''
498 (^
499 (?P<s>
500 ( [\w@/-][=.]? )* # s=args
501 )?
502 /?$) # trailing slash
503 ''', re.X)
504
506 "rewrite and parse incoming URL"
507
508
509
510
511
512
513
514 regex_select(env=environ, request=request)
515
516 if thread.routes.routes_in:
517 environ = regex_filter_in(environ)
518
519 for (key, value) in environ.items():
520 request.env[key.lower().replace('.', '_')] = value
521
522 path = request.env.path_info.replace('\\', '/')
523
524
525
526
527
528 match = regex_static.match(regex_space.sub('_', path))
529 if match and match.group('x'):
530 static_file = os.path.join(request.env.applications_parent,
531 'applications', match.group('b'),
532 'static', match.group('x'))
533 return (static_file, environ)
534
535
536
537
538
539 path = re.sub('%20', ' ', path)
540 match = regex_url.match(path)
541 if not match or match.group('c') == 'static':
542 raise HTTP(400,
543 thread.routes.error_message % 'invalid request',
544 web2py_error='invalid path')
545
546 request.application = \
547 regex_space.sub('_', match.group('a') or thread.routes.default_application)
548 request.controller = \
549 regex_space.sub('_', match.group('c') or thread.routes.default_controller)
550 request.function = \
551 regex_space.sub('_', match.group('f') or thread.routes.default_function)
552 group_e = match.group('e')
553 request.raw_extension = group_e and regex_space.sub('_', group_e) or None
554 request.extension = request.raw_extension or 'html'
555 request.raw_args = match.group('r')
556 request.args = List([])
557 if request.application in thread.routes.routes_apps_raw:
558
559 request.args = None
560 elif request.raw_args:
561 match = regex_args.match(request.raw_args.replace(' ', '_'))
562 if match:
563 group_s = match.group('s')
564 request.args = \
565 List((group_s and group_s.split('/')) or [])
566 if request.args and request.args[-1] == '':
567 request.args.pop()
568 else:
569 raise HTTP(400,
570 thread.routes.error_message % 'invalid request',
571 web2py_error='invalid path (args)')
572 return (None, environ)
573
574
576 "regex rewrite outgoing URL"
577 if not hasattr(thread, 'routes'):
578 regex_select()
579 if routers:
580 return url
581 if thread.routes.routes_out:
582 items = url.split('?', 1)
583 if e:
584 host = e.get('http_host', 'localhost').lower()
585 i = host.find(':')
586 if i > 0:
587 host = host[:i]
588 items[0] = '%s:%s://%s:%s %s' % \
589 (e.get('remote_addr', ''),
590 e.get('wsgi_url_scheme', 'http').lower(), host,
591 e.get('request_method', 'get').lower(), items[0])
592 else:
593 items[0] = ':http://localhost:get %s' % items[0]
594 for (regex, value) in thread.routes.routes_out:
595 if regex.match(items[0]):
596 rewritten = '?'.join([regex.sub(value, items[0])] + items[1:])
597 logger.debug('routes_out: [%s] -> %s' % (url, rewritten))
598 return rewritten
599 logger.debug('routes_out: [%s] not rewritten' % url)
600 return url
601
602
603 -def filter_url(url, method='get', remote='0.0.0.0', out=False, app=False, lang=None, domain=(None,None), env=False):
604 "doctest/unittest interface to regex_filter_in() and regex_filter_out()"
605 regex_url = re.compile(r'^(?P<scheme>http|https|HTTP|HTTPS)\://(?P<host>[^/]*)(?P<uri>.*)')
606 match = regex_url.match(url)
607 scheme = match.group('scheme').lower()
608 host = match.group('host').lower()
609 uri = match.group('uri')
610 k = uri.find('?')
611 if k < 0:
612 k = len(uri)
613 (path_info, query_string) = (uri[:k], uri[k+1:])
614 path_info = urllib.unquote(path_info)
615 e = {
616 'REMOTE_ADDR': remote,
617 'REQUEST_METHOD': method,
618 'WSGI_URL_SCHEME': scheme,
619 'HTTP_HOST': host,
620 'REQUEST_URI': uri,
621 'PATH_INFO': path_info,
622 'QUERY_STRING': query_string,
623
624 'remote_addr': remote,
625 'request_method': method,
626 'wsgi_url_scheme': scheme,
627 'http_host': host
628 }
629
630 request = Storage()
631 e["applications_parent"] = global_settings.applications_parent
632 request.env = Storage(e)
633 request.uri_language = lang
634
635
636
637 if app:
638 if routers:
639 return map_url_in(request, e, app=True)
640 return regex_select(e)
641
642
643
644 if out:
645 (request.env.domain_application, request.env.domain_controller) = domain
646 items = path_info.lstrip('/').split('/')
647 if items[-1] == '':
648 items.pop()
649 assert len(items) >= 3, "at least /a/c/f is required"
650 a = items.pop(0)
651 c = items.pop(0)
652 f = items.pop(0)
653 if not routers:
654 return regex_filter_out(uri, e)
655 acf = map_url_out(request, a, c, f, items)
656 if items:
657 url = '%s/%s' % (acf, '/'.join(items))
658 if items[-1] == '':
659 url += '/'
660 else:
661 url = acf
662 if query_string:
663 url += '?' + query_string
664 return url
665
666
667
668 (static, e) = url_in(request, e)
669 if static:
670 return static
671 result = "/%s/%s/%s" % (request.application, request.controller, request.function)
672 if request.extension and request.extension != 'html':
673 result += ".%s" % request.extension
674 if request.args:
675 result += " %s" % request.args
676 if e['QUERY_STRING']:
677 result += " ?%s" % e['QUERY_STRING']
678 if request.uri_language:
679 result += " (%s)" % request.uri_language
680 if env:
681 return request.env
682 return result
683
684
685 -def filter_err(status, application='app', ticket='tkt'):
686 "doctest/unittest interface to routes_onerror"
687 if status > 399 and thread.routes.routes_onerror:
688 keys = set(('%s/%s' % (application, status),
689 '%s/*' % (application),
690 '*/%s' % (status),
691 '*/*'))
692 for (key,redir) in thread.routes.routes_onerror:
693 if key in keys:
694 if redir == '!':
695 break
696 elif '?' in redir:
697 url = redir + '&' + 'code=%s&ticket=%s' % (status,ticket)
698 else:
699 url = redir + '?' + 'code=%s&ticket=%s' % (status,ticket)
700 return url
701 return status
702
703
704
706 "logic for mapping incoming URLs"
707
708 - def __init__(self, request=None, env=None):
709 "initialize a map-in object"
710 self.request = request
711 self.env = env
712
713 self.router = None
714 self.application = None
715 self.language = None
716 self.controller = None
717 self.function = None
718 self.extension = 'html'
719
720 self.controllers = set()
721 self.functions = set()
722 self.languages = set()
723 self.default_language = None
724 self.map_hyphen = True
725
726 path = self.env['PATH_INFO']
727 self.query = self.env.get('QUERY_STRING', None)
728 path = path.lstrip('/')
729 self.env['PATH_INFO'] = '/' + path
730 self.env['WEB2PY_ORIGINAL_URI'] = self.env['PATH_INFO'] + (self.query and ('?' + self.query) or '')
731
732
733
734
735 if path.endswith('/'):
736 path = path[:-1]
737 self.args = List(path and path.split('/') or [])
738
739
740 self.remote_addr = self.env.get('REMOTE_ADDR','localhost')
741 self.scheme = self.env.get('WSGI_URL_SCHEME', 'http').lower()
742 self.method = self.env.get('REQUEST_METHOD', 'get').lower()
743 self.host = self.env.get('HTTP_HOST')
744 self.port = None
745 if not self.host:
746 self.host = self.env.get('SERVER_NAME')
747 self.port = self.env.get('SERVER_PORT')
748 if not self.host:
749 self.host = 'localhost'
750 self.port = '80'
751 if ':' in self.host:
752 (self.host, self.port) = self.host.split(':')
753 if not self.port:
754 if self.scheme == 'https':
755 self.port = '443'
756 else:
757 self.port = '80'
758
760 "strip path prefix, if present in its entirety"
761 prefix = routers.BASE.path_prefix
762 if prefix:
763 prefixlen = len(prefix)
764 if prefixlen > len(self.args):
765 return
766 for i in xrange(prefixlen):
767 if prefix[i] != self.args[i]:
768 return
769 self.args = List(self.args[prefixlen:])
770
772 "determine application name"
773 base = routers.BASE
774 self.domain_application = None
775 self.domain_controller = None
776 arg0 = self.harg0
777 if base.applications and arg0 in base.applications:
778 self.application = arg0
779 elif (self.host, self.port) in base.domains:
780 (self.application, self.domain_controller) = base.domains[(self.host, self.port)]
781 self.env['domain_application'] = self.application
782 self.env['domain_controller'] = self.domain_controller
783 elif (self.host, None) in base.domains:
784 (self.application, self.domain_controller) = base.domains[(self.host, None)]
785 self.env['domain_application'] = self.application
786 self.env['domain_controller'] = self.domain_controller
787 elif arg0 and not base.applications:
788 self.application = arg0
789 else:
790 self.application = base.default_application or ''
791 self.pop_arg_if(self.application == arg0)
792
793 if not base._acfe_match.match(self.application):
794 raise HTTP(400, thread.routes.error_message % 'invalid request',
795 web2py_error="invalid application: '%s'" % self.application)
796
797 if self.application not in routers and \
798 (self.application != thread.routes.default_application or self.application == 'welcome'):
799 raise HTTP(400, thread.routes.error_message % 'invalid request',
800 web2py_error="unknown application: '%s'" % self.application)
801
802
803
804 logger.debug("select application=%s" % self.application)
805 self.request.application = self.application
806 if self.application not in routers:
807 self.router = routers.BASE
808 else:
809 self.router = routers[self.application]
810 self.controllers = self.router.controllers
811 self.default_controller = self.domain_controller or self.router.default_controller
812 self.functions = self.router.functions
813 self.languages = self.router.languages
814 self.default_language = self.router.default_language
815 self.map_hyphen = self.router.map_hyphen
816 self._acfe_match = self.router._acfe_match
817 self._file_match = self.router._file_match
818 self._args_match = self.router._args_match
819
821 '''
822 handle root-static files (no hyphen mapping)
823
824 a root-static file is one whose incoming URL expects it to be at the root,
825 typically robots.txt & favicon.ico
826 '''
827 if len(self.args) == 1 and self.arg0 in self.router.root_static:
828 self.controller = self.request.controller = 'static'
829 root_static_file = os.path.join(self.request.env.applications_parent,
830 'applications', self.application,
831 self.controller, self.arg0)
832 logger.debug("route: root static=%s" % root_static_file)
833 return root_static_file
834 return None
835
847
849 "identify controller"
850
851
852 arg0 = self.harg0
853 if not arg0 or (self.controllers and arg0 not in self.controllers):
854 self.controller = self.default_controller or ''
855 else:
856 self.controller = arg0
857 self.pop_arg_if(arg0 == self.controller)
858 logger.debug("route: controller=%s" % self.controller)
859 if not self.router._acfe_match.match(self.controller):
860 raise HTTP(400, thread.routes.error_message % 'invalid request',
861 web2py_error='invalid controller')
862
864 '''
865 handle static files
866 file_match but no hyphen mapping
867 '''
868 if self.controller != 'static':
869 return None
870 file = '/'.join(self.args)
871 if not self.router._file_match.match(file):
872 raise HTTP(400, thread.routes.error_message % 'invalid request',
873 web2py_error='invalid static file')
874
875
876
877
878
879 if self.language:
880 static_file = os.path.join(self.request.env.applications_parent,
881 'applications', self.application,
882 'static', self.language, file)
883 if not self.language or not os.path.isfile(static_file):
884 static_file = os.path.join(self.request.env.applications_parent,
885 'applications', self.application,
886 'static', file)
887 logger.debug("route: static=%s" % static_file)
888 return static_file
889
891 "handle function.extension"
892 arg0 = self.harg0
893 if not arg0 or self.functions and arg0 not in self.functions and self.controller == self.default_controller:
894 self.function = self.router.default_function or ""
895 self.pop_arg_if(arg0 and self.function == arg0)
896 else:
897 func_ext = arg0.split('.')
898 if len(func_ext) > 1:
899 self.function = func_ext[0]
900 self.extension = func_ext[-1]
901 else:
902 self.function = arg0
903 self.pop_arg_if(True)
904 logger.debug("route: function.ext=%s.%s" % (self.function, self.extension))
905
906 if not self.router._acfe_match.match(self.function):
907 raise HTTP(400, thread.routes.error_message % 'invalid request',
908 web2py_error='invalid function')
909 if self.extension and not self.router._acfe_match.match(self.extension):
910 raise HTTP(400, thread.routes.error_message % 'invalid request',
911 web2py_error='invalid extension')
912
914 '''
915 check args against validation pattern
916 '''
917 for arg in self.args:
918 if not self.router._args_match.match(arg):
919 raise HTTP(400, thread.routes.error_message % 'invalid request',
920 web2py_error='invalid arg <%s>' % arg)
921
923 '''
924 update request from self
925 build env.request_uri
926 make lower-case versions of http headers in env
927 '''
928 self.request.application = self.application
929 self.request.controller = self.controller
930 self.request.function = self.function
931 self.request.extension = self.extension
932 self.request.args = self.args
933 if self.language:
934 self.request.uri_language = self.language
935 uri = '/%s/%s/%s' % (self.application, self.controller, self.function)
936 if self.map_hyphen:
937 uri = uri.replace('_', '-')
938 if self.extension != 'html':
939 uri += '.' + self.extension
940 if self.language:
941 uri = '/%s%s' % (self.language, uri)
942 uri += self.args and urllib.quote('/' + '/'.join([str(x) for x in self.args])) or ''
943 uri += (self.query and ('?' + self.query) or '')
944 self.env['REQUEST_URI'] = uri
945 for (key, value) in self.env.items():
946 self.request.env[key.lower().replace('.', '_')] = value
947
948 @property
950 "return first arg"
951 return self.args(0)
952
953 @property
955 "return first arg with optional hyphen mapping"
956 if self.map_hyphen and self.args(0):
957 return self.args(0).replace('-', '_')
958 return self.args(0)
959
961 "conditionally remove first arg and return new first arg"
962 if dopop:
963 self.args.pop(0)
964
966 "logic for mapping outgoing URLs"
967
968 - def __init__(self, application, controller, function, args, request):
969 "initialize a map-out object"
970 self.default_application = routers.BASE.default_application
971 if application in routers:
972 self.router = routers[application]
973 else:
974 self.router = routers.BASE
975 self.application = application
976 self.controller = controller
977 self.function = function
978 self.args = args
979 self.request = request
980
981 self.applications = routers.BASE.applications
982 self.controllers = self.router.controllers
983 self.functions = self.router.functions
984 self.languages = self.router.languages
985 self.default_language = self.router.default_language
986 self.map_hyphen = self.router.map_hyphen
987 self.map_static = self.router.map_static
988 self.path_prefix = routers.BASE.path_prefix
989
990 self.domain_application = request and self.request.env.domain_application
991 self.domain_controller = request and self.request.env.domain_controller
992 self.default_function = self.router.default_function
993
994 lang = request and request.uri_language
995 if lang and self.languages and lang in self.languages:
996 self.language = lang
997 else:
998 self.language = None
999
1000 self.omit_application = False
1001 self.omit_language = False
1002 self.omit_controller = False
1003 self.omit_function = False
1004
1006 "omit language if possible"
1007
1008 if not self.language or self.language == self.default_language:
1009 self.omit_language = True
1010
1012 "omit what we can of a/c/f"
1013
1014 router = self.router
1015
1016
1017
1018 if not self.args and self.function == router.default_function:
1019 self.omit_function = True
1020 if self.controller == router.default_controller:
1021 self.omit_controller = True
1022 if self.application == self.default_application:
1023 self.omit_application = True
1024
1025
1026
1027
1028 default_application = self.domain_application or self.default_application
1029 if self.application == default_application:
1030 self.omit_application = True
1031
1032
1033
1034 default_controller = ((self.application == self.domain_application) and self.domain_controller) or router.default_controller or ''
1035 if self.controller == default_controller:
1036 self.omit_controller = True
1037
1038
1039
1040 if self.functions and self.function == self.default_function and self.omit_controller:
1041 self.omit_function = True
1042
1043
1044
1045
1046
1047 if self.omit_language:
1048 if not self.applications or self.controller in self.applications:
1049 self.omit_application = False
1050 if self.omit_application:
1051 if not self.applications or self.function in self.applications:
1052 self.omit_controller = False
1053 if not self.controllers or self.function in self.controllers:
1054 self.omit_controller = False
1055 if self.args:
1056 if self.args[0] in self.functions or self.args[0] in self.controllers or self.args[0] in self.applications:
1057 self.omit_function = False
1058 if self.omit_controller:
1059 if self.function in self.controllers or self.function in self.applications:
1060 self.omit_controller = False
1061 if self.omit_application:
1062 if self.controller in self.applications:
1063 self.omit_application = False
1064
1065
1066
1067
1068 if self.controller == 'static' or self.controller.startswith('static/'):
1069 if not self.map_static:
1070 self.omit_application = False
1071 if self.language:
1072 self.omit_language = False
1073 self.omit_controller = False
1074 self.omit_function = False
1075
1077 "build acf from components"
1078 acf = ''
1079 if self.map_hyphen:
1080 self.application = self.application.replace('_', '-')
1081 self.controller = self.controller.replace('_', '-')
1082 if self.controller != 'static' and not self.controller.startswith('static/'):
1083 self.function = self.function.replace('_', '-')
1084 if not self.omit_application:
1085 acf += '/' + self.application
1086 if not self.omit_language:
1087 acf += '/' + self.language
1088 if not self.omit_controller:
1089 acf += '/' + self.controller
1090 if not self.omit_function:
1091 acf += '/' + self.function
1092 if self.path_prefix:
1093 acf = '/' + '/'.join(self.path_prefix) + acf
1094 if self.args:
1095 return acf
1096 return acf or '/'
1097
1099 "convert components to /app/lang/controller/function"
1100
1101 if not routers:
1102 return None
1103 self.omit_lang()
1104 self.omit_acf()
1105 return self.build_acf()
1106
1107
1138
1139 -def map_url_out(request, application, controller, function, args):
1140 '''
1141 supply /a/c/f (or /a/lang/c/f) portion of outgoing url
1142
1143 The basic rule is that we can only make transformations
1144 that map_url_in can reverse.
1145
1146 Suppose that the incoming arguments are a,c,f,args,lang
1147 and that the router defaults are da, dc, df, dl.
1148
1149 We can perform these transformations trivially if args=[] and lang=None or dl:
1150
1151 /da/dc/df => /
1152 /a/dc/df => /a
1153 /a/c/df => /a/c
1154
1155 We would also like to be able to strip the default application or application/controller
1156 from URLs with function/args present, thus:
1157
1158 /da/c/f/args => /c/f/args
1159 /da/dc/f/args => /f/args
1160
1161 We use [applications] and [controllers] to suppress ambiguous omissions.
1162
1163 We assume that language names do not collide with a/c/f names.
1164 '''
1165 map = MapUrlOut(application, controller, function, args, request)
1166 return map.acf()
1167
1169 "return a private copy of the effective router for the specified application"
1170 if not routers or appname not in routers:
1171 return None
1172 return Storage(routers[appname])
1173