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