vendor/tramline

changeset 53:692f926b7530 xsendfile

manual rebasing on latest of gracinet-fix-range branch
author Georges Racinet on purity.racinet.fr <georges@racinet.fr>
date Sun, 30 Sep 2012 22:58:29 +0200
parents 811fdddf96dc
children b06099b97500
files src/tramline/core.py
diffstat 1 files changed, 96 insertions(+), 45 deletions(-) [+]
line diff
     1.1 --- a/src/tramline/core.py
     1.2 +++ b/src/tramline/core.py
     1.3 @@ -1,6 +1,6 @@
     1.4  import os, tempfile, random, sys, errno, mimetools
     1.5  import cgi
     1.6 -from mod_python import apache
     1.7 +from mod_python import apache, Cookie
     1.8  
     1.9  OPTION_ALLOW_GROUP_WRITE = 'allow_group_write'
    1.10  TRAMLINE_RANGE_HEADER = 'X-Tramline-Original-Range'
    1.11 @@ -48,7 +48,9 @@
    1.12                             tramline_repository_path(req))):
    1.13          if not os.path.isdir(p):
    1.14              os.mkdir(p)
    1.15 -            if i == 3 and group_write(req):
    1.16 +            import sys
    1.17 +            sys.stderr.write('creating path: %r\n' % p)
    1.18 +	    if i == 3 and group_write(req):
    1.19                  # drwxrwsr-x
    1.20                  # (set-group-ID-on-execution bit)
    1.21                  os.chmod(p, 02775)
    1.22 @@ -85,16 +87,28 @@
    1.23          pass_on(filter)
    1.24          return
    1.25  
    1.26 -    # we only handle POST requests
    1.27 -    if filter.req.method != 'POST':
    1.28 +    req_method = filter.req.method
    1.29 +
    1.30 +    # we only affect POST and PUT requests from now on
    1.31 +    if filter.req.method not in ('POST', 'PUT'):
    1.32          pass_on(filter)
    1.33          return
    1.34  
    1.35 -    # we only handle multipart/form-data
    1.36 -    enctype = filter.req.headers_in.get('Content-Type')
    1.37 -    if enctype[:19] != 'multipart/form-data':
    1.38 -        pass_on(filter)
    1.39 -        return
    1.40 +    if req_method == 'POST':
    1.41 +        # we only handle multipart/form-data
    1.42 +        enctype = filter.req.headers_in.get('Content-Type')
    1.43 +        if enctype[:19] != 'multipart/form-data':
    1.44 +            pass_on(filter)
    1.45 +            return
    1.46 +    elif req_method == 'PUT':
    1.47 +        # we only handle request that have the 'X-Tramline-Enable' header
    1.48 +        # or 'tramline_enable' cookie. 
    1.49 +        # Cookie enabling is here to support those agents that
    1.50 +        # can't set a custom header (e.g., zopeedit)
    1.51 +        if filter.req.headers_in.get('X-Tramline-Enable') is None \
    1.52 +                and Cookie.get_cookie(filter.req, 'tramline_enable') is None:
    1.53 +            pass_on(filter)
    1.54 +            return
    1.55  
    1.56      # check whether we have an id already
    1.57      id = filter.req.headers_in.get('tramline_id')
    1.58 @@ -102,7 +116,7 @@
    1.59      if id is None:
    1.60          # no id, so create new processor instance and store
    1.61          # away id
    1.62 -        processor = theProcessorRegistry.createProcessor()
    1.63 +        processor = theProcessorRegistry.createProcessor(req_method=req_method)
    1.64          processor.initFromInputFilter(filter)
    1.65          filter.req.headers_in['tramline_id'] = str(processor.id)
    1.66      else:
    1.67 @@ -144,7 +158,7 @@
    1.68  
    1.69      # in case of post request, we may need to do a commit/abort
    1.70      # of previous input round
    1.71 -    if filter.req.method == 'POST':
    1.72 +    if filter.req.method in ('POST', 'PUT'):
    1.73          outputfilter_post(filter)
    1.74          return
    1.75      pass_on(filter)
    1.76 @@ -179,22 +193,27 @@
    1.77      #pass_on(filter)
    1.78      filter.pass_on()
    1.79  
    1.80 -    
    1.81 +
    1.82  class ProcessorRegistry:
    1.83 +
    1.84 +    _processor_classes = {}
    1.85 +
    1.86      def __init__(self):
    1.87          self._processors = {}
    1.88  
    1.89      def getProcessor(self, id):
    1.90          return self._processors[id]
    1.91  
    1.92 -    def createProcessor(self):
    1.93 +    def createProcessor(self, req_method='POST'):
    1.94          # XXX thread issues?
    1.95          while True:
    1.96              id = random.randrange(sys.maxint)
    1.97              if id not in self._processors:
    1.98                  break
    1.99 -        result = self._processors[id] = Processor(id)
   1.100 -        return result
   1.101 +
   1.102 +        ProcessorClass = self._processor_classes.get(req_method)
   1.103 +        proc = self._processors[id] = ProcessorClass(id)
   1.104 +        return proc
   1.105      
   1.106      def removeProcessor(self, processor):
   1.107          del self._processors[processor.id]
   1.108 @@ -205,7 +224,7 @@
   1.109      return os.path.join(tramline_stat_path(req), progress_id)
   1.110  
   1.111  class Processor:
   1.112 -    """Processor for Post requests. 
   1.113 +    """Base class for processors.
   1.114  
   1.115      API atributes:
   1.116         id: this is the main identifier in the registry. It is used for 
   1.117 @@ -221,10 +240,9 @@
   1.118         uploaded: this is the total file data upload this processor has seen. 
   1.119             there might be more than one file in this request.
   1.120  
   1.121 -       upload_length: the full length of the POST request, as taken from the
   1.122 +       upload_length: the full length of the input request, as taken from the
   1.123         header
   1.124      """
   1.125 -       
   1.126      def __init__(self, id):
   1.127          self.uploaded = 0L
   1.128          self.id = id
   1.129 @@ -233,11 +251,7 @@
   1.130          self._upload_files = []
   1.131          self._incoming = []
   1.132  	self._isize = 0
   1.133 -        # we use a state pattern where the handle method gets
   1.134 -        # replaced by the current handle method for this state.
   1.135 -        self.handle = self.handle_first_boundary
   1.136 -        self.vars_to_handle = []
   1.137 -        self._enable_vars=''
   1.138 +        self._f = None # output file object
   1.139  
   1.140      def initFromInputFilter(self, filter):
   1.141          headers = filter.req.headers_in
   1.142 @@ -249,11 +263,6 @@
   1.143                # parse_qs always produces lists
   1.144                self.progress_id = filter.req.connection.remote_ip + '-' + gu_ids[0]
   1.145  
   1.146 -    def pushInput(self, data, out):
   1.147 -        lines = data.splitlines(True)
   1.148 -        for line in lines:
   1.149 -            self.pushInputLine(line, out)
   1.150 -
   1.151      def logProgress(self, req):
   1.152          """Log progress upload to a file for async requests to use
   1.153  
   1.154 @@ -274,12 +283,66 @@
   1.155              f.write(str({'state': 1, 'percent': int(percent)}))
   1.156              f.close()
   1.157  
   1.158 +    def commit(self, req):
   1.159 +        # XXX works under the assumption that the last segment of 
   1.160 +        # file path is the tramline id
   1.161 +        for upload_file in self._upload_files:
   1.162 +            dummy, filename = os.path.split(upload_file)
   1.163 +            os.rename(upload_file, id_to_path(tramline_path(req), filename))
   1.164 +
   1.165 +    def abort(self):
   1.166 +        for upload_file in self._upload_files:
   1.167 +            os.remove(upload_file)
   1.168 +
   1.169 +    def initUploadFile(self, out, with_newline=True):
   1.170 +        """Create the dump file, write id to out and keep needed references."""
   1.171 +        fd, pathname, file_id = createUniqueFile(out.req)
   1.172 +
   1.173 +        self._f = os.fdopen(fd, 'wb')
   1.174 +        self._upload_files.append(pathname)
   1.175 +        out.write(file_id)
   1.176 +        if with_newline:
   1.177 +            out.write('\r\n')
   1.178 +
   1.179 +class PutRequestProcessor(Processor):
   1.180 +   """Directly dump inconditionaly incoming data."""
   1.181 +
   1.182 +   def pushInput(self, data, out):
   1.183 +       if self._f is None:
   1.184 +           self.initUploadFile(out, with_newline=False)
   1.185 +       self._f.write(data)
   1.186 +
   1.187 +   def finalizeInput(self, out):
   1.188 +       out.req.headers_in['tramline'] = ''
   1.189 +       if self._f is not None: # one never knows
   1.190 +           self._f.close()
   1.191 +
   1.192 +class PostRequestProcessor(Processor):
   1.193 +    """Processor for Post requests. 
   1.194 +
   1.195 +    Handles multipart/form-data content, looks for enabling info in the form
   1.196 +    data, and if enabled, intercept those parts that are file uploads.
   1.197 +    """
   1.198 +
   1.199 +    def __init__(self, pid):
   1.200 +        Processor.__init__(self, pid)
   1.201 +        # we use a state pattern where the handle method gets
   1.202 +        # replaced by the current handle method for this state.
   1.203 +        self.handle = self.handle_first_boundary
   1.204 +        self.vars_to_handle = []
   1.205 +        self._enable_vars=''
   1.206 +
   1.207 +    def pushInput(self, data, out):
   1.208 +        lines = data.splitlines(True)
   1.209 +        for line in lines:
   1.210 +            self.pushInputLine(line, out)
   1.211 +
   1.212      def pushInputLine(self, data, out):
   1.213          # collect data
   1.214          self._incoming.append(data)
   1.215  	self._isize += len(data)
   1.216  
   1.217 -        # if we're not at the end of the line, input was broken
   1.218 +       # if we're not at the end of the line, input was broken
   1.219          # somewhere, unless we are handling file data which might be binary.
   1.220  	# We return to collect more first (also for file data if too small)
   1.221          if data[-1] != '\n' and (
   1.222 @@ -301,17 +364,6 @@
   1.223          if self._upload_files:
   1.224              out.req.headers_in['tramline'] = ''
   1.225  
   1.226 -    def commit(self, req):
   1.227 -        # XXX works under the assumption that the last segment of 
   1.228 -        # file path is the tramline id
   1.229 -        for upload_file in self._upload_files:
   1.230 -            dummy, filename = os.path.split(upload_file)
   1.231 -            os.rename(upload_file, id_to_path(tramline_path(req), filename))
   1.232 -
   1.233 -    def abort(self):
   1.234 -        for upload_file in self._upload_files:
   1.235 -            os.remove(upload_file)
   1.236 -    
   1.237      def handle_first_boundary(self, line, out):
   1.238          self._boundary = line
   1.239          self._last_boundary = self._boundary.rstrip() + '--\r\n'
   1.240 @@ -357,12 +409,8 @@
   1.241                self._disposition_options.get('name') not in self.vars_to_handle:
   1.242              self.handle = self.handle_data
   1.243              return
   1.244 -        fd, pathname, file_id = createUniqueFile(out.req)
   1.245  
   1.246 -        self._f = os.fdopen(fd, 'wb')
   1.247 -        self._upload_files.append(pathname)
   1.248 -        out.write(file_id)
   1.249 -        out.write('\r\n')
   1.250 +        self.initUploadFile(out)
   1.251          
   1.252          self._previous_line = None
   1.253          self.handle = self.handle_file_data
   1.254 @@ -483,6 +531,9 @@
   1.255                  continue # try again
   1.256              raise
   1.257  
   1.258 +ProcessorRegistry._processor_classes = dict(PUT=PutRequestProcessor,
   1.259 +                                            POST=PostRequestProcessor)
   1.260 +
   1.261  def log(data, req):
   1.262      f = open(os.path.join(tramline_path(req), 'tramline.log'), 'ab')
   1.263      f.write(data)