22"""
33__docformat__ = 'restructuredtext'
44
5- import base64 , binascii , cgi , codecs , mimetypes , os
5+ import base64 , binascii , cgi , codecs , httplib , mimetypes , os
66import quopri , random , re , rfc822 , stat , sys , time , urllib , urlparse
77import Cookie , socket , errno
88from Cookie import CookieError , BaseCookie , SimpleCookie
@@ -463,6 +463,7 @@ def inner_main(self):
463463 self .header ()
464464 except Unauthorised , message :
465465 # users may always see the front page
466+ self .response_code = 403
466467 self .classname = self .nodeid = None
467468 self .template = ''
468469 self .error_message .append (message )
@@ -632,9 +633,7 @@ def determine_user(self):
632633 login .verifyLogin (username , password )
633634 except LoginError , err :
634635 self .make_user_anonymous ()
635- self .response_code = 403
636636 raise Unauthorised , err
637-
638637 user = username
639638
640639 # if user was not set by http authorization, try session lookup
@@ -879,14 +878,8 @@ def _serve_file(self, lmt, mime_type, content=None, filename=None):
879878 ''' guts of serve_file() and serve_static_file()
880879 '''
881880
882- if not content :
883- length = os .stat (filename )[stat .ST_SIZE ]
884- else :
885- length = len (content )
886-
887881 # spit out headers
888882 self .additional_headers ['Content-Type' ] = mime_type
889- self .additional_headers ['Content-Length' ] = str (length )
890883 self .additional_headers ['Last-Modified' ] = rfc822 .formatdate (lmt )
891884
892885 ims = None
@@ -903,27 +896,11 @@ def _serve_file(self, lmt, mime_type, content=None, filename=None):
903896 if lmtt <= ims :
904897 raise NotModified
905898
906- if not self .headers_done :
907- self .header ()
908-
909- if self .env ['REQUEST_METHOD' ] == 'HEAD' :
910- return
911-
912- # If we have a file, and the 'sendfile' method is available,
913- # we can bypass reading and writing the content into application
914- # memory entirely.
915899 if filename :
916- if hasattr (self .request , 'sendfile' ):
917- self ._socket_op (self .request .sendfile , filename )
918- return
919- f = open (filename , 'rb' )
920- try :
921- content = f .read ()
922- finally :
923- f .close ()
924-
925- self ._socket_op (self .request .wfile .write , content )
926-
900+ self .write_file (filename )
901+ else :
902+ self .additional_headers ['Content-Length' ] = str (len (content ))
903+ self .write (content )
927904
928905 def renderContext (self ):
929906 ''' Return a PageTemplate for the named page
@@ -1071,6 +1048,14 @@ def _socket_op(self, call, *args, **kwargs):
10711048 pass
10721049 if err_errno not in self .IGNORE_NET_ERRORS :
10731050 raise
1051+ except IOError :
1052+ # Apache's mod_python will raise IOError -- without an
1053+ # accompanying errno -- when a write to the client fails.
1054+ # A common case is that the client has closed the
1055+ # connection. There's no way to be certain that this is
1056+ # the situation that has occurred here, but that is the
1057+ # most likely case.
1058+ pass
10741059
10751060 def write (self , content ):
10761061 if not self .headers_done :
@@ -1098,6 +1083,227 @@ def write_html(self, content):
10981083 # and write
10991084 self ._socket_op (self .request .wfile .write , content )
11001085
1086+ def http_strip (self , content ):
1087+ """Remove HTTP Linear White Space from 'content'.
1088+
1089+ 'content' -- A string.
1090+
1091+ returns -- 'content', with all leading and trailing LWS
1092+ removed."""
1093+
1094+ # RFC 2616 2.2: Basic Rules
1095+ #
1096+ # LWS = [CRLF] 1*( SP | HT )
1097+ return content .strip (" \r \n \t " )
1098+
1099+ def http_split (self , content ):
1100+ """Split an HTTP list.
1101+
1102+ 'content' -- A string, giving a list of items.
1103+
1104+ returns -- A sequence of strings, containing the elements of
1105+ the list."""
1106+
1107+ # RFC 2616 2.1: Augmented BNF
1108+ #
1109+ # Grammar productions of the form "#rule" indicate a
1110+ # comma-separated list of elements matching "rule". LWS
1111+ # is then removed from each element, and empty elements
1112+ # removed.
1113+
1114+ # Split at commas.
1115+ elements = content .split ("," )
1116+ # Remove linear whitespace at either end of the string.
1117+ elements = [self .http_strip (e ) for e in elements ]
1118+ # Remove any now-empty elements.
1119+ return [e for e in elements if e ]
1120+
1121+ def handle_range_header (self , length , etag ):
1122+ """Handle the 'Range' and 'If-Range' headers.
1123+
1124+ 'length' -- the length of the content available for the
1125+ resource.
1126+
1127+ 'etag' -- the entity tag for this resources.
1128+
1129+ returns -- If the request headers (including 'Range' and
1130+ 'If-Range') indicate that only a portion of the entity should
1131+ be returned, then the return value is a pair '(offfset,
1132+ length)' indicating the first byte and number of bytes of the
1133+ content that should be returned to the client. In addition,
1134+ this method will set 'self.response_code' to indicate Partial
1135+ Content. In all other cases, the return value is 'None'. If
1136+ appropriate, 'self.response_code' will be
1137+ set to indicate 'REQUESTED_RANGE_NOT_SATISFIABLE'. In that
1138+ case, the caller should not send any data to the client."""
1139+
1140+ # RFC 2616 14.35: Range
1141+ #
1142+ # See if the Range header is present.
1143+ ranges_specifier = self .env .get ("HTTP_RANGE" )
1144+ if ranges_specifier is None :
1145+ return None
1146+ # RFC 2616 14.27: If-Range
1147+ #
1148+ # Check to see if there is an If-Range header.
1149+ # Because the specification says:
1150+ #
1151+ # The If-Range header ... MUST be ignored if the request
1152+ # does not include a Range header, we check for If-Range
1153+ # after checking for Range.
1154+ if_range = self .env .get ("HTTP_IF_RANGE" )
1155+ if if_range :
1156+ # The grammar for the If-Range header is:
1157+ #
1158+ # If-Range = "If-Range" ":" ( entity-tag | HTTP-date )
1159+ # entity-tag = [ weak ] opaque-tag
1160+ # weak = "W/"
1161+ # opaque-tag = quoted-string
1162+ #
1163+ # We only support strong entity tags.
1164+ if_range = self .http_strip (if_range )
1165+ if (not if_range .startswith ('"' )
1166+ or not if_range .endswith ('"' )):
1167+ return None
1168+ # If the condition doesn't match the entity tag, then we
1169+ # must send the client the entire file.
1170+ if if_range != etag :
1171+ return
1172+ # The grammar for the Range header value is:
1173+ #
1174+ # ranges-specifier = byte-ranges-specifier
1175+ # byte-ranges-specifier = bytes-unit "=" byte-range-set
1176+ # byte-range-set = 1#( byte-range-spec | suffix-byte-range-spec )
1177+ # byte-range-spec = first-byte-pos "-" [last-byte-pos]
1178+ # first-byte-pos = 1*DIGIT
1179+ # last-byte-pos = 1*DIGIT
1180+ # suffix-byte-range-spec = "-" suffix-length
1181+ # suffix-length = 1*DIGIT
1182+ #
1183+ # Look for the "=" separating the units from the range set.
1184+ specs = ranges_specifier .split ("=" , 1 )
1185+ if len (specs ) != 2 :
1186+ return None
1187+ # Check that the bytes-unit is in fact "bytes". If it is not,
1188+ # we do not know how to process this range.
1189+ bytes_unit = self .http_strip (specs [0 ])
1190+ if bytes_unit != "bytes" :
1191+ return None
1192+ # Seperate the range-set into range-specs.
1193+ byte_range_set = self .http_strip (specs [1 ])
1194+ byte_range_specs = self .http_split (byte_range_set )
1195+ # We only handle exactly one range at this time.
1196+ if len (byte_range_specs ) != 1 :
1197+ return None
1198+ # Parse the spec.
1199+ byte_range_spec = byte_range_specs [0 ]
1200+ pos = byte_range_spec .split ("-" , 1 )
1201+ if len (pos ) != 2 :
1202+ return None
1203+ # Get the first and last bytes.
1204+ first = self .http_strip (pos [0 ])
1205+ last = self .http_strip (pos [1 ])
1206+ # We do not handle suffix ranges.
1207+ if not first :
1208+ return None
1209+ # Convert the first and last positions to integers.
1210+ try :
1211+ first = int (first )
1212+ if last :
1213+ last = int (last )
1214+ else :
1215+ last = length - 1
1216+ except :
1217+ # The positions could not be parsed as integers.
1218+ return None
1219+ # Check that the range makes sense.
1220+ if (first < 0 or last < 0 or last < first ):
1221+ return None
1222+ if last >= length :
1223+ # RFC 2616 10.4.17: 416 Requested Range Not Satisfiable
1224+ #
1225+ # If there is an If-Range header, RFC 2616 says that we
1226+ # should just ignore the invalid Range header.
1227+ if if_range :
1228+ return None
1229+ # Return code 416 with a Content-Range header giving the
1230+ # allowable range.
1231+ self .response_code = httplib .REQUESTED_RANGE_NOT_SATISFIABLE
1232+ self .setHeader ("Content-Range" , "bytes */%d" % length )
1233+ return None
1234+ # RFC 2616 10.2.7: 206 Partial Content
1235+ #
1236+ # Tell the client that we are honoring the Range request by
1237+ # indicating that we are providing partial content.
1238+ self .response_code = httplib .PARTIAL_CONTENT
1239+ # RFC 2616 14.16: Content-Range
1240+ #
1241+ # Tell the client what data we are providing.
1242+ #
1243+ # content-range-spec = byte-content-range-spec
1244+ # byte-content-range-spec = bytes-unit SP
1245+ # byte-range-resp-spec "/"
1246+ # ( instance-length | "*" )
1247+ # byte-range-resp-spec = (first-byte-pos "-" last-byte-pos)
1248+ # | "*"
1249+ # instance-length = 1 * DIGIT
1250+ self .setHeader ("Content-Range" ,
1251+ "bytes %d-%d/%d" % (first , last , length ))
1252+ return (first , last - first + 1 )
1253+
1254+ def write_file (self , filename ):
1255+ '''Send the contents of 'filename' to the user.'''
1256+
1257+ # Determine the length of the file.
1258+ stat_info = os .stat (filename )
1259+ length = stat_info [stat .ST_SIZE ]
1260+ # Assume we will return the entire file.
1261+ offset = 0
1262+ # If the headers have not already been finalized,
1263+ if not self .headers_done :
1264+ # RFC 2616 14.19: ETag
1265+ #
1266+ # Compute the entity tag, in a format similar to that
1267+ # used by Apache.
1268+ etag = '"%x-%x-%x"' % (stat_info [stat .ST_INO ],
1269+ length ,
1270+ stat_info [stat .ST_MTIME ])
1271+ self .setHeader ("ETag" , etag )
1272+ # RFC 2616 14.5: Accept-Ranges
1273+ #
1274+ # Let the client know that we will accept range requests.
1275+ self .setHeader ("Accept-Ranges" , "bytes" )
1276+ # RFC 2616 14.35: Range
1277+ #
1278+ # If there is a Range header, we may be able to avoid
1279+ # sending the entire file.
1280+ content_range = self .handle_range_header (length , etag )
1281+ if content_range :
1282+ offset , length = content_range
1283+ # RFC 2616 14.13: Content-Length
1284+ #
1285+ # Tell the client how much data we are providing.
1286+ self .setHeader ("Content-Length" , length )
1287+ # Send the HTTP header.
1288+ self .header ()
1289+ # If the client doesn't actually want the body, or if we are
1290+ # indicating an invalid range.
1291+ if (self .env ['REQUEST_METHOD' ] == 'HEAD'
1292+ or self .response_code == httplib .REQUESTED_RANGE_NOT_SATISFIABLE ):
1293+ return
1294+ # Use the optimized "sendfile" operation, if possible.
1295+ if hasattr (self .request , "sendfile" ):
1296+ self ._socket_op (self .request .sendfile , filename , offset , length )
1297+ return
1298+ # Fallback to the "write" operation.
1299+ f = open (filename , 'rb' )
1300+ try :
1301+ if offset :
1302+ f .seek (offset )
1303+ content = f .read (length )
1304+ finally :
1305+ f .close ()
1306+ self .write (content )
11011307
11021308 def setHeader (self , header , value ):
11031309 '''Override a header to be returned to the user's browser.
0 commit comments