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