comparison libervia/server/pages.py @ 1509:106bae41f5c8

massive refactoring from camelCase -> snake_case. See backend commit log for more details
author Goffi <goffi@goffi.org>
date Sat, 08 Apr 2023 13:44:11 +0200
parents ce879da7fcf7
children 16228994ca3b
comparison
equal deleted inserted replaced
1508:ec3ad9abf9f9 1509:106bae41f5c8
37 from twisted.words.protocols.jabber import jid 37 from twisted.words.protocols.jabber import jid
38 from twisted.python import failure 38 from twisted.python import failure
39 39
40 from sat.core.i18n import _ 40 from sat.core.i18n import _
41 from sat.core import exceptions 41 from sat.core import exceptions
42 from sat.tools.utils import asDeferred 42 from sat.tools.utils import as_deferred
43 from sat.tools.common import date_utils 43 from sat.tools.common import date_utils
44 from sat.tools.common import utils 44 from sat.tools.common import utils
45 from sat.tools.common import data_format 45 from sat.tools.common import data_format
46 from sat.core.log import getLogger 46 from sat.core.log import getLogger
47 from sat_frontends.bridge.bridge_frontend import BridgeException 47 from sat_frontends.bridge.bridge_frontend import BridgeException
135 this URL may not be valid, as pages may require path arguments 135 this URL may not be valid, as pages may require path arguments
136 @param name(unicode, None): if not None, a unique name to identify the page 136 @param name(unicode, None): if not None, a unique name to identify the page
137 can then be used for e.g. redirection 137 can then be used for e.g. redirection
138 "/" is not allowed in names (as it can be used to construct URL paths) 138 "/" is not allowed in names (as it can be used to construct URL paths)
139 @param redirect(unicode, None): if not None, this page will be redirected. 139 @param redirect(unicode, None): if not None, this page will be redirected.
140 A redirected parameter is used as in self.pageRedirect. 140 A redirected parameter is used as in self.page_redirect.
141 parse_url will not be skipped 141 parse_url will not be skipped
142 using this redirect parameter is called "full redirection" 142 using this redirect parameter is called "full redirection"
143 using self.pageRedirect is called "partial redirection" (because some 143 using self.page_redirect is called "partial redirection" (because some
144 rendering method can still be used, e.g. parse_url) 144 rendering method can still be used, e.g. parse_url)
145 @param access(unicode, None): permission needed to access the page 145 @param access(unicode, None): permission needed to access the page
146 None means public access. 146 None means public access.
147 Pages inherit from parent pages: e.g. if a "settings" page is restricted 147 Pages inherit from parent pages: e.g. if a "settings" page is restricted
148 to admins, and if "settings/blog" is public, it still can only be accessed by 148 to admins, and if "settings/blog" is public, it still can only be accessed by
215 lambda x: x is not None 215 lambda x: x is not None
216 for x in (parse_url, prepare_render, render, template) 216 for x in (parse_url, prepare_render, render, template)
217 ): 217 ):
218 raise ValueError( 218 raise ValueError(
219 _("you can't use full page redirection with other rendering" 219 _("you can't use full page redirection with other rendering"
220 "method, check self.pageRedirect if you need to use them")) 220 "method, check self.page_redirect if you need to use them"))
221 self.redirect = redirect 221 self.redirect = redirect
222 else: 222 else:
223 self.redirect = None 223 self.redirect = None
224 self.parse_url = parse_url 224 self.parse_url = parse_url
225 self.add_breadcrumb = add_breadcrumb 225 self.add_breadcrumb = add_breadcrumb
272 @property 272 @property
273 def site_themes(self): 273 def site_themes(self):
274 return self.vhost_root.site_themes 274 return self.vhost_root.site_themes
275 275
276 @staticmethod 276 @staticmethod
277 def createPage(host, meta_path, vhost_root, url_elts, replace_on_conflict=False): 277 def create_page(host, meta_path, vhost_root, url_elts, replace_on_conflict=False):
278 """Create a LiberviaPage instance 278 """Create a LiberviaPage instance
279 279
280 @param meta_path(Path): path to the page_meta.py file 280 @param meta_path(Path): path to the page_meta.py file
281 @param vhost_root(resource.Resource): root resource of the virtual host 281 @param vhost_root(resource.Resource): root resource of the virtual host
282 @param url_elts(list[unicode]): list of path element from root site to this page 282 @param url_elts(list[unicode]): list of path element from root site to this page
312 url_cache=page_data.get("url_cache", False), 312 url_cache=page_data.get("url_cache", False),
313 replace_on_conflict=replace_on_conflict 313 replace_on_conflict=replace_on_conflict
314 ) 314 )
315 315
316 @staticmethod 316 @staticmethod
317 def createBrowserData( 317 def create_browser_data(
318 vhost_root, 318 vhost_root,
319 resource: Optional[LiberviaPage], 319 resource: Optional[LiberviaPage],
320 browser_path: Path, 320 browser_path: Path,
321 path_elts: Optional[List[str]], 321 path_elts: Optional[List[str]],
322 engine: str = "brython" 322 engine: str = "brython"
353 vhost_root.browser_modules.setdefault( 353 vhost_root.browser_modules.setdefault(
354 engine, []).append(dyn_data) 354 engine, []).append(dyn_data)
355 355
356 356
357 @classmethod 357 @classmethod
358 def importPages(cls, host, vhost_root, root_path=None, _parent=None, _path=None, 358 def import_pages(cls, host, vhost_root, root_path=None, _parent=None, _path=None,
359 _extra_pages=False): 359 _extra_pages=False):
360 """Recursively import Libervia pages 360 """Recursively import Libervia pages
361 361
362 @param host(Libervia): Libervia instance 362 @param host(Libervia): Libervia instance
363 @param vhost_root(LiberviaRootResource): root of this VirtualHost 363 @param vhost_root(LiberviaRootResource): root of this VirtualHost
379 root_dir = root_path / C.PAGES_DIR 379 root_dir = root_path / C.PAGES_DIR
380 _extra_pages = True 380 _extra_pages = True
381 _parent = vhost_root 381 _parent = vhost_root
382 root_browser_path = root_dir / C.PAGES_BROWSER_DIR 382 root_browser_path = root_dir / C.PAGES_BROWSER_DIR
383 if root_browser_path.is_dir(): 383 if root_browser_path.is_dir():
384 cls.createBrowserData(vhost_root, None, root_browser_path, None) 384 cls.create_browser_data(vhost_root, None, root_browser_path, None)
385 else: 385 else:
386 root_dir = _parent.root_dir 386 root_dir = _parent.root_dir
387 387
388 for d in os.listdir(root_dir): 388 for d in os.listdir(root_dir):
389 dir_path = root_dir / d 389 dir_path = root_dir / d
395 continue 395 continue
396 meta_path = dir_path / C.PAGES_META_FILE 396 meta_path = dir_path / C.PAGES_META_FILE
397 if meta_path.is_file(): 397 if meta_path.is_file():
398 new_path = _path + [d] 398 new_path = _path + [d]
399 try: 399 try:
400 page_data, resource = cls.createPage( 400 page_data, resource = cls.create_page(
401 host, meta_path, vhost_root, new_path) 401 host, meta_path, vhost_root, new_path)
402 except exceptions.ConflictError as e: 402 except exceptions.ConflictError as e:
403 if _extra_pages: 403 if _extra_pages:
404 # extra pages are discarded if there is already an existing page 404 # extra pages are discarded if there is already an existing page
405 continue 405 continue
429 except KeyError: 429 except KeyError:
430 log.error(_("missing {name} method to handle {1}/{2}") 430 log.error(_("missing {name} method to handle {1}/{2}")
431 .format(name=cb_name, *uri_tuple)) 431 .format(name=cb_name, *uri_tuple))
432 continue 432 continue
433 else: 433 else:
434 resource.registerURI(uri_tuple, cb) 434 resource.register_uri(uri_tuple, cb)
435 435
436 LiberviaPage.importPages( 436 LiberviaPage.import_pages(
437 host, vhost_root, _parent=resource, _path=new_path, 437 host, vhost_root, _parent=resource, _path=new_path,
438 _extra_pages=_extra_pages) 438 _extra_pages=_extra_pages)
439 # now we check if there is some code for browser 439 # now we check if there is some code for browser
440 browser_path = dir_path / C.PAGES_BROWSER_DIR 440 browser_path = dir_path / C.PAGES_BROWSER_DIR
441 if browser_path.is_dir(): 441 if browser_path.is_dir():
442 cls.createBrowserData(vhost_root, resource, browser_path, new_path) 442 cls.create_browser_data(vhost_root, resource, browser_path, new_path)
443 443
444 @classmethod 444 @classmethod
445 def onFileChange(cls, host, file_path, flags, site_root, site_path): 445 def on_file_change(cls, host, file_path, flags, site_root, site_path):
446 """Method triggered by file_watcher when something is changed in files 446 """Method triggered by file_watcher when something is changed in files
447 447
448 This method is used in dev mode to reload pages when needed 448 This method is used in dev mode to reload pages when needed
449 @param file_path(filepath.FilePath): path of the file which triggered the event 449 @param file_path(filepath.FilePath): path of the file which triggered the event
450 @param flags[list[unicode]): human readable flags of the event (from 450 @param flags[list[unicode]): human readable flags of the event (from
504 except AttributeError: 504 except AttributeError:
505 parent = page 505 parent = page
506 506
507 try: 507 try:
508 # we (re)create a page with the new/modified code 508 # we (re)create a page with the new/modified code
509 __, resource = cls.createPage(host, path, site_root, path_elts, 509 __, resource = cls.create_page(host, path, site_root, path_elts,
510 replace_on_conflict=True) 510 replace_on_conflict=True)
511 if not new_page: 511 if not new_page:
512 try: 512 try:
513 resource.children = page.original.children 513 resource.children = page.original.children
514 except AttributeError: 514 except AttributeError:
526 parent.putChild(url_elt, resource) 526 parent.putChild(url_elt, resource)
527 527
528 # is there any browser data to create? 528 # is there any browser data to create?
529 browser_path = resource.root_dir / C.PAGES_BROWSER_DIR 529 browser_path = resource.root_dir / C.PAGES_BROWSER_DIR
530 if browser_path.is_dir(): 530 if browser_path.is_dir():
531 cls.createBrowserData( 531 cls.create_browser_data(
532 resource.vhost_root, 532 resource.vhost_root,
533 resource, 533 resource,
534 browser_path, 534 browser_path,
535 resource.url.split('/') 535 resource.url.split('/')
536 ) 536 )
538 if new_page: 538 if new_page:
539 log.info(_("{page} created").format(page=resource)) 539 log.info(_("{page} created").format(page=resource))
540 else: 540 else:
541 log.info(_("{page} reloaded").format(page=resource)) 541 log.info(_("{page} reloaded").format(page=resource))
542 542
543 def checkCSRF(self, request): 543 def check_csrf(self, request):
544 session = self.host.getSessionData( 544 session = self.host.get_session_data(
545 request, session_iface.IWebSession 545 request, session_iface.IWebSession
546 ) 546 )
547 if session.profile is None: 547 if session.profile is None:
548 # CSRF doesn't make sense when no user is logged 548 # CSRF doesn't make sense when no user is logged
549 log.debug("disabling CSRF check because service profile is used") 549 log.debug("disabling CSRF check because service profile is used")
550 return 550 return
551 csrf_token = session.csrf_token 551 csrf_token = session.csrf_token
552 given_csrf = request.getHeader("X-Csrf-Token") 552 given_csrf = request.getHeader("X-Csrf-Token")
553 if given_csrf is None: 553 if given_csrf is None:
554 try: 554 try:
555 given_csrf = self.getPostedData(request, "csrf_token") 555 given_csrf = self.get_posted_data(request, "csrf_token")
556 except KeyError: 556 except KeyError:
557 pass 557 pass
558 if given_csrf is None or given_csrf != csrf_token: 558 if given_csrf is None or given_csrf != csrf_token:
559 log.warning( 559 log.warning(
560 _("invalid CSRF token, hack attempt? URL: {url}, IP: {ip}").format( 560 _("invalid CSRF token, hack attempt? URL: {url}, IP: {ip}").format(
561 url=request.uri, ip=request.getClientIP() 561 url=request.uri, ip=request.getClientIP()
562 ) 562 )
563 ) 563 )
564 self.pageError(request, C.HTTP_FORBIDDEN) 564 self.page_error(request, C.HTTP_FORBIDDEN)
565 565
566 def exposeToScripts( 566 def expose_to_scripts(
567 self, 567 self,
568 request: server.Request, 568 request: server.Request,
569 **kwargs: str 569 **kwargs: str
570 ) -> None: 570 ) -> None:
571 """Make a local variable available to page script as a global variable 571 """Make a local variable available to page script as a global variable
583 value = repr(str(value)) 583 value = repr(str(value))
584 else: 584 else:
585 value = repr(value) 585 value = repr(value)
586 scripts.add(Script(content=f"var {name}={value};")) 586 scripts.add(Script(content=f"var {name}={value};"))
587 587
588 def registerURI(self, uri_tuple, get_uri_cb): 588 def register_uri(self, uri_tuple, get_uri_cb):
589 """Register a URI handler 589 """Register a URI handler
590 590
591 @param uri_tuple(tuple[unicode, unicode]): type or URIs handler 591 @param uri_tuple(tuple[unicode, unicode]): type or URIs handler
592 type/subtype as returned by tools/common/parseXMPPUri 592 type/subtype as returned by tools/common/parse_xmpp_uri
593 or type/None to handle all subtypes 593 or type/None to handle all subtypes
594 @param get_uri_cb(callable): method which take uri_data dict as only argument 594 @param get_uri_cb(callable): method which take uri_data dict as only argument
595 and return absolute path with correct arguments or None if the page 595 and return absolute path with correct arguments or None if the page
596 can't handle this URL 596 can't handle this URL
597 """ 597 """
598 if uri_tuple in self.uri_callbacks: 598 if uri_tuple in self.uri_callbacks:
599 log.info(_("{}/{} URIs are already handled, replacing by the new handler") 599 log.info(_("{}/{} URIs are already handled, replacing by the new handler")
600 .format( *uri_tuple)) 600 .format( *uri_tuple))
601 self.uri_callbacks[uri_tuple] = (self, get_uri_cb) 601 self.uri_callbacks[uri_tuple] = (self, get_uri_cb)
602 602
603 def getConfig(self, key, default=None, value_type=None): 603 def config_get(self, key, default=None, value_type=None):
604 return self.host.getConfig(self.vhost_root, key=key, default=default, 604 return self.host.config_get(self.vhost_root, key=key, default=default,
605 value_type=value_type) 605 value_type=value_type)
606 606
607 def getBuildPath(self, session_data): 607 def get_build_path(self, session_data):
608 return session_data.cache_dir + self.vhost.site_name 608 return session_data.cache_dir + self.vhost.site_name
609 609
610 def getPageByName(self, name): 610 def get_page_by_name(self, name):
611 return self.vhost_root.getPageByName(name) 611 return self.vhost_root.get_page_by_name(name)
612 612
613 def getPagePathFromURI(self, uri): 613 def get_page_path_from_uri(self, uri):
614 return self.vhost_root.getPagePathFromURI(uri) 614 return self.vhost_root.get_page_path_from_uri(uri)
615 615
616 def getPageRedirectURL(self, request, page_name="login", url=None): 616 def get_page_redirect_url(self, request, page_name="login", url=None):
617 """generate URL for a page with redirect_url parameter set 617 """generate URL for a page with redirect_url parameter set
618 618
619 mainly used for login page with redirection to current page 619 mainly used for login page with redirection to current page
620 @param request(server.Request): current HTTP request 620 @param request(server.Request): current HTTP request
621 @param page_name(unicode): name of the page to go 621 @param page_name(unicode): name of the page to go
622 @param url(None, unicode): url to redirect to 622 @param url(None, unicode): url to redirect to
623 None to use request path (i.e. current page) 623 None to use request path (i.e. current page)
624 @return (unicode): URL to use 624 @return (unicode): URL to use
625 """ 625 """
626 return "{root_url}?redirect_url={redirect_url}".format( 626 return "{root_url}?redirect_url={redirect_url}".format(
627 root_url=self.getPageByName(page_name).url, 627 root_url=self.get_page_by_name(page_name).url,
628 redirect_url=urllib.parse.quote_plus(request.uri) 628 redirect_url=urllib.parse.quote_plus(request.uri)
629 if url is None 629 if url is None
630 else url.encode("utf-8"), 630 else url.encode("utf-8"),
631 ) 631 )
632 632
633 def getURL(self, *args: str, **kwargs: str) -> str: 633 def get_url(self, *args: str, **kwargs: str) -> str:
634 """retrieve URL of the page set arguments 634 """retrieve URL of the page set arguments
635 635
636 @param *args: arguments to add to the URL as path elements empty or None 636 @param *args: arguments to add to the URL as path elements empty or None
637 arguments will be ignored 637 arguments will be ignored
638 @param **kwargs: query parameters 638 @param **kwargs: query parameters
660 encoded = urllib.parse.urlencode( 660 encoded = urllib.parse.urlencode(
661 {k: v for k, v in kwargs.items()} 661 {k: v for k, v in kwargs.items()}
662 ) 662 )
663 url += f"?{encoded}" 663 url += f"?{encoded}"
664 664
665 return self.host.checkRedirection( 665 return self.host.check_redirection(
666 self.vhost_root, 666 self.vhost_root,
667 url 667 url
668 ) 668 )
669 669
670 def getCurrentURL(self, request): 670 def get_current_url(self, request):
671 """retrieve URL used to access this page 671 """retrieve URL used to access this page
672 672
673 @return(unicode): current URL 673 @return(unicode): current URL
674 """ 674 """
675 # we get url in the following way (splitting request.path instead of using 675 # we get url in the following way (splitting request.path instead of using
683 if request.postpath: 683 if request.postpath:
684 if not request.postpath[-1]: 684 if not request.postpath[-1]:
685 #  we remove trailing slash 685 #  we remove trailing slash
686 request.postpath = request.postpath[:-1] 686 request.postpath = request.postpath[:-1]
687 if request.postpath: 687 if request.postpath:
688 #  getSubPageURL must return subpage from the point where 688 #  get_sub_page_url must return subpage from the point where
689 # the it is called, so we have to remove remanining 689 # the it is called, so we have to remove remanining
690 # path elements 690 # path elements
691 path_elts = path_elts[: -len(request.postpath)] 691 path_elts = path_elts[: -len(request.postpath)]
692 692
693 return "/" + "/".join(path_elts) 693 return "/" + "/".join(path_elts)
694 694
695 def getParamURL(self, request, **kwargs): 695 def get_param_url(self, request, **kwargs):
696 """use URL of current request but modify the parameters in query part 696 """use URL of current request but modify the parameters in query part
697 697
698 **kwargs(dict[str, unicode]): argument to use as query parameters 698 **kwargs(dict[str, unicode]): argument to use as query parameters
699 @return (unicode): constructed URL 699 @return (unicode): constructed URL
700 """ 700 """
701 current_url = self.getCurrentURL(request) 701 current_url = self.get_current_url(request)
702 if kwargs: 702 if kwargs:
703 encoded = urllib.parse.urlencode( 703 encoded = urllib.parse.urlencode(
704 {k: v for k, v in kwargs.items()} 704 {k: v for k, v in kwargs.items()}
705 ) 705 )
706 current_url = current_url + "?" + encoded 706 current_url = current_url + "?" + encoded
707 return current_url 707 return current_url
708 708
709 def getSubPageByName(self, subpage_name, parent=None): 709 def get_sub_page_by_name(self, subpage_name, parent=None):
710 """retrieve a subpage and its path using its name 710 """retrieve a subpage and its path using its name
711 711
712 @param subpage_name(unicode): name of the sub page 712 @param subpage_name(unicode): name of the sub page
713 it must be a direct children of parent page 713 it must be a direct children of parent page
714 @param parent(LiberviaPage, None): parent page 714 @param parent(LiberviaPage, None): parent page
728 return path.decode('utf-8'), child 728 return path.decode('utf-8'), child
729 raise exceptions.NotFound( 729 raise exceptions.NotFound(
730 _("requested sub page has not been found ({subpage_name})").format( 730 _("requested sub page has not been found ({subpage_name})").format(
731 subpage_name=subpage_name)) 731 subpage_name=subpage_name))
732 732
733 def getSubPageURL(self, request, page_name, *args): 733 def get_sub_page_url(self, request, page_name, *args):
734 """retrieve a page in direct children and build its URL according to request 734 """retrieve a page in direct children and build its URL according to request
735 735
736 request's current path is used as base (at current parsing point, 736 request's current path is used as base (at current parsing point,
737 i.e. it's more prepath than path). 737 i.e. it's more prepath than path).
738 Requested page is checked in children and an absolute URL is then built 738 Requested page is checked in children and an absolute URL is then built
739 by the resulting combination. 739 by the resulting combination.
740 This method is useful to construct absolute URLs for children instead of 740 This method is useful to construct absolute URLs for children instead of
741 using relative path, which may not work in subpages, and are linked to the 741 using relative path, which may not work in subpages, and are linked to the
742 names of directories (i.e. relative URL will break if subdirectory is renamed 742 names of directories (i.e. relative URL will break if subdirectory is renamed
743 while getSubPageURL won't as long as page_name is consistent). 743 while get_sub_page_url won't as long as page_name is consistent).
744 Also, request.path is used, keeping real path used by user, 744 Also, request.path is used, keeping real path used by user,
745 and potential redirections. 745 and potential redirections.
746 @param request(server.Request): current HTTP request 746 @param request(server.Request): current HTTP request
747 @param page_name(unicode): name of the page to retrieve 747 @param page_name(unicode): name of the page to retrieve
748 it must be a direct children of current page 748 it must be a direct children of current page
749 @param *args(list[unicode]): arguments to add as path elements 749 @param *args(list[unicode]): arguments to add as path elements
750 if an arg is None, it will be ignored 750 if an arg is None, it will be ignored
751 @return (unicode): absolute URL to the sub page 751 @return (unicode): absolute URL to the sub page
752 """ 752 """
753 current_url = self.getCurrentURL(request) 753 current_url = self.get_current_url(request)
754 path, child = self.getSubPageByName(page_name) 754 path, child = self.get_sub_page_by_name(page_name)
755 return os.path.join( 755 return os.path.join(
756 "/", current_url, path, *[quote(a) for a in args if a is not None] 756 "/", current_url, path, *[quote(a) for a in args if a is not None]
757 ) 757 )
758 758
759 def getURLByNames(self, named_path): 759 def get_url_by_names(self, named_path):
760 """Retrieve URL from pages names and arguments 760 """Retrieve URL from pages names and arguments
761 761
762 @param named_path(list[tuple[unicode, list[unicode]]]): path to the page as a list 762 @param named_path(list[tuple[unicode, list[unicode]]]): path to the page as a list
763 of tuples of 2 items: 763 of tuples of 2 items:
764 - first item is page name 764 - first item is page name
768 """ 768 """
769 current_page = None 769 current_page = None
770 path = [] 770 path = []
771 for page_name, page_args in named_path: 771 for page_name, page_args in named_path:
772 if current_page is None: 772 if current_page is None:
773 current_page = self.getPageByName(page_name) 773 current_page = self.get_page_by_name(page_name)
774 path.append(current_page.getURL(*page_args)) 774 path.append(current_page.get_url(*page_args))
775 else: 775 else:
776 sub_path, current_page = self.getSubPageByName( 776 sub_path, current_page = self.get_sub_page_by_name(
777 page_name, parent=current_page 777 page_name, parent=current_page
778 ) 778 )
779 path.append(sub_path) 779 path.append(sub_path)
780 if page_args: 780 if page_args:
781 path.extend([quote(a) for a in page_args]) 781 path.extend([quote(a) for a in page_args])
782 return self.host.checkRedirection(self.vhost_root, "/".join(path)) 782 return self.host.check_redirection(self.vhost_root, "/".join(path))
783 783
784 def getURLByPath(self, *args): 784 def get_url_by_path(self, *args):
785 """Generate URL by path 785 """Generate URL by path
786 786
787 this method as a similar effect as getURLByNames, but it is more readable 787 this method as a similar effect as get_url_by_names, but it is more readable
788 by using SubPage to get pages instead of using tuples 788 by using SubPage to get pages instead of using tuples
789 @param *args: path element: 789 @param *args: path element:
790 - if unicode, will be used as argument 790 - if unicode, will be used as argument
791 - if util.SubPage instance, must be the name of a subpage 791 - if util.SubPage instance, must be the name of a subpage
792 @return (unicode): generated path 792 @return (unicode): generated path
797 # root page is the one needed to construct the base of the URL 797 # root page is the one needed to construct the base of the URL
798 # if first arg is not a SubPage instance, we use current page 798 # if first arg is not a SubPage instance, we use current page
799 if not isinstance(args[0], SubPage): 799 if not isinstance(args[0], SubPage):
800 root = self 800 root = self
801 else: 801 else:
802 root = self.getPageByName(args.pop(0)) 802 root = self.get_page_by_name(args.pop(0))
803 # we keep track of current page to check subpage 803 # we keep track of current page to check subpage
804 current_page = root 804 current_page = root
805 url_elts = [] 805 url_elts = []
806 arguments = [] 806 arguments = []
807 while True: 807 while True:
808 while args and not isinstance(args[0], SubPage): 808 while args and not isinstance(args[0], SubPage):
809 arguments.append(quote(args.pop(0))) 809 arguments.append(quote(args.pop(0)))
810 if not url_elts: 810 if not url_elts:
811 url_elts.append(root.getURL(*arguments)) 811 url_elts.append(root.get_url(*arguments))
812 else: 812 else:
813 url_elts.extend(arguments) 813 url_elts.extend(arguments)
814 if not args: 814 if not args:
815 break 815 break
816 else: 816 else:
817 path, current_page = current_page.getSubPageByName(args.pop(0)) 817 path, current_page = current_page.get_sub_page_by_name(args.pop(0))
818 arguments = [path] 818 arguments = [path]
819 return self.host.checkRedirection(self.vhost_root, "/".join(url_elts)) 819 return self.host.check_redirection(self.vhost_root, "/".join(url_elts))
820 820
821 def getChildWithDefault(self, path, request): 821 def getChildWithDefault(self, path, request):
822 # we handle children ourselves 822 # we handle children ourselves
823 raise exceptions.InternalError( 823 raise exceptions.InternalError(
824 "this method should not be used with LiberviaPage" 824 "this method should not be used with LiberviaPage"
825 ) 825 )
826 826
827 def nextPath(self, request): 827 def next_path(self, request):
828 """get next URL path segment, and update request accordingly 828 """get next URL path segment, and update request accordingly
829 829
830 will move first segment of postpath in prepath 830 will move first segment of postpath in prepath
831 @param request(server.Request): current HTTP request 831 @param request(server.Request): current HTTP request
832 @return (unicode): unquoted segment 832 @return (unicode): unquoted segment
834 """ 834 """
835 pathElement = request.postpath.pop(0) 835 pathElement = request.postpath.pop(0)
836 request.prepath.append(pathElement) 836 request.prepath.append(pathElement)
837 return urllib.parse.unquote(pathElement.decode('utf-8')) 837 return urllib.parse.unquote(pathElement.decode('utf-8'))
838 838
839 def _filterPathValue(self, value, handler, name, request): 839 def _filter_path_value(self, value, handler, name, request):
840 """Modify a path value according to handler (see [getPathArgs])""" 840 """Modify a path value according to handler (see [get_path_args])"""
841 if handler in ("@", "@jid") and value == "@": 841 if handler in ("@", "@jid") and value == "@":
842 value = None 842 value = None
843 843
844 if handler in ("", "@"): 844 if handler in ("", "@"):
845 if value is None: 845 if value is None:
848 if value: 848 if value:
849 try: 849 try:
850 return jid.JID(value) 850 return jid.JID(value)
851 except (RuntimeError, jid.InvalidFormat): 851 except (RuntimeError, jid.InvalidFormat):
852 log.warning(_("invalid jid argument: {value}").format(value=value)) 852 log.warning(_("invalid jid argument: {value}").format(value=value))
853 self.pageError(request, C.HTTP_BAD_REQUEST) 853 self.page_error(request, C.HTTP_BAD_REQUEST)
854 else: 854 else:
855 return "" 855 return ""
856 else: 856 else:
857 return handler(self, value, name, request) 857 return handler(self, value, name, request)
858 858
859 return value 859 return value
860 860
861 def getPathArgs(self, request, names, min_args=0, **kwargs): 861 def get_path_args(self, request, names, min_args=0, **kwargs):
862 """get several path arguments at once 862 """get several path arguments at once
863 863
864 Arguments will be put in request data. 864 Arguments will be put in request data.
865 Missing arguments will have None value 865 Missing arguments will have None value
866 @param names(list[unicode]): list of arguments to get 866 @param names(list[unicode]): list of arguments to get
875 - 'jid': value must be converted to jid.JID if it exists, else empty 875 - 'jid': value must be converted to jid.JID if it exists, else empty
876 string is used 876 string is used
877 - '@jid': if value of arguments is empty or '@', empty string will be 877 - '@jid': if value of arguments is empty or '@', empty string will be
878 used, else it will be converted to jid 878 used, else it will be converted to jid
879 """ 879 """
880 data = self.getRData(request) 880 data = self.get_r_data(request)
881 881
882 for idx, name in enumerate(names): 882 for idx, name in enumerate(names):
883 if name[0] == "*": 883 if name[0] == "*":
884 value = data[name[1:]] = [] 884 value = data[name[1:]] = []
885 while True: 885 while True:
886 try: 886 try:
887 value.append(self.nextPath(request)) 887 value.append(self.next_path(request))
888 except IndexError: 888 except IndexError:
889 idx -= 1 889 idx -= 1
890 break 890 break
891 else: 891 else:
892 idx += 1 892 idx += 1
893 else: 893 else:
894 try: 894 try:
895 value = data[name] = self.nextPath(request) 895 value = data[name] = self.next_path(request)
896 except IndexError: 896 except IndexError:
897 data[name] = None 897 data[name] = None
898 idx -= 1 898 idx -= 1
899 break 899 break
900 900
901 values_count = idx + 1 901 values_count = idx + 1
902 if values_count < min_args: 902 if values_count < min_args:
903 log.warning(_("Missing arguments in URL (got {count}, expected at least " 903 log.warning(_("Missing arguments in URL (got {count}, expected at least "
904 "{min_args})").format(count=values_count, min_args=min_args)) 904 "{min_args})").format(count=values_count, min_args=min_args))
905 self.pageError(request, C.HTTP_BAD_REQUEST) 905 self.page_error(request, C.HTTP_BAD_REQUEST)
906 906
907 for name in names[values_count:]: 907 for name in names[values_count:]:
908 data[name] = None 908 data[name] = None
909 909
910 for name, handler in kwargs.items(): 910 for name, handler in kwargs.items():
911 if name[0] == "*": 911 if name[0] == "*":
912 data[name] = [ 912 data[name] = [
913 self._filterPathValue(v, handler, name, request) for v in data[name] 913 self._filter_path_value(v, handler, name, request) for v in data[name]
914 ] 914 ]
915 else: 915 else:
916 data[name] = self._filterPathValue(data[name], handler, name, request) 916 data[name] = self._filter_path_value(data[name], handler, name, request)
917 917
918 ## Pagination/Filtering ## 918 ## Pagination/Filtering ##
919 919
920 def getPubsubExtra(self, request, page_max=10, params=None, extra=None, 920 def get_pubsub_extra(self, request, page_max=10, params=None, extra=None,
921 order_by=C.ORDER_BY_CREATION): 921 order_by=C.ORDER_BY_CREATION):
922 """Set extra dict to retrieve PubSub items corresponding to URL parameters 922 """Set extra dict to retrieve PubSub items corresponding to URL parameters
923 923
924 Following parameters are used: 924 Following parameters are used:
925 - after: set rsm_after with ID of item 925 - after: set rsm_after with ID of item
926 - before: set rsm_before with ID of item 926 - before: set rsm_before with ID of item
927 @param request(server.Request): current HTTP request 927 @param request(server.Request): current HTTP request
928 @param page_max(int): required number of items per page 928 @param page_max(int): required number of items per page
929 @param params(None, dict[unicode, list[unicode]]): params as returned by 929 @param params(None, dict[unicode, list[unicode]]): params as returned by
930 self.getAllPostedData. 930 self.get_all_posted_data.
931 None to parse URL automatically 931 None to parse URL automatically
932 @param extra(None, dict): extra dict to use, or None to use a new one 932 @param extra(None, dict): extra dict to use, or None to use a new one
933 @param order_by(unicode, None): key to order by 933 @param order_by(unicode, None): key to order by
934 None to not specify order 934 None to not specify order
935 @return (dict): fill extra data 935 @return (dict): fill extra data
936 """ 936 """
937 if params is None: 937 if params is None:
938 params = self.getAllPostedData(request, multiple=False) 938 params = self.get_all_posted_data(request, multiple=False)
939 if extra is None: 939 if extra is None:
940 extra = {} 940 extra = {}
941 else: 941 else:
942 assert not {"rsm_max", "rsm_after", "rsm_before", 942 assert not {"rsm_max", "rsm_after", "rsm_before",
943 C.KEY_ORDER_BY}.intersection(list(extra.keys())) 943 C.KEY_ORDER_BY}.intersection(list(extra.keys()))
952 # RSM returns list in order (oldest first), but we want most recent first 952 # RSM returns list in order (oldest first), but we want most recent first
953 # so we start by the end 953 # so we start by the end
954 extra['rsm_before'] = "" 954 extra['rsm_before'] = ""
955 return extra 955 return extra
956 956
957 def setPagination(self, request: server.Request, pubsub_data: dict) -> None: 957 def set_pagination(self, request: server.Request, pubsub_data: dict) -> None:
958 """Add to template_data if suitable 958 """Add to template_data if suitable
959 959
960 "previous_page_url" and "next_page_url" will be added using respectively 960 "previous_page_url" and "next_page_url" will be added using respectively
961 "before" and "after" URL parameters 961 "before" and "after" URL parameters
962 @param request: current HTTP request 962 @param request: current HTTP request
970 except KeyError: 970 except KeyError:
971 # no pagination available 971 # no pagination available
972 return 972 return
973 973
974 # if we have a search query, we must keep it 974 # if we have a search query, we must keep it
975 search = self.getPostedData(request, 'search', raise_on_missing=False) 975 search = self.get_posted_data(request, 'search', raise_on_missing=False)
976 if search is not None: 976 if search is not None:
977 extra['search'] = search.strip() 977 extra['search'] = search.strip()
978 978
979 # same for page_max 979 # same for page_max
980 page_max = self.getPostedData(request, 'page_max', raise_on_missing=False) 980 page_max = self.get_posted_data(request, 'page_max', raise_on_missing=False)
981 if page_max is not None: 981 if page_max is not None:
982 extra['page_max'] = page_max 982 extra['page_max'] = page_max
983 983
984 if rsm.get("index", 1) > 0: 984 if rsm.get("index", 1) > 0:
985 # We only show previous button if it's not the first page already. 985 # We only show previous button if it's not the first page already.
986 # If we have no index, we default to display the button anyway 986 # If we have no index, we default to display the button anyway
987 # as we can't know if we are on the first page or not. 987 # as we can't know if we are on the first page or not.
988 first_id = rsm["first"] 988 first_id = rsm["first"]
989 template_data['previous_page_url'] = self.getParamURL( 989 template_data['previous_page_url'] = self.get_param_url(
990 request, before=first_id, **extra) 990 request, before=first_id, **extra)
991 if not pubsub_data["complete"]: 991 if not pubsub_data["complete"]:
992 # we also show the page next button if complete is None because we 992 # we also show the page next button if complete is None because we
993 # can't know where we are in the feed in this case. 993 # can't know where we are in the feed in this case.
994 template_data['next_page_url'] = self.getParamURL( 994 template_data['next_page_url'] = self.get_param_url(
995 request, after=last_id, **extra) 995 request, after=last_id, **extra)
996 996
997 997
998 ## Cache handling ## 998 ## Cache handling ##
999 999
1000 def _setCacheHeaders(self, request, cache): 1000 def _set_cache_headers(self, request, cache):
1001 """Set ETag and Last-Modified HTTP headers, used for caching""" 1001 """Set ETag and Last-Modified HTTP headers, used for caching"""
1002 request.setHeader("ETag", cache.hash) 1002 request.setHeader("ETag", cache.hash)
1003 last_modified = self.host.getHTTPDate(cache.created) 1003 last_modified = self.host.get_http_date(cache.created)
1004 request.setHeader("Last-Modified", last_modified) 1004 request.setHeader("Last-Modified", last_modified)
1005 1005
1006 def _checkCacheHeaders(self, request, cache): 1006 def _check_cache_headers(self, request, cache):
1007 """Check if a cache condition is set on the request 1007 """Check if a cache condition is set on the request
1008 1008
1009 if condition is valid, C.HTTP_NOT_MODIFIED is returned 1009 if condition is valid, C.HTTP_NOT_MODIFIED is returned
1010 """ 1010 """
1011 etag_match = request.getHeader("If-None-Match") 1011 etag_match = request.getHeader("If-None-Match")
1012 if etag_match is not None: 1012 if etag_match is not None:
1013 if cache.hash == etag_match: 1013 if cache.hash == etag_match:
1014 self.pageError(request, C.HTTP_NOT_MODIFIED, no_body=True) 1014 self.page_error(request, C.HTTP_NOT_MODIFIED, no_body=True)
1015 else: 1015 else:
1016 modified_match = request.getHeader("If-Modified-Since") 1016 modified_match = request.getHeader("If-Modified-Since")
1017 if modified_match is not None: 1017 if modified_match is not None:
1018 modified = date_utils.date_parse(modified_match) 1018 modified = date_utils.date_parse(modified_match)
1019 if modified >= int(cache.created): 1019 if modified >= int(cache.created):
1020 self.pageError(request, C.HTTP_NOT_MODIFIED, no_body=True) 1020 self.page_error(request, C.HTTP_NOT_MODIFIED, no_body=True)
1021 1021
1022 def checkCacheSubscribeCb(self, sub_id, service, node): 1022 def check_cache_subscribe_cb(self, sub_id, service, node):
1023 self.cache_pubsub_sub.add((service, node, sub_id)) 1023 self.cache_pubsub_sub.add((service, node, sub_id))
1024 1024
1025 def checkCacheSubscribeEb(self, failure_, service, node): 1025 def check_cache_subscribe_eb(self, failure_, service, node):
1026 log.warning(_("Can't subscribe to node: {msg}").format(msg=failure_)) 1026 log.warning(_("Can't subscribe to node: {msg}").format(msg=failure_))
1027 # FIXME: cache must be marked as unusable here 1027 # FIXME: cache must be marked as unusable here
1028 1028
1029 def psNodeWatchAddEb(self, failure_, service, node): 1029 def ps_node_watch_add_eb(self, failure_, service, node):
1030 log.warning(_("Can't add node watched: {msg}").format(msg=failure_)) 1030 log.warning(_("Can't add node watched: {msg}").format(msg=failure_))
1031 1031
1032 def useCache(self, request: server.Request) -> bool: 1032 def use_cache(self, request: server.Request) -> bool:
1033 """Indicate if the cache should be used 1033 """Indicate if the cache should be used
1034 1034
1035 test request header to see if it is requested to skip the cache 1035 test request header to see if it is requested to skip the cache
1036 @return: True if cache should be used 1036 @return: True if cache should be used
1037 """ 1037 """
1038 return request.getHeader('cache-control') != 'no-cache' 1038 return request.getHeader('cache-control') != 'no-cache'
1039 1039
1040 def checkCache(self, request, cache_type, **kwargs): 1040 def check_cache(self, request, cache_type, **kwargs):
1041 """check if a page is in cache and return cached version if suitable 1041 """check if a page is in cache and return cached version if suitable
1042 1042
1043 this method may perform extra operation to handle cache (e.g. subscribing to a 1043 this method may perform extra operation to handle cache (e.g. subscribing to a
1044 pubsub node) 1044 pubsub node)
1045 @param request(server.Request): current HTTP request 1045 @param request(server.Request): current HTTP request
1058 if request.uri != request.path: 1058 if request.uri != request.path:
1059 # we don't cache page with query arguments as there can be a lot of variants 1059 # we don't cache page with query arguments as there can be a lot of variants
1060 # influencing page results (e.g. search terms) 1060 # influencing page results (e.g. search terms)
1061 log.debug("ignoring cache due to query arguments") 1061 log.debug("ignoring cache due to query arguments")
1062 1062
1063 no_cache = not self.useCache(request) 1063 no_cache = not self.use_cache(request)
1064 1064
1065 profile = self.getProfile(request) or C.SERVICE_PROFILE 1065 profile = self.get_profile(request) or C.SERVICE_PROFILE
1066 1066
1067 if cache_type == C.CACHE_PUBSUB: 1067 if cache_type == C.CACHE_PUBSUB:
1068 service, node = kwargs["service"], kwargs["node"] 1068 service, node = kwargs["service"], kwargs["node"]
1069 if not node: 1069 if not node:
1070 try: 1070 try:
1076 'registered')) 1076 'registered'))
1077 return 1077 return
1078 if profile != C.SERVICE_PROFILE: 1078 if profile != C.SERVICE_PROFILE:
1079 #  only service profile is cached for now 1079 #  only service profile is cached for now
1080 return 1080 return
1081 session_data = self.host.getSessionData(request, session_iface.IWebSession) 1081 session_data = self.host.get_session_data(request, session_iface.IWebSession)
1082 locale = session_data.locale 1082 locale = session_data.locale
1083 if locale == C.DEFAULT_LOCALE: 1083 if locale == C.DEFAULT_LOCALE:
1084 # no need to duplicate cache here 1084 # no need to duplicate cache here
1085 locale = None 1085 locale = None
1086 try: 1086 try:
1087 cache = (self.cache[profile][cache_type][service][node] 1087 cache = (self.cache[profile][cache_type][service][node]
1088 [self.vhost_root][request.uri][locale][self]) 1088 [self.vhost_root][request.uri][locale][self])
1089 except KeyError: 1089 except KeyError:
1090 # no cache yet, let's subscribe to the pubsub node 1090 # no cache yet, let's subscribe to the pubsub node
1091 d1 = self.host.bridgeCall( 1091 d1 = self.host.bridge_call(
1092 "psSubscribe", service.full(), node, "", profile 1092 "ps_subscribe", service.full(), node, "", profile
1093 ) 1093 )
1094 d1.addCallback(self.checkCacheSubscribeCb, service, node) 1094 d1.addCallback(self.check_cache_subscribe_cb, service, node)
1095 d1.addErrback(self.checkCacheSubscribeEb, service, node) 1095 d1.addErrback(self.check_cache_subscribe_eb, service, node)
1096 d2 = self.host.bridgeCall("psNodeWatchAdd", service.full(), node, profile) 1096 d2 = self.host.bridge_call("ps_node_watch_add", service.full(), node, profile)
1097 d2.addErrback(self.psNodeWatchAddEb, service, node) 1097 d2.addErrback(self.ps_node_watch_add_eb, service, node)
1098 self._do_cache = [self, profile, cache_type, service, node, 1098 self._do_cache = [self, profile, cache_type, service, node,
1099 self.vhost_root, request.uri, locale] 1099 self.vhost_root, request.uri, locale]
1100 #  we don't return the Deferreds as it is not needed to wait for 1100 #  we don't return the Deferreds as it is not needed to wait for
1101 # the subscription to continue with page rendering 1101 # the subscription to continue with page rendering
1102 return 1102 return
1109 1109
1110 else: 1110 else:
1111 raise exceptions.InternalError("Unknown cache_type") 1111 raise exceptions.InternalError("Unknown cache_type")
1112 log.debug("using cache for {page}".format(page=self)) 1112 log.debug("using cache for {page}".format(page=self))
1113 cache.last_access = time.time() 1113 cache.last_access = time.time()
1114 self._setCacheHeaders(request, cache) 1114 self._set_cache_headers(request, cache)
1115 self._checkCacheHeaders(request, cache) 1115 self._check_cache_headers(request, cache)
1116 request.write(cache.rendered) 1116 request.write(cache.rendered)
1117 request.finish() 1117 request.finish()
1118 raise failure.Failure(exceptions.CancelError("cache is used")) 1118 raise failure.Failure(exceptions.CancelError("cache is used"))
1119 1119
1120 def _cacheURL(self, request, profile): 1120 def _cache_url(self, request, profile):
1121 self.cached_urls.setdefault(profile, {})[request.uri] = CacheURL(request) 1121 self.cached_urls.setdefault(profile, {})[request.uri] = CacheURL(request)
1122 1122
1123 @classmethod 1123 @classmethod
1124 def onNodeEvent(cls, host, service, node, event_type, items, profile): 1124 def on_node_event(cls, host, service, node, event_type, items, profile):
1125 """Invalidate cache for all pages linked to this node""" 1125 """Invalidate cache for all pages linked to this node"""
1126 try: 1126 try:
1127 cache = cls.cache[profile][C.CACHE_PUBSUB][jid.JID(service)][node] 1127 cache = cls.cache[profile][C.CACHE_PUBSUB][jid.JID(service)][node]
1128 except KeyError: 1128 except KeyError:
1129 log.info(_( 1129 log.info(_(
1130 "Removing subscription for {service}/{node}: " 1130 "Removing subscription for {service}/{node}: "
1131 "the page is not cached").format(service=service, node=node)) 1131 "the page is not cached").format(service=service, node=node))
1132 d1 = host.bridgeCall("psUnsubscribe", service, node, profile) 1132 d1 = host.bridge_call("ps_unsubscribe", service, node, profile)
1133 d1.addErrback( 1133 d1.addErrback(
1134 lambda failure_: log.warning( 1134 lambda failure_: log.warning(
1135 _("Can't unsubscribe from {service}/{node}: {msg}").format( 1135 _("Can't unsubscribe from {service}/{node}: {msg}").format(
1136 service=service, node=node, msg=failure_))) 1136 service=service, node=node, msg=failure_)))
1137 d2 = host.bridgeCall("psNodeWatchAdd", service, node, profile) 1137 d2 = host.bridge_call("ps_node_watch_add", service, node, profile)
1138 # TODO: check why the page is not in cache, remove subscription? 1138 # TODO: check why the page is not in cache, remove subscription?
1139 d2.addErrback( 1139 d2.addErrback(
1140 lambda failure_: log.warning( 1140 lambda failure_: log.warning(
1141 _("Can't remove watch for {service}/{node}: {msg}").format( 1141 _("Can't remove watch for {service}/{node}: {msg}").format(
1142 service=service, node=node, msg=failure_))) 1142 service=service, node=node, msg=failure_)))
1143 else: 1143 else:
1144 cache.clear() 1144 cache.clear()
1145 1145
1146 # identities 1146 # identities
1147 1147
1148 async def fillMissingIdentities( 1148 async def fill_missing_identities(
1149 self, 1149 self,
1150 request: server.Request, 1150 request: server.Request,
1151 entities: List[Union[str, jid.JID, None]], 1151 entities: List[Union[str, jid.JID, None]],
1152 ) -> None: 1152 ) -> None:
1153 """Check if all entities have an identity cache, get missing ones from backend 1153 """Check if all entities have an identity cache, get missing ones from backend
1154 1154
1155 @param request: request with a plugged profile 1155 @param request: request with a plugged profile
1156 @param entities: entities to check, None or empty strings will be filtered 1156 @param entities: entities to check, None or empty strings will be filtered
1157 """ 1157 """
1158 entities = {str(e) for e in entities if e} 1158 entities = {str(e) for e in entities if e}
1159 profile = self.getProfile(request) or C.SERVICE_PROFILE 1159 profile = self.get_profile(request) or C.SERVICE_PROFILE
1160 identities = self.host.getSessionData( 1160 identities = self.host.get_session_data(
1161 request, 1161 request,
1162 session_iface.IWebSession 1162 session_iface.IWebSession
1163 ).identities 1163 ).identities
1164 for e in entities: 1164 for e in entities:
1165 if e not in identities: 1165 if e not in identities:
1166 id_raw = await self.host.bridgeCall( 1166 id_raw = await self.host.bridge_call(
1167 'identityGet', e, [], True, profile) 1167 'identity_get', e, [], True, profile)
1168 identities[e] = data_format.deserialise(id_raw) 1168 identities[e] = data_format.deserialise(id_raw)
1169 1169
1170 # signals, server => browser communication 1170 # signals, server => browser communication
1171 1171
1172 def delegateToResource(self, request, resource): 1172 def delegate_to_resource(self, request, resource):
1173 """continue workflow with Twisted Resource""" 1173 """continue workflow with Twisted Resource"""
1174 buf = resource.render(request) 1174 buf = resource.render(request)
1175 if buf == server.NOT_DONE_YET: 1175 if buf == server.NOT_DONE_YET:
1176 pass 1176 pass
1177 else: 1177 else:
1178 request.write(buf) 1178 request.write(buf)
1179 request.finish() 1179 request.finish()
1180 raise failure.Failure(exceptions.CancelError("resource delegation")) 1180 raise failure.Failure(exceptions.CancelError("resource delegation"))
1181 1181
1182 def HTTPRedirect(self, request, url): 1182 def http_redirect(self, request, url):
1183 """redirect to an URL using HTTP redirection 1183 """redirect to an URL using HTTP redirection
1184 1184
1185 @param request(server.Request): current HTTP request 1185 @param request(server.Request): current HTTP request
1186 @param url(unicode): url to redirect to 1186 @param url(unicode): url to redirect to
1187 """ 1187 """
1188 web_util.redirectTo(url.encode("utf-8"), request) 1188 web_util.redirectTo(url.encode("utf-8"), request)
1189 request.finish() 1189 request.finish()
1190 raise failure.Failure(exceptions.CancelError("HTTP redirection is used")) 1190 raise failure.Failure(exceptions.CancelError("HTTP redirection is used"))
1191 1191
1192 def redirectOrContinue(self, request, redirect_arg="redirect_url"): 1192 def redirect_or_continue(self, request, redirect_arg="redirect_url"):
1193 """Helper method to redirect a page to an url given as arg 1193 """Helper method to redirect a page to an url given as arg
1194 1194
1195 if the arg is not present, the page will continue normal workflow 1195 if the arg is not present, the page will continue normal workflow
1196 @param request(server.Request): current HTTP request 1196 @param request(server.Request): current HTTP request
1197 @param redirect_arg(unicode): argument to use to get redirection URL 1197 @param redirect_arg(unicode): argument to use to get redirection URL
1198 @interrupt: redirect the page to requested URL 1198 @interrupt: redirect the page to requested URL
1199 @interrupt pageError(C.HTTP_BAD_REQUEST): empty or non local URL is used 1199 @interrupt page_error(C.HTTP_BAD_REQUEST): empty or non local URL is used
1200 """ 1200 """
1201 redirect_arg = redirect_arg.encode('utf-8') 1201 redirect_arg = redirect_arg.encode('utf-8')
1202 try: 1202 try:
1203 url = request.args[redirect_arg][0].decode('utf-8') 1203 url = request.args[redirect_arg][0].decode('utf-8')
1204 except (KeyError, IndexError): 1204 except (KeyError, IndexError):
1205 pass 1205 pass
1206 else: 1206 else:
1207 #  a redirection is requested 1207 #  a redirection is requested
1208 if not url or url[0] != "/": 1208 if not url or url[0] != "/":
1209 # we only want local urls 1209 # we only want local urls
1210 self.pageError(request, C.HTTP_BAD_REQUEST) 1210 self.page_error(request, C.HTTP_BAD_REQUEST)
1211 else: 1211 else:
1212 self.HTTPRedirect(request, url) 1212 self.http_redirect(request, url)
1213 1213
1214 def pageRedirect(self, page_path, request, skip_parse_url=True, path_args=None): 1214 def page_redirect(self, page_path, request, skip_parse_url=True, path_args=None):
1215 """redirect a page to a named page 1215 """redirect a page to a named page
1216 1216
1217 the workflow will continue with the workflow of the named page, 1217 the workflow will continue with the workflow of the named page,
1218 skipping named page's parse_url method if it exist. 1218 skipping named page's parse_url method if it exist.
1219 If you want to do a HTTP redirection, use HTTPRedirect 1219 If you want to do a HTTP redirection, use http_redirect
1220 @param page_path(unicode): path to page (elements are separated by "/"): 1220 @param page_path(unicode): path to page (elements are separated by "/"):
1221 if path starts with a "/": 1221 if path starts with a "/":
1222 path is a full path starting from root 1222 path is a full path starting from root
1223 else: 1223 else:
1224 - first element is name as registered in name variable 1224 - first element is name as registered in name variable
1254 # if cache is needed, it will be handled by final page 1254 # if cache is needed, it will be handled by final page
1255 redirect_page._do_cache = self._do_cache 1255 redirect_page._do_cache = self._do_cache
1256 self._do_cache = None 1256 self._do_cache = None
1257 1257
1258 defer.ensureDeferred( 1258 defer.ensureDeferred(
1259 redirect_page.renderPage(request, skip_parse_url=skip_parse_url) 1259 redirect_page.render_page(request, skip_parse_url=skip_parse_url)
1260 ) 1260 )
1261 raise failure.Failure(exceptions.CancelError("page redirection is used")) 1261 raise failure.Failure(exceptions.CancelError("page redirection is used"))
1262 1262
1263 def pageError(self, request, code=C.HTTP_NOT_FOUND, no_body=False): 1263 def page_error(self, request, code=C.HTTP_NOT_FOUND, no_body=False):
1264 """generate an error page and terminate the request 1264 """generate an error page and terminate the request
1265 1265
1266 @param request(server.Request): HTTP request 1266 @param request(server.Request): HTTP request
1267 @param core(int): error code to use 1267 @param core(int): error code to use
1268 @param no_body: don't write body if True 1268 @param no_body: don't write body if True
1274 if no_body: 1274 if no_body:
1275 request.finish() 1275 request.finish()
1276 else: 1276 else:
1277 template = "error/" + str(code) + ".html" 1277 template = "error/" + str(code) + ".html"
1278 template_data = request.template_data 1278 template_data = request.template_data
1279 session_data = self.host.getSessionData(request, session_iface.IWebSession) 1279 session_data = self.host.get_session_data(request, session_iface.IWebSession)
1280 if session_data.locale is not None: 1280 if session_data.locale is not None:
1281 template_data['locale'] = session_data.locale 1281 template_data['locale'] = session_data.locale
1282 if self.vhost_root.site_name: 1282 if self.vhost_root.site_name:
1283 template_data['site'] = self.vhost_root.site_name 1283 template_data['site'] = self.vhost_root.site_name
1284 1284
1290 site_themes=self.site_themes, 1290 site_themes=self.site_themes,
1291 error_code=code, 1291 error_code=code,
1292 **template_data 1292 **template_data
1293 ) 1293 )
1294 1294
1295 self.writeData(rendered, request) 1295 self.write_data(rendered, request)
1296 raise failure.Failure(exceptions.CancelError("error page is used")) 1296 raise failure.Failure(exceptions.CancelError("error page is used"))
1297 1297
1298 def writeData(self, data, request): 1298 def write_data(self, data, request):
1299 """write data to transport and finish the request""" 1299 """write data to transport and finish the request"""
1300 if data is None: 1300 if data is None:
1301 self.pageError(request) 1301 self.page_error(request)
1302 data_encoded = data.encode("utf-8") 1302 data_encoded = data.encode("utf-8")
1303 1303
1304 if self._do_cache is not None: 1304 if self._do_cache is not None:
1305 redirected_page = self._do_cache.pop(0) 1305 redirected_page = self._do_cache.pop(0)
1306 cache = reduce(lambda d, k: d.setdefault(k, {}), self._do_cache, self.cache) 1306 cache = reduce(lambda d, k: d.setdefault(k, {}), self._do_cache, self.cache)
1307 page_cache = cache[redirected_page] = CachePage(data_encoded) 1307 page_cache = cache[redirected_page] = CachePage(data_encoded)
1308 self._setCacheHeaders(request, page_cache) 1308 self._set_cache_headers(request, page_cache)
1309 log.debug(_("{page} put in cache for [{profile}]") 1309 log.debug(_("{page} put in cache for [{profile}]")
1310 .format( page=self, profile=self._do_cache[0])) 1310 .format( page=self, profile=self._do_cache[0]))
1311 self._do_cache = None 1311 self._do_cache = None
1312 self._checkCacheHeaders(request, page_cache) 1312 self._check_cache_headers(request, page_cache)
1313 1313
1314 try: 1314 try:
1315 request.write(data_encoded) 1315 request.write(data_encoded)
1316 except AttributeError: 1316 except AttributeError:
1317 log.warning(_("Can't write page, the request has probably been cancelled " 1317 log.warning(_("Can't write page, the request has probably been cancelled "
1318 "(browser tab closed or reloaded)")) 1318 "(browser tab closed or reloaded)"))
1319 return 1319 return
1320 request.finish() 1320 request.finish()
1321 1321
1322 def _subpagesHandler(self, request): 1322 def _subpages_handler(self, request):
1323 """render subpage if suitable 1323 """render subpage if suitable
1324 1324
1325 this method checks if there is still an unmanaged part of the path 1325 this method checks if there is still an unmanaged part of the path
1326 and check if it corresponds to a subpage. If so, it render the subpage 1326 and check if it corresponds to a subpage. If so, it render the subpage
1327 else it render a NoResource. 1327 else it render a NoResource.
1328 If there is no unmanaged part of the segment, current page workflow is pursued 1328 If there is no unmanaged part of the segment, current page workflow is pursued
1329 """ 1329 """
1330 if request.postpath: 1330 if request.postpath:
1331 subpage = self.nextPath(request).encode('utf-8') 1331 subpage = self.next_path(request).encode('utf-8')
1332 try: 1332 try:
1333 child = self.children[subpage] 1333 child = self.children[subpage]
1334 except KeyError: 1334 except KeyError:
1335 self.pageError(request) 1335 self.page_error(request)
1336 else: 1336 else:
1337 child.render(request) 1337 child.render(request)
1338 raise failure.Failure(exceptions.CancelError("subpage page is used")) 1338 raise failure.Failure(exceptions.CancelError("subpage page is used"))
1339 1339
1340 def _prepare_dynamic(self, request): 1340 def _prepare_dynamic(self, request):
1341 session_data = self.host.getSessionData(request, session_iface.IWebSession) 1341 session_data = self.host.get_session_data(request, session_iface.IWebSession)
1342 # we need to activate dynamic page 1342 # we need to activate dynamic page
1343 # we set data for template, and create/register token 1343 # we set data for template, and create/register token
1344 # socket_token = str(uuid.uuid4()) 1344 # socket_token = str(uuid.uuid4())
1345 socket_url = self.host.get_websocket_url(request) 1345 socket_url = self.host.get_websocket_url(request)
1346 # as for CSRF, it is important to not let the socket token if we use the service 1346 # as for CSRF, it is important to not let the socket token if we use the service
1347 # profile, as those pages can be cached, and then the token leaked. 1347 # profile, as those pages can be cached, and then the token leaked.
1348 socket_token = '' if session_data.profile is None else session_data.ws_token 1348 socket_token = '' if session_data.profile is None else session_data.ws_token
1349 socket_debug = C.boolConst(self.host.debug) 1349 socket_debug = C.bool_const(self.host.debug)
1350 request.template_data["websocket"] = WebsocketMeta( 1350 request.template_data["websocket"] = WebsocketMeta(
1351 socket_url, socket_token, socket_debug 1351 socket_url, socket_token, socket_debug
1352 ) 1352 )
1353 # we will keep track of handlers to remove 1353 # we will keep track of handlers to remove
1354 request._signals_registered = [] 1354 request._signals_registered = []
1357 1357
1358 def _render_template(self, request): 1358 def _render_template(self, request):
1359 template_data = request.template_data 1359 template_data = request.template_data
1360 1360
1361 # if confirm variable is set in case of successfuly data post 1361 # if confirm variable is set in case of successfuly data post
1362 session_data = self.host.getSessionData(request, session_iface.IWebSession) 1362 session_data = self.host.get_session_data(request, session_iface.IWebSession)
1363 template_data['identities'] = session_data.identities 1363 template_data['identities'] = session_data.identities
1364 if session_data.popPageFlag(self, C.FLAG_CONFIRM): 1364 if session_data.pop_page_flag(self, C.FLAG_CONFIRM):
1365 template_data["confirm"] = True 1365 template_data["confirm"] = True
1366 notifs = session_data.popPageNotifications(self) 1366 notifs = session_data.pop_page_notifications(self)
1367 if notifs: 1367 if notifs:
1368 template_data["notifications"] = notifs 1368 template_data["notifications"] = notifs
1369 if session_data.jid is not None: 1369 if session_data.jid is not None:
1370 template_data["own_jid"] = session_data.jid 1370 template_data["own_jid"] = session_data.jid
1371 if session_data.locale is not None: 1371 if session_data.locale is not None:
1391 for key, value in data_common["template"].items(): 1391 for key, value in data_common["template"].items():
1392 if key not in template_data: 1392 if key not in template_data:
1393 template_data[key] = value 1393 template_data[key] = value
1394 1394
1395 theme = session_data.theme or self.default_theme 1395 theme = session_data.theme or self.default_theme
1396 self.exposeToScripts( 1396 self.expose_to_scripts(
1397 request, 1397 request,
1398 cache_path=session_data.cache_dir, 1398 cache_path=session_data.cache_dir,
1399 templates_root_url=str(self.vhost_root.getFrontURL(theme)), 1399 templates_root_url=str(self.vhost_root.get_front_url(theme)),
1400 profile=session_data.profile) 1400 profile=session_data.profile)
1401 1401
1402 uri = request.uri.decode() 1402 uri = request.uri.decode()
1403 try: 1403 try:
1404 template_data["current_page"] = next( 1404 template_data["current_page"] = next(
1409 1409
1410 return self.host.renderer.render( 1410 return self.host.renderer.render(
1411 self.template, 1411 self.template,
1412 theme=theme, 1412 theme=theme,
1413 site_themes=self.site_themes, 1413 site_themes=self.site_themes,
1414 page_url=self.getURL(), 1414 page_url=self.get_url(),
1415 media_path=f"/{C.MEDIA_DIR}", 1415 media_path=f"/{C.MEDIA_DIR}",
1416 build_path=f"/{C.BUILD_DIR}/", 1416 build_path=f"/{C.BUILD_DIR}/",
1417 cache_path=session_data.cache_dir, 1417 cache_path=session_data.cache_dir,
1418 main_menu=self.main_menu, 1418 main_menu=self.main_menu,
1419 **template_data) 1419 **template_data)
1439 else: 1439 else:
1440 ret = tuple(ret) 1440 ret = tuple(ret)
1441 raise NotImplementedError( 1441 raise NotImplementedError(
1442 _("iterable in on_data_post return value is not used yet") 1442 _("iterable in on_data_post return value is not used yet")
1443 ) 1443 )
1444 session_data = self.host.getSessionData(request, session_iface.IWebSession) 1444 session_data = self.host.get_session_data(request, session_iface.IWebSession)
1445 request_data = self.getRData(request) 1445 request_data = self.get_r_data(request)
1446 if "post_redirect_page" in request_data: 1446 if "post_redirect_page" in request_data:
1447 redirect_page_data = request_data["post_redirect_page"] 1447 redirect_page_data = request_data["post_redirect_page"]
1448 if isinstance(redirect_page_data, tuple): 1448 if isinstance(redirect_page_data, tuple):
1449 redirect_page = redirect_page_data[0] 1449 redirect_page = redirect_page_data[0]
1450 redirect_page_args = redirect_page_data[1:] 1450 redirect_page_args = redirect_page_data[1:]
1451 redirect_uri = redirect_page.getURL(*redirect_page_args) 1451 redirect_uri = redirect_page.get_url(*redirect_page_args)
1452 else: 1452 else:
1453 redirect_page = redirect_page_data 1453 redirect_page = redirect_page_data
1454 redirect_uri = redirect_page.url 1454 redirect_uri = redirect_page.url
1455 else: 1455 else:
1456 redirect_page = self 1456 redirect_page = self
1457 redirect_uri = request.uri 1457 redirect_uri = request.uri
1458 1458
1459 if not C.POST_NO_CONFIRM in ret: 1459 if not C.POST_NO_CONFIRM in ret:
1460 session_data.setPageFlag(redirect_page, C.FLAG_CONFIRM) 1460 session_data.set_page_flag(redirect_page, C.FLAG_CONFIRM)
1461 request.setResponseCode(C.HTTP_SEE_OTHER) 1461 request.setResponseCode(C.HTTP_SEE_OTHER)
1462 request.setHeader(b"location", redirect_uri) 1462 request.setHeader(b"location", redirect_uri)
1463 request.finish() 1463 request.finish()
1464 raise failure.Failure(exceptions.CancelError("Post/Redirect/Get is used")) 1464 raise failure.Failure(exceptions.CancelError("Post/Redirect/Get is used"))
1465 1465
1466 async def _on_data_post(self, request): 1466 async def _on_data_post(self, request):
1467 self.checkCSRF(request) 1467 self.check_csrf(request)
1468 try: 1468 try:
1469 ret = await asDeferred(self.on_data_post, self, request) 1469 ret = await as_deferred(self.on_data_post, self, request)
1470 except exceptions.DataError as e: 1470 except exceptions.DataError as e:
1471 # something is wrong with the posted data, we re-display the page with a 1471 # something is wrong with the posted data, we re-display the page with a
1472 # warning notification 1472 # warning notification
1473 session_data = self.host.getSessionData(request, session_iface.IWebSession) 1473 session_data = self.host.get_session_data(request, session_iface.IWebSession)
1474 session_data.setPageNotification(self, str(e), C.LVL_WARNING) 1474 session_data.set_page_notification(self, str(e), C.LVL_WARNING)
1475 request.setResponseCode(C.HTTP_SEE_OTHER) 1475 request.setResponseCode(C.HTTP_SEE_OTHER)
1476 request.setHeader("location", request.uri) 1476 request.setHeader("location", request.uri)
1477 request.finish() 1477 request.finish()
1478 raise failure.Failure(exceptions.CancelError("Post/Redirect/Get is used")) 1478 raise failure.Failure(exceptions.CancelError("Post/Redirect/Get is used"))
1479 else: 1479 else:
1480 if ret != "continue": 1480 if ret != "continue":
1481 self._on_data_post_redirect(ret, request) 1481 self._on_data_post_redirect(ret, request)
1482 1482
1483 def getPostedData( 1483 def get_posted_data(
1484 self, 1484 self,
1485 request: server.Request, 1485 request: server.Request,
1486 keys, 1486 keys,
1487 multiple: bool = False, 1487 multiple: bool = False,
1488 raise_on_missing: bool = True, 1488 raise_on_missing: bool = True,
1530 if len(keys) == 1: 1530 if len(keys) == 1:
1531 return ret[0] 1531 return ret[0]
1532 else: 1532 else:
1533 return ret 1533 return ret
1534 1534
1535 def getAllPostedData(self, request, except_=(), multiple=True): 1535 def get_all_posted_data(self, request, except_=(), multiple=True):
1536 """get all posted data 1536 """get all posted data
1537 1537
1538 @param request(server.Request): request linked to the session 1538 @param request(server.Request): request linked to the session
1539 @param except_(iterable[unicode]): key of values to ignore 1539 @param except_(iterable[unicode]): key of values to ignore
1540 csrf_token will always be ignored 1540 csrf_token will always be ignored
1553 ret[key] = urllib.parse.unquote(values[0]) 1553 ret[key] = urllib.parse.unquote(values[0])
1554 else: 1554 else:
1555 ret[key] = [urllib.parse.unquote(v) for v in values] 1555 ret[key] = [urllib.parse.unquote(v) for v in values]
1556 return ret 1556 return ret
1557 1557
1558 def getProfile(self, request): 1558 def get_profile(self, request):
1559 """Helper method to easily get current profile 1559 """Helper method to easily get current profile
1560 1560
1561 @return (unicode, None): current profile 1561 @return (unicode, None): current profile
1562 None if no profile session is started 1562 None if no profile session is started
1563 """ 1563 """
1564 web_session = self.host.getSessionData(request, session_iface.IWebSession) 1564 web_session = self.host.get_session_data(request, session_iface.IWebSession)
1565 return web_session.profile 1565 return web_session.profile
1566 1566
1567 def getJid(self, request): 1567 def get_jid(self, request):
1568 """Helper method to easily get current jid 1568 """Helper method to easily get current jid
1569 1569
1570 @return: current jid 1570 @return: current jid
1571 """ 1571 """
1572 web_session = self.host.getSessionData(request, session_iface.IWebSession) 1572 web_session = self.host.get_session_data(request, session_iface.IWebSession)
1573 return web_session.jid 1573 return web_session.jid
1574 1574
1575 1575
1576 def getRData(self, request): 1576 def get_r_data(self, request):
1577 """Helper method to get request data dict 1577 """Helper method to get request data dict
1578 1578
1579 this dictionnary if for the request only, it is not saved in session 1579 this dictionnary if for the request only, it is not saved in session
1580 It is mainly used to pass data between pages/methods called during request 1580 It is mainly used to pass data between pages/methods called during request
1581 workflow 1581 workflow
1585 return request.data 1585 return request.data
1586 except AttributeError: 1586 except AttributeError:
1587 request.data = {} 1587 request.data = {}
1588 return request.data 1588 return request.data
1589 1589
1590 def getPageData(self, request, key): 1590 def get_page_data(self, request, key):
1591 """Helper method to retrieve reload resistant data""" 1591 """Helper method to retrieve reload resistant data"""
1592 web_session = self.host.getSessionData(request, session_iface.IWebSession) 1592 web_session = self.host.get_session_data(request, session_iface.IWebSession)
1593 return web_session.getPageData(self, key) 1593 return web_session.get_page_data(self, key)
1594 1594
1595 def setPageData(self, request, key, value): 1595 def set_page_data(self, request, key, value):
1596 """Helper method to set reload resistant data""" 1596 """Helper method to set reload resistant data"""
1597 web_session = self.host.getSessionData(request, session_iface.IWebSession) 1597 web_session = self.host.get_session_data(request, session_iface.IWebSession)
1598 return web_session.setPageData(self, key, value) 1598 return web_session.set_page_data(self, key, value)
1599 1599
1600 def handleSearch(self, request, extra): 1600 def handle_search(self, request, extra):
1601 """Manage Full-Text Search 1601 """Manage Full-Text Search
1602 1602
1603 Check if "search" query argument is present, and add MAM filter for it if 1603 Check if "search" query argument is present, and add MAM filter for it if
1604 necessary. 1604 necessary.
1605 If used, the "search" variable will also be available in template data, thus 1605 If used, the "search" variable will also be available in template data, thus
1606 frontend can display some information about it. 1606 frontend can display some information about it.
1607 """ 1607 """
1608 search = self.getPostedData(request, 'search', raise_on_missing=False) 1608 search = self.get_posted_data(request, 'search', raise_on_missing=False)
1609 if search is not None: 1609 if search is not None:
1610 search = search.strip() 1610 search = search.strip()
1611 if search: 1611 if search:
1612 try: 1612 try:
1613 extra[f'mam_filter_{self.host.ns_map["fulltextmam"]}'] = search 1613 extra[f'mam_filter_{self.host.ns_map["fulltextmam"]}'] = search
1614 except KeyError: 1614 except KeyError:
1615 log.warning(_("Full-text search is not available")) 1615 log.warning(_("Full-text search is not available"))
1616 else: 1616 else:
1617 request.template_data['search'] = search 1617 request.template_data['search'] = search
1618 1618
1619 def _checkAccess(self, request): 1619 def _check_access(self, request):
1620 """Check access according to self.access 1620 """Check access according to self.access
1621 1621
1622 if access is not granted, show a HTTP_FORBIDDEN pageError and stop request, 1622 if access is not granted, show a HTTP_FORBIDDEN page_error and stop request,
1623 else return data (so it can be inserted in deferred chain 1623 else return data (so it can be inserted in deferred chain
1624 """ 1624 """
1625 if self.access == C.PAGES_ACCESS_PUBLIC: 1625 if self.access == C.PAGES_ACCESS_PUBLIC:
1626 pass 1626 pass
1627 elif self.access == C.PAGES_ACCESS_PROFILE: 1627 elif self.access == C.PAGES_ACCESS_PROFILE:
1628 profile = self.getProfile(request) 1628 profile = self.get_profile(request)
1629 if not profile: 1629 if not profile:
1630 # registration allowed, we redirect to login page 1630 # registration allowed, we redirect to login page
1631 login_url = self.getPageRedirectURL(request) 1631 login_url = self.get_page_redirect_url(request)
1632 self.HTTPRedirect(request, login_url) 1632 self.http_redirect(request, login_url)
1633 1633
1634 def setBestLocale(self, request): 1634 def set_best_locale(self, request):
1635 """Guess the best locale when it is not specified explicitly by user 1635 """Guess the best locale when it is not specified explicitly by user
1636 1636
1637 This method will check "accept-language" header, and set locale to first 1637 This method will check "accept-language" header, and set locale to first
1638 matching value with available translations. 1638 matching value with available translations.
1639 """ 1639 """
1646 lang = lang.split(';')[0].strip().lower() 1646 lang = lang.split(';')[0].strip().lower()
1647 if not lang: 1647 if not lang:
1648 continue 1648 continue
1649 for a in available: 1649 for a in available:
1650 if a.lower().startswith(lang): 1650 if a.lower().startswith(lang):
1651 session_data = self.host.getSessionData(request, 1651 session_data = self.host.get_session_data(request,
1652 session_iface.IWebSession) 1652 session_iface.IWebSession)
1653 session_data.locale = a 1653 session_data.locale = a
1654 return 1654 return
1655 1655
1656 async def renderPage(self, request, skip_parse_url=False): 1656 async def render_page(self, request, skip_parse_url=False):
1657 """Main method to handle the workflow of a LiberviaPage""" 1657 """Main method to handle the workflow of a LiberviaPage"""
1658 # template_data are the variables passed to template 1658 # template_data are the variables passed to template
1659 if not hasattr(request, "template_data"): 1659 if not hasattr(request, "template_data"):
1660 # if template_data doesn't exist, it's the beginning of the request workflow 1660 # if template_data doesn't exist, it's the beginning of the request workflow
1661 # so we fill essential data 1661 # so we fill essential data
1662 session_data = self.host.getSessionData(request, session_iface.IWebSession) 1662 session_data = self.host.get_session_data(request, session_iface.IWebSession)
1663 profile = session_data.profile 1663 profile = session_data.profile
1664 request.template_data = { 1664 request.template_data = {
1665 "profile": profile, 1665 "profile": profile,
1666 # it's important to not add CSRF token and session uuid if service profile 1666 # it's important to not add CSRF token and session uuid if service profile
1667 # is used because the page may be cached, and the token then leaked 1667 # is used because the page may be cached, and the token then leaked
1692 locale = None 1692 locale = None
1693 session_data.locale = locale 1693 session_data.locale = locale
1694 1694
1695 # if locale is not specified, we try to find one requested by browser 1695 # if locale is not specified, we try to find one requested by browser
1696 if session_data.locale is None: 1696 if session_data.locale is None:
1697 self.setBestLocale(request) 1697 self.set_best_locale(request)
1698 1698
1699 # theme 1699 # theme
1700 key_theme = C.KEY_THEME.encode() 1700 key_theme = C.KEY_THEME.encode()
1701 if key_theme in request.args: 1701 if key_theme in request.args:
1702 theme = request.args.pop(key_theme)[0].decode() 1702 theme = request.args.pop(key_theme)[0].decode()
1708 else: 1708 else:
1709 session_data.theme = theme 1709 session_data.theme = theme
1710 try: 1710 try:
1711 1711
1712 try: 1712 try:
1713 self._checkAccess(request) 1713 self._check_access(request)
1714 1714
1715 if self.redirect is not None: 1715 if self.redirect is not None:
1716 self.pageRedirect(self.redirect, request, skip_parse_url=False) 1716 self.page_redirect(self.redirect, request, skip_parse_url=False)
1717 1717
1718 if self.parse_url is not None and not skip_parse_url: 1718 if self.parse_url is not None and not skip_parse_url:
1719 if self.url_cache: 1719 if self.url_cache:
1720 profile = self.getProfile(request) 1720 profile = self.get_profile(request)
1721 try: 1721 try:
1722 cache_url = self.cached_urls[profile][request.uri] 1722 cache_url = self.cached_urls[profile][request.uri]
1723 except KeyError: 1723 except KeyError:
1724 # no cache for this URI yet 1724 # no cache for this URI yet
1725 #  we do normal URL parsing, and then the cache 1725 #  we do normal URL parsing, and then the cache
1726 await asDeferred(self.parse_url, self, request) 1726 await as_deferred(self.parse_url, self, request)
1727 self._cacheURL(request, profile) 1727 self._cache_url(request, profile)
1728 else: 1728 else:
1729 log.debug(f"using URI cache for {self}") 1729 log.debug(f"using URI cache for {self}")
1730 cache_url.use(request) 1730 cache_url.use(request)
1731 else: 1731 else:
1732 await asDeferred(self.parse_url, self, request) 1732 await as_deferred(self.parse_url, self, request)
1733 1733
1734 if self.add_breadcrumb is None: 1734 if self.add_breadcrumb is None:
1735 label = ( 1735 label = (
1736 self.label 1736 self.label
1737 or self.name 1737 or self.name
1741 "url": self.url, 1741 "url": self.url,
1742 "label": label.title(), 1742 "label": label.title(),
1743 } 1743 }
1744 request.template_data["breadcrumbs"].append(breadcrumb) 1744 request.template_data["breadcrumbs"].append(breadcrumb)
1745 else: 1745 else:
1746 await asDeferred( 1746 await as_deferred(
1747 self.add_breadcrumb, 1747 self.add_breadcrumb,
1748 self, 1748 self,
1749 request, 1749 request,
1750 request.template_data["breadcrumbs"] 1750 request.template_data["breadcrumbs"]
1751 ) 1751 )
1752 1752
1753 self._subpagesHandler(request) 1753 self._subpages_handler(request)
1754 1754
1755 if request.method not in (C.HTTP_METHOD_GET, C.HTTP_METHOD_POST): 1755 if request.method not in (C.HTTP_METHOD_GET, C.HTTP_METHOD_POST):
1756 # only HTTP GET and POST are handled so far 1756 # only HTTP GET and POST are handled so far
1757 self.pageError(request, C.HTTP_BAD_REQUEST) 1757 self.page_error(request, C.HTTP_BAD_REQUEST)
1758 1758
1759 if request.method == C.HTTP_METHOD_POST: 1759 if request.method == C.HTTP_METHOD_POST:
1760 if self.on_data_post == 'continue': 1760 if self.on_data_post == 'continue':
1761 pass 1761 pass
1762 elif self.on_data_post is None: 1762 elif self.on_data_post is None:
1763 # if we don't have on_data_post, the page was not expecting POST 1763 # if we don't have on_data_post, the page was not expecting POST
1764 # so we return an error 1764 # so we return an error
1765 self.pageError(request, C.HTTP_BAD_REQUEST) 1765 self.page_error(request, C.HTTP_BAD_REQUEST)
1766 else: 1766 else:
1767 await self._on_data_post(request) 1767 await self._on_data_post(request)
1768 # by default, POST follow normal behaviour after on_data_post is called 1768 # by default, POST follow normal behaviour after on_data_post is called
1769 # this can be changed by a redirection or other method call in on_data_post 1769 # this can be changed by a redirection or other method call in on_data_post
1770 1770
1771 if self.dynamic: 1771 if self.dynamic:
1772 self._prepare_dynamic(request) 1772 self._prepare_dynamic(request)
1773 1773
1774 if self.prepare_render: 1774 if self.prepare_render:
1775 await asDeferred(self.prepare_render, self, request) 1775 await as_deferred(self.prepare_render, self, request)
1776 1776
1777 if self.template: 1777 if self.template:
1778 rendered = self._render_template(request) 1778 rendered = self._render_template(request)
1779 elif self.render_method: 1779 elif self.render_method:
1780 rendered = await asDeferred(self.render_method, self, request) 1780 rendered = await as_deferred(self.render_method, self, request)
1781 else: 1781 else:
1782 raise exceptions.InternalError( 1782 raise exceptions.InternalError(
1783 "No method set to render page, please set a template or use a " 1783 "No method set to render page, please set a template or use a "
1784 "render method" 1784 "render method"
1785 ) 1785 )
1786 1786
1787 self.writeData(rendered, request) 1787 self.write_data(rendered, request)
1788 1788
1789 except failure.Failure as f: 1789 except failure.Failure as f:
1790 # we have to unpack the Failure to catch the right Exception 1790 # we have to unpack the Failure to catch the right Exception
1791 raise f.value 1791 raise f.value
1792 1792
1793 except exceptions.CancelError: 1793 except exceptions.CancelError:
1794 pass 1794 pass
1795 except BridgeException as e: 1795 except BridgeException as e:
1796 if e.condition == 'not-allowed': 1796 if e.condition == 'not-allowed':
1797 log.warning("not allowed exception catched") 1797 log.warning("not allowed exception catched")
1798 self.pageError(request, C.HTTP_FORBIDDEN) 1798 self.page_error(request, C.HTTP_FORBIDDEN)
1799 elif e.condition == 'item-not-found' or e.classname == 'NotFound': 1799 elif e.condition == 'item-not-found' or e.classname == 'NotFound':
1800 self.pageError(request, C.HTTP_NOT_FOUND) 1800 self.page_error(request, C.HTTP_NOT_FOUND)
1801 elif e.condition == 'remote-server-not-found': 1801 elif e.condition == 'remote-server-not-found':
1802 self.pageError(request, C.HTTP_NOT_FOUND) 1802 self.page_error(request, C.HTTP_NOT_FOUND)
1803 elif e.condition == 'forbidden': 1803 elif e.condition == 'forbidden':
1804 if self.getProfile(request) is None: 1804 if self.get_profile(request) is None:
1805 log.debug("access forbidden, we're redirecting to log-in page") 1805 log.debug("access forbidden, we're redirecting to log-in page")
1806 self.HTTPRedirect(request, self.getPageRedirectURL(request)) 1806 self.http_redirect(request, self.get_page_redirect_url(request))
1807 else: 1807 else:
1808 self.pageError(request, C.HTTP_FORBIDDEN) 1808 self.page_error(request, C.HTTP_FORBIDDEN)
1809 else: 1809 else:
1810 log.error( 1810 log.error(
1811 _("Uncatched bridge exception for HTTP request on {url}: {e}\n" 1811 _("Uncatched bridge exception for HTTP request on {url}: {e}\n"
1812 "page name: {name}\npath: {path}\nURL: {full_url}\n{tb}") 1812 "page name: {name}\npath: {path}\nURL: {full_url}\n{tb}")
1813 .format( 1813 .format(
1818 full_url=request.URLPath(), 1818 full_url=request.URLPath(),
1819 tb=traceback.format_exc(), 1819 tb=traceback.format_exc(),
1820 ) 1820 )
1821 ) 1821 )
1822 try: 1822 try:
1823 self.pageError(request, C.HTTP_INTERNAL_ERROR) 1823 self.page_error(request, C.HTTP_INTERNAL_ERROR)
1824 except exceptions.CancelError: 1824 except exceptions.CancelError:
1825 pass 1825 pass
1826 except Exception as e: 1826 except Exception as e:
1827 log.error( 1827 log.error(
1828 _("Uncatched error for HTTP request on {url}: {e}\npage name: " 1828 _("Uncatched error for HTTP request on {url}: {e}\npage name: "
1835 full_url=request.URLPath(), 1835 full_url=request.URLPath(),
1836 tb=traceback.format_exc(), 1836 tb=traceback.format_exc(),
1837 ) 1837 )
1838 ) 1838 )
1839 try: 1839 try:
1840 self.pageError(request, C.HTTP_INTERNAL_ERROR) 1840 self.page_error(request, C.HTTP_INTERNAL_ERROR)
1841 except exceptions.CancelError: 1841 except exceptions.CancelError:
1842 pass 1842 pass
1843 1843
1844 def render_GET(self, request): 1844 def render_GET(self, request):
1845 defer.ensureDeferred(self.renderPage(request)) 1845 defer.ensureDeferred(self.render_page(request))
1846 return server.NOT_DONE_YET 1846 return server.NOT_DONE_YET
1847 1847
1848 def render_POST(self, request): 1848 def render_POST(self, request):
1849 defer.ensureDeferred(self.renderPage(request)) 1849 defer.ensureDeferred(self.render_page(request))
1850 return server.NOT_DONE_YET 1850 return server.NOT_DONE_YET