web/request.py
brancholdstable
changeset 5422 0865e1e90674
parent 5421 8167de96c523
child 5423 e15abfdcce38
child 5424 8ecbcbff9777
equal deleted inserted replaced
4985:02b52bf9f5f8 5422:0865e1e90674
       
     1 # copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     3 #
       
     4 # This file is part of CubicWeb.
       
     5 #
       
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
       
     7 # terms of the GNU Lesser General Public License as published by the Free
       
     8 # Software Foundation, either version 2.1 of the License, or (at your option)
       
     9 # any later version.
       
    10 #
       
    11 # logilab-common is distributed in the hope that it will be useful, but WITHOUT
       
    12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
       
    13 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
       
    14 # details.
       
    15 #
       
    16 # You should have received a copy of the GNU Lesser General Public License along
       
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
     1 """abstract class for http request
    18 """abstract class for http request
     2 
    19 
     3 :organization: Logilab
       
     4 :copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
       
     5 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     6 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
       
     7 """
    20 """
     8 __docformat__ = "restructuredtext en"
    21 __docformat__ = "restructuredtext en"
     9 
    22 
    10 import Cookie
    23 import Cookie
    11 import sha
    24 import sha
    14 import base64
    27 import base64
    15 from datetime import date
    28 from datetime import date
    16 from urlparse import urlsplit
    29 from urlparse import urlsplit
    17 from itertools import count
    30 from itertools import count
    18 
    31 
    19 from simplejson import dumps
       
    20 
       
    21 from rql.utils import rqlvar_maker
    32 from rql.utils import rqlvar_maker
    22 
    33 
    23 from logilab.common.decorators import cached
    34 from logilab.common.decorators import cached
    24 from logilab.common.deprecation import deprecated
    35 from logilab.common.deprecation import deprecated
    25 from logilab.mtconverter import xml_escape
    36 from logilab.mtconverter import xml_escape
    28 from cubicweb.mail import header
    39 from cubicweb.mail import header
    29 from cubicweb.uilib import remove_html_tags
    40 from cubicweb.uilib import remove_html_tags
    30 from cubicweb.utils import SizeConstrainedList, HTMLHead, make_uid
    41 from cubicweb.utils import SizeConstrainedList, HTMLHead, make_uid
    31 from cubicweb.view import STRICT_DOCTYPE, TRANSITIONAL_DOCTYPE_NOEXT
    42 from cubicweb.view import STRICT_DOCTYPE, TRANSITIONAL_DOCTYPE_NOEXT
    32 from cubicweb.web import (INTERNAL_FIELD_VALUE, LOGGER, NothingToEdit,
    43 from cubicweb.web import (INTERNAL_FIELD_VALUE, LOGGER, NothingToEdit,
    33                           RequestError, StatusResponse)
    44                           RequestError, StatusResponse, json)
       
    45 dumps = json.dumps
    34 
    46 
    35 _MARKER = object()
    47 _MARKER = object()
    36 
    48 
    37 
    49 
    38 def list_form_param(form, param, pop=False):
    50 def list_form_param(form, param, pop=False):
    66     """abstract HTTP request, should be extended according to the HTTP backend"""
    78     """abstract HTTP request, should be extended according to the HTTP backend"""
    67     json_request = False # to be set to True by json controllers
    79     json_request = False # to be set to True by json controllers
    68 
    80 
    69     def __init__(self, vreg, https, form=None):
    81     def __init__(self, vreg, https, form=None):
    70         super(CubicWebRequestBase, self).__init__(vreg)
    82         super(CubicWebRequestBase, self).__init__(vreg)
    71         self.message = None
       
    72         self.authmode = vreg.config['auth-mode']
    83         self.authmode = vreg.config['auth-mode']
    73         self.https = https
    84         self.https = https
    74         # raw html headers that can be added from any view
    85         # raw html headers that can be added from any view
    75         self.html_headers = HTMLHead()
    86         self.html_headers = HTMLHead()
    76         # form parameters
    87         # form parameters
   124         """method called by the session handler when the user is authenticated
   135         """method called by the session handler when the user is authenticated
   125         or an anonymous connection is open
   136         or an anonymous connection is open
   126         """
   137         """
   127         super(CubicWebRequestBase, self).set_connection(cnx, user)
   138         super(CubicWebRequestBase, self).set_connection(cnx, user)
   128         # set request language
   139         # set request language
   129         try:
   140         vreg = self.vreg
   130             vreg = self.vreg
   141         if self.user:
   131             if self.user:
   142             try:
   132                 try:
   143                 # 1. user specified language
   133                     # 1. user specified language
   144                 lang = vreg.typed_value('ui.language',
   134                     lang = vreg.typed_value('ui.language',
   145                                         self.user.properties['ui.language'])
   135                                             self.user.properties['ui.language'])
   146                 self.set_language(lang)
       
   147                 return
       
   148             except KeyError:
       
   149                 pass
       
   150         if vreg.config['language-negociation']:
       
   151             # 2. http negociated language
       
   152             for lang in self.header_accept_language():
       
   153                 if lang in self.translations:
   136                     self.set_language(lang)
   154                     self.set_language(lang)
   137                     return
   155                     return
   138                 except KeyError:
   156         # 3. default language
   139                     pass
   157         self.set_default_language(vreg)
   140             if vreg.config['language-negociation']:
       
   141                 # 2. http negociated language
       
   142                 for lang in self.header_accept_language():
       
   143                     if lang in self.translations:
       
   144                         self.set_language(lang)
       
   145                         return
       
   146             # 3. default language
       
   147             self.set_default_language(vreg)
       
   148         finally:
       
   149             # XXX code smell
       
   150             # have to be done here because language is not yet set in setup_params
       
   151             #
       
   152             # special key for created entity, added in controller's reset method
       
   153             # if no message set, we don't want this neither
       
   154             if '__createdpath' in self.form and self.message:
       
   155                 self.message += ' (<a href="%s">%s</a>)' % (
       
   156                     self.build_url(self.form.pop('__createdpath')),
       
   157                     self._('click here to see created entity'))
       
   158 
   158 
   159     def set_language(self, lang):
   159     def set_language(self, lang):
   160         gettext, self.pgettext = self.translations[lang]
   160         gettext, self.pgettext = self.translations[lang]
   161         self._ = self.__ = gettext
   161         self._ = self.__ = gettext
   162         self.lang = lang
   162         self.lang = lang
   177     def setup_params(self, params):
   177     def setup_params(self, params):
   178         """WARNING: we're intentionaly leaving INTERNAL_FIELD_VALUE here
   178         """WARNING: we're intentionaly leaving INTERNAL_FIELD_VALUE here
   179 
   179 
   180         subclasses should overrides to
   180         subclasses should overrides to
   181         """
   181         """
       
   182         self.form = {}
   182         if params is None:
   183         if params is None:
   183             params = {}
   184             return
   184         self.form = params
       
   185         encoding = self.encoding
   185         encoding = self.encoding
   186         for k, v in params.items():
   186         for param, val in params.iteritems():
   187             if isinstance(v, (tuple, list)):
   187             if isinstance(val, (tuple, list)):
   188                 v = [unicode(x, encoding) for x in v]
   188                 val = [unicode(x, encoding) for x in val]
   189                 if len(v) == 1:
   189                 if len(val) == 1:
   190                     v = v[0]
   190                     val = val[0]
   191             if k in self.no_script_form_params:
   191             elif isinstance(val, str):
   192                 v = self.no_script_form_param(k, value=v)
   192                 val = unicode(val, encoding)
   193             if isinstance(v, str):
   193             if param in self.no_script_form_params and val:
   194                 v = unicode(v, encoding)
   194                 val = self.no_script_form_param(param, val)
   195             if k == '__message':
   195             if param == '_cwmsgid':
   196                 self.set_message(v)
   196                 self.set_message_id(val)
   197                 del self.form[k]
   197             elif param == '__message':
       
   198                 self.set_message(val)
   198             else:
   199             else:
   199                 self.form[k] = v
   200                 self.form[param] = val
   200 
   201 
   201     def no_script_form_param(self, param, default=None, value=None):
   202     def no_script_form_param(self, param, value):
   202         """ensure there is no script in a user form param
   203         """ensure there is no script in a user form param
   203 
   204 
   204         by default return a cleaned string instead of raising a security
   205         by default return a cleaned string instead of raising a security
   205         exception
   206         exception
   206 
   207 
   207         this method should be called on every user input (form at least) fields
   208         this method should be called on every user input (form at least) fields
   208         that are at some point inserted in a generated html page to protect
   209         that are at some point inserted in a generated html page to protect
   209         against script kiddies
   210         against script kiddies
   210         """
   211         """
   211         if value is None:
   212         # safety belt for strange urls like http://...?vtitle=yo&vtitle=yo
   212             value = self.form.get(param, default)
   213         if isinstance(value, (list, tuple)):
   213         if not value is default and value:
   214             self.error('no_script_form_param got a list (%s). Who generated the URL ?',
   214             # safety belt for strange urls like http://...?vtitle=yo&vtitle=yo
   215                        repr(value))
   215             if isinstance(value, (list, tuple)):
   216             value = value[0]
   216                 self.error('no_script_form_param got a list (%s). Who generated the URL ?',
   217         return remove_html_tags(value)
   217                            repr(value))
       
   218                 value = value[0]
       
   219             return remove_html_tags(value)
       
   220         return value
       
   221 
   218 
   222     def list_form_param(self, param, form=None, pop=False):
   219     def list_form_param(self, param, form=None, pop=False):
   223         """get param from form parameters and return its value as a list,
   220         """get param from form parameters and return its value as a list,
   224         skipping internal markers if any
   221         skipping internal markers if any
   225 
   222 
   243         self.html_headers = HTMLHead()
   240         self.html_headers = HTMLHead()
   244         return self
   241         return self
   245 
   242 
   246     # web state helpers #######################################################
   243     # web state helpers #######################################################
   247 
   244 
       
   245     @property
       
   246     def message(self):
       
   247         try:
       
   248             return self.get_session_data(self._msgid, default=u'', pop=True)
       
   249         except AttributeError:
       
   250             try:
       
   251                 return self._msg
       
   252             except AttributeError:
       
   253                 return None
       
   254 
   248     def set_message(self, msg):
   255     def set_message(self, msg):
   249         assert isinstance(msg, unicode)
   256         assert isinstance(msg, unicode)
   250         self.message = msg
   257         self._msg = msg
       
   258 
       
   259     def set_message_id(self, msgid):
       
   260         self._msgid = msgid
       
   261 
       
   262     @cached
       
   263     def redirect_message_id(self):
       
   264         return make_uid()
       
   265 
       
   266     def set_redirect_message(self, msg):
       
   267         assert isinstance(msg, unicode)
       
   268         msgid = self.redirect_message_id()
       
   269         self.set_session_data(msgid, msg)
       
   270         return msgid
       
   271 
       
   272     def append_to_redirect_message(self, msg):
       
   273         msgid = self.redirect_message_id()
       
   274         currentmsg = self.get_session_data(msgid)
       
   275         if currentmsg is not None:
       
   276             currentmsg = '%s %s' % (currentmsg, msg)
       
   277         else:
       
   278             currentmsg = msg
       
   279         self.set_session_data(msgid, currentmsg)
       
   280         return msgid
       
   281 
       
   282     def reset_message(self):
       
   283         if hasattr(self, '_msg'):
       
   284             del self._msg
       
   285         if hasattr(self, '_msgid'):
       
   286             del self._msgid
   251 
   287 
   252     def update_search_state(self):
   288     def update_search_state(self):
   253         """update the current search state"""
   289         """update the current search state"""
   254         searchstate = self.form.get('__mode')
   290         searchstate = self.form.get('__mode')
   255         if not searchstate and self.cnx is not None:
   291         if not searchstate and self.cnx is not None:
   479                             % filename)
   515                             % filename)
   480 
   516 
   481     # high level methods for HTML headers management ##########################
   517     # high level methods for HTML headers management ##########################
   482 
   518 
   483     def add_onload(self, jscode):
   519     def add_onload(self, jscode):
   484         self.html_headers.add_onload(jscode, self.json_request)
   520         self.html_headers.add_onload(jscode)
   485 
   521 
   486     def add_js(self, jsfiles, localfile=True):
   522     def add_js(self, jsfiles, localfile=True):
   487         """specify a list of JS files to include in the HTML headers
   523         """specify a list of JS files to include in the HTML headers
   488         :param jsfiles: a JS filename or a list of JS filenames
   524         :param jsfiles: a JS filename or a list of JS filenames
   489         :param localfile: if True, the default data dir prefix is added to the
   525         :param localfile: if True, the default data dir prefix is added to the
   497             self.html_headers.add_js(jsfile)
   533             self.html_headers.add_js(jsfile)
   498 
   534 
   499     def add_css(self, cssfiles, media=u'all', localfile=True, ieonly=False,
   535     def add_css(self, cssfiles, media=u'all', localfile=True, ieonly=False,
   500                 iespec=u'[if lt IE 8]'):
   536                 iespec=u'[if lt IE 8]'):
   501         """specify a CSS file to include in the HTML headers
   537         """specify a CSS file to include in the HTML headers
       
   538 
   502         :param cssfiles: a CSS filename or a list of CSS filenames
   539         :param cssfiles: a CSS filename or a list of CSS filenames
   503         :param media: the CSS's media if necessary
   540         :param media: the CSS's media if necessary
   504         :param localfile: if True, the default data dir prefix is added to the
   541         :param localfile: if True, the default data dir prefix is added to the
   505                           CSS filename
   542                           CSS filename
   506         :param ieonly: True if this css is specific to IE
   543         :param ieonly: True if this css is specific to IE
   524                 cssfile = self.datadir_url + cssfile
   561                 cssfile = self.datadir_url + cssfile
   525             add_css(cssfile, media, *extraargs)
   562             add_css(cssfile, media, *extraargs)
   526 
   563 
   527     def build_ajax_replace_url(self, nodeid, rql, vid, replacemode='replace',
   564     def build_ajax_replace_url(self, nodeid, rql, vid, replacemode='replace',
   528                                **extraparams):
   565                                **extraparams):
   529         """builds an ajax url that will replace `nodeid`s content
   566         """builds an ajax url that will replace nodeid's content
       
   567 
   530         :param nodeid: the dom id of the node to replace
   568         :param nodeid: the dom id of the node to replace
   531         :param rql: rql to execute
   569         :param rql: rql to execute
   532         :param vid: the view to apply on the resultset
   570         :param vid: the view to apply on the resultset
   533         :param replacemode: defines how the replacement should be done.
   571         :param replacemode: defines how the replacement should be done.
       
   572 
   534         Possible values are :
   573         Possible values are :
   535          - 'replace' to replace the node's content with the generated HTML
   574         - 'replace' to replace the node's content with the generated HTML
   536          - 'swap' to replace the node itself with the generated HTML
   575         - 'swap' to replace the node itself with the generated HTML
   537          - 'append' to append the generated HTML to the node's content
   576         - 'append' to append the generated HTML to the node's content
   538         """
   577         """
   539         url = self.build_url('view', rql=rql, vid=vid, __notemplate=1,
   578         url = self.build_url('view', rql=rql, vid=vid, __notemplate=1,
   540                              **extraparams)
   579                              **extraparams)
   541         return "javascript: loadxhtml('%s', '%s', '%s')" % (
   580         return "javascript: loadxhtml('%s', '%s', '%s')" % (
   542             nodeid, xml_escape(url), replacemode)
   581             nodeid, xml_escape(url), replacemode)