comparison libervia/server/pages.py @ 1216:b2d067339de3

python 3 port: /!\ Python 3.6+ is now needed to use libervia /!\ instability may occur and features may not be working anymore, this will improve with time /!\ TxJSONRPC dependency has been removed The same procedure as in backend has been applied (check backend commit ab2696e34d29 logs for details). Removed now deprecated code (Pyjamas compiled browser part, legacy blog, JSON RPC related code). Adapted code to work without `html` and `themes` dirs.
author Goffi <goffi@goffi.org>
date Tue, 13 Aug 2019 19:12:31 +0200
parents 584e29d9510a
children 62bf4f87c249
comparison
equal deleted inserted replaced
1215:f14ab8a25e8b 1216:b2d067339de3
34 from libervia.server.utils import quote, SubPage 34 from libervia.server.utils import quote, SubPage
35 from libervia.server.classes import WebsocketMeta 35 from libervia.server.classes import WebsocketMeta
36 36
37 import uuid 37 import uuid
38 import os.path 38 import os.path
39 import urllib 39 import urllib.request, urllib.parse, urllib.error
40 import time 40 import time
41 import hashlib 41 import hashlib
42 import copy 42 import copy
43 from functools import reduce
43 44
44 log = getLogger(__name__) 45 log = getLogger(__name__)
45 46
46 47
47 class CacheBase(object): 48 class CacheBase(object):
174 self.name = name 175 self.name = name
175 if name is not None: 176 if name is not None:
176 if (name in self.named_pages 177 if (name in self.named_pages
177 and not (replace_on_conflict and self.named_pages[name].url == url)): 178 and not (replace_on_conflict and self.named_pages[name].url == url)):
178 raise exceptions.ConflictError( 179 raise exceptions.ConflictError(
179 _(u'a Libervia page named "{}" already exists'.format(name))) 180 _('a Libervia page named "{}" already exists'.format(name)))
180 if u"/" in name: 181 if "/" in name:
181 raise ValueError(_(u'"/" is not allowed in page names')) 182 raise ValueError(_('"/" is not allowed in page names'))
182 if not name: 183 if not name:
183 raise ValueError(_(u"a page name can't be empty")) 184 raise ValueError(_("a page name can't be empty"))
184 self.named_pages[name] = self 185 self.named_pages[name] = self
185 if access is None: 186 if access is None:
186 access = C.PAGES_ACCESS_PUBLIC 187 access = C.PAGES_ACCESS_PUBLIC
187 if access not in ( 188 if access not in (
188 C.PAGES_ACCESS_PUBLIC, 189 C.PAGES_ACCESS_PUBLIC,
189 C.PAGES_ACCESS_PROFILE, 190 C.PAGES_ACCESS_PROFILE,
190 C.PAGES_ACCESS_NONE, 191 C.PAGES_ACCESS_NONE,
191 ): 192 ):
192 raise NotImplementedError( 193 raise NotImplementedError(
193 _(u"{} access is not implemented yet").format(access) 194 _("{} access is not implemented yet").format(access)
194 ) 195 )
195 self.access = access 196 self.access = access
196 self.dynamic = dynamic 197 self.dynamic = dynamic
197 if redirect is not None: 198 if redirect is not None:
198 # only page access and name make sense in case of full redirection 199 # only page access and name make sense in case of full redirection
200 if not all( 201 if not all(
201 lambda x: x is not None 202 lambda x: x is not None
202 for x in (parse_url, prepare_render, render, template) 203 for x in (parse_url, prepare_render, render, template)
203 ): 204 ):
204 raise ValueError( 205 raise ValueError(
205 _(u"you can't use full page redirection with other rendering" 206 _("you can't use full page redirection with other rendering"
206 u"method, check self.pageRedirect if you need to use them")) 207 "method, check self.pageRedirect if you need to use them"))
207 self.redirect = redirect 208 self.redirect = redirect
208 else: 209 else:
209 self.redirect = None 210 self.redirect = None
210 self.parse_url = parse_url 211 self.parse_url = parse_url
211 self.prepare_render = prepare_render 212 self.prepare_render = prepare_render
217 self.url_cache = url_cache 218 self.url_cache = url_cache
218 if access == C.PAGES_ACCESS_NONE: 219 if access == C.PAGES_ACCESS_NONE:
219 # none pages just return a 404, no further check is needed 220 # none pages just return a 404, no further check is needed
220 return 221 return
221 if template is not None and render is not None: 222 if template is not None and render is not None:
222 log.error(_(u"render and template methods can't be used at the same time")) 223 log.error(_("render and template methods can't be used at the same time"))
223 if parse_url is not None and not callable(parse_url): 224 if parse_url is not None and not callable(parse_url):
224 log.error(_(u"parse_url must be a callable")) 225 log.error(_("parse_url must be a callable"))
225 226
226 # if not None, next rendering will be cached 227 # if not None, next rendering will be cached
227 #  it must then contain a list of the the keys to use (without the page instance) 228 #  it must then contain a list of the the keys to use (without the page instance)
228 # e.g. [C.SERVICE_PROFILE, "pubsub", server@example.tld, pubsub_node] 229 # e.g. [C.SERVICE_PROFILE, "pubsub", server@example.tld, pubsub_node]
229 self._do_cache = None 230 self._do_cache = None
230 231
231 def __unicode__(self):
232 return u"LiberviaPage {name} at {url} (vhost: {vhost_root})".format(
233 name=self.name or u"<anonymous>", url=self.url, vhost_root=self.vhost_root)
234
235 def __str__(self): 232 def __str__(self):
236 return self.__unicode__().encode("utf-8") 233 return "LiberviaPage {name} at {url} (vhost: {vhost_root})".format(
237 234 name=self.name or "<anonymous>", url=self.url, vhost_root=self.vhost_root)
238 235
239 @property 236 @property
240 def named_pages(self): 237 def named_pages(self):
241 return self.vhost_root.named_pages 238 return self.vhost_root.named_pages
242 239
267 @return (tuple[dict, LiberviaPage]): tuple with: 264 @return (tuple[dict, LiberviaPage]): tuple with:
268 - page_data: dict containing data of the page 265 - page_data: dict containing data of the page
269 - libervia_page: created resource 266 - libervia_page: created resource
270 """ 267 """
271 dir_path = os.path.dirname(meta_path) 268 dir_path = os.path.dirname(meta_path)
272 page_data = {"__name__": u".".join([u"page"] + url_elts)} 269 page_data = {"__name__": ".".join(["page"] + url_elts)}
273 # we don't want to force the presence of __init__.py 270 # we don't want to force the presence of __init__.py
274 # so we use execfile instead of import. 271 # so we use execfile instead of import.
275 # TODO: when moved to Python 3, __init__.py is not mandatory anymore 272 # TODO: when moved to Python 3, __init__.py is not mandatory anymore
276 # so we can switch to import 273 # so we can switch to import
277 execfile(meta_path, page_data) 274 exec(compile(open(meta_path, "rb").read(), meta_path, 'exec'), page_data)
278 return page_data, LiberviaPage( 275 return page_data, LiberviaPage(
279 host=host, 276 host=host,
280 vhost_root=vhost_root, 277 vhost_root=vhost_root,
281 root_dir=dir_path, 278 root_dir=dir_path,
282 url=u"/" + u"/".join(url_elts), 279 url="/" + "/".join(url_elts),
283 name=page_data.get(u"name"), 280 name=page_data.get("name"),
284 redirect=page_data.get(u"redirect"), 281 redirect=page_data.get("redirect"),
285 access=page_data.get(u"access"), 282 access=page_data.get("access"),
286 dynamic=page_data.get(u"dynamic", False), 283 dynamic=page_data.get("dynamic", False),
287 parse_url=page_data.get(u"parse_url"), 284 parse_url=page_data.get("parse_url"),
288 prepare_render=page_data.get(u"prepare_render"), 285 prepare_render=page_data.get("prepare_render"),
289 render=page_data.get(u"render"), 286 render=page_data.get("render"),
290 template=page_data.get(u"template"), 287 template=page_data.get("template"),
291 on_data_post=page_data.get(u"on_data_post"), 288 on_data_post=page_data.get("on_data_post"),
292 on_data=page_data.get(u"on_data"), 289 on_data=page_data.get("on_data"),
293 on_signal=page_data.get(u"on_signal"), 290 on_signal=page_data.get("on_signal"),
294 url_cache=page_data.get(u"url_cache", False), 291 url_cache=page_data.get("url_cache", False),
295 replace_on_conflict=replace_on_conflict 292 replace_on_conflict=replace_on_conflict
296 ) 293 )
297 294
298 @classmethod 295 @classmethod
299 def importPages(cls, host, vhost_root, root_path=None, _parent=None, _path=None, 296 def importPages(cls, host, vhost_root, root_path=None, _parent=None, _path=None,
325 for d in os.listdir(root_dir): 322 for d in os.listdir(root_dir):
326 dir_path = os.path.join(root_dir, d) 323 dir_path = os.path.join(root_dir, d)
327 if not os.path.isdir(dir_path): 324 if not os.path.isdir(dir_path):
328 continue 325 continue
329 if _extra_pages and d in _parent.children: 326 if _extra_pages and d in _parent.children:
330 log.debug(_(u"[{host_name}] {path} is already present, ignoring it") 327 log.debug(_("[{host_name}] {path} is already present, ignoring it")
331 .format(host_name=vhost_root.host_name, path=u'/'.join(_path+[d]))) 328 .format(host_name=vhost_root.host_name, path='/'.join(_path+[d])))
332 continue 329 continue
333 meta_path = os.path.join(dir_path, C.PAGES_META_FILE) 330 meta_path = os.path.join(dir_path, C.PAGES_META_FILE)
334 if os.path.isfile(meta_path): 331 if os.path.isfile(meta_path):
335 new_path = _path + [d] 332 new_path = _path + [d]
336 try: 333 try:
339 if _extra_pages: 336 if _extra_pages:
340 # extra pages are discarded if there is already an existing page 337 # extra pages are discarded if there is already an existing page
341 continue 338 continue
342 else: 339 else:
343 raise e 340 raise e
344 _parent.putChild(d, resource) 341 _parent.putChild(d.encode('utf-8'), resource)
345 log_msg = (u"[{host_name}] Added /{path} page".format( 342 log_msg = ("[{host_name}] Added /{path} page".format(
346 host_name=vhost_root.host_name, 343 host_name=vhost_root.host_name,
347 path=u"[…]/".join(new_path))) 344 path="[…]/".join(new_path)))
348 if _extra_pages: 345 if _extra_pages:
349 log.debug(log_msg) 346 log.debug(log_msg)
350 else: 347 else:
351 log.info(log_msg) 348 log.info(log_msg)
352 if "uri_handlers" in page_data: 349 if "uri_handlers" in page_data:
353 if not isinstance(page_data, dict): 350 if not isinstance(page_data, dict):
354 log.error(_(u"uri_handlers must be a dict")) 351 log.error(_("uri_handlers must be a dict"))
355 else: 352 else:
356 for uri_tuple, cb_name in page_data["uri_handlers"].iteritems(): 353 for uri_tuple, cb_name in page_data["uri_handlers"].items():
357 if len(uri_tuple) != 2 or not isinstance(cb_name, basestring): 354 if len(uri_tuple) != 2 or not isinstance(cb_name, str):
358 log.error(_(u"invalid uri_tuple")) 355 log.error(_("invalid uri_tuple"))
359 continue 356 continue
360 if not _extra_pages: 357 if not _extra_pages:
361 log.info(_(u"setting {}/{} URIs handler") 358 log.info(_("setting {}/{} URIs handler")
362 .format(*uri_tuple)) 359 .format(*uri_tuple))
363 try: 360 try:
364 cb = page_data[cb_name] 361 cb = page_data[cb_name]
365 except KeyError: 362 except KeyError:
366 log.error(_(u"missing {name} method to handle {1}/{2}") 363 log.error(_("missing {name} method to handle {1}/{2}")
367 .format(name=cb_name, *uri_tuple)) 364 .format(name=cb_name, *uri_tuple))
368 continue 365 continue
369 else: 366 else:
370 resource.registerURI(uri_tuple, cb) 367 resource.registerURI(uri_tuple, cb)
371 368
386 """ 383 """
387 if flags == ['create']: 384 if flags == ['create']:
388 return 385 return
389 path = file_path.path.decode('utf-8') 386 path = file_path.path.decode('utf-8')
390 base_name = os.path.basename(path) 387 base_name = os.path.basename(path)
391 if base_name != u"page_meta.py": 388 if base_name != "page_meta.py":
392 # we only handle libervia pages 389 # we only handle libervia pages
393 return 390 return
394 391
395 log.debug(u"{flags} event(s) received for {file_path}".format( 392 log.debug("{flags} event(s) received for {file_path}".format(
396 flags=u", ".join(flags), file_path=file_path)) 393 flags=", ".join(flags), file_path=file_path))
397 394
398 dir_path = os.path.dirname(path) 395 dir_path = os.path.dirname(path)
399 if not dir_path.startswith(site_path): 396 if not dir_path.startswith(site_path):
400 raise exceptions.InternalError(u"watched file should start with site path") 397 raise exceptions.InternalError("watched file should start with site path")
401 398
402 path_elts = [p for p in dir_path[len(site_path):].split('/') if p] 399 path_elts = [p for p in dir_path[len(site_path):].split('/') if p]
403 if not path_elts: 400 if not path_elts:
404 return 401 return
405 402
420 page = page.children[child_name] 417 page = page.children[child_name]
421 except KeyError: 418 except KeyError:
422 if idx != len(path_elts)-1: 419 if idx != len(path_elts)-1:
423 # a page has been created in a subdir when one or more 420 # a page has been created in a subdir when one or more
424 # page_meta.py are missing on the way 421 # page_meta.py are missing on the way
425 log.warning(_(u"Can't create a page at {path}, missing parents") 422 log.warning(_("Can't create a page at {path}, missing parents")
426 .format(path=path)) 423 .format(path=path))
427 return 424 return
428 new_page = True 425 new_page = True
429 else: 426 else:
430 if idx<len(path_elts)-1: 427 if idx<len(path_elts)-1:
440 except AttributeError: 437 except AttributeError:
441 # FIXME: this .original handling madness is due to EncodingResourceWrapper 438 # FIXME: this .original handling madness is due to EncodingResourceWrapper
442 # EncodingResourceWrapper should probably be removed 439 # EncodingResourceWrapper should probably be removed
443 resource.children = page.children 440 resource.children = page.children
444 except Exception as e: 441 except Exception as e:
445 log.warning(_(u"Can't create page: {reason}").format(reason=e)) 442 log.warning(_("Can't create page: {reason}").format(reason=e))
446 else: 443 else:
447 url_elt = path_elts[-1] 444 url_elt = path_elts[-1]
448 if not new_page: 445 if not new_page:
449 # the page was already existing, we remove it 446 # the page was already existing, we remove it
450 del parent.children[url_elt] 447 del parent.children[url_elt]
451 # we can now add the new page 448 # we can now add the new page
452 parent.putChild(url_elt, resource) 449 parent.putChild(url_elt, resource)
453 if new_page: 450 if new_page:
454 log.info(_(u"{page} created").format(page=resource)) 451 log.info(_("{page} created").format(page=resource))
455 else: 452 else:
456 log.info(_(u"{page} reloaded").format(page=resource)) 453 log.info(_("{page} reloaded").format(page=resource))
457 454
458 def registerURI(self, uri_tuple, get_uri_cb): 455 def registerURI(self, uri_tuple, get_uri_cb):
459 """Register a URI handler 456 """Register a URI handler
460 457
461 @param uri_tuple(tuple[unicode, unicode]): type or URIs handler 458 @param uri_tuple(tuple[unicode, unicode]): type or URIs handler
464 @param get_uri_cb(callable): method which take uri_data dict as only argument 461 @param get_uri_cb(callable): method which take uri_data dict as only argument
465 and return absolute path with correct arguments or None if the page 462 and return absolute path with correct arguments or None if the page
466 can't handle this URL 463 can't handle this URL
467 """ 464 """
468 if uri_tuple in self.uri_callbacks: 465 if uri_tuple in self.uri_callbacks:
469 log.info(_(u"{}/{} URIs are already handled, replacing by the new handler") 466 log.info(_("{}/{} URIs are already handled, replacing by the new handler")
470 .format( *uri_tuple)) 467 .format( *uri_tuple))
471 self.uri_callbacks[uri_tuple] = (self, get_uri_cb) 468 self.uri_callbacks[uri_tuple] = (self, get_uri_cb)
472 469
473 def getSignalId(self, request): 470 def getSignalId(self, request):
474 """Retrieve signal_id for a request 471 """Retrieve signal_id for a request
497 are doing if you unset this option /!\ 494 are doing if you unset this option /!\
498 """ 495 """
499 # FIXME: add a timeout; if socket is not opened before it, signal handler 496 # FIXME: add a timeout; if socket is not opened before it, signal handler
500 # must be removed 497 # must be removed
501 if not self.dynamic: 498 if not self.dynamic:
502 log.error(_(u"You can't register signal if page is not dynamic")) 499 log.error(_("You can't register signal if page is not dynamic"))
503 return 500 return
504 signal_id = self.getSignalId(request) 501 signal_id = self.getSignalId(request)
505 LiberviaPage.signals_handlers.setdefault(signal, {})[signal_id] = [ 502 LiberviaPage.signals_handlers.setdefault(signal, {})[signal_id] = [
506 self, 503 self,
507 request, 504 request,
520 return self.vhost_root.getPageByName(name) 517 return self.vhost_root.getPageByName(name)
521 518
522 def getPagePathFromURI(self, uri): 519 def getPagePathFromURI(self, uri):
523 return self.vhost_root.getPagePathFromURI(uri) 520 return self.vhost_root.getPagePathFromURI(uri)
524 521
525 def getPageRedirectURL(self, request, page_name=u"login", url=None): 522 def getPageRedirectURL(self, request, page_name="login", url=None):
526 """generate URL for a page with redirect_url parameter set 523 """generate URL for a page with redirect_url parameter set
527 524
528 mainly used for login page with redirection to current page 525 mainly used for login page with redirection to current page
529 @param request(server.Request): current HTTP request 526 @param request(server.Request): current HTTP request
530 @param page_name(unicode): name of the page to go 527 @param page_name(unicode): name of the page to go
531 @param url(None, unicode): url to redirect to 528 @param url(None, unicode): url to redirect to
532 None to use request path (i.e. current page) 529 None to use request path (i.e. current page)
533 @return (unicode): URL to use 530 @return (unicode): URL to use
534 """ 531 """
535 return u"{root_url}?redirect_url={redirect_url}".format( 532 return "{root_url}?redirect_url={redirect_url}".format(
536 root_url=self.getPageByName(page_name).url, 533 root_url=self.getPageByName(page_name).url,
537 redirect_url=urllib.quote_plus(request.uri) 534 redirect_url=urllib.parse.quote_plus(request.uri)
538 if url is None 535 if url is None
539 else url.encode("utf-8"), 536 else url.encode("utf-8"),
540 ) 537 )
541 538
542 def getURL(self, *args): 539 def getURL(self, *args):
549 546
550 if self.name is not None and self.name in self.pages_redirects: 547 if self.name is not None and self.name in self.pages_redirects:
551 #  we check for redirection 548 #  we check for redirection
552 redirect_data = self.pages_redirects[self.name] 549 redirect_data = self.pages_redirects[self.name]
553 args_hash = tuple(args) 550 args_hash = tuple(args)
554 for limit in xrange(len(args) + 1): 551 for limit in range(len(args) + 1):
555 current_hash = args_hash[:limit] 552 current_hash = args_hash[:limit]
556 if current_hash in redirect_data: 553 if current_hash in redirect_data:
557 url_base = redirect_data[current_hash] 554 url_base = redirect_data[current_hash]
558 remaining = args[limit:] 555 remaining = args[limit:]
559 remaining_url = "/".join(remaining) 556 remaining_url = "/".join(remaining)
570 # request.prepath) because request.prepath may have been modified by 567 # request.prepath) because request.prepath may have been modified by
571 # redirection (if redirection args have been specified), while path reflect 568 # redirection (if redirection args have been specified), while path reflect
572 # the real request 569 # the real request
573 570
574 # we ignore empty path elements (i.e. double '/' or '/' at the end) 571 # we ignore empty path elements (i.e. double '/' or '/' at the end)
575 path_elts = [p for p in request.path.split("/") if p] 572 path_elts = [p for p in request.path.decode('utf-8').split("/") if p]
576 573
577 if request.postpath: 574 if request.postpath:
578 if not request.postpath[-1]: 575 if not request.postpath[-1]:
579 #  we remove trailing slash 576 #  we remove trailing slash
580 request.postpath = request.postpath[:-1] 577 request.postpath = request.postpath[:-1]
582 #  getSubPageURL must return subpage from the point where 579 #  getSubPageURL must return subpage from the point where
583 # the it is called, so we have to remove remanining 580 # the it is called, so we have to remove remanining
584 # path elements 581 # path elements
585 path_elts = path_elts[: -len(request.postpath)] 582 path_elts = path_elts[: -len(request.postpath)]
586 583
587 return u"/" + "/".join(path_elts).decode("utf-8") 584 return "/" + "/".join(path_elts)
588 585
589 def getParamURL(self, request, **kwargs): 586 def getParamURL(self, request, **kwargs):
590 """use URL of current request but modify the parameters in query part 587 """use URL of current request but modify the parameters in query part
591 588
592 **kwargs(dict[str, unicode]): argument to use as query parameters 589 **kwargs(dict[str, unicode]): argument to use as query parameters
593 @return (unicode): constructed URL 590 @return (unicode): constructed URL
594 """ 591 """
595 current_url = self.getCurrentURL(request) 592 current_url = self.getCurrentURL(request)
596 if kwargs: 593 if kwargs:
597 encoded = urllib.urlencode( 594 encoded = urllib.parse.urlencode(
598 {k: v.encode("utf-8") for k, v in kwargs.iteritems()} 595 {k: v for k, v in kwargs.items()}
599 ).decode("utf-8") 596 )
600 current_url = current_url + u"?" + encoded 597 current_url = current_url + "?" + encoded
601 return current_url 598 return current_url
602 599
603 def getSubPageByName(self, subpage_name, parent=None): 600 def getSubPageByName(self, subpage_name, parent=None):
604 """retrieve a subpage and its path using its name 601 """retrieve a subpage and its path using its name
605 602
610 @return (tuple[str, LiberviaPage]): page subpath and instance 607 @return (tuple[str, LiberviaPage]): page subpath and instance
611 @raise exceptions.NotFound: no page has been found 608 @raise exceptions.NotFound: no page has been found
612 """ 609 """
613 if parent is None: 610 if parent is None:
614 parent = self 611 parent = self
615 for path, child in parent.children.iteritems(): 612 for path, child in parent.children.items():
616 try: 613 try:
617 child_name = child.name 614 child_name = child.name
618 except AttributeError: 615 except AttributeError:
619 #  LiberviaPages have a name, but maybe this is an other Resource 616 #  LiberviaPages have a name, but maybe this is an other Resource
620 continue 617 continue
621 if child_name == subpage_name: 618 if child_name == subpage_name:
622 return path, child 619 return path.decode('utf-8'), child
623 raise exceptions.NotFound(_(u"requested sub page has not been found")) 620 raise exceptions.NotFound(_("requested sub page has not been found"))
624 621
625 def getSubPageURL(self, request, page_name, *args): 622 def getSubPageURL(self, request, page_name, *args):
626 """retrieve a page in direct children and build its URL according to request 623 """retrieve a page in direct children and build its URL according to request
627 624
628 request's current path is used as base (at current parsing point, 625 request's current path is used as base (at current parsing point,
643 @return (unicode): absolute URL to the sub page 640 @return (unicode): absolute URL to the sub page
644 """ 641 """
645 current_url = self.getCurrentURL(request) 642 current_url = self.getCurrentURL(request)
646 path, child = self.getSubPageByName(page_name) 643 path, child = self.getSubPageByName(page_name)
647 return os.path.join( 644 return os.path.join(
648 u"/", current_url, path, *[quote(a) for a in args if a is not None] 645 "/", current_url, path, *[quote(a) for a in args if a is not None]
649 ) 646 )
650 647
651 def getURLByNames(self, named_path): 648 def getURLByNames(self, named_path):
652 """Retrieve URL from pages names and arguments 649 """Retrieve URL from pages names and arguments
653 650
669 page_name, parent=current_page 666 page_name, parent=current_page
670 ) 667 )
671 path.append(sub_path) 668 path.append(sub_path)
672 if page_args: 669 if page_args:
673 path.extend([quote(a) for a in page_args]) 670 path.extend([quote(a) for a in page_args])
674 return self.host.checkRedirection(self.vhost_root, u"/".join(path)) 671 return self.host.checkRedirection(self.vhost_root, "/".join(path))
675 672
676 def getURLByPath(self, *args): 673 def getURLByPath(self, *args):
677 """Generate URL by path 674 """Generate URL by path
678 675
679 this method as a similar effect as getURLByNames, but it is more readable 676 this method as a similar effect as getURLByNames, but it is more readable
706 if not args: 703 if not args:
707 break 704 break
708 else: 705 else:
709 path, current_page = current_page.getSubPageByName(args.pop(0)) 706 path, current_page = current_page.getSubPageByName(args.pop(0))
710 arguments = [path] 707 arguments = [path]
711 return self.host.checkRedirection(self.vhost_root, u"/".join(url_elts)) 708 return self.host.checkRedirection(self.vhost_root, "/".join(url_elts))
712 709
713 def getChildWithDefault(self, path, request): 710 def getChildWithDefault(self, path, request):
714 # we handle children ourselves 711 # we handle children ourselves
715 raise exceptions.InternalError( 712 raise exceptions.InternalError(
716 u"this method should not be used with LiberviaPage" 713 "this method should not be used with LiberviaPage"
717 ) 714 )
718 715
719 def nextPath(self, request): 716 def nextPath(self, request):
720 """get next URL path segment, and update request accordingly 717 """get next URL path segment, and update request accordingly
721 718
724 @return (unicode): unquoted segment 721 @return (unicode): unquoted segment
725 @raise IndexError: there is no segment left 722 @raise IndexError: there is no segment left
726 """ 723 """
727 pathElement = request.postpath.pop(0) 724 pathElement = request.postpath.pop(0)
728 request.prepath.append(pathElement) 725 request.prepath.append(pathElement)
729 return urllib.unquote(pathElement).decode("utf-8") 726 return urllib.parse.unquote(pathElement.decode('utf-8'))
730 727
731 def _filterPathValue(self, value, handler, name, request): 728 def _filterPathValue(self, value, handler, name, request):
732 """Modify a path value according to handler (see [getPathArgs])""" 729 """Modify a path value according to handler (see [getPathArgs])"""
733 if handler in (u"@", u"@jid") and value == u"@": 730 if handler in ("@", "@jid") and value == "@":
734 value = None 731 value = None
735 732
736 if handler in (u"", u"@"): 733 if handler in ("", "@"):
737 if value is None: 734 if value is None:
738 return u"" 735 return ""
739 elif handler in (u"jid", u"@jid"): 736 elif handler in ("jid", "@jid"):
740 if value: 737 if value:
741 try: 738 try:
742 return jid.JID(value) 739 return jid.JID(value)
743 except RuntimeError: 740 except RuntimeError:
744 log.warning(_(u"invalid jid argument: {value}").format(value=value)) 741 log.warning(_("invalid jid argument: {value}").format(value=value))
745 self.pageError(request, C.HTTP_BAD_REQUEST) 742 self.pageError(request, C.HTTP_BAD_REQUEST)
746 else: 743 else:
747 return u"" 744 return ""
748 else: 745 else:
749 return handler(self, value, name, request) 746 return handler(self, value, name, request)
750 747
751 return value 748 return value
752 749
770 used, else it will be converted to jid 767 used, else it will be converted to jid
771 """ 768 """
772 data = self.getRData(request) 769 data = self.getRData(request)
773 770
774 for idx, name in enumerate(names): 771 for idx, name in enumerate(names):
775 if name[0] == u"*": 772 if name[0] == "*":
776 value = data[name[1:]] = [] 773 value = data[name[1:]] = []
777 while True: 774 while True:
778 try: 775 try:
779 value.append(self.nextPath(request)) 776 value.append(self.nextPath(request))
780 except IndexError: 777 except IndexError:
790 idx -= 1 787 idx -= 1
791 break 788 break
792 789
793 values_count = idx + 1 790 values_count = idx + 1
794 if values_count < min_args: 791 if values_count < min_args:
795 log.warning(_(u"Missing arguments in URL (got {count}, expected at least " 792 log.warning(_("Missing arguments in URL (got {count}, expected at least "
796 u"{min_args})").format(count=values_count, min_args=min_args)) 793 "{min_args})").format(count=values_count, min_args=min_args))
797 self.pageError(request, C.HTTP_BAD_REQUEST) 794 self.pageError(request, C.HTTP_BAD_REQUEST)
798 795
799 for name in names[values_count:]: 796 for name in names[values_count:]:
800 data[name] = None 797 data[name] = None
801 798
802 for name, handler in kwargs.iteritems(): 799 for name, handler in kwargs.items():
803 if name[0] == "*": 800 if name[0] == "*":
804 data[name] = [ 801 data[name] = [
805 self._filterPathValue(v, handler, name, request) for v in data[name] 802 self._filterPathValue(v, handler, name, request) for v in data[name]
806 ] 803 ]
807 else: 804 else:
829 if params is None: 826 if params is None:
830 params = self.getAllPostedData(request, multiple=False) 827 params = self.getAllPostedData(request, multiple=False)
831 if extra is None: 828 if extra is None:
832 extra = {} 829 extra = {}
833 else: 830 else:
834 assert not {u"rsm_max", u"rsm_after", u"rsm_before", 831 assert not {"rsm_max", "rsm_after", "rsm_before",
835 C.KEY_ORDER_BY}.intersection(extra.keys()) 832 C.KEY_ORDER_BY}.intersection(list(extra.keys()))
836 extra[u"rsm_max"] = unicode(page_max) 833 extra["rsm_max"] = str(page_max)
837 if order_by is not None: 834 if order_by is not None:
838 extra[C.KEY_ORDER_BY] = order_by 835 extra[C.KEY_ORDER_BY] = order_by
839 if u'after' in params: 836 if 'after' in params:
840 extra[u'rsm_after'] = params[u'after'] 837 extra['rsm_after'] = params['after']
841 elif u'before' in params: 838 elif 'before' in params:
842 extra[u'rsm_before'] = params[u'before'] 839 extra['rsm_before'] = params['before']
843 return extra 840 return extra
844 841
845 def setPagination(self, request, pubsub_data): 842 def setPagination(self, request, pubsub_data):
846 """Add to template_data if suitable 843 """Add to template_data if suitable
847 844
851 @param pubsub_data(dict): pubsub metadata parsed with 848 @param pubsub_data(dict): pubsub metadata parsed with
852 data_objects.parsePubSubMetadata 849 data_objects.parsePubSubMetadata
853 """ 850 """
854 template_data = request.template_data 851 template_data = request.template_data
855 try: 852 try:
856 last_id = pubsub_data[u"rsm_last"] 853 last_id = pubsub_data["rsm_last"]
857 except KeyError: 854 except KeyError:
858 # no pagination available 855 # no pagination available
859 return 856 return
860 857
861 if pubsub_data.get("rsm_index", 1) > 0: 858 if pubsub_data.get("rsm_index", 1) > 0:
862 # We only show previous button if it's not the first page already. 859 # We only show previous button if it's not the first page already.
863 # If we have no index, we default to display the button anyway 860 # If we have no index, we default to display the button anyway
864 # as we can't know if we are on the first page or not. 861 # as we can't know if we are on the first page or not.
865 first_id = pubsub_data[u"rsm_first"] 862 first_id = pubsub_data["rsm_first"]
866 template_data['previous_page_url'] = self.getParamURL(request, 863 template_data['previous_page_url'] = self.getParamURL(request,
867 before=first_id) 864 before=first_id)
868 if not pubsub_data[u"complete"]: 865 if not pubsub_data["complete"]:
869 # we also show the page next button if complete is None because we 866 # we also show the page next button if complete is None because we
870 # can't know where we are in the feed in this case. 867 # can't know where we are in the feed in this case.
871 template_data['next_page_url'] = self.getParamURL(request, after=last_id) 868 template_data['next_page_url'] = self.getParamURL(request, after=last_id)
872 869
873 870
897 894
898 def checkCacheSubscribeCb(self, sub_id, service, node): 895 def checkCacheSubscribeCb(self, sub_id, service, node):
899 self.cache_pubsub_sub.add((service, node, sub_id)) 896 self.cache_pubsub_sub.add((service, node, sub_id))
900 897
901 def checkCacheSubscribeEb(self, failure_, service, node): 898 def checkCacheSubscribeEb(self, failure_, service, node):
902 log.warning(_(u"Can't subscribe to node: {msg}").format(msg=failure_)) 899 log.warning(_("Can't subscribe to node: {msg}").format(msg=failure_))
903 # FIXME: cache must be marked as unusable here 900 # FIXME: cache must be marked as unusable here
904 901
905 def psNodeWatchAddEb(self, failure_, service, node): 902 def psNodeWatchAddEb(self, failure_, service, node):
906 log.warning(_(u"Can't add node watched: {msg}").format(msg=failure_)) 903 log.warning(_("Can't add node watched: {msg}").format(msg=failure_))
907 904
908 def checkCache(self, request, cache_type, **kwargs): 905 def checkCache(self, request, cache_type, **kwargs):
909 """check if a page is in cache and return cached version if suitable 906 """check if a page is in cache and return cached version if suitable
910 907
911 this method may perform extra operation to handle cache (e.g. subscribing to a 908 this method may perform extra operation to handle cache (e.g. subscribing to a
930 if not node: 927 if not node:
931 try: 928 try:
932 short = kwargs["short"] 929 short = kwargs["short"]
933 node = self.host.ns_map[short] 930 node = self.host.ns_map[short]
934 except KeyError: 931 except KeyError:
935 log.warning(_(u'Can\'t use cache for empty node without namespace ' 932 log.warning(_('Can\'t use cache for empty node without namespace '
936 u'set, please ensure to set "short" and that it is ' 933 'set, please ensure to set "short" and that it is '
937 u'registered')) 934 'registered'))
938 return 935 return
939 if profile != C.SERVICE_PROFILE: 936 if profile != C.SERVICE_PROFILE:
940 #  only service profile is cache for now 937 #  only service profile is cache for now
941 return 938 return
942 session_data = self.host.getSessionData(request, session_iface.ISATSession) 939 session_data = self.host.getSessionData(request, session_iface.ISATSession)
961 #  we don't return the Deferreds as it is not needed to wait for 958 #  we don't return the Deferreds as it is not needed to wait for
962 # the subscription to continue with page rendering 959 # the subscription to continue with page rendering
963 return 960 return
964 961
965 else: 962 else:
966 raise exceptions.InternalError(u"Unknown cache_type") 963 raise exceptions.InternalError("Unknown cache_type")
967 log.debug(u"using cache for {page}".format(page=self)) 964 log.debug("using cache for {page}".format(page=self))
968 cache.last_access = time.time() 965 cache.last_access = time.time()
969 self._setCacheHeaders(request, cache) 966 self._setCacheHeaders(request, cache)
970 self._checkCacheHeaders(request, cache) 967 self._checkCacheHeaders(request, cache)
971 request.write(cache.rendered) 968 request.write(cache.rendered)
972 request.finish() 969 request.finish()
973 raise failure.Failure(exceptions.CancelError(u"cache is used")) 970 raise failure.Failure(exceptions.CancelError("cache is used"))
974 971
975 def _cacheURL(self, __, request, profile): 972 def _cacheURL(self, __, request, profile):
976 self.cached_urls.setdefault(profile, {})[request.uri] = CacheURL(request) 973 self.cached_urls.setdefault(profile, {})[request.uri] = CacheURL(request)
977 974
978 @classmethod 975 @classmethod
980 """Invalidate cache for all pages linked to this node""" 977 """Invalidate cache for all pages linked to this node"""
981 try: 978 try:
982 cache = cls.cache[profile][C.CACHE_PUBSUB][jid.JID(service)][node] 979 cache = cls.cache[profile][C.CACHE_PUBSUB][jid.JID(service)][node]
983 except KeyError: 980 except KeyError:
984 log.info(_( 981 log.info(_(
985 u"Removing subscription for {service}/{node}: " 982 "Removing subscription for {service}/{node}: "
986 u"the page is not cached").format(service=service, node=node)) 983 "the page is not cached").format(service=service, node=node))
987 d1 = host.bridgeCall("psUnsubscribe", service, node, profile) 984 d1 = host.bridgeCall("psUnsubscribe", service, node, profile)
988 d1.addErrback( 985 d1.addErrback(
989 lambda failure_: log.warning( 986 lambda failure_: log.warning(
990 _(u"Can't unsubscribe from {service}/{node}: {msg}").format( 987 _("Can't unsubscribe from {service}/{node}: {msg}").format(
991 service=service, node=node, msg=failure_))) 988 service=service, node=node, msg=failure_)))
992 d2 = host.bridgeCall("psNodeWatchAdd", service, node, profile) 989 d2 = host.bridgeCall("psNodeWatchAdd", service, node, profile)
993 # TODO: check why the page is not in cache, remove subscription? 990 # TODO: check why the page is not in cache, remove subscription?
994 d2.addErrback( 991 d2.addErrback(
995 lambda failure_: log.warning( 992 lambda failure_: log.warning(
996 _(u"Can't remove watch for {service}/{node}: {msg}").format( 993 _("Can't remove watch for {service}/{node}: {msg}").format(
997 service=service, node=node, msg=failure_))) 994 service=service, node=node, msg=failure_)))
998 else: 995 else:
999 cache.clear() 996 cache.clear()
1000 997
1001 @classmethod 998 @classmethod
1007 @param signal(unicode): name of the signal 1004 @param signal(unicode): name of the signal
1008 @param *args: args of the signals 1005 @param *args: args of the signals
1009 """ 1006 """
1010 for page, request, check_profile in cls.signals_handlers.get( 1007 for page, request, check_profile in cls.signals_handlers.get(
1011 signal, {} 1008 signal, {}
1012 ).itervalues(): 1009 ).values():
1013 if check_profile: 1010 if check_profile:
1014 signal_profile = args[-1] 1011 signal_profile = args[-1]
1015 request_profile = page.getProfile(request) 1012 request_profile = page.getProfile(request)
1016 if not request_profile: 1013 if not request_profile:
1017 # if you want to use signal without session, unset check_profile 1014 # if you want to use signal without session, unset check_profile
1018 # (be sure to know what you are doing) 1015 # (be sure to know what you are doing)
1019 log.error(_(u"no session started, signal can't be checked")) 1016 log.error(_("no session started, signal can't be checked"))
1020 continue 1017 continue
1021 if signal_profile != request_profile: 1018 if signal_profile != request_profile:
1022 #  we ignore the signal, it's not for our profile 1019 #  we ignore the signal, it's not for our profile
1023 continue 1020 continue
1024 if request._signals_cache is not None: 1021 if request._signals_cache is not None:
1025 # socket is not yet opened, we cache the signal 1022 # socket is not yet opened, we cache the signal
1026 request._signals_cache.append((request, signal, args)) 1023 request._signals_cache.append((request, signal, args))
1027 log.debug( 1024 log.debug(
1028 u"signal [{signal}] cached: {args}".format(signal=signal, args=args) 1025 "signal [{signal}] cached: {args}".format(signal=signal, args=args)
1029 ) 1026 )
1030 else: 1027 else:
1031 page.on_signal(page, request, signal, *args) 1028 page.on_signal(page, request, signal, *args)
1032 1029
1033 def onSocketOpen(self, request): 1030 def onSocketOpen(self, request):
1037 """ 1034 """
1038 assert request._signals_cache is not None 1035 assert request._signals_cache is not None
1039 # we need to replace corresponding original requests by this websocket request 1036 # we need to replace corresponding original requests by this websocket request
1040 # in signals_handlers 1037 # in signals_handlers
1041 signal_id = request.signal_id 1038 signal_id = request.signal_id
1042 for signal_handlers_map in self.__class__.signals_handlers.itervalues(): 1039 for signal_handlers_map in self.__class__.signals_handlers.values():
1043 if signal_id in signal_handlers_map: 1040 if signal_id in signal_handlers_map:
1044 signal_handlers_map[signal_id][1] = request 1041 signal_handlers_map[signal_id][1] = request
1045 1042
1046 cache = request._signals_cache 1043 cache = request._signals_cache
1047 request._signals_cache = None 1044 request._signals_cache = None
1056 for signal in request._signals_registered: 1053 for signal in request._signals_registered:
1057 signal_id = self.getSignalId(request) 1054 signal_id = self.getSignalId(request)
1058 try: 1055 try:
1059 del LiberviaPage.signals_handlers[signal][signal_id] 1056 del LiberviaPage.signals_handlers[signal][signal_id]
1060 except KeyError: 1057 except KeyError:
1061 log.error(_(u"Can't find signal handler for [{signal}], this should not " 1058 log.error(_("Can't find signal handler for [{signal}], this should not "
1062 u"happen").format(signal=signal)) 1059 "happen").format(signal=signal))
1063 else: 1060 else:
1064 log.debug(_(u"Removed signal handler")) 1061 log.debug(_("Removed signal handler"))
1065 1062
1066 def delegateToResource(self, request, resource): 1063 def delegateToResource(self, request, resource):
1067 """continue workflow with Twisted Resource""" 1064 """continue workflow with Twisted Resource"""
1068 buf = resource.render(request) 1065 buf = resource.render(request)
1069 if buf == server.NOT_DONE_YET: 1066 if buf == server.NOT_DONE_YET:
1070 pass 1067 pass
1071 else: 1068 else:
1072 request.write(buf) 1069 request.write(buf)
1073 request.finish() 1070 request.finish()
1074 raise failure.Failure(exceptions.CancelError(u"resource delegation")) 1071 raise failure.Failure(exceptions.CancelError("resource delegation"))
1075 1072
1076 def HTTPRedirect(self, request, url): 1073 def HTTPRedirect(self, request, url):
1077 """redirect to an URL using HTTP redirection 1074 """redirect to an URL using HTTP redirection
1078 1075
1079 @param request(server.Request): current HTTP request 1076 @param request(server.Request): current HTTP request
1080 @param url(unicode): url to redirect to 1077 @param url(unicode): url to redirect to
1081 """ 1078 """
1082 web_util.redirectTo(url.encode("utf-8"), request) 1079 web_util.redirectTo(url.encode("utf-8"), request)
1083 request.finish() 1080 request.finish()
1084 raise failure.Failure(exceptions.CancelError(u"HTTP redirection is used")) 1081 raise failure.Failure(exceptions.CancelError("HTTP redirection is used"))
1085 1082
1086 def redirectOrContinue(self, request, redirect_arg=u"redirect_url"): 1083 def redirectOrContinue(self, request, redirect_arg="redirect_url"):
1087 """helper method to redirect a page to an url given as arg 1084 """helper method to redirect a page to an url given as arg
1088 1085
1089 if the arg is not present, the page will continue normal workflow 1086 if the arg is not present, the page will continue normal workflow
1090 @param request(server.Request): current HTTP request 1087 @param request(server.Request): current HTTP request
1091 @param redirect_arg(unicode): argument to use to get redirection URL 1088 @param redirect_arg(unicode): argument to use to get redirection URL
1092 @interrupt: redirect the page to requested URL 1089 @interrupt: redirect the page to requested URL
1093 @interrupt pageError(C.HTTP_BAD_REQUEST): empty or non local URL is used 1090 @interrupt pageError(C.HTTP_BAD_REQUEST): empty or non local URL is used
1094 """ 1091 """
1092 redirect_arg = redirect_arg.encode('utf-8')
1095 try: 1093 try:
1096 url = request.args["redirect_url"][0] 1094 url = request.args[redirect_arg][0].decode('utf-8')
1097 except (KeyError, IndexError): 1095 except (KeyError, IndexError):
1098 pass 1096 pass
1099 else: 1097 else:
1100 #  a redirection is requested 1098 #  a redirection is requested
1101 if not url or url[0] != u"/": 1099 if not url or url[0] != "/":
1102 # we only want local urls 1100 # we only want local urls
1103 self.pageError(request, C.HTTP_BAD_REQUEST) 1101 self.pageError(request, C.HTTP_BAD_REQUEST)
1104 else: 1102 else:
1105 self.HTTPRedirect(request, url) 1103 self.HTTPRedirect(request, url)
1106 1104
1124 skipped 1122 skipped
1125 @param path_args(list[unicode], None): path arguments to use in redirected page 1123 @param path_args(list[unicode], None): path arguments to use in redirected page
1126 @raise KeyError: there is no known page with this name 1124 @raise KeyError: there is no known page with this name
1127 """ 1125 """
1128 # FIXME: render non LiberviaPage resources 1126 # FIXME: render non LiberviaPage resources
1129 path = page_path.rstrip(u"/").split(u"/") 1127 path = page_path.rstrip("/").split("/")
1130 if not path[0]: 1128 if not path[0]:
1131 redirect_page = self.vhost_root 1129 redirect_page = self.vhost_root
1132 else: 1130 else:
1133 redirect_page = self.named_pages[path[0]] 1131 redirect_page = self.named_pages[path[0]]
1134 1132
1135 for subpage in path[1:]: 1133 for subpage in path[1:]:
1134 subpage = subpage.encode('utf-8')
1136 if redirect_page is self.vhost_root: 1135 if redirect_page is self.vhost_root:
1137 redirect_page = redirect_page.children[subpage] 1136 redirect_page = redirect_page.children[subpage]
1138 else: 1137 else:
1139 redirect_page = redirect_page.original.children[subpage] 1138 redirect_page = redirect_page.original.children[subpage]
1140 1139
1146 # if cache is needed, it will be handled by final page 1145 # if cache is needed, it will be handled by final page
1147 redirect_page._do_cache = self._do_cache 1146 redirect_page._do_cache = self._do_cache
1148 self._do_cache = None 1147 self._do_cache = None
1149 1148
1150 redirect_page.renderPage(request, skip_parse_url=skip_parse_url) 1149 redirect_page.renderPage(request, skip_parse_url=skip_parse_url)
1151 raise failure.Failure(exceptions.CancelError(u"page redirection is used")) 1150 raise failure.Failure(exceptions.CancelError("page redirection is used"))
1152 1151
1153 def pageError(self, request, code=C.HTTP_NOT_FOUND, no_body=False): 1152 def pageError(self, request, code=C.HTTP_NOT_FOUND, no_body=False):
1154 """generate an error page and terminate the request 1153 """generate an error page and terminate the request
1155 1154
1156 @param request(server.Request): HTTP request 1155 @param request(server.Request): HTTP request
1162 self._do_cache = None 1161 self._do_cache = None
1163 request.setResponseCode(code) 1162 request.setResponseCode(code)
1164 if no_body: 1163 if no_body:
1165 request.finish() 1164 request.finish()
1166 else: 1165 else:
1167 template = u"error/" + unicode(code) + ".html" 1166 template = "error/" + str(code) + ".html"
1168 template_data = request.template_data 1167 template_data = request.template_data
1169 session_data = self.host.getSessionData(request, session_iface.ISATSession) 1168 session_data = self.host.getSessionData(request, session_iface.ISATSession)
1170 if session_data.locale is not None: 1169 if session_data.locale is not None:
1171 template_data[u'locale'] = session_data.locale 1170 template_data['locale'] = session_data.locale
1172 if self.vhost_root.site_name: 1171 if self.vhost_root.site_name:
1173 template_data[u'site'] = self.vhost_root.site_name 1172 template_data['site'] = self.vhost_root.site_name
1174 1173
1175 rendered = self.host.renderer.render( 1174 rendered = self.host.renderer.render(
1176 template, 1175 template,
1177 error_code=code, 1176 error_code=code,
1178 **template_data 1177 **template_data
1179 ) 1178 )
1180 1179
1181 self.writeData(rendered, request) 1180 self.writeData(rendered, request)
1182 raise failure.Failure(exceptions.CancelError(u"error page is used")) 1181 raise failure.Failure(exceptions.CancelError("error page is used"))
1183 1182
1184 def writeData(self, data, request): 1183 def writeData(self, data, request):
1185 """write data to transport and finish the request""" 1184 """write data to transport and finish the request"""
1186 if data is None: 1185 if data is None:
1187 self.pageError(request) 1186 self.pageError(request)
1190 if self._do_cache is not None: 1189 if self._do_cache is not None:
1191 redirected_page = self._do_cache.pop(0) 1190 redirected_page = self._do_cache.pop(0)
1192 cache = reduce(lambda d, k: d.setdefault(k, {}), self._do_cache, self.cache) 1191 cache = reduce(lambda d, k: d.setdefault(k, {}), self._do_cache, self.cache)
1193 page_cache = cache[redirected_page] = CachePage(data_encoded) 1192 page_cache = cache[redirected_page] = CachePage(data_encoded)
1194 self._setCacheHeaders(request, page_cache) 1193 self._setCacheHeaders(request, page_cache)
1195 log.debug(_(u"{page} put in cache for [{profile}]") 1194 log.debug(_("{page} put in cache for [{profile}]")
1196 .format( page=self, profile=self._do_cache[0])) 1195 .format( page=self, profile=self._do_cache[0]))
1197 self._do_cache = None 1196 self._do_cache = None
1198 self._checkCacheHeaders(request, page_cache) 1197 self._checkCacheHeaders(request, page_cache)
1199 1198
1200 try: 1199 try:
1201 request.write(data_encoded) 1200 request.write(data_encoded)
1202 except AttributeError: 1201 except AttributeError:
1203 log.warning(_(u"Can't write page, the request has probably been cancelled " 1202 log.warning(_("Can't write page, the request has probably been cancelled "
1204 u"(browser tab closed or reloaded)")) 1203 "(browser tab closed or reloaded)"))
1205 return 1204 return
1206 request.finish() 1205 request.finish()
1207 1206
1208 def _subpagesHandler(self, __, request): 1207 def _subpagesHandler(self, __, request):
1209 """render subpage if suitable 1208 """render subpage if suitable
1212 and check if it corresponds to a subpage. If so, it render the subpage 1211 and check if it corresponds to a subpage. If so, it render the subpage
1213 else it render a NoResource. 1212 else it render a NoResource.
1214 If there is no unmanaged part of the segment, current page workflow is pursued 1213 If there is no unmanaged part of the segment, current page workflow is pursued
1215 """ 1214 """
1216 if request.postpath: 1215 if request.postpath:
1217 subpage = self.nextPath(request) 1216 subpage = self.nextPath(request).encode('utf-8')
1218 try: 1217 try:
1219 child = self.children[subpage] 1218 child = self.children[subpage]
1220 except KeyError: 1219 except KeyError:
1221 self.pageError(request) 1220 self.pageError(request)
1222 else: 1221 else:
1223 child.render(request) 1222 child.render(request)
1224 raise failure.Failure(exceptions.CancelError(u"subpage page is used")) 1223 raise failure.Failure(exceptions.CancelError("subpage page is used"))
1225 1224
1226 def _prepare_dynamic(self, __, request): 1225 def _prepare_dynamic(self, __, request):
1227 # we need to activate dynamic page 1226 # we need to activate dynamic page
1228 # we set data for template, and create/register token 1227 # we set data for template, and create/register token
1229 socket_token = unicode(uuid.uuid4()) 1228 socket_token = str(uuid.uuid4())
1230 socket_url = self.host.getWebsocketURL(request) 1229 socket_url = self.host.getWebsocketURL(request)
1231 socket_debug = C.boolConst(self.host.debug) 1230 socket_debug = C.boolConst(self.host.debug)
1232 request.template_data["websocket"] = WebsocketMeta( 1231 request.template_data["websocket"] = WebsocketMeta(
1233 socket_url, socket_token, socket_debug 1232 socket_url, socket_token, socket_debug
1234 ) 1233 )
1248 template_data = request.template_data 1247 template_data = request.template_data
1249 1248
1250 # if confirm variable is set in case of successfuly data post 1249 # if confirm variable is set in case of successfuly data post
1251 session_data = self.host.getSessionData(request, session_iface.ISATSession) 1250 session_data = self.host.getSessionData(request, session_iface.ISATSession)
1252 if session_data.popPageFlag(self, C.FLAG_CONFIRM): 1251 if session_data.popPageFlag(self, C.FLAG_CONFIRM):
1253 template_data[u"confirm"] = True 1252 template_data["confirm"] = True
1254 notifs = session_data.popPageNotifications(self) 1253 notifs = session_data.popPageNotifications(self)
1255 if notifs: 1254 if notifs:
1256 template_data[u"notifications"] = notifs 1255 template_data["notifications"] = notifs
1257 if session_data.locale is not None: 1256 if session_data.locale is not None:
1258 template_data[u'locale'] = session_data.locale 1257 template_data['locale'] = session_data.locale
1259 if self.vhost_root.site_name: 1258 if self.vhost_root.site_name:
1260 template_data[u'site'] = self.vhost_root.site_name 1259 template_data['site'] = self.vhost_root.site_name
1261 1260
1262 return self.host.renderer.render( 1261 return self.host.renderer.render(
1263 self.template, 1262 self.template,
1264 page_url=self.getURL(), 1263 page_url=self.getURL(),
1265 media_path=u"/" + C.MEDIA_DIR, 1264 media_path="/" + C.MEDIA_DIR,
1266 cache_path=session_data.cache_dir, 1265 cache_path=session_data.cache_dir,
1267 build_path=u"/" + C.BUILD_DIR + u"/", 1266 build_path="/" + C.BUILD_DIR + "/",
1268 main_menu=self.main_menu, 1267 main_menu=self.main_menu,
1269 **template_data) 1268 **template_data)
1270 1269
1271 def _renderEb(self, failure_, request): 1270 def _renderEb(self, failure_, request):
1272 """don't raise error on CancelError""" 1271 """don't raise error on CancelError"""
1273 failure_.trap(exceptions.CancelError) 1272 failure_.trap(exceptions.CancelError)
1274 1273
1275 def _internalError(self, failure_, request): 1274 def _internalError(self, failure_, request):
1276 """called if an error is not catched""" 1275 """called if an error is not catched"""
1277 if failure_.check(BridgeException) and failure_.value.condition == u'not-allowed': 1276 if failure_.check(BridgeException) and failure_.value.condition == 'not-allowed':
1278 log.warning(u"not allowed exception catched") 1277 log.warning("not allowed exception catched")
1279 self.pageError(request, C.HTTP_FORBIDDEN) 1278 self.pageError(request, C.HTTP_FORBIDDEN)
1280 log.error(_(u"Uncatched error for HTTP request on {url}: {msg}") 1279 log.error(_("Uncatched error for HTTP request on {url}: {msg}")
1281 .format( url=request.URLPath(), msg=failure_)) 1280 .format( url=request.URLPath(), msg=failure_))
1282 self.pageError(request, C.HTTP_INTERNAL_ERROR) 1281 self.pageError(request, C.HTTP_INTERNAL_ERROR)
1283 1282
1284 def _on_data_post_error(self, failure_, request): 1283 def _on_data_post_error(self, failure_, request):
1285 failure_.trap(exceptions.DataError) 1284 failure_.trap(exceptions.DataError)
1288 session_data = self.host.getSessionData(request, session_iface.ISATSession) 1287 session_data = self.host.getSessionData(request, session_iface.ISATSession)
1289 session_data.setPageNotification(self, failure_.value.message, C.LVL_WARNING) 1288 session_data.setPageNotification(self, failure_.value.message, C.LVL_WARNING)
1290 request.setResponseCode(C.HTTP_SEE_OTHER) 1289 request.setResponseCode(C.HTTP_SEE_OTHER)
1291 request.setHeader("location", request.uri) 1290 request.setHeader("location", request.uri)
1292 request.finish() 1291 request.finish()
1293 raise failure.Failure(exceptions.CancelError(u"Post/Redirect/Get is used")) 1292 raise failure.Failure(exceptions.CancelError("Post/Redirect/Get is used"))
1294 1293
1295 def _on_data_post_redirect(self, ret, request): 1294 def _on_data_post_redirect(self, ret, request):
1296 """called when page's on_data_post has been done successfuly 1295 """called when page's on_data_post has been done successfuly
1297 1296
1298 This will do a Post/Redirect/Get pattern. 1297 This will do a Post/Redirect/Get pattern.
1306 @param ret(None, unicode, iterable): on_data_post return value 1305 @param ret(None, unicode, iterable): on_data_post return value
1307 see LiberviaPage.__init__ on_data_post docstring 1306 see LiberviaPage.__init__ on_data_post docstring
1308 """ 1307 """
1309 if ret is None: 1308 if ret is None:
1310 ret = () 1309 ret = ()
1311 elif isinstance(ret, basestring): 1310 elif isinstance(ret, str):
1312 ret = (ret,) 1311 ret = (ret,)
1313 else: 1312 else:
1314 ret = tuple(ret) 1313 ret = tuple(ret)
1315 raise NotImplementedError( 1314 raise NotImplementedError(
1316 _(u"iterable in on_data_post return value is not used yet") 1315 _("iterable in on_data_post return value is not used yet")
1317 ) 1316 )
1318 session_data = self.host.getSessionData(request, session_iface.ISATSession) 1317 session_data = self.host.getSessionData(request, session_iface.ISATSession)
1319 request_data = self.getRData(request) 1318 request_data = self.getRData(request)
1320 if "post_redirect_page" in request_data: 1319 if "post_redirect_page" in request_data:
1321 redirect_page_data = request_data["post_redirect_page"] 1320 redirect_page_data = request_data["post_redirect_page"]
1331 redirect_uri = request.uri 1330 redirect_uri = request.uri
1332 1331
1333 if not C.POST_NO_CONFIRM in ret: 1332 if not C.POST_NO_CONFIRM in ret:
1334 session_data.setPageFlag(redirect_page, C.FLAG_CONFIRM) 1333 session_data.setPageFlag(redirect_page, C.FLAG_CONFIRM)
1335 request.setResponseCode(C.HTTP_SEE_OTHER) 1334 request.setResponseCode(C.HTTP_SEE_OTHER)
1336 request.setHeader("location", redirect_uri) 1335 request.setHeader(b"location", redirect_uri)
1337 request.finish() 1336 request.finish()
1338 raise failure.Failure(exceptions.CancelError(u"Post/Redirect/Get is used")) 1337 raise failure.Failure(exceptions.CancelError("Post/Redirect/Get is used"))
1339 1338
1340 def _on_data_post(self, __, request): 1339 def _on_data_post(self, __, request):
1341 csrf_token = self.host.getSessionData( 1340 csrf_token = self.host.getSessionData(
1342 request, session_iface.ISATSession 1341 request, session_iface.ISATSession
1343 ).csrf_token 1342 ).csrf_token
1344 try: 1343 try:
1345 given_csrf = self.getPostedData(request, u"csrf_token") 1344 given_csrf = self.getPostedData(request, "csrf_token")
1346 except KeyError: 1345 except KeyError:
1347 given_csrf = None 1346 given_csrf = None
1348 if given_csrf is None or given_csrf != csrf_token: 1347 if given_csrf is None or given_csrf != csrf_token:
1349 log.warning( 1348 log.warning(
1350 _(u"invalid CSRF token, hack attempt? URL: {url}, IP: {ip}").format( 1349 _("invalid CSRF token, hack attempt? URL: {url}, IP: {ip}").format(
1351 url=request.uri, ip=request.getClientIP() 1350 url=request.uri, ip=request.getClientIP()
1352 ) 1351 )
1353 ) 1352 )
1354 self.pageError(request, C.HTTP_FORBIDDEN) 1353 self.pageError(request, C.HTTP_FORBIDDEN)
1355 d = defer.maybeDeferred(self.on_data_post, self, request) 1354 d = defer.maybeDeferred(self.on_data_post, self, request)
1372 values received for this(these) key(s) 1371 values received for this(these) key(s)
1373 @raise KeyError: one specific key has been requested, and it is missing 1372 @raise KeyError: one specific key has been requested, and it is missing
1374 """ 1373 """
1375 #  FIXME: request.args is already unquoting the value, it seems we are doing 1374 #  FIXME: request.args is already unquoting the value, it seems we are doing
1376 # double unquote 1375 # double unquote
1377 if isinstance(keys, basestring): 1376 if isinstance(keys, str):
1378 keys = [keys] 1377 keys = [keys]
1379 get_first = True 1378 get_first = True
1380 else: 1379 else:
1381 get_first = False 1380 get_first = False
1382 1381
1382 keys = [k.encode('utf-8') for k in keys]
1383
1383 ret = [] 1384 ret = []
1384 for key in keys: 1385 for key in keys:
1385 gen = (urllib.unquote(v).decode("utf-8") for v in request.args.get(key, [])) 1386 gen = (urllib.parse.unquote(v.decode("utf-8"))
1387 for v in request.args.get(key, []))
1386 if multiple: 1388 if multiple:
1387 ret.append(gen) 1389 ret.append(gen)
1388 else: 1390 else:
1389 try: 1391 try:
1390 ret.append(next(gen)) 1392 ret.append(next(gen))
1403 @param except_(iterable[unicode]): key of values to ignore 1405 @param except_(iterable[unicode]): key of values to ignore
1404 csrf_token will always be ignored 1406 csrf_token will always be ignored
1405 @param multiple(bool): if False, only the first values are returned 1407 @param multiple(bool): if False, only the first values are returned
1406 @return (dict[unicode, list[unicode]]): post values 1408 @return (dict[unicode, list[unicode]]): post values
1407 """ 1409 """
1408 except_ = tuple(except_) + (u"csrf_token",) 1410 except_ = tuple(except_) + ("csrf_token",)
1409 ret = {} 1411 ret = {}
1410 for key, values in request.args.iteritems(): 1412 for key, values in request.args.items():
1411 key = urllib.unquote(key).decode("utf-8") 1413 key = key.decode('utf-8')
1414 key = urllib.parse.unquote(key)
1412 if key in except_: 1415 if key in except_:
1413 continue 1416 continue
1417 values = [v.decode('utf-8') for v in values]
1414 if not multiple: 1418 if not multiple:
1415 ret[key] = urllib.unquote(values[0]).decode("utf-8") 1419 ret[key] = urllib.parse.unquote(values[0])
1416 else: 1420 else:
1417 ret[key] = [urllib.unquote(v).decode("utf-8") for v in values] 1421 ret[key] = [urllib.parse.unquote(v) for v in values]
1418 return ret 1422 return ret
1419 1423
1420 def getProfile(self, request): 1424 def getProfile(self, request):
1421 """helper method to easily get current profile 1425 """helper method to easily get current profile
1422 1426
1470 """ 1474 """
1471 accept_language = request.getHeader("accept-language") 1475 accept_language = request.getHeader("accept-language")
1472 if not accept_language: 1476 if not accept_language:
1473 return 1477 return
1474 accepted = {a.strip() for a in accept_language.split(',')} 1478 accepted = {a.strip() for a in accept_language.split(',')}
1475 available = [unicode(l) for l in self.host.renderer.translations] 1479 available = [str(l) for l in self.host.renderer.translations]
1476 for lang in accepted: 1480 for lang in accepted:
1477 lang = lang.split(';')[0].strip().lower() 1481 lang = lang.split(';')[0].strip().lower()
1478 if not lang: 1482 if not lang:
1479 continue 1483 continue
1480 for a in available: 1484 for a in available:
1492 @param template(unicode): path of the template to render 1496 @param template(unicode): path of the template to render
1493 @param template_data(dict): template_data to use 1497 @param template_data(dict): template_data to use
1494 """ 1498 """
1495 if not self.dynamic: 1499 if not self.dynamic:
1496 raise exceptions.InternalError( 1500 raise exceptions.InternalError(
1497 _(u"renderPartial must only be used with dynamic pages") 1501 _("renderPartial must only be used with dynamic pages")
1498 ) 1502 )
1499 session_data = self.host.getSessionData(request, session_iface.ISATSession) 1503 session_data = self.host.getSessionData(request, session_iface.ISATSession)
1500 if session_data.locale is not None: 1504 if session_data.locale is not None:
1501 template_data[u'locale'] = session_data.locale 1505 template_data['locale'] = session_data.locale
1502 if self.vhost_root.site_name: 1506 if self.vhost_root.site_name:
1503 template_data[u'site'] = self.vhost_root.site_name 1507 template_data['site'] = self.vhost_root.site_name
1504 1508
1505 return self.host.renderer.render( 1509 return self.host.renderer.render(
1506 template, 1510 template,
1507 page_url=self.getURL(), 1511 page_url=self.getURL(),
1508 media_path=u"/" + C.MEDIA_DIR, 1512 media_path="/" + C.MEDIA_DIR,
1509 cache_path=session_data.cache_dir, 1513 cache_path=session_data.cache_dir,
1510 build_path=u"/" + C.BUILD_DIR + u"/", 1514 build_path="/" + C.BUILD_DIR + "/",
1511 main_menu=self.main_menu, 1515 main_menu=self.main_menu,
1512 **template_data 1516 **template_data
1513 ) 1517 )
1514 1518
1515 def renderAndUpdate( 1519 def renderAndUpdate(
1531 template_data = request.template_data.copy() 1535 template_data = request.template_data.copy()
1532 template_data.update(template_data_update) 1536 template_data.update(template_data_update)
1533 html = self.renderPartial(request, template, template_data) 1537 html = self.renderPartial(request, template, template_data)
1534 try: 1538 try:
1535 request.sendData( 1539 request.sendData(
1536 u"dom", selectors=selectors, update_type=update_type, html=html) 1540 "dom", selectors=selectors, update_type=update_type, html=html)
1537 except Exception as e: 1541 except Exception as e:
1538 log.error(u"Can't renderAndUpdate, html was: {html}".format(html=html)) 1542 log.error("Can't renderAndUpdate, html was: {html}".format(html=html))
1539 raise e 1543 raise e
1540 1544
1541 def renderPage(self, request, skip_parse_url=False): 1545 def renderPage(self, request, skip_parse_url=False):
1542 """Main method to handle the workflow of a LiberviaPage""" 1546 """Main method to handle the workflow of a LiberviaPage"""
1543 1547
1544 # template_data are the variables passed to template 1548 # template_data are the variables passed to template
1545 if not hasattr(request, "template_data"): 1549 if not hasattr(request, "template_data"):
1546 session_data = self.host.getSessionData(request, session_iface.ISATSession) 1550 session_data = self.host.getSessionData(request, session_iface.ISATSession)
1547 csrf_token = session_data.csrf_token 1551 csrf_token = session_data.csrf_token
1548 request.template_data = { 1552 request.template_data = {
1549 u"profile": session_data.profile, 1553 "profile": session_data.profile,
1550 u"csrf_token": csrf_token, 1554 "csrf_token": csrf_token,
1551 } 1555 }
1552 1556
1553 # XXX: here is the code which need to be executed once 1557 # XXX: here is the code which need to be executed once
1554 # at the beginning of the request hanling 1558 # at the beginning of the request hanling
1555 if request.postpath and not request.postpath[-1]: 1559 if request.postpath and not request.postpath[-1]:
1559 # i18n 1563 # i18n
1560 if C.KEY_LANG in request.args: 1564 if C.KEY_LANG in request.args:
1561 try: 1565 try:
1562 locale = request.args.pop(C.KEY_LANG)[0] 1566 locale = request.args.pop(C.KEY_LANG)[0]
1563 except IndexError: 1567 except IndexError:
1564 log.warning(u"empty lang received") 1568 log.warning("empty lang received")
1565 else: 1569 else:
1566 if u"/" in locale: 1570 if "/" in locale:
1567 # "/" is refused because locale may sometime be used to access 1571 # "/" is refused because locale may sometime be used to access
1568 # path, if localised documents are available for instance 1572 # path, if localised documents are available for instance
1569 log.warning(_(u'illegal char found in locale ("/"), hack ' 1573 log.warning(_('illegal char found in locale ("/"), hack '
1570 u'attempt? locale={locale}').format(locale=locale)) 1574 'attempt? locale={locale}').format(locale=locale))
1571 locale = None 1575 locale = None
1572 session_data.locale = locale 1576 session_data.locale = locale
1573 1577
1574 # if locale is not specified, we try to find one requested by browser 1578 # if locale is not specified, we try to find one requested by browser
1575 if session_data.locale is None: 1579 if session_data.locale is None:
1594 # no cache for this URI yet 1598 # no cache for this URI yet
1595 #  we do normal URL parsing, and then the cache 1599 #  we do normal URL parsing, and then the cache
1596 d.addCallback(self.parse_url, request) 1600 d.addCallback(self.parse_url, request)
1597 d.addCallback(self._cacheURL, request, profile) 1601 d.addCallback(self._cacheURL, request, profile)
1598 else: 1602 else:
1599 log.debug(_(u"using URI cache for {page}").format(page=self)) 1603 log.debug(_("using URI cache for {page}").format(page=self))
1600 cache_url.use(request) 1604 cache_url.use(request)
1601 else: 1605 else:
1602 d.addCallback(self.parse_url, request) 1606 d.addCallback(self.parse_url, request)
1603 1607
1604 d.addCallback(self._subpagesHandler, request) 1608 d.addCallback(self._subpagesHandler, request)