|
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) |