comparison roundup/cgi/client.py @ 4362:74476eaac38a

more modernisation
author Richard Jones <richard@users.sourceforge.net>
date Fri, 26 Feb 2010 00:38:53 +0000
parents 85b00a3820b3
children fa5587802af9
comparison
equal deleted inserted replaced
4360:661466ba19cd 4362:74476eaac38a
1 """WWW request handler (also used in the stand-alone server). 1 """WWW request handler (also used in the stand-alone server).
2 """ 2 """
3 __docformat__ = 'restructuredtext' 3 __docformat__ = 'restructuredtext'
4 4
5 import base64, binascii, cgi, codecs, httplib, mimetypes, os 5 import base64, binascii, cgi, codecs, mimetypes, os
6 import quopri, random, re, rfc822, stat, sys, time, urllib, urlparse 6 import quopri, random, re, rfc822, stat, sys, time
7 import Cookie, socket, errno 7 import socket, errno
8 from Cookie import CookieError, BaseCookie, SimpleCookie
9 from cStringIO import StringIO
10 8
11 from roundup import roundupdb, date, hyperdb, password 9 from roundup import roundupdb, date, hyperdb, password
12 from roundup.cgi import templating, cgitb, TranslationService 10 from roundup.cgi import templating, cgitb, TranslationService
13 from roundup.cgi.actions import * 11 from roundup.cgi.actions import *
14 from roundup.exceptions import * 12 from roundup.exceptions import *
16 from roundup.cgi.form_parser import FormParser 14 from roundup.cgi.form_parser import FormParser
17 from roundup.mailer import Mailer, MessageSendError, encode_quopri 15 from roundup.mailer import Mailer, MessageSendError, encode_quopri
18 from roundup.cgi import accept_language 16 from roundup.cgi import accept_language
19 from roundup import xmlrpc 17 from roundup import xmlrpc
20 18
19 from roundup.anypy.cookie_ import CookieError, BaseCookie, SimpleCookie, \
20 get_cookie_date
21 from roundup.anypy.io_ import StringIO
22 from roundup.anypy import http_
23 from roundup.anypy import urllib_
24
21 def initialiseSecurity(security): 25 def initialiseSecurity(security):
22 '''Create some Permissions and Roles on the security object 26 '''Create some Permissions and Roles on the security object
23 27
24 This function is directly invoked by security.Security.__init__() 28 This function is directly invoked by security.Security.__init__()
25 as a part of the Security object instantiation. 29 as a part of the Security object instantiation.
41 def clean_message(message, mc=re.compile(CLEAN_MESSAGE_RE, re.I)): 45 def clean_message(message, mc=re.compile(CLEAN_MESSAGE_RE, re.I)):
42 return mc.sub(clean_message_callback, message) 46 return mc.sub(clean_message_callback, message)
43 def clean_message_callback(match, ok={'a':1,'i':1,'b':1,'br':1}): 47 def clean_message_callback(match, ok={'a':1,'i':1,'b':1,'br':1}):
44 """ Strip all non <a>,<i>,<b> and <br> tags from a string 48 """ Strip all non <a>,<i>,<b> and <br> tags from a string
45 """ 49 """
46 if ok.has_key(match.group(3).lower()): 50 if match.group(3).lower() in ok:
47 return match.group(1) 51 return match.group(1)
48 return '&lt;%s&gt;'%match.group(2) 52 return '&lt;%s&gt;'%match.group(2)
49 53
50 54
51 error_message = ''"""<html><head><title>An error has occurred</title></head> 55 error_message = ''"""<html><head><title>An error has occurred</title></head>
291 if not self.base.endswith('/'): 295 if not self.base.endswith('/'):
292 self.base = self.base + '/' 296 self.base = self.base + '/'
293 297
294 # this is the "cookie path" for this tracker (ie. the path part of 298 # this is the "cookie path" for this tracker (ie. the path part of
295 # the "base" url) 299 # the "base" url)
296 self.cookie_path = urlparse.urlparse(self.base)[2] 300 self.cookie_path = urllib_.urlparse(self.base)[2]
297 # cookies to set in http responce 301 # cookies to set in http responce
298 # {(path, name): (value, expire)} 302 # {(path, name): (value, expire)}
299 self._cookies = {} 303 self._cookies = {}
300 304
301 # see if we need to re-parse the environment for the form (eg Zope) 305 # see if we need to re-parse the environment for the form (eg Zope)
499 # The user tried to log in, but did not provide a valid 503 # The user tried to log in, but did not provide a valid
500 # username and password. If we support HTTP 504 # username and password. If we support HTTP
501 # authorization, send back a response that will cause the 505 # authorization, send back a response that will cause the
502 # browser to prompt the user again. 506 # browser to prompt the user again.
503 if self.instance.config.WEB_HTTP_AUTH: 507 if self.instance.config.WEB_HTTP_AUTH:
504 self.response_code = httplib.UNAUTHORIZED 508 self.response_code = http_.client.UNAUTHORIZED
505 realm = self.instance.config.TRACKER_NAME 509 realm = self.instance.config.TRACKER_NAME
506 self.setHeader("WWW-Authenticate", 510 self.setHeader("WWW-Authenticate",
507 "Basic realm=\"%s\"" % realm) 511 "Basic realm=\"%s\"" % realm)
508 else: 512 else:
509 self.response_code = httplib.FORBIDDEN 513 self.response_code = http_.client.FORBIDDEN
510 self.renderFrontPage(message) 514 self.renderFrontPage(message)
511 except Unauthorised, message: 515 except Unauthorised, message:
512 # users may always see the front page 516 # users may always see the front page
513 self.response_code = 403 517 self.response_code = 403
514 self.renderFrontPage(message) 518 self.renderFrontPage(message)
524 self.write_html(self.renderContext()) 528 self.write_html(self.renderContext())
525 except KeyError: 529 except KeyError:
526 # we can't map the URL to a class we know about 530 # we can't map the URL to a class we know about
527 # reraise the NotFound and let roundup_server 531 # reraise the NotFound and let roundup_server
528 # handle it 532 # handle it
529 raise NotFound, e 533 raise NotFound(e)
530 except FormError, e: 534 except FormError, e:
531 self.error_message.append(self._('Form Error: ') + str(e)) 535 self.error_message.append(self._('Form Error: ') + str(e))
532 self.write_html(self.renderContext()) 536 self.write_html(self.renderContext())
533 except: 537 except:
534 # Something has gone badly wrong. Therefore, we should 538 # Something has gone badly wrong. Therefore, we should
535 # make sure that the response code indicates failure. 539 # make sure that the response code indicates failure.
536 if self.response_code == httplib.OK: 540 if self.response_code == http_.client.OK:
537 self.response_code = httplib.INTERNAL_SERVER_ERROR 541 self.response_code = http_.client.INTERNAL_SERVER_ERROR
538 # Help the administrator work out what went wrong. 542 # Help the administrator work out what went wrong.
539 html = ("<h1>Traceback</h1>" 543 html = ("<h1>Traceback</h1>"
540 + cgitb.html(i18n=self.translator) 544 + cgitb.html(i18n=self.translator)
541 + ("<h1>Environment Variables</h1><table>%s</table>" 545 + ("<h1>Environment Variables</h1><table>%s</table>"
542 % cgitb.niceDict("", self.env))) 546 % cgitb.niceDict("", self.env)))
581 If the charset is found, and differs from the storage charset, 585 If the charset is found, and differs from the storage charset,
582 recode all form fields of type 'text/plain' 586 recode all form fields of type 'text/plain'
583 """ 587 """
584 # look for client charset 588 # look for client charset
585 charset_parameter = 0 589 charset_parameter = 0
586 if self.form.has_key('@charset'): 590 if '@charset' in self.form:
587 charset = self.form['@charset'].value 591 charset = self.form['@charset'].value
588 if charset.lower() == "none": 592 if charset.lower() == "none":
589 charset = "" 593 charset = ""
590 charset_parameter = 1 594 charset_parameter = 1
591 elif self.cookie.has_key('roundup_charset'): 595 elif 'roundup_charset' in self.cookie:
592 charset = self.cookie['roundup_charset'].value 596 charset = self.cookie['roundup_charset'].value
593 else: 597 else:
594 charset = None 598 charset = None
595 if charset: 599 if charset:
596 # make sure the charset is recognized 600 # make sure the charset is recognized
623 uc = int(num[1:], 16) 627 uc = int(num[1:], 16)
624 else: 628 else:
625 uc = int(num) 629 uc = int(num)
626 return unichr(uc) 630 return unichr(uc)
627 631
628 for field_name in self.form.keys(): 632 for field_name in self.form:
629 field = self.form[field_name] 633 field = self.form[field_name]
630 if (field.type == 'text/plain') and not field.filename: 634 if (field.type == 'text/plain') and not field.filename:
631 try: 635 try:
632 value = decoder(field.value)[0] 636 value = decoder(field.value)[0]
633 except UnicodeError: 637 except UnicodeError:
638 def determine_language(self): 642 def determine_language(self):
639 """Determine the language""" 643 """Determine the language"""
640 # look for language parameter 644 # look for language parameter
641 # then for language cookie 645 # then for language cookie
642 # last for the Accept-Language header 646 # last for the Accept-Language header
643 if self.form.has_key("@language"): 647 if "@language" in self.form:
644 language = self.form["@language"].value 648 language = self.form["@language"].value
645 if language.lower() == "none": 649 if language.lower() == "none":
646 language = "" 650 language = ""
647 self.add_cookie("roundup_language", language) 651 self.add_cookie("roundup_language", language)
648 elif self.cookie.has_key("roundup_language"): 652 elif "roundup_language" in self.cookie:
649 language = self.cookie["roundup_language"].value 653 language = self.cookie["roundup_language"].value
650 elif self.instance.config["WEB_USE_BROWSER_LANGUAGE"]: 654 elif self.instance.config["WEB_USE_BROWSER_LANGUAGE"]:
651 hal = self.env.get('HTTP_ACCEPT_LANGUAGE') 655 hal = self.env.get('HTTP_ACCEPT_LANGUAGE')
652 language = accept_language.parse(hal) 656 language = accept_language.parse(hal)
653 else: 657 else:
671 self.clean_up() 675 self.clean_up()
672 676
673 user = None 677 user = None
674 # first up, try http authorization if enabled 678 # first up, try http authorization if enabled
675 if self.instance.config['WEB_HTTP_AUTH']: 679 if self.instance.config['WEB_HTTP_AUTH']:
676 if self.env.has_key('REMOTE_USER'): 680 if 'REMOTE_USER' in self.env:
677 # we have external auth (e.g. by Apache) 681 # we have external auth (e.g. by Apache)
678 user = self.env['REMOTE_USER'] 682 user = self.env['REMOTE_USER']
679 elif self.env.get('HTTP_AUTHORIZATION', ''): 683 elif self.env.get('HTTP_AUTHORIZATION', ''):
680 # try handling Basic Auth ourselves 684 # try handling Basic Auth ourselves
681 auth = self.env['HTTP_AUTHORIZATION'] 685 auth = self.env['HTTP_AUTHORIZATION']
727 """Check that the Anonymous user is actually allowed to use the web 731 """Check that the Anonymous user is actually allowed to use the web
728 interface and short-circuit all further processing if they're not. 732 interface and short-circuit all further processing if they're not.
729 """ 733 """
730 # allow Anonymous to use the "login" and "register" actions (noting 734 # allow Anonymous to use the "login" and "register" actions (noting
731 # that "register" has its own "Register" permission check) 735 # that "register" has its own "Register" permission check)
732 if self.form.has_key(':action'): 736 if ':action' in self.form:
733 action = self.form[':action'].value.lower() 737 action = self.form[':action'].value.lower()
734 elif self.form.has_key('@action'): 738 elif '@action' in self.form:
735 action = self.form['@action'].value.lower() 739 action = self.form['@action'].value.lower()
736 else: 740 else:
737 action = None 741 action = None
738 if action in ('login', 'register'): 742 if action in ('login', 'register'):
739 return 743 return
745 return 749 return
746 750
747 # otherwise for everything else 751 # otherwise for everything else
748 if self.user == 'anonymous': 752 if self.user == 'anonymous':
749 if not self.db.security.hasPermission('Web Access', self.userid): 753 if not self.db.security.hasPermission('Web Access', self.userid):
750 raise Unauthorised, self._("Anonymous users are not " 754 raise Unauthorised(self._("Anonymous users are not "
751 "allowed to use the web interface") 755 "allowed to use the web interface"))
752 756
753 def opendb(self, username): 757 def opendb(self, username):
754 """Open the database and set the current user. 758 """Open the database and set the current user.
755 759
756 Opens a database once. On subsequent calls only the user is set on 760 Opens a database once. On subsequent calls only the user is set on
820 self.classname = None 824 self.classname = None
821 self.nodeid = None 825 self.nodeid = None
822 826
823 # see if a template or messages are specified 827 # see if a template or messages are specified
824 template_override = ok_message = error_message = None 828 template_override = ok_message = error_message = None
825 for key in self.form.keys(): 829 for key in self.form:
826 if self.FV_TEMPLATE.match(key): 830 if self.FV_TEMPLATE.match(key):
827 template_override = self.form[key].value 831 template_override = self.form[key].value
828 elif self.FV_OK_MESSAGE.match(key): 832 elif self.FV_OK_MESSAGE.match(key):
829 ok_message = self.form[key].value 833 ok_message = self.form[key].value
830 ok_message = clean_message(ok_message) 834 ok_message = clean_message(ok_message)
845 self.template = template_override 849 self.template = template_override
846 else: 850 else:
847 self.template = '' 851 self.template = ''
848 return 852 return
849 elif path[0] in ('_file', '@@file'): 853 elif path[0] in ('_file', '@@file'):
850 raise SendStaticFile, os.path.join(*path[1:]) 854 raise SendStaticFile(os.path.join(*path[1:]))
851 else: 855 else:
852 self.classname = path[0] 856 self.classname = path[0]
853 if len(path) > 1: 857 if len(path) > 1:
854 # send the file identified by the designator in path[0] 858 # send the file identified by the designator in path[0]
855 raise SendFile, path[0] 859 raise SendFile(path[0])
856 860
857 # see if we got a designator 861 # see if we got a designator
858 m = dre.match(self.classname) 862 m = dre.match(self.classname)
859 if m: 863 if m:
860 self.classname = m.group(1) 864 self.classname = m.group(1)
861 self.nodeid = m.group(2) 865 self.nodeid = m.group(2)
862 try: 866 try:
863 klass = self.db.getclass(self.classname) 867 klass = self.db.getclass(self.classname)
864 except KeyError: 868 except KeyError:
865 raise NotFound, '%s/%s'%(self.classname, self.nodeid) 869 raise NotFound('%s/%s'%(self.classname, self.nodeid))
866 if not klass.hasnode(self.nodeid): 870 if not klass.hasnode(self.nodeid):
867 raise NotFound, '%s/%s'%(self.classname, self.nodeid) 871 raise NotFound('%s/%s'%(self.classname, self.nodeid))
868 # with a designator, we default to item view 872 # with a designator, we default to item view
869 self.template = 'item' 873 self.template = 'item'
870 else: 874 else:
871 # with only a class, we default to index view 875 # with only a class, we default to index view
872 self.template = 'index' 876 self.template = 'index'
873 877
874 # make sure the classname is valid 878 # make sure the classname is valid
875 try: 879 try:
876 self.db.getclass(self.classname) 880 self.db.getclass(self.classname)
877 except KeyError: 881 except KeyError:
878 raise NotFound, self.classname 882 raise NotFound(self.classname)
879 883
880 # see if we have a template override 884 # see if we have a template override
881 if template_override is not None: 885 if template_override is not None:
882 self.template = template_override 886 self.template = template_override
883 887
884 def serve_file(self, designator, dre=re.compile(r'([^\d]+)(\d+)')): 888 def serve_file(self, designator, dre=re.compile(r'([^\d]+)(\d+)')):
885 """ Serve the file from the content property of the designated item. 889 """ Serve the file from the content property of the designated item.
886 """ 890 """
887 m = dre.match(str(designator)) 891 m = dre.match(str(designator))
888 if not m: 892 if not m:
889 raise NotFound, str(designator) 893 raise NotFound(str(designator))
890 classname, nodeid = m.group(1), m.group(2) 894 classname, nodeid = m.group(1), m.group(2)
891 895
892 try: 896 try:
893 klass = self.db.getclass(classname) 897 klass = self.db.getclass(classname)
894 except KeyError: 898 except KeyError:
895 # The classname was not valid. 899 # The classname was not valid.
896 raise NotFound, str(designator) 900 raise NotFound(str(designator))
897 901
898 # perform the Anonymous user access check 902 # perform the Anonymous user access check
899 self.check_anonymous_access() 903 self.check_anonymous_access()
900 904
901 # make sure we have the appropriate properties 905 # make sure we have the appropriate properties
902 props = klass.getprops() 906 props = klass.getprops()
903 if not props.has_key('type'): 907 if 'type' not in props:
904 raise NotFound, designator 908 raise NotFound(designator)
905 if not props.has_key('content'): 909 if 'content' not in props:
906 raise NotFound, designator 910 raise NotFound(designator)
907 911
908 # make sure we have permission 912 # make sure we have permission
909 if not self.db.security.hasPermission('View', self.userid, 913 if not self.db.security.hasPermission('View', self.userid,
910 classname, 'content', nodeid): 914 classname, 'content', nodeid):
911 raise Unauthorised, self._("You are not allowed to view " 915 raise Unauthorised(self._("You are not allowed to view "
912 "this file.") 916 "this file."))
913 917
914 mime_type = klass.get(nodeid, 'type') 918 mime_type = klass.get(nodeid, 'type')
915 # Can happen for msg class: 919 # Can happen for msg class:
916 if not mime_type: 920 if not mime_type:
917 mime_type = 'text/plain' 921 mime_type = 'text/plain'
960 prefix = os.path.normpath(prefix) 964 prefix = os.path.normpath(prefix)
961 filename = os.path.normpath(os.path.join(prefix, file)) 965 filename = os.path.normpath(os.path.join(prefix, file))
962 if os.path.isfile(filename) and filename.startswith(prefix): 966 if os.path.isfile(filename) and filename.startswith(prefix):
963 break 967 break
964 else: 968 else:
965 raise NotFound, file 969 raise NotFound(file)
966 970
967 # last-modified time 971 # last-modified time
968 lmt = os.stat(filename)[stat.ST_MTIME] 972 lmt = os.stat(filename)[stat.ST_MTIME]
969 973
970 # detemine meta-type 974 # detemine meta-type
989 ims = None 993 ims = None
990 # see if there's an if-modified-since... 994 # see if there's an if-modified-since...
991 # XXX see which interfaces set this 995 # XXX see which interfaces set this
992 #if hasattr(self.request, 'headers'): 996 #if hasattr(self.request, 'headers'):
993 #ims = self.request.headers.getheader('if-modified-since') 997 #ims = self.request.headers.getheader('if-modified-since')
994 if self.env.has_key('HTTP_IF_MODIFIED_SINCE'): 998 if 'HTTP_IF_MODIFIED_SINCE' in self.env:
995 # cgi will put the header in the env var 999 # cgi will put the header in the env var
996 ims = self.env['HTTP_IF_MODIFIED_SINCE'] 1000 ims = self.env['HTTP_IF_MODIFIED_SINCE']
997 if ims: 1001 if ims:
998 ims = rfc822.parsedate(ims)[:6] 1002 ims = rfc822.parsedate(ims)[:6]
999 lmtt = time.gmtime(lmt)[:6] 1003 lmtt = time.gmtime(lmt)[:6]
1060 result = result.replace('</body>', s) 1064 result = result.replace('</body>', s)
1061 return result 1065 return result
1062 except templating.NoTemplate, message: 1066 except templating.NoTemplate, message:
1063 return '<strong>%s</strong>'%message 1067 return '<strong>%s</strong>'%message
1064 except templating.Unauthorised, message: 1068 except templating.Unauthorised, message:
1065 raise Unauthorised, str(message) 1069 raise Unauthorised(str(message))
1066 except: 1070 except:
1067 # everything else 1071 # everything else
1068 if self.instance.config.WEB_DEBUG: 1072 if self.instance.config.WEB_DEBUG:
1069 return cgitb.pt_html(i18n=self.translator) 1073 return cgitb.pt_html(i18n=self.translator)
1070 exc_info = sys.exc_info() 1074 exc_info = sys.exc_info()
1078 except: 1082 except:
1079 # Reraise the original exception. The user will 1083 # Reraise the original exception. The user will
1080 # receive an error message, and the adminstrator will 1084 # receive an error message, and the adminstrator will
1081 # receive a traceback, albeit with less information 1085 # receive a traceback, albeit with less information
1082 # than the one we tried to generate above. 1086 # than the one we tried to generate above.
1083 raise exc_info[0], exc_info[1], exc_info[2] 1087 raise exc_info[0](exc_info[1]).with_traceback(exc_info[2])
1084 1088
1085 # these are the actions that are available 1089 # these are the actions that are available
1086 actions = ( 1090 actions = (
1087 ('edit', EditItemAction), 1091 ('edit', EditItemAction),
1088 ('editcsv', EditCSVAction), 1092 ('editcsv', EditCSVAction),
1108 user, bypassing the usual template rendering. 1112 user, bypassing the usual template rendering.
1109 1113
1110 We explicitly catch Reject and ValueError exceptions and 1114 We explicitly catch Reject and ValueError exceptions and
1111 present their messages to the user. 1115 present their messages to the user.
1112 """ 1116 """
1113 if self.form.has_key(':action'): 1117 if ':action' in self.form:
1114 action = self.form[':action'].value.lower() 1118 action = self.form[':action'].value.lower()
1115 elif self.form.has_key('@action'): 1119 elif '@action' in self.form:
1116 action = self.form['@action'].value.lower() 1120 action = self.form['@action'].value.lower()
1117 else: 1121 else:
1118 return None 1122 return None
1119 1123
1120 try: 1124 try:
1130 except (ValueError, Reject), err: 1134 except (ValueError, Reject), err:
1131 self.error_message.append(str(err)) 1135 self.error_message.append(str(err))
1132 1136
1133 def get_action_class(self, action_name): 1137 def get_action_class(self, action_name):
1134 if (hasattr(self.instance, 'cgi_actions') and 1138 if (hasattr(self.instance, 'cgi_actions') and
1135 self.instance.cgi_actions.has_key(action_name)): 1139 action_name in self.instance.cgi_actions):
1136 # tracker-defined action 1140 # tracker-defined action
1137 action_klass = self.instance.cgi_actions[action_name] 1141 action_klass = self.instance.cgi_actions[action_name]
1138 else: 1142 else:
1139 # go with a default 1143 # go with a default
1140 for name, action_klass in self.actions: 1144 for name, action_klass in self.actions:
1141 if name == action_name: 1145 if name == action_name:
1142 break 1146 break
1143 else: 1147 else:
1144 raise ValueError, 'No such action "%s"'%action_name 1148 raise ValueError('No such action "%s"'%action_name)
1145 return action_klass 1149 return action_klass
1146 1150
1147 def _socket_op(self, call, *args, **kwargs): 1151 def _socket_op(self, call, *args, **kwargs):
1148 """Execute socket-related operation, catch common network errors 1152 """Execute socket-related operation, catch common network errors
1149 1153
1179 self._socket_op(self.request.wfile.write, content) 1183 self._socket_op(self.request.wfile.write, content)
1180 1184
1181 def write_html(self, content): 1185 def write_html(self, content):
1182 if not self.headers_done: 1186 if not self.headers_done:
1183 # at this point, we are sure about Content-Type 1187 # at this point, we are sure about Content-Type
1184 if not self.additional_headers.has_key('Content-Type'): 1188 if 'Content-Type' not in self.additional_headers:
1185 self.additional_headers['Content-Type'] = \ 1189 self.additional_headers['Content-Type'] = \
1186 'text/html; charset=%s' % self.charset 1190 'text/html; charset=%s' % self.charset
1187 self.header() 1191 self.header()
1188 1192
1189 if self.env['REQUEST_METHOD'] == 'HEAD': 1193 if self.env['REQUEST_METHOD'] == 'HEAD':
1341 # should just ignore the invalid Range header. 1345 # should just ignore the invalid Range header.
1342 if if_range: 1346 if if_range:
1343 return None 1347 return None
1344 # Return code 416 with a Content-Range header giving the 1348 # Return code 416 with a Content-Range header giving the
1345 # allowable range. 1349 # allowable range.
1346 self.response_code = httplib.REQUESTED_RANGE_NOT_SATISFIABLE 1350 self.response_code = http_.client.REQUESTED_RANGE_NOT_SATISFIABLE
1347 self.setHeader("Content-Range", "bytes */%d" % length) 1351 self.setHeader("Content-Range", "bytes */%d" % length)
1348 return None 1352 return None
1349 # RFC 2616 10.2.7: 206 Partial Content 1353 # RFC 2616 10.2.7: 206 Partial Content
1350 # 1354 #
1351 # Tell the client that we are honoring the Range request by 1355 # Tell the client that we are honoring the Range request by
1352 # indicating that we are providing partial content. 1356 # indicating that we are providing partial content.
1353 self.response_code = httplib.PARTIAL_CONTENT 1357 self.response_code = http_.client.PARTIAL_CONTENT
1354 # RFC 2616 14.16: Content-Range 1358 # RFC 2616 14.16: Content-Range
1355 # 1359 #
1356 # Tell the client what data we are providing. 1360 # Tell the client what data we are providing.
1357 # 1361 #
1358 # content-range-spec = byte-content-range-spec 1362 # content-range-spec = byte-content-range-spec
1402 # Send the HTTP header. 1406 # Send the HTTP header.
1403 self.header() 1407 self.header()
1404 # If the client doesn't actually want the body, or if we are 1408 # If the client doesn't actually want the body, or if we are
1405 # indicating an invalid range. 1409 # indicating an invalid range.
1406 if (self.env['REQUEST_METHOD'] == 'HEAD' 1410 if (self.env['REQUEST_METHOD'] == 'HEAD'
1407 or self.response_code == httplib.REQUESTED_RANGE_NOT_SATISFIABLE): 1411 or self.response_code == http_.client.REQUESTED_RANGE_NOT_SATISFIABLE):
1408 return 1412 return
1409 # Use the optimized "sendfile" operation, if possible. 1413 # Use the optimized "sendfile" operation, if possible.
1410 if hasattr(self.request, "sendfile"): 1414 if hasattr(self.request, "sendfile"):
1411 self._socket_op(self.request.sendfile, filename, offset, length) 1415 self._socket_op(self.request.sendfile, filename, offset, length)
1412 return 1416 return
1437 headers.update(self.additional_headers) 1441 headers.update(self.additional_headers)
1438 1442
1439 if headers.get('Content-Type', 'text/html') == 'text/html': 1443 if headers.get('Content-Type', 'text/html') == 'text/html':
1440 headers['Content-Type'] = 'text/html; charset=utf-8' 1444 headers['Content-Type'] = 'text/html; charset=utf-8'
1441 1445
1442 headers = headers.items() 1446 headers = list(headers.items())
1443 1447
1444 for ((path, name), (value, expire)) in self._cookies.items(): 1448 for ((path, name), (value, expire)) in self._cookies.iteritems():
1445 cookie = "%s=%s; Path=%s;"%(name, value, path) 1449 cookie = "%s=%s; Path=%s;"%(name, value, path)
1446 if expire is not None: 1450 if expire is not None:
1447 cookie += " expires=%s;"%Cookie._getdate(expire) 1451 cookie += " expires=%s;"%get_cookie_date(expire)
1448 headers.append(('Set-Cookie', cookie)) 1452 headers.append(('Set-Cookie', cookie))
1449 1453
1450 self._socket_op(self.request.start_response, headers, response) 1454 self._socket_op(self.request.start_response, headers, response)
1451 1455
1452 self.headers_done = 1 1456 self.headers_done = 1

Roundup Issue Tracker: http://roundup-tracker.org/