9797import mimetypes
9898import os
9999import posixpath
100+ import re
100101import select
101102import shutil
102103import socket # For gethostbyaddr()
@@ -676,7 +677,7 @@ def do_GET(self):
676677 f = self .send_head ()
677678 if f :
678679 try :
679- self .copyfile (f , self .wfile )
680+ self .copyfile (f , self .wfile , range = self . range )
680681 finally :
681682 f .close ()
682683
@@ -699,6 +700,7 @@ def send_head(self):
699700 """
700701 path = self .translate_path (self .path )
701702 f = None
703+ self .range = self .get_range ()
702704 if os .path .isdir (path ):
703705 parts = urllib .parse .urlsplit (self .path )
704706 if not parts .path .endswith ('/' ):
@@ -763,9 +765,26 @@ def send_head(self):
763765 f .close ()
764766 return None
765767
766- self .send_response (HTTPStatus .OK )
768+ if self .range :
769+ start , end = self .range
770+ if start >= fs .st_size :
771+ # 416 REQUESTED_RANGE_NOT_SATISFIABLE means that none of the range values overlap the extent of the resource
772+ f .close ()
773+ self .send_error (HTTPStatus .REQUESTED_RANGE_NOT_SATISFIABLE )
774+ return None
775+ if end is None or end >= fs .st_size :
776+ end = fs .st_size - 1
777+ self .send_response (HTTPStatus .PARTIAL_CONTENT )
778+ self .send_header ("Content-Range" , "bytes %s-%s/%s" % (start , end , fs .st_size ))
779+ self .send_header ("Content-Length" , str (end - start + 1 ))
780+
781+ # Update range to be sent to be used later in copyfile
782+ self .range = (start , end )
783+ else :
784+ self .send_response (HTTPStatus .OK )
785+ self .send_header ("Accept-Ranges" , "bytes" )
786+ self .send_header ("Content-Length" , str (fs [6 ]))
767787 self .send_header ("Content-type" , ctype )
768- self .send_header ("Content-Length" , str (fs [6 ]))
769788 self .send_header ("Last-Modified" ,
770789 self .date_time_string (fs .st_mtime ))
771790 self .end_headers ()
@@ -861,21 +880,37 @@ def translate_path(self, path):
861880 path += '/'
862881 return path
863882
864- def copyfile (self , source , outputfile ):
865- """Copy all data between two file objects.
883+ def copyfile (self , source , outputfile , range = None ):
884+ """Copy all data between two file objects if range is None.
885+ Otherwise, copy data between two file objects based on the
886+ inclusive range (start, end).
866887
867888 The SOURCE argument is a file object open for reading
868- (or anything with a read() method) and the DESTINATION
869- argument is a file object open for writing (or
870- anything with a write() method).
889+ (or anything with read() and seek() method) and the
890+ DESTINATION argument is a file object open for writing
891+ (or anything with a write() method).
871892
872893 The only reason for overriding this would be to change
873894 the block size or perhaps to replace newlines by CRLF
874895 -- note however that this the default server uses this
875896 to copy binary data as well.
876897
877898 """
878- shutil .copyfileobj (source , outputfile )
899+ if range is None :
900+ shutil .copyfileobj (source , outputfile )
901+ else :
902+ start , end = range
903+ length = end - start + 1
904+ source .seek (start )
905+ while True :
906+ if length <= 0 :
907+ break
908+ buf = source .read (min (length , shutil .COPY_BUFSIZE ))
909+ if not buf :
910+ break
911+ length -= len (buf )
912+ outputfile .write (buf )
913+
879914
880915 def guess_type (self , path ):
881916 """Guess the type of a file.
@@ -902,6 +937,24 @@ def guess_type(self, path):
902937 return guess
903938 return 'application/octet-stream'
904939
940+ def get_range (self ):
941+ """Return a tuple of (start, end) representing the range header in
942+ the HTTP request. If the range header is missing or not resolvable,
943+ None is returned. This only supports single part ranges.
944+
945+ """
946+ range_header = self .headers .get ('range' )
947+ if not range_header :
948+ return None
949+ m = re .match (r'bytes=(\d+)-(\d*)$' , range_header )
950+ if not m :
951+ return None
952+ start = m .group (1 )
953+ if not m .group (2 ):
954+ return int (start ), None
955+ end = m .group (2 )
956+ return int (start ), int (end )
957+
905958
906959# Utilities for CGIHTTPRequestHandler
907960
0 commit comments