comparison MoinMoin/request.py @ 0:77665d8e2254

tag of nonpublic@localhost--archive/moin--enterprise--1.5--base-0 (automatically generated log message) imported from: moin--main--1.5--base-0
author Thomas Waldmann <tw-public@gmx.de>
date Thu, 22 Sep 2005 15:09:50 +0000
parents
children e63790f39ebd
comparison
equal deleted inserted replaced
-1:000000000000 0:77665d8e2254
1 # -*- coding: iso-8859-1 -*-
2 """
3 MoinMoin - Data associated with a single Request
4
5 @copyright: 2001-2003 by Jürgen Hermann <jh@web.de>
6 @copyright: 2003-2004 by Thomas Waldmann
7 @license: GNU GPL, see COPYING for details.
8 """
9
10 import os, time, sys, cgi
11 from cStringIO import StringIO
12 from MoinMoin import config, wikiutil, user, error
13 from MoinMoin.util import MoinMoinNoFooter, IsWin9x
14 import MoinMoin.error
15
16
17 # Timing ---------------------------------------------------------------
18
19 class Clock:
20 """ Helper class for code profiling
21 we do not use time.clock() as this does not work across threads
22 """
23
24 def __init__(self):
25 self.timings = {'total': time.time()}
26
27 def start(self, timer):
28 self.timings[timer] = time.time() - self.timings.get(timer, 0)
29
30 def stop(self, timer):
31 self.timings[timer] = time.time() - self.timings[timer]
32
33 def value(self, timer):
34 return "%.3f" % (self.timings[timer],)
35
36 def dump(self):
37 outlist = []
38 for timing in self.timings.items():
39 outlist.append("%s = %.3fs" % timing)
40 outlist.sort()
41 return outlist
42
43
44 # Utilities
45
46 def cgiMetaVariable(header, scheme='http'):
47 """ Return CGI meta variable for header name
48
49 e.g 'User-Agent' -> 'HTTP_USER_AGENT'
50 See http://www.faqs.org/rfcs/rfc3875.html section 4.1.18
51 """
52 var = '%s_%s' % (scheme, header)
53 return var.upper().replace('-', '_')
54
55
56 # Request Base ----------------------------------------------------------
57
58 class RequestBase(object):
59 """ A collection for all data associated with ONE request. """
60
61 # Header set to force misbehaved proxies and browsers to keep their
62 # hands off a page
63 # Details: http://support.microsoft.com/support/kb/articles/Q234/0/67.ASP
64 nocache = [
65 "Pragma: no-cache",
66 "Cache-Control: no-cache",
67 "Expires: -1",
68 ]
69
70 # Defaults (used by sub classes)
71 http_accept_language = 'en'
72 server_name = 'localhost'
73 server_port = '80'
74
75 # Extra headers we support. Both standalone and twisted store
76 # headers as lowercase.
77 moin_location = 'x-moin-location'
78 proxy_host = 'x-forwarded-host'
79
80 def __init__(self, properties={}):
81 # Decode values collected by sub classes
82 self.path_info = self.decodePagename(self.path_info)
83
84 self.failed = 0
85 self._available_actions = None
86 self._known_actions = None
87
88 # Pages meta data that we collect in one request
89 self.pages = {}
90
91 self.sent_headers = 0
92 self.user_headers = []
93 self.page = None
94 self._dicts = None
95
96 # Fix dircaching problems on Windows 9x
97 if IsWin9x():
98 import dircache
99 dircache.reset()
100
101 # Check for dumb proxy requests
102 # TODO relying on request_uri will not work on all servers, especially
103 # not on external non-Apache servers
104 self.forbidden = False
105 if self.request_uri.startswith('http://'):
106 self.makeForbidden()
107
108 # Init
109 else:
110 self.writestack = []
111 self.clock = Clock()
112 # order is important here!
113 self._load_multi_cfg()
114
115 # Set decode charsets. Input from the user is always in
116 # config.charset, which is the page charsets. Except
117 # path_info, which may use utf-8, and handled by decodePagename.
118 self.decode_charsets = [config.charset]
119
120 # hierarchical wiki - set rootpage
121 from MoinMoin.Page import Page
122 #path = self.getPathinfo()
123 #if path.startswith('/'):
124 # pages = path[1:].split('/')
125 # if 0: # len(path) > 1:
126 # ## breaks MainPage/SubPage on flat storage
127 # rootname = u'/'.join(pages[:-1])
128 # else:
129 # # this is the usual case, as it ever was...
130 # rootname = u""
131 #else:
132 # # no extra path after script name
133 # rootname = u""
134
135 self.args = {}
136 self.form = {}
137
138 rootname = u''
139 self.rootpage = Page(self, rootname, is_rootpage=1)
140
141 self.user = self.get_user()
142
143 from MoinMoin import i18n
144
145 # Set theme - forced theme, user theme or wiki default
146 if self.cfg.theme_force:
147 theme_name = self.cfg.theme_default
148 else:
149 theme_name = self.user.theme_name
150 self.loadTheme(theme_name)
151
152 self.logger = None
153 self.pragma = {}
154 self.mode_getpagelinks = 0
155 self.no_closing_html_code = 0
156
157 self.__dict__.update(properties)
158
159 self.i18n = i18n
160 self.lang = i18n.requestLanguage(self)
161 # Language for content. Page content should use the wiki
162 # default lang, but generated content like search results
163 # should use the user language.
164 self.content_lang = self.cfg.default_lang
165 self.getText = lambda text, i18n=self.i18n, request=self, lang=self.lang, **kv: i18n.getText(text, request, lang, kv.get('formatted', True))
166
167 self.opened_logs = 0
168 self.reset()
169
170 def getDicts(self):
171 """ Lazy initialize the dicts on the first access """
172 if self._dicts is None:
173 from MoinMoin import wikidicts
174 dicts = wikidicts.GroupDict(self)
175 dicts.scandicts()
176 self._dicts = dicts
177 return self._dicts
178
179 def delDicts(self):
180 """ Delete the dicts, used by some tests """
181 del self._dicts
182 self._dicts = None
183
184 dicts = property(getDicts, None, delDicts)
185
186 def _load_multi_cfg(self):
187 # protect against calling multiple times
188 if not hasattr(self, 'cfg'):
189 from MoinMoin import multiconfig
190 self.cfg = multiconfig.getConfig(self.url)
191
192 def setAcceptedCharsets(self, accept_charset):
193 """ Set accepted_charsets by parsing accept-charset header
194
195 Set self.accepted_charsets to an ordered list based on
196 http_accept_charset.
197
198 Reference: http://www.w3.org/Protocols/rfc2616/rfc2616.txt
199
200 TODO: currently no code use this value.
201
202 @param accept_charset: accept-charset header
203 """
204 charsets = []
205 if accept_charset:
206 accept_charset = accept_charset.lower()
207 # Add iso-8859-1 if needed
208 if (not '*' in accept_charset and
209 accept_charset.find('iso-8859-1') < 0):
210 accept_charset += ',iso-8859-1'
211
212 # Make a list, sorted by quality value, using Schwartzian Transform
213 # Create list of tuples (value, name) , sort, extract names
214 for item in accept_charset.split(','):
215 if ';' in item:
216 name, qval = item.split(';')
217 qval = 1.0 - float(qval.split('=')[1])
218 else:
219 name, qval = item, 0
220 charsets.append((qval, name))
221 charsets.sort()
222 # Remove *, its not clear what we should do with it later
223 charsets = [name for qval, name in charsets if name != '*']
224
225 self.accepted_charsets = charsets
226
227 def _setup_vars_from_std_env(self, env):
228 """ Set common request variables from CGI environment
229
230 Parse a standard CGI environment as created by common web
231 servers. Reference: http://www.faqs.org/rfcs/rfc3875.html
232
233 @param env: dict like object containing cgi meta variables
234 """
235 # Values we can just copy
236 self.env = env
237 self.http_accept_language = env.get('HTTP_ACCEPT_LANGUAGE',
238 self.http_accept_language)
239 self.server_name = env.get('SERVER_NAME', self.server_name)
240 self.server_port = env.get('SERVER_PORT', self.server_port)
241 self.saved_cookie = env.get('HTTP_COOKIE', '')
242 self.script_name = env.get('SCRIPT_NAME', '')
243 self.path_info = env.get('PATH_INFO', '')
244 self.query_string = env.get('QUERY_STRING', '')
245 self.request_method = env.get('REQUEST_METHOD', None)
246 self.remote_addr = env.get('REMOTE_ADDR', '')
247 self.http_user_agent = env.get('HTTP_USER_AGENT', '')
248
249 # REQUEST_URI is not part of CGI spec, but an addition of
250 # Apache.
251 self.request_uri = env.get('REQUEST_URI', '')
252
253 # Values that need more work
254 self.setHttpReferer(env.get('HTTP_REFERER'))
255 self.setIsSSL(env)
256 self.setHost(env.get('HTTP_HOST'))
257 self.fixURI(env)
258 self.setURL(env)
259
260 ##self.debugEnvironment(env)
261
262 def setHttpReferer(self, referer):
263 """ Set http_referer, making sure its ascii
264
265 IE might send non-ascii value.
266 """
267 value = ''
268 if referer:
269 value = unicode(referer, 'ascii', 'replace')
270 value = value.encode('ascii', 'replace')
271 self.http_referer = value
272
273 def setIsSSL(self, env):
274 """ Set is_ssl
275
276 @param env: dict like object containing cgi meta variables
277 """
278 self.is_ssl = (env.get('SSL_PROTOCOL') or
279 env.get('SSL_PROTOCOL_VERSION') or
280 env.get('HTTPS') == 'on')
281
282 def setHost(self, host=None):
283 """ Set http_host
284
285 Create from server name and port if missing. Previous code
286 default to localhost.
287 """
288 if not host:
289 port = ''
290 standardPort = ('80', '443')[self.is_ssl]
291 if self.server_port != standardPort:
292 port = ':' + self.server_port
293 host = self.server_name + port
294 self.http_host = host
295
296 def fixURI(self, env):
297 """ Fix problems with script_name and path_info
298
299 Handle the strange charset semantics on Windows and other non
300 posix systems. path_info is transformed into the system code
301 page by the web server. Additionally, paths containing dots let
302 most webservers choke.
303
304 Broken environment variables in different environments:
305 path_info script_name
306 Apache1 X X PI does not contain dots
307 Apache2 X X PI is not encoded correctly
308 IIS X X path_info include script_name
309 Other ? - ? := Possible and even RFC-compatible.
310 - := Hopefully not.
311
312 @param env: dict like object containing cgi meta variables
313 """
314 # Fix the script_name when using Apache on Windows.
315 server_software = env.get('SERVER_SOFTWARE', '')
316 if os.name == 'nt' and server_software.find('Apache/') != -1:
317 # Removes elements ending in '.' from the path.
318 self.script_name = '/'.join([x for x in self.script_name.split('/')
319 if not x.endswith('.')])
320
321 # Fix path_info
322 if os.name != 'posix' and self.request_uri != '':
323 # Try to recreate path_info from request_uri.
324 import urlparse, urllib
325 scriptAndPath = urlparse.urlparse(self.request_uri)[2]
326 path = scriptAndPath.replace(self.script_name, '', 1)
327 self.path_info = urllib.unquote(path)
328 elif os.name == 'nt':
329 # Recode path_info to utf-8
330 path = wikiutil.decodeWindowsPath(self.path_info)
331 self.path_info = path.encode("utf-8")
332
333 # Fix bug in IIS/4.0 when path_info contain script_name
334 if self.path_info.startswith(self.script_name):
335 self.path_info = self.path_info[len(self.script_name):]
336
337 def setURL(self, env):
338 """ Set url, used to locate wiki config
339
340 This is the place to manipulate url parts as needed.
341
342 @param env: dict like object containing cgi meta variables or
343 http headers.
344 """
345 # If we serve on localhost:8000 and use a proxy on
346 # example.com/wiki, our urls will be example.com/wiki/pagename
347 # Same for the wiki config - they must use the proxy url.
348 self.rewriteHost(env)
349 self.rewriteURI(env)
350
351 if not self.request_uri:
352 self.request_uri = self.makeURI()
353 self.url = self.http_host + self.request_uri
354
355 def rewriteHost(self, env):
356 """ Rewrite http_host transparently
357
358 Get the proxy host using 'X-Forwarded-Host' header, added by
359 Apache 2 and other proxy software.
360
361 TODO: Will not work for Apache 1 or others that don't add this
362 header.
363
364 TODO: If we want to add an option to disable this feature it
365 should be in the server script, because the config is not
366 loaded at this point, and must be loaded after url is set.
367
368 @param env: dict like object containing cgi meta variables or
369 http headers.
370 """
371 proxy_host = (env.get(self.proxy_host) or
372 env.get(cgiMetaVariable(self.proxy_host)))
373 if proxy_host:
374 self.http_host = proxy_host
375
376 def rewriteURI(self, env):
377 """ Rewrite request_uri, script_name and path_info transparently
378
379 Useful when running mod python or when running behind a proxy,
380 e.g run on localhost:8000/ and serve as example.com/wiki/.
381
382 Uses private 'X-Moin-Location' header to set the script name.
383 This allow setting the script name when using Apache 2
384 <location> directive::
385
386 <Location /my/wiki/>
387 RequestHeader set X-Moin-Location /my/wiki/
388 </location>
389
390 TODO: does not work for Apache 1 and others that do not allow
391 setting custom headers per request.
392
393 @param env: dict like object containing cgi meta variables or
394 http headers.
395 """
396 location = (env.get(self.moin_location) or
397 env.get(cgiMetaVariable(self.moin_location)))
398 if location is None:
399 return
400
401 scriptAndPath = self.script_name + self.path_info
402 location = location.rstrip('/')
403 self.script_name = location
404
405 # This may happen when using mod_python
406 if scriptAndPath.startswith(location):
407 self.path_info = scriptAndPath[len(location):]
408
409 # Recreate the URI from the modified parts
410 if self.request_uri:
411 self.request_uri = self.makeURI()
412
413 def makeURI(self):
414 """ Return uri created from uri parts """
415 import urllib
416 uri = self.script_name + urllib.quote(self.path_info)
417 if self.query_string:
418 uri += '?' + self.query_string
419 return uri
420
421 def splitURI(self, uri):
422 """ Return path and query splited from uri
423
424 Just like CGI environment, the path is unquoted, the query is
425 not.
426 """
427 import urllib
428 if '?' in uri:
429 path, query = uri.split('?', 1)
430 else:
431 path, query = uri, ''
432 return urllib.unquote(path), query
433
434 def get_user(self):
435 for auth in self.cfg.auth:
436 the_user = auth(self)
437 if the_user: return the_user
438
439 # XXX create
440 return user.User(self)
441
442 def reset(self):
443 """ Reset request state.
444
445 Called after saving a page, before serving the updated
446 page. Solves some practical problems with request state
447 modified during saving.
448
449 """
450 # This is the content language and has nothing to do with
451 # The user interface language. The content language can change
452 # during the rendering of a page by lang macros
453 self.current_lang = self.cfg.default_lang
454
455 self._footer_fragments = {}
456 self._all_pages = None
457 # caches unique ids
458 self._page_ids = {}
459 # keeps track of pagename/heading combinations
460 # parsers should use this dict and not a local one, so that
461 # macros like TableOfContents in combination with Include
462 # can work
463 self._page_headings = {}
464
465 if hasattr(self, "_fmt_hd_counters"):
466 del self._fmt_hd_counters
467
468 def loadTheme(self, theme_name):
469 """ Load the Theme to use for this request.
470
471 @param theme_name: the name of the theme
472 @type theme_name: str
473 @returns: 0 on success, 1 if user theme could not be loaded,
474 2 if a hard fallback to modern theme was required.
475 @rtype: int
476 @return: success code
477 """
478 fallback = 0
479 if theme_name == "<default>":
480 theme_name = self.cfg.theme_default
481 Theme = wikiutil.importPlugin(self.cfg, 'theme', theme_name, 'Theme')
482 if Theme is None:
483 fallback = 1
484 Theme = wikiutil.importPlugin(self.cfg, 'theme',
485 self.cfg.theme_default, 'Theme')
486 if Theme is None:
487 fallback = 2
488 from MoinMoin.theme.modern import Theme
489 self.theme = Theme(self)
490
491 return fallback
492
493 def setContentLanguage(self, lang):
494 """ Set the content language, used for the content div
495
496 Actions that generate content in the user language, like search,
497 should set the content direction to the user language before they
498 call send_title!
499 """
500 self.content_lang = lang
501 self.current_lang = lang
502
503 def add2footer(self, key, htmlcode):
504 """ Add a named HTML fragment to the footer, after the default links
505 """
506 self._footer_fragments[key] = htmlcode
507
508 def getPragma(self, key, defval=None):
509 """ Query a pragma value (#pragma processing instruction)
510
511 Keys are not case-sensitive.
512 """
513 return self.pragma.get(key.lower(), defval)
514
515 def setPragma(self, key, value):
516 """ Set a pragma value (#pragma processing instruction)
517
518 Keys are not case-sensitive.
519 """
520 self.pragma[key.lower()] = value
521
522 def getPathinfo(self):
523 """ Return the remaining part of the URL. """
524 return self.path_info
525
526 def getScriptname(self):
527 """ Return the scriptname part of the URL ('/path/to/my.cgi'). """
528 if self.script_name == '/':
529 return ''
530 return self.script_name
531
532 def getPageNameFromQueryString(self):
533 """ Try to get pagename from the query string
534
535 Support urls like http://netloc/script/?page_name. Allow
536 solving path_info encodig problems by calling with the page
537 name as a query.
538 """
539 import urllib
540 pagename = urllib.unquote(self.query_string)
541 pagename = self.decodePagename(pagename)
542 pagename = self.normalizePagename(pagename)
543 return pagename
544
545 def getKnownActions(self):
546 """ Create a dict of avaiable actions
547
548 Return cached version if avaiable.
549
550 @rtype: dict
551 @return: dict of all known actions
552 """
553 try:
554 self.cfg._known_actions # check
555 except AttributeError:
556 from MoinMoin import wikiaction
557 # Add built in actions from wikiaction
558 actions = [name[3:] for name in wikiaction.__dict__
559 if name.startswith('do_')]
560
561 # Add plugins
562 dummy, plugins = wikiaction.getPlugins(self)
563 actions.extend(plugins)
564
565 # Add extensions
566 from MoinMoin.action import extension_actions
567 actions.extend(extension_actions)
568
569 # TODO: Use set when we require Python 2.3
570 actions = dict(zip(actions, [''] * len(actions)))
571 self.cfg._known_actions = actions
572
573 # Return a copy, so clients will not change the dict.
574 return self.cfg._known_actions.copy()
575
576 def getAvailableActions(self, page):
577 """ Get list of avaiable actions for this request
578
579 The dict does not contain actions that starts with lower
580 case. Themes use this dict to display the actions to the user.
581
582 @param page: current page, Page object
583 @rtype: dict
584 @return: dict of avaiable actions
585 """
586 if self._available_actions is None:
587 # Add actions for existing pages only, including deleted pages.
588 # Fix *OnNonExistingPage bugs.
589 if not (page.exists(includeDeleted=1) and
590 self.user.may.read(page.page_name)):
591 return []
592
593 # Filter non ui actions (starts with lower case letter)
594 actions = self.getKnownActions()
595 for key in actions.keys():
596 if key[0].islower():
597 del actions[key]
598
599 # Filter wiki excluded actions
600 for key in self.cfg.actions_excluded:
601 if key in actions:
602 del actions[key]
603
604 # Filter actions by page type, acl and user state
605 excluded = []
606 if ((page.isUnderlayPage() and not page.isStandardPage()) or
607 not self.user.may.write(page.page_name)):
608 # Prevent modification of underlay only pages, or pages
609 # the user can't write to
610 excluded = [u'RenamePage', u'DeletePage',] # AttachFile must NOT be here!
611 elif not self.user.valid:
612 # Prevent rename and delete for non registered users
613 excluded = [u'RenamePage', u'DeletePage']
614 for key in excluded:
615 if key in actions:
616 del actions[key]
617
618 self._available_actions = actions
619
620 # Return a copy, so clients will not change the dict.
621 return self._available_actions.copy()
622
623 def redirectedOutput(self, function, *args, **kw):
624 """ Redirect output during function, return redirected output """
625 import StringIO
626 buffer = StringIO.StringIO()
627 self.redirect(buffer)
628 try:
629 function(*args, **kw)
630 finally:
631 self.redirect()
632 text = buffer.getvalue()
633 buffer.close()
634 return text
635
636 def redirect(self, file=None):
637 """ Redirect output to file, or restore saved output """
638 if file:
639 self.writestack.append(self.write)
640 self.write = file.write
641 else:
642 self.write = self.writestack.pop()
643
644 def reset_output(self):
645 """ restore default output method
646 destroy output stack
647 (useful for error messages)
648 """
649 if self.writestack:
650 self.write = self.writestack[0]
651 self.writestack = []
652
653 def log(self, msg):
654 """ Log to stderr, which may be error.log """
655 msg = msg.strip()
656 # Encode unicode msg
657 if isinstance(msg, unicode):
658 msg = msg.encode(config.charset)
659 # Add time stamp
660 msg = '[%s] %s\n' % (time.asctime(), msg)
661 sys.stderr.write(msg)
662
663 def write(self, *data):
664 """ Write to output stream.
665 """
666 raise NotImplementedError
667
668 def encode(self, data):
669 """ encode data (can be both unicode strings and strings),
670 preparing for a single write()
671 """
672 wd = []
673 for d in data:
674 try:
675 if isinstance(d, unicode):
676 # if we are REALLY sure, we can use "strict"
677 d = d.encode(config.charset, 'replace')
678 wd.append(d)
679 except UnicodeError:
680 print >>sys.stderr, "Unicode error on: %s" % repr(d)
681 return ''.join(wd)
682
683 def decodePagename(self, name):
684 """ Decode path, possibly using non ascii characters
685
686 Does not change the name, only decode to Unicode.
687
688 First split the path to pages, then decode each one. This enables
689 us to decode one page using config.charset and another using
690 utf-8. This situation happens when you try to add to a name of
691 an existing page.
692
693 See http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.2.1
694
695 @param name: page name, string
696 @rtype: unicode
697 @return decoded page name
698 """
699 # Split to pages and decode each one
700 pages = name.split('/')
701 decoded = []
702 for page in pages:
703 # Recode from utf-8 into config charset. If the path
704 # contains user typed parts, they are encoded using 'utf-8'.
705 if config.charset != 'utf-8':
706 try:
707 page = unicode(page, 'utf-8', 'strict')
708 # Fit data into config.charset, replacing what won't
709 # fit. Better have few "?" in the name than crash.
710 page = page.encode(config.charset, 'replace')
711 except UnicodeError:
712 pass
713
714 # Decode from config.charset, replacing what can't be decoded.
715 page = unicode(page, config.charset, 'replace')
716 decoded.append(page)
717
718 # Assemble decoded parts
719 name = u'/'.join(decoded)
720 return name
721
722 def normalizePagename(self, name):
723 """ Normalize page name
724
725 Convert '_' to spaces - allows using nice URLs with spaces, with no
726 need to quote.
727
728 Prevent creating page names with invisible characters or funny
729 whitespace that might confuse the users or abuse the wiki, or
730 just does not make sense.
731
732 Restrict even more group pages, so they can be used inside acl
733 lines.
734
735 @param name: page name, unicode
736 @rtype: unicode
737 @return: decoded and sanitized page name
738 """
739 # Replace underscores with spaces
740 name = name.replace(u'_', u' ')
741
742 # Strip invalid characters
743 name = config.page_invalid_chars_regex.sub(u'', name)
744
745 # Split to pages and normalize each one
746 pages = name.split(u'/')
747 normalized = []
748 for page in pages:
749 # Ignore empty or whitespace only pages
750 if not page or page.isspace():
751 continue
752
753 # Cleanup group pages.
754 # Strip non alpha numeric characters, keep white space
755 if wikiutil.isGroupPage(self, page):
756 page = u''.join([c for c in page
757 if c.isalnum() or c.isspace()])
758
759 # Normalize white space. Each name can contain multiple
760 # words separated with only one space. Split handle all
761 # 30 unicode spaces (isspace() == True)
762 page = u' '.join(page.split())
763
764 normalized.append(page)
765
766 # Assemble components into full pagename
767 name = u'/'.join(normalized)
768 return name
769
770 def read(self, n):
771 """ Read n bytes from input stream.
772 """
773 raise NotImplementedError
774
775 def flush(self):
776 """ Flush output stream.
777 """
778 raise NotImplementedError
779
780 def isForbidden(self):
781 """ check for web spiders and refuse anything except viewing """
782 forbidden = 0
783 # we do not have a parsed query string here
784 # so we can just do simple matching
785 if ((self.query_string != '' or self.request_method != 'GET') and
786 self.query_string != 'action=rss_rc' and not
787 # allow spiders to get attachments and do 'show'
788 (self.query_string.find('action=AttachFile') >= 0 and self.query_string.find('do=get') >= 0) and not
789 (self.query_string.find('action=show') >= 0)
790 ):
791 from MoinMoin.util import web
792 forbidden = web.isSpiderAgent(self)
793
794 if not forbidden and self.cfg.hosts_deny:
795 ip = self.remote_addr
796 for host in self.cfg.hosts_deny:
797 if ip == host or host[-1] == '.' and ip.startswith(host):
798 forbidden = 1
799 break
800 return forbidden
801
802 def setup_args(self, form=None):
803 """ Return args dict
804
805 In POST request, invoke _setup_args_from_cgi_form to handle
806 possible file uploads. For other request simply parse the query
807 string.
808
809 Warning: calling with a form might fail, depending on the type
810 of the request! Only the request know which kind of form it can
811 handle.
812
813 TODO: The form argument should be removed in 1.5.
814 """
815 if form is not None or self.request_method == 'POST':
816 return self._setup_args_from_cgi_form(form)
817 args = cgi.parse_qs(self.query_string, keep_blank_values=1)
818 return self.decodeArgs(args)
819
820 def _setup_args_from_cgi_form(self, form=None):
821 """ Return args dict from a FieldStorage
822
823 Create the args from a standard cgi.FieldStorage or from given
824 form. Each key contain a list of values.
825
826 @keyword form: a cgi.FieldStorage
827 @rtype: dict
828 @return dict with form keys, each contains a list of values
829 """
830 if form is None:
831 form = cgi.FieldStorage()
832
833 args = {}
834 for key in form:
835 values = form[key]
836 if not isinstance(values, list):
837 values = [values]
838 fixedResult = []
839 for item in values:
840 fixedResult.append(item.value)
841 if isinstance(item, cgi.FieldStorage) and item.filename:
842 # Save upload file name in a separate key
843 args[key + '__filename__'] = item.filename
844 args[key] = fixedResult
845
846 return self.decodeArgs(args)
847
848 def decodeArgs(self, args):
849 """ Decode args dict
850
851 Decoding is done in a separate path because it is reused by
852 other methods and sub classes.
853 """
854 decode = wikiutil.decodeUserInput
855 result = {}
856 for key in args:
857 if key + '__filename__' in args:
858 # Copy file data as is
859 result[key] = args[key]
860 elif key.endswith('__filename__'):
861 result[key] = decode(args[key], self.decode_charsets)
862 else:
863 result[key] = [decode(value, self.decode_charsets)
864 for value in args[key]]
865 return result
866
867 def getBaseURL(self):
868 """ Return a fully qualified URL to this script. """
869 return self.getQualifiedURL(self.getScriptname())
870
871 def getQualifiedURL(self, uri=''):
872 """ Return an absolute URL starting with schema and host.
873
874 Already qualified urls are returned unchanged.
875
876 @param uri: server rootted uri e.g /scriptname/pagename. It
877 must start with a slash. Must be ascii and url encoded.
878 """
879 import urlparse
880 scheme = urlparse.urlparse(uri)[0]
881 if scheme:
882 return uri
883
884 schema = ('http', 'https')[self.is_ssl]
885 result = "%s://%s%s" % (schema, self.http_host, uri)
886
887 # This might break qualified urls in redirects!
888 # e.g. mapping 'http://netloc' -> '/'
889 return wikiutil.mapURL(self, result)
890
891 def getUserAgent(self):
892 """ Get the user agent. """
893 return self.http_user_agent
894
895 def makeForbidden(self):
896 self.forbidden = True
897 self.http_headers([
898 'Status: 403 FORBIDDEN',
899 'Content-Type: text/plain'
900 ])
901 self.write('You are not allowed to access this!\r\n')
902 self.setResponseCode(403)
903
904 def run(self):
905 # __init__ may have failed
906 if self.failed or self.forbidden:
907 return self.finish()
908
909 if self.isForbidden():
910 self.makeForbidden()
911 if self.forbidden:
912 return self.finish()
913
914 self.open_logs()
915 _ = self.getText
916 self.clock.start('run')
917
918 # Imports
919 from MoinMoin.Page import Page
920
921 if self.query_string == 'action=xmlrpc':
922 from MoinMoin.wikirpc import xmlrpc
923 xmlrpc(self)
924 return self.finish()
925
926 if self.query_string == 'action=xmlrpc2':
927 from MoinMoin.wikirpc import xmlrpc2
928 xmlrpc2(self)
929 return self.finish()
930
931 # parse request data
932 try:
933 self.args = self.setup_args()
934 self.form = self.args
935 action = self.form.get('action',[None])[0]
936
937 # Get pagename
938 # The last component in path_info is the page name, if any
939 path = self.getPathinfo()
940 if path.startswith('/'):
941 pagename = self.normalizePagename(path)
942 else:
943 pagename = None
944 except: # catch and print any exception
945 self.reset_output()
946 self.http_headers()
947 self.print_exception()
948 return self.finish()
949
950 try:
951 # Handle request. We have these options:
952
953 # 1. If user has a bad user name, delete its bad cookie and
954 # send him to UserPreferences to make a new account.
955 if not user.isValidName(self, self.user.name):
956 msg = _("""Invalid user name {{{'%s'}}}.
957 Name may contain any Unicode alpha numeric character, with optional one
958 space between words. Group page name is not allowed.""") % self.user.name
959 self.deleteCookie()
960 page = wikiutil.getSysPage(self, 'UserPreferences')
961 page.send_page(self, msg=msg)
962
963 # 2. Or jump to page where user left off
964 elif not pagename and not action and self.user.remember_last_visit:
965 pagetrail = self.user.getTrail()
966 if pagetrail:
967 # Redirect to last page visited
968 if ":" in pagetrail[-1]:
969 wikitag, wikiurl, wikitail, error = wikiutil.resolve_wiki(self, pagetrail[-1])
970 url = wikiurl + wikitail
971 else:
972 url = Page(self, pagetrail[-1]).url(self)
973 else:
974 # Or to localized FrontPage
975 url = wikiutil.getFrontPage(self).url(self)
976 self.http_redirect(url)
977 return self.finish()
978
979 # 3. Or save drawing
980 elif (self.form.has_key('filepath') and
981 self.form.has_key('noredirect')):
982 # looks like user wants to save a drawing
983 from MoinMoin.action.AttachFile import execute
984 # TODO: what if pagename is None?
985 execute(pagename, self)
986 raise MoinMoinNoFooter
987
988 # 4. Or handle action
989 elif action:
990 # Use localized FrontPage if pagename is empty
991 if not pagename:
992 self.page = wikiutil.getFrontPage(self)
993 else:
994 self.page = Page(self, pagename)
995
996 # Complain about unknown actions
997 if not action in self.getKnownActions():
998 self.http_headers()
999 self.write(u'<html><body><h1>Unknown action %s</h1></body>' % wikiutil.escape(action))
1000
1001 # Disallow non available actions
1002 elif (action[0].isupper() and
1003 not action in self.getAvailableActions(self.page)):
1004 # Send page with error
1005 msg = _("You are not allowed to do %s on this page.") % wikiutil.escape(action)
1006 if not self.user.valid:
1007 # Suggest non valid user to login
1008 login = wikiutil.getSysPage(self, 'UserPreferences')
1009 login = login.link_to(self, _('Login'))
1010 msg += _(" %s and try again.", formatted=0) % login
1011 self.page.send_page(self, msg=msg)
1012
1013 # Try action
1014 else:
1015 from MoinMoin.wikiaction import getHandler
1016 handler = getHandler(self, action)
1017 handler(self.page.page_name, self)
1018
1019 # 5. Or redirect to another page
1020 elif self.form.has_key('goto'):
1021 self.http_redirect(Page(self, self.form['goto'][0]).url(self))
1022 return self.finish()
1023
1024 # 6. Or (at last) visit pagename
1025 else:
1026 if not pagename and self.query_string:
1027 pagename = self.getPageNameFromQueryString()
1028 # pagename could be empty after normalization e.g. '///' -> ''
1029 if not pagename:
1030 pagename = wikiutil.getFrontPage(self).page_name
1031
1032 # Visit pagename
1033 self.page = Page(self, pagename)
1034 self.page.send_page(self, count_hit=1)
1035
1036 # generate page footer (actions that do not want this footer
1037 # use raise util.MoinMoinNoFooter to break out of the
1038 # default execution path, see the "except MoinMoinNoFooter"
1039 # below)
1040
1041 self.clock.stop('run')
1042 self.clock.stop('total')
1043
1044 # Close html code
1045 if not self.no_closing_html_code:
1046 if (self.cfg.show_timings and
1047 self.form.get('action', [None])[0] != 'print'):
1048 self.write('<ul id="timings">\n')
1049 for t in self.clock.dump():
1050 self.write('<li>%s</li>\n' % t)
1051 self.write('</ul>\n')
1052
1053 self.write('</body>\n</html>\n\n')
1054
1055 except MoinMoinNoFooter:
1056 pass
1057
1058 except MoinMoin.error.FatalError, err:
1059 self.fail(err)
1060 return self.finish()
1061
1062 except:
1063 # Catch and print any exception
1064 saved_exc = sys.exc_info()
1065 self.reset_output()
1066
1067 # Send 500 error code
1068 self.http_headers(['Status: 500 MoinMoin Internal Error'])
1069 self.setResponseCode(500)
1070 self.http_headers()
1071
1072 self.write(u"\n<!-- ERROR REPORT FOLLOWS -->\n")
1073 try:
1074 from MoinMoin.support import cgitb
1075 except:
1076 # no cgitb, for whatever reason
1077 self.print_exception(*saved_exc)
1078 else:
1079 try:
1080 cgitb.Hook(file=self).handle(saved_exc)
1081 # was: cgitb.handler()
1082 except:
1083 self.print_exception(*saved_exc)
1084 self.write("\n\n<hr>\n")
1085 self.write("<p><strong>Additionally, cgitb raised this exception:</strong></p>\n")
1086 self.print_exception()
1087 del saved_exc
1088
1089 return self.finish()
1090
1091 def http_redirect(self, url):
1092 """ Redirect to a fully qualified, or server-rooted URL
1093
1094 @param url: relative or absolute url, ascii using url encoding.
1095 """
1096 url = self.getQualifiedURL(url)
1097 self.http_headers(["Status: 302", "Location: %s" % url])
1098
1099 def setHttpHeader(self, header):
1100 """ Save header for later send. """
1101 self.user_headers.append(header)
1102
1103 def setResponseCode(self, code, message=None):
1104 pass
1105
1106 def fail(self, err):
1107 """ Fail with nice error message when we can't continue
1108
1109 Log the error, then try to print nice error message. Send 500
1110 status code with the error name. Reference:
1111 http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1.1
1112
1113 @param err: MoinMoin.error.FatalError instance or subclass.
1114 """
1115 self.failed = 1 # save state for self.run()
1116 self.log(err.asLog())
1117 self.http_headers(['Status: 500 %(name)s' % err])
1118 self.setResponseCode(500)
1119 self.write(err.asHTML())
1120
1121 def print_exception(self, type=None, value=None, tb=None, limit=None):
1122 if type is None:
1123 type, value, tb = sys.exc_info()
1124 import traceback
1125 self.write("<h2>request.print_exception handler</h2>\n")
1126 self.write("<h3>Traceback (most recent call last):</h3>\n")
1127 list = traceback.format_tb(tb, limit) + \
1128 traceback.format_exception_only(type, value)
1129 self.write("<pre>%s<strong>%s</strong></pre>\n" % (
1130 wikiutil.escape("".join(list[:-1])),
1131 wikiutil.escape(list[-1]),))
1132 del tb
1133
1134 def open_logs(self):
1135 pass
1136
1137 def makeUniqueID(self, base):
1138 """
1139 Generates a unique ID using a given base name. Appends a
1140 running count to the base.
1141
1142 @param base: the base of the id
1143 @type base: unicode
1144
1145 @returns: an unique id
1146 @rtype: unicode
1147 """
1148 if not isinstance(base, unicode):
1149 base = unicode(str(base), 'ascii', 'ignore')
1150 count = self._page_ids.get(base, -1) + 1
1151 self._page_ids[base] = count
1152 if count == 0:
1153 return base
1154 return u'%s_%04d' % (base, count)
1155
1156 def httpDate(self, when=None, rfc='1123'):
1157 """ Returns http date string, according to rfc2068
1158
1159 See http://www.cse.ohio-state.edu/cgi-bin/rfc/rfc2068.html#sec-3.3
1160
1161 A http 1.1 server should use only rfc1123 date, but cookie's
1162 "expires" field should use the older obsolete rfc850 date.
1163
1164 Note: we can not use strftime() because that honors the locale
1165 and rfc2822 requires english day and month names.
1166
1167 We can not use email.Utils.formatdate because it formats the
1168 zone as '-0000' instead of 'GMT', and creates only rfc1123
1169 dates. This is a modified version of email.Utils.formatdate
1170 from Python 2.4.
1171
1172 @param when: seconds from epoch, as returned by time.time()
1173 @param rfc: conform to rfc ('1123' or '850')
1174 @rtype: string
1175 @return: http date conforming to rfc1123 or rfc850
1176 """
1177 if when is None:
1178 when = time.time()
1179 now = time.gmtime(when)
1180 month = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul',
1181 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][now.tm_mon - 1]
1182 if rfc == '1123':
1183 day = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'][now.tm_wday]
1184 date = '%02d %s %04d' % (now.tm_mday, month, now.tm_year)
1185 elif rfc == '850':
1186 day = ["Monday", "Tuesday", "Wednesday", "Thursday",
1187 "Friday", "Saturday", "Sunday"][now.tm_wday]
1188 date = '%02d-%s-%s' % (now.tm_mday, month, str(now.tm_year)[-2:])
1189 else:
1190 raise ValueError("Invalid rfc value: %s" % rfc)
1191
1192 return '%s, %s %02d:%02d:%02d GMT' % (day, date, now.tm_hour,
1193 now.tm_min, now.tm_sec)
1194
1195 def disableHttpCaching(self):
1196 """ Prevent caching of pages that should not be cached
1197
1198 This is important to prevent caches break acl by providing one
1199 user pages meant to be seen only by another user, when both users
1200 share the same caching proxy.
1201 """
1202 # Run only once
1203 if hasattr(self, 'http_caching_disabled'):
1204 return
1205 self.http_caching_disabled = 1
1206
1207 # Set Cache control header for http 1.1 caches
1208 # See http://www.cse.ohio-state.edu/cgi-bin/rfc/rfc2109.html#sec-4.2.3
1209 # and http://www.cse.ohio-state.edu/cgi-bin/rfc/rfc2068.html#sec-14.9
1210 self.setHttpHeader('Cache-Control: no-cache="set-cookie"')
1211 self.setHttpHeader('Cache-Control: private')
1212 self.setHttpHeader('Cache-Control: max-age=0')
1213
1214 # Set Expires for http 1.0 caches (does not support Cache-Control)
1215 yearago = time.time() - (3600 * 24 * 365)
1216 self.setHttpHeader('Expires: %s' % self.httpDate(when=yearago))
1217
1218 # Set Pragma for http 1.0 caches
1219 # See http://www.cse.ohio-state.edu/cgi-bin/rfc/rfc2068.html#sec-14.32
1220 self.setHttpHeader('Pragma: no-cache')
1221
1222 def setCookie(self):
1223 """ Set cookie for the current user
1224
1225 cfg.cookie_lifetime and the user 'remember_me' setting set the
1226 lifetime of the cookie. lifetime in int hours, see table:
1227
1228 value cookie lifetime
1229 ----------------------------------------------------------------
1230 = 0 forever, ignoring user 'remember_me' setting
1231 > 0 n hours, or forever if user checked 'remember_me'
1232 < 0 -n hours, ignoring user 'remember_me' setting
1233
1234 TODO: do we really need this cookie_lifetime setting?
1235 """
1236 # Calculate cookie maxage and expires
1237 lifetime = int(self.cfg.cookie_lifetime) * 3600
1238 forever = 10*365*24*3600 # 10 years
1239 now = time.time()
1240 if not lifetime:
1241 maxage = forever
1242 elif lifetime > 0:
1243 if self.user.remember_me:
1244 maxage = forever
1245 else:
1246 maxage = lifetime
1247 elif lifetime < 0:
1248 maxage = (-lifetime)
1249 expires = now + maxage
1250
1251 # Set the cookie
1252 from Cookie import SimpleCookie
1253 c = SimpleCookie()
1254 c['MOIN_ID'] = self.user.id
1255 c['MOIN_ID']['max-age'] = maxage
1256 if self.cfg.cookie_domain:
1257 c['MOIN_ID']['domain'] = self.cfg.cookie_domain
1258 if self.cfg.cookie_path:
1259 c['MOIN_ID']['path'] = self.cfg.cookie_path
1260 else:
1261 c['MOIN_ID']['path'] = self.getScriptname()
1262 # Set expires for older clients
1263 c['MOIN_ID']['expires'] = self.httpDate(when=expires, rfc='850')
1264 self.setHttpHeader(c.output())
1265
1266 # Update the saved cookie, so other code works with new setup
1267 self.saved_cookie = c.output()
1268
1269 # IMPORTANT: Prevent caching of current page and cookie
1270 self.disableHttpCaching()
1271
1272 def deleteCookie(self):
1273 """ Delete the user cookie by sending expired cookie with null value
1274
1275 According to http://www.cse.ohio-state.edu/cgi-bin/rfc/rfc2109.html#sec-4.2.2
1276 Deleted cookie should have Max-Age=0. We also have expires
1277 attribute, which is probably needed for older browsers.
1278
1279 Finally, delete the saved cookie and create a new user based on
1280 the new settings.
1281 """
1282 # Set cookie
1283 from Cookie import SimpleCookie
1284 c = SimpleCookie()
1285 c['MOIN_ID'] = ''
1286 if self.cfg.cookie_domain:
1287 c['MOIN_ID']['domain'] = self.cfg.cookie_domain
1288 c['MOIN_ID']['path'] = self.getScriptname()
1289 c['MOIN_ID']['max-age'] = 0
1290 # Set expires to one year ago for older clients
1291 yearago = time.time() - (3600 * 24 * 365)
1292 c['MOIN_ID']['expires'] = self.httpDate(when=yearago, rfc='850')
1293 self.setHttpHeader(c.output())
1294
1295 # Update saved cookie and set new unregistered user
1296 self.saved_cookie = ''
1297 self.user = user.User(self)
1298
1299 # IMPORTANT: Prevent caching of current page and cookie
1300 self.disableHttpCaching()
1301
1302 def finish(self):
1303 """ General cleanup on end of request
1304
1305 Delete circular references - all object that we create using
1306 self.name = class(self)
1307 This helps Python to collect these objects and keep our
1308 memory footprint lower
1309 """
1310 try:
1311 del self.user
1312 del self.theme
1313 del self.dicts
1314 except:
1315 pass
1316
1317 # ------------------------------------------------------------------
1318 # Debug
1319
1320 def debugEnvironment(self, env):
1321 """ Environment debugging aid """
1322 # Keep this one name per line so its easy to comment stuff
1323 names = [
1324 # 'http_accept_language',
1325 # 'http_host',
1326 # 'http_referer',
1327 # 'http_user_agent',
1328 # 'is_ssl',
1329 'path_info',
1330 'query_string',
1331 # 'remote_addr',
1332 'request_method',
1333 # 'request_uri',
1334 # 'saved_cookie',
1335 'script_name',
1336 # 'server_name',
1337 # 'server_port',
1338 ]
1339 names.sort()
1340 attributes = []
1341 for name in names:
1342 attributes.append(' %s = %r\n' % (name,
1343 getattr(self, name, None)))
1344 attributes = ''.join(attributes)
1345
1346 environment = []
1347 names = env.keys()
1348 names.sort()
1349 for key in names:
1350 environment.append(' %s = %r\n' % (key, env[key]))
1351 environment = ''.join(environment)
1352
1353 data = '\nRequest Attributes\n%s\nEnviroment\n%s' % (attributes,
1354 environment)
1355 f = open('/tmp/env.log','a')
1356 try:
1357 f.write(data)
1358 finally:
1359 f.close()
1360
1361
1362 # CGI ---------------------------------------------------------------
1363
1364 class RequestCGI(RequestBase):
1365 """ specialized on CGI requests """
1366
1367 def __init__(self, properties={}):
1368 self.open_logs()
1369 try:
1370 self._setup_vars_from_std_env(os.environ)
1371 RequestBase.__init__(self, properties)
1372
1373 # force input/output to binary
1374 if sys.platform == "win32":
1375 import msvcrt
1376 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
1377 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
1378
1379 except error.FatalError, err:
1380 self.fail(err)
1381
1382 def open_logs_late(self):
1383 # create log file for catching stderr output
1384 if not self.opened_logs:
1385 sys.stderr = open(os.path.join(self.cfg.data_dir, 'error.log'), 'at')
1386 self.opened_logs = 1
1387
1388 def open_logs(self):
1389 # create log file for catching stderr output
1390 sys.stderr = open('/org/moin_tw/moin--arcelor--1.3/server/data/error.log', 'at')
1391 sys.stderr.write("Log opened\n")
1392 self.opened_logs = 1
1393
1394 def read(self, n=None):
1395 """ Read from input stream.
1396 """
1397 if n is None:
1398 return sys.stdin.read()
1399 else:
1400 return sys.stdin.read(n)
1401
1402 def write(self, *data):
1403 """ Write to output stream.
1404 """
1405 sys.stdout.write(self.encode(data))
1406
1407 def flush(self):
1408 sys.stdout.flush()
1409
1410 def finish(self):
1411 RequestBase.finish(self)
1412 # flush the output, ignore errors caused by the user closing the socket
1413 try:
1414 sys.stdout.flush()
1415 except IOError, ex:
1416 import errno
1417 if ex.errno != errno.EPIPE: raise
1418
1419 # Headers ----------------------------------------------------------
1420
1421 def http_headers(self, more_headers=[]):
1422 # Send only once
1423 if getattr(self, 'sent_headers', None):
1424 return
1425
1426 self.sent_headers = 1
1427 have_ct = 0
1428
1429 # send http headers
1430 for header in more_headers + getattr(self, 'user_headers', []):
1431 if header.lower().startswith("content-type:"):
1432 # don't send content-type multiple times!
1433 if have_ct: continue
1434 have_ct = 1
1435 if type(header) is unicode:
1436 header = header.encode('ascii')
1437 self.write("%s\r\n" % header)
1438
1439 if not have_ct:
1440 self.write("Content-type: text/html;charset=%s\r\n" % config.charset)
1441
1442 self.write('\r\n')
1443
1444 #from pprint import pformat
1445 #sys.stderr.write(pformat(more_headers))
1446 #sys.stderr.write(pformat(self.user_headers))
1447
1448
1449 # Twisted -----------------------------------------------------------
1450
1451 class RequestTwisted(RequestBase):
1452 """ specialized on Twisted requests """
1453
1454 def __init__(self, twistedRequest, pagename, reactor, properties={}):
1455 try:
1456 self.twistd = twistedRequest
1457 self.reactor = reactor
1458
1459 # Copy headers
1460 self.http_accept_language = self.twistd.getHeader('Accept-Language')
1461 self.saved_cookie = self.twistd.getHeader('Cookie')
1462 self.http_user_agent = self.twistd.getHeader('User-Agent')
1463
1464 # Copy values from twisted request
1465 self.server_protocol = self.twistd.clientproto
1466 self.server_name = self.twistd.getRequestHostname().split(':')[0]
1467 self.server_port = str(self.twistd.getHost()[2])
1468 self.is_ssl = self.twistd.isSecure()
1469 self.path_info = '/' + '/'.join([pagename] + self.twistd.postpath)
1470 self.request_method = self.twistd.method
1471 self.remote_host = self.twistd.getClient()
1472 self.remote_addr = self.twistd.getClientIP()
1473 self.request_uri = self.twistd.uri
1474 self.script_name = "/" + '/'.join(self.twistd.prepath[:-1])
1475
1476 # Values that need more work
1477 self.query_string = self.splitURI(self.twistd.uri)[1]
1478 self.setHttpReferer(self.twistd.getHeader('Referer'))
1479 self.setHost()
1480 self.setURL(self.twistd.getAllHeaders())
1481
1482 ##self.debugEnvironment(twistedRequest.getAllHeaders())
1483
1484 RequestBase.__init__(self, properties)
1485
1486 except error.FatalError, err:
1487 self.delayedError = err
1488
1489 def run(self):
1490 """ Handle delayed errors then invoke base class run """
1491 if hasattr(self, 'delayedError'):
1492 self.fail(self.delayedError)
1493 return self.finish()
1494 RequestBase.run(self)
1495
1496 def setup_args(self, form=None):
1497 """ Return args dict
1498
1499 Twisted already parsed args, including __filename__ hacking,
1500 but did not decoded the values.
1501 """
1502 return self.decodeArgs(self.twistd.args)
1503
1504 def read(self, n=None):
1505 """ Read from input stream.
1506 """
1507 # XXX why is that wrong?:
1508 #rd = self.reactor.callFromThread(self.twistd.read)
1509
1510 # XXX do we need self.reactor.callFromThread with that?
1511 # XXX if yes, why doesn't it work?
1512 self.twistd.content.seek(0, 0)
1513 if n is None:
1514 rd = self.twistd.content.read()
1515 else:
1516 rd = self.twistd.content.read(n)
1517 #print "request.RequestTwisted.read: data=\n" + str(rd)
1518 return rd
1519
1520 def write(self, *data):
1521 """ Write to output stream.
1522 """
1523 #print "request.RequestTwisted.write: data=\n" + wd
1524 self.reactor.callFromThread(self.twistd.write, self.encode(data))
1525
1526 def flush(self):
1527 pass # XXX is there a flush in twisted?
1528
1529 def finish(self):
1530 RequestBase.finish(self)
1531 self.reactor.callFromThread(self.twistd.finish)
1532
1533 def open_logs(self):
1534 return
1535 # create log file for catching stderr output
1536 if not self.opened_logs:
1537 sys.stderr = open(os.path.join(self.cfg.data_dir, 'error.log'), 'at')
1538 self.opened_logs = 1
1539
1540 # Headers ----------------------------------------------------------
1541
1542 def __setHttpHeader(self, header):
1543 if type(header) is unicode:
1544 header = header.encode('ascii')
1545 key, value = header.split(':',1)
1546 value = value.lstrip()
1547 if key.lower()=='set-cookie':
1548 key, value = value.split('=',1)
1549 self.twistd.addCookie(key, value)
1550 else:
1551 self.twistd.setHeader(key, value)
1552 #print "request.RequestTwisted.setHttpHeader: %s" % header
1553
1554 def http_headers(self, more_headers=[]):
1555 if getattr(self, 'sent_headers', None):
1556 return
1557 self.sent_headers = 1
1558 have_ct = 0
1559
1560 # set http headers
1561 for header in more_headers + getattr(self, 'user_headers', []):
1562 if header.lower().startswith("content-type:"):
1563 # don't send content-type multiple times!
1564 if have_ct: continue
1565 have_ct = 1
1566 self.__setHttpHeader(header)
1567
1568 if not have_ct:
1569 self.__setHttpHeader("Content-type: text/html;charset=%s" % config.charset)
1570
1571 def http_redirect(self, url):
1572 """ Redirect to a fully qualified, or server-rooted URL
1573
1574 @param url: relative or absolute url, ascii using url encoding.
1575 """
1576 url = self.getQualifiedURL(url)
1577 self.twistd.redirect(url)
1578 # calling finish here will send the rest of the data to the next
1579 # request. leave the finish call to run()
1580 #self.twistd.finish()
1581 raise MoinMoinNoFooter
1582
1583 def setResponseCode(self, code, message=None):
1584 self.twistd.setResponseCode(code, message)
1585
1586 # CLI ------------------------------------------------------------------
1587
1588 class RequestCLI(RequestBase):
1589 """ specialized on command line interface and script requests """
1590
1591 def __init__(self, url='CLI', pagename='', properties={}):
1592 self.saved_cookie = ''
1593 self.path_info = '/' + pagename
1594 self.query_string = ''
1595 self.remote_addr = '127.0.0.1'
1596 self.is_ssl = 0
1597 self.http_user_agent = 'CLI/Script'
1598 self.url = url
1599 self.request_method = 'GET'
1600 self.request_uri = '/' + pagename # TODO check
1601 self.http_host = 'localhost'
1602 self.http_referer = ''
1603 self.script_name = '.'
1604 RequestBase.__init__(self, properties)
1605 self.cfg.caching_formats = [] # don't spoil the cache
1606
1607 def read(self, n=None):
1608 """ Read from input stream.
1609 """
1610 if n is None:
1611 return sys.stdin.read()
1612 else:
1613 return sys.stdin.read(n)
1614
1615 def write(self, *data):
1616 """ Write to output stream.
1617 """
1618 sys.stdout.write(self.encode(data))
1619
1620 def flush(self):
1621 sys.stdout.flush()
1622
1623 def finish(self):
1624 RequestBase.finish(self)
1625 # flush the output, ignore errors caused by the user closing the socket
1626 try:
1627 sys.stdout.flush()
1628 except IOError, ex:
1629 import errno
1630 if ex.errno != errno.EPIPE: raise
1631
1632 def isForbidden(self):
1633 """ Nothing is forbidden """
1634 return 0
1635
1636 # Accessors --------------------------------------------------------
1637
1638 def getQualifiedURL(self, uri=None):
1639 """ Return a full URL starting with schema and host
1640
1641 TODO: does this create correct pages when you render wiki pages
1642 within a cli request?!
1643 """
1644 return uri
1645
1646 # Headers ----------------------------------------------------------
1647
1648 def setHttpHeader(self, header):
1649 pass
1650
1651 def http_headers(self, more_headers=[]):
1652 pass
1653
1654 def http_redirect(self, url):
1655 """ Redirect to a fully qualified, or server-rooted URL
1656
1657 TODO: Does this work for rendering redirect pages?
1658 """
1659 raise Exception("Redirect not supported for command line tools!")
1660
1661
1662 # StandAlone Server ----------------------------------------------------
1663
1664 class RequestStandAlone(RequestBase):
1665 """
1666 specialized on StandAlone Server (MoinMoin.server.standalone) requests
1667 """
1668 script_name = ''
1669
1670 def __init__(self, sa, properties={}):
1671 """
1672 @param sa: stand alone server object
1673 @param properties: ...
1674 """
1675 try:
1676 self.sareq = sa
1677 self.wfile = sa.wfile
1678 self.rfile = sa.rfile
1679 self.headers = sa.headers
1680 self.is_ssl = 0
1681
1682 # TODO: remove in 1.5
1683 #accept = []
1684 #for line in sa.headers.getallmatchingheaders('accept'):
1685 # if line[:1] in string.whitespace:
1686 # accept.append(line.strip())
1687 # else:
1688 # accept = accept + line[7:].split(',')
1689 #
1690 #env['HTTP_ACCEPT'] = ','.join(accept)
1691
1692 # Copy headers
1693 self.http_accept_language = (sa.headers.getheader('accept-language')
1694 or self.http_accept_language)
1695 self.http_user_agent = sa.headers.getheader('user-agent', '')
1696 co = filter(None, sa.headers.getheaders('cookie'))
1697 self.saved_cookie = ', '.join(co) or ''
1698
1699 # Copy rest from standalone request
1700 self.server_name = sa.server.server_name
1701 self.server_port = str(sa.server.server_port)
1702 self.request_method = sa.command
1703 self.request_uri = sa.path
1704 self.remote_addr = sa.client_address[0]
1705
1706 # Values that need more work
1707 self.path_info, self.query_string = self.splitURI(sa.path)
1708 self.setHttpReferer(sa.headers.getheader('referer'))
1709 self.setHost(sa.headers.getheader('host'))
1710 self.setURL(sa.headers)
1711
1712 # TODO: remove in 1.5
1713 # from standalone script:
1714 # XXX AUTH_TYPE
1715 # XXX REMOTE_USER
1716 # XXX REMOTE_IDENT
1717 #env['PATH_TRANSLATED'] = uqrest #self.translate_path(uqrest)
1718 #host = self.address_string()
1719 #if host != self.client_address[0]:
1720 # env['REMOTE_HOST'] = host
1721 # env['SERVER_PROTOCOL'] = self.protocol_version
1722
1723 ##self.debugEnvironment(sa.headers)
1724
1725 RequestBase.__init__(self, properties)
1726
1727 except error.FatalError, err:
1728 self.fail(err)
1729
1730 def _setup_args_from_cgi_form(self, form=None):
1731 """ Override to create standlone form """
1732 form = cgi.FieldStorage(self.rfile,
1733 headers=self.headers,
1734 environ={'REQUEST_METHOD': 'POST'})
1735 return RequestBase._setup_args_from_cgi_form(self, form)
1736
1737 def read(self, n=None):
1738 """ Read from input stream
1739
1740 Since self.rfile.read() will block, content-length will be used
1741 instead.
1742
1743 TODO: test with n > content length, or when calling several times
1744 with smaller n but total over content length.
1745 """
1746 if n is None:
1747 try:
1748 n = int(self.headers.get('content-length'))
1749 except (TypeError, ValueError):
1750 import warnings
1751 warnings.warn("calling request.read() when content-length is "
1752 "not available will block")
1753 return self.rfile.read()
1754 return self.rfile.read(n)
1755
1756 def write(self, *data):
1757 """ Write to output stream.
1758 """
1759 self.wfile.write(self.encode(data))
1760
1761 def flush(self):
1762 self.wfile.flush()
1763
1764 def finish(self):
1765 RequestBase.finish(self)
1766 self.wfile.flush()
1767
1768 # Headers ----------------------------------------------------------
1769
1770 def http_headers(self, more_headers=[]):
1771 if getattr(self, 'sent_headers', None):
1772 return
1773
1774 self.sent_headers = 1
1775 user_headers = getattr(self, 'user_headers', [])
1776
1777 # check for status header and send it
1778 our_status = 200
1779 for header in more_headers + user_headers:
1780 if header.lower().startswith("status:"):
1781 try:
1782 our_status = int(header.split(':',1)[1].strip().split(" ", 1)[0])
1783 except:
1784 pass
1785 # there should be only one!
1786 break
1787 # send response
1788 self.sareq.send_response(our_status)
1789
1790 # send http headers
1791 have_ct = 0
1792 for header in more_headers + user_headers:
1793 if type(header) is unicode:
1794 header = header.encode('ascii')
1795 if header.lower().startswith("content-type:"):
1796 # don't send content-type multiple times!
1797 if have_ct: continue
1798 have_ct = 1
1799
1800 self.write("%s\r\n" % header)
1801
1802 if not have_ct:
1803 self.write("Content-type: text/html;charset=%s\r\n" % config.charset)
1804
1805 self.write('\r\n')
1806
1807 #from pprint import pformat
1808 #sys.stderr.write(pformat(more_headers))
1809 #sys.stderr.write(pformat(self.user_headers))
1810
1811
1812 # mod_python/Apache ----------------------------------------------------
1813
1814 class RequestModPy(RequestBase):
1815 """ specialized on mod_python requests """
1816
1817 def __init__(self, req):
1818 """ Saves mod_pythons request and sets basic variables using
1819 the req.subprocess_env, cause this provides a standard
1820 way to access the values we need here.
1821
1822 @param req: the mod_python request instance
1823 """
1824 try:
1825 # flags if headers sent out contained content-type or status
1826 self._have_ct = 0
1827 self._have_status = 0
1828
1829 req.add_common_vars()
1830 self.mpyreq = req
1831 # some mod_python 2.7.X has no get method for table objects,
1832 # so we make a real dict out of it first.
1833 if not hasattr(req.subprocess_env,'get'):
1834 env=dict(req.subprocess_env)
1835 else:
1836 env=req.subprocess_env
1837 self._setup_vars_from_std_env(env)
1838 RequestBase.__init__(self)
1839
1840 except error.FatalError, err:
1841 self.fail(err)
1842
1843 def rewriteURI(self, env):
1844 """ Use PythonOption directive to rewrite URI
1845
1846 This is needed when using Apache 1 or other server which does
1847 not support adding custom headers per request. With mod python we
1848 can the PythonOption directive:
1849
1850 <Location /url/to/mywiki/>
1851 PythonOption X-Moin-Location /url/to/mywiki/
1852 </location>
1853 """
1854 # Be compatible with release 1.3.5 "Location" option
1855 # TODO: Remove in later release, we should have one option only.
1856 old_location = 'Location'
1857 options = self.mpyreq.get_options()
1858 location = options.get(self.moin_location) or options.get(old_location)
1859 if location:
1860 env[self.moin_location] = location
1861 RequestBase.rewriteURI(self, env)
1862
1863 def _setup_args_from_cgi_form(self, form=None):
1864 """ Override to use mod_python.util.FieldStorage
1865
1866 Its little different from cgi.FieldStorage, so we need to
1867 duplicate the conversion code.
1868 """
1869 from mod_python import util
1870 if form is None:
1871 form = util.FieldStorage(self.mpyreq)
1872
1873 args = {}
1874 for key in form.keys():
1875 values = form[key]
1876 if not isinstance(values, list):
1877 values = [values]
1878 fixedResult = []
1879
1880 if not isinstance(values, list):
1881 values = [values]
1882 fixedResult = []
1883 for item in values:
1884 # Remember filenames with a name hack
1885 if hasattr(item, 'filename') and item.filename:
1886 args[key + '__filename__'] = item.filename
1887 # mod_python 2.7 might return strings instead of Field
1888 # objects.
1889 if hasattr(item, 'value'):
1890 item = item.value
1891 fixedResult.append(item)
1892 args[key] = fixedResult
1893
1894 return self.decodeArgs(args)
1895
1896 def run(self, req):
1897 """ mod_python calls this with its request object. We don't
1898 need it cause its already passed to __init__. So ignore
1899 it and just return RequestBase.run.
1900
1901 @param req: the mod_python request instance
1902 """
1903 return RequestBase.run(self)
1904
1905 def setup_args(self, form=None):
1906 return {}
1907
1908 def read(self, n=None):
1909 """ Read from input stream.
1910 """
1911 if n is None:
1912 return self.mpyreq.read()
1913 else:
1914 return self.mpyreq.read(n)
1915
1916 def write(self, *data):
1917 """ Write to output stream.
1918 """
1919 self.mpyreq.write(self.encode(data))
1920
1921 def flush(self):
1922 """ We can't flush it, so do nothing.
1923 """
1924 pass
1925
1926 def finish(self):
1927 """ Just return apache.OK. Status is set in req.status.
1928 """
1929 RequestBase.finish(self)
1930 # is it possible that we need to return something else here?
1931 from mod_python import apache
1932 return apache.OK
1933
1934 # Headers ----------------------------------------------------------
1935
1936 def setHttpHeader(self, header):
1937 """ Filters out content-type and status to set them directly
1938 in the mod_python request. Rest is put into the headers_out
1939 member of the mod_python request.
1940
1941 @param header: string, containing valid HTTP header.
1942 """
1943 if type(header) is unicode:
1944 header = header.encode('ascii')
1945 key, value = header.split(':',1)
1946 value = value.lstrip()
1947 if key.lower() == 'content-type':
1948 # save content-type for http_headers
1949 if not self._have_ct:
1950 # we only use the first content-type!
1951 self.mpyreq.content_type = value
1952 self._have_ct = 1
1953 elif key.lower() == 'status':
1954 # save status for finish
1955 try:
1956 self.mpyreq.status = int(value.split(' ',1)[0])
1957 except:
1958 pass
1959 else:
1960 self._have_status = 1
1961 else:
1962 # this is a header we sent out
1963 self.mpyreq.headers_out[key]=value
1964
1965 def http_headers(self, more_headers=[]):
1966 """ Sends out headers and possibly sets default content-type
1967 and status.
1968
1969 @keyword more_headers: list of strings, defaults to []
1970 """
1971 for header in more_headers + getattr(self, 'user_headers', []):
1972 self.setHttpHeader(header)
1973 # if we don't had an content-type header, set text/html
1974 if self._have_ct == 0:
1975 self.mpyreq.content_type = "text/html;charset=%s" % config.charset
1976 # if we don't had a status header, set 200
1977 if self._have_status == 0:
1978 self.mpyreq.status = 200
1979 # this is for mod_python 2.7.X, for 3.X it's a NOP
1980 self.mpyreq.send_http_header()
1981
1982 # FastCGI -----------------------------------------------------------
1983
1984 class RequestFastCGI(RequestBase):
1985 """ specialized on FastCGI requests """
1986
1987 def __init__(self, fcgRequest, env, form, properties={}):
1988 """ Initializes variables from FastCGI environment and saves
1989 FastCGI request and form for further use.
1990
1991 @param fcgRequest: the FastCGI request instance.
1992 @param env: environment passed by FastCGI.
1993 @param form: FieldStorage passed by FastCGI.
1994 """
1995 try:
1996 self.fcgreq = fcgRequest
1997 self.fcgenv = env
1998 self.fcgform = form
1999 self._setup_vars_from_std_env(env)
2000 RequestBase.__init__(self, properties)
2001
2002 except error.FatalError, err:
2003 self.fail(err)
2004
2005 def _setup_args_from_cgi_form(self, form=None):
2006 """ Override to use FastCGI form """
2007 if form is None:
2008 form = self.fcgform
2009 return RequestBase._setup_args_from_cgi_form(self, form)
2010
2011 def read(self, n=None):
2012 """ Read from input stream.
2013 """
2014 if n is None:
2015 return self.fcgreq.stdin.read()
2016 else:
2017 return self.fcgreq.stdin.read(n)
2018
2019 def write(self, *data):
2020 """ Write to output stream.
2021 """
2022 self.fcgreq.out.write(self.encode(data))
2023
2024 def flush(self):
2025 """ Flush output stream.
2026 """
2027 self.fcgreq.flush_out()
2028
2029 def finish(self):
2030 """ Call finish method of FastCGI request to finish handling
2031 of this request.
2032 """
2033 RequestBase.finish(self)
2034 self.fcgreq.finish()
2035
2036 # Headers ----------------------------------------------------------
2037
2038 def http_headers(self, more_headers=[]):
2039 """ Send out HTTP headers. Possibly set a default content-type.
2040 """
2041 if getattr(self, 'sent_headers', None):
2042 return
2043 self.sent_headers = 1
2044 have_ct = 0
2045
2046 # send http headers
2047 for header in more_headers + getattr(self, 'user_headers', []):
2048 if type(header) is unicode:
2049 header = header.encode('ascii')
2050 if header.lower().startswith("content-type:"):
2051 # don't send content-type multiple times!
2052 if have_ct: continue
2053 have_ct = 1
2054 self.write("%s\r\n" % header)
2055
2056 if not have_ct:
2057 self.write("Content-type: text/html;charset=%s\r\n" % config.charset)
2058
2059 self.write('\r\n')
2060
2061 #from pprint import pformat
2062 #sys.stderr.write(pformat(more_headers))
2063 #sys.stderr.write(pformat(self.user_headers))
2064
2065 # WSGI --------------------------------------------------------------
2066
2067 class RequestWSGI(RequestBase):
2068 def __init__(self, env):
2069 try:
2070 self.env = env
2071 self.hasContentType = False
2072
2073 self.stdin = env['wsgi.input']
2074 self.stdout = StringIO()
2075
2076 self.status = '200 OK'
2077 self.headers = []
2078
2079 self._setup_vars_from_std_env(env)
2080 RequestBase.__init__(self, {})
2081
2082 except error.FatalError, err:
2083 self.fail(err)
2084
2085 def setup_args(self, form=None):
2086 if form is None:
2087 form = cgi.FieldStorage(fp=self.stdin, environ=self.env, keep_blank_values=1)
2088 return self._setup_args_from_cgi_form(form)
2089
2090 def read(self, n=None):
2091 if n is None:
2092 return self.stdin.read()
2093 else:
2094 return self.stdin.read(n)
2095
2096 def write(self, *data):
2097 self.stdout.write(self.encode(data))
2098
2099 def reset_output(self):
2100 self.stdout = StringIO()
2101
2102 def setHttpHeader(self, header):
2103 if type(header) is unicode:
2104 header = header.encode('ascii')
2105
2106 key, value = header.split(':', 1)
2107 value = value.lstrip()
2108 if key.lower() == 'content-type':
2109 # save content-type for http_headers
2110 if self.hasContentType:
2111 # we only use the first content-type!
2112 return
2113 else:
2114 self.hasContentType = True
2115
2116 elif key.lower() == 'status':
2117 # save status for finish
2118 self.status = value
2119 return
2120
2121 self.headers.append((key, value))
2122
2123 def http_headers(self, more_headers=[]):
2124 for header in more_headers:
2125 self.setHttpHeader(header)
2126
2127 if not self.hasContentType:
2128 self.headers.insert(0, ('Content-Type', 'text/html;charset=%s' % config.charset))
2129
2130 def flush(self):
2131 pass
2132
2133 def finish(self):
2134 pass
2135
2136 def output(self):
2137 return self.stdout.getvalue()
2138
2139