Ejemplo n.º 1
0
def handle_put(reqinfo, start_response):
    '''Write to a single file, possibly replacing an existing one.'''
    real_path = reqinfo.get_request_path('w')

    if os.path.isdir(real_path):
        raise DAVError('405 Method Not Allowed: Overwriting directory')

    if os.path.exists(real_path):
        etag = davutils.create_etag(real_path)
    else:
        etag = None

    if not reqinfo.check_ifmatch(etag):
        raise DAVError('412 Precondition Failed')

    new_file = not os.path.exists(real_path)
    if not new_file:
        # Unlink the old file to reset mode bits.
        # This has the additional benefit that old GET operations can
        # continue even if the file is replaced.
        os.unlink(real_path)

    outfile = open(real_path, 'wb')
    block_generator = davutils.read_blocks(reqinfo.wsgi_input)
    davutils.write_blocks(outfile, block_generator)

    if new_file:
        start_response('201 Created', [])
    else:
        start_response('204 No Content', [])

    return ""
Ejemplo n.º 2
0
 def _sql_query(self, *args, **kwargs):
     '''Run a database query and wrap SQLite OperationalErrors, such
     as locked databases.
     '''
     try:
         self.db_cursor.execute(*args, **kwargs)
     except sqlite3.OperationalError, e:
         if 'locked' in e.message:
             raise DAVError('503 Service Unavailable: Lock DB is busy')
         else:
             raise DAVError('500 Internal Server Error: Lock DB: ' +
                            e.message)
Ejemplo n.º 3
0
def handle_lock(reqinfo, start_response):
    '''Create a lock or refresh an existing one.'''
    timeout = reqinfo.get_timeout()
    depth = reqinfo.get_depth()
    real_path = reqinfo.get_request_path('wl')
    rel_path = davutils.get_relpath(real_path, config.root_dir)

    if not reqinfo.lockmanager:
        raise DAVError('501 Not Implemented: Lock support disabled')

    if not reqinfo.length:
        # Handle lock refresh
        lock = reqinfo.lockmanager.refresh_lock(rel_path,
                                                reqinfo.provided_tokens[0][1],
                                                timeout)
    else:
        # Create new lock
        shared, owner = reqinfo.parse_lock_body()
        lock = reqinfo.lockmanager.create_lock(rel_path, shared, owner, depth,
                                               timeout)

    if not os.path.exists(real_path):
        status = "201 Created"
        open(real_path, 'w').write('')
    else:
        status = "200 OK"

    start_response(status, [('Content-Type', 'text/xml; charset=utf-8'),
                            ('Lock-Token', lock.urn)])
    t = activelock.Template(lock=lock, reqinfo=reqinfo, part_only=False)
    return [t.serialize(output='xml')]
Ejemplo n.º 4
0
def handle_copy_move(reqinfo, start_response):
    '''Copy or move a file or a directory.'''
    reqinfo.assert_nobody()
    depth = reqinfo.get_depth()
    real_source = reqinfo.get_request_path('r')
    real_dest = reqinfo.get_destination_path('w')

    new_resource = not os.path.exists(real_dest)
    if not new_resource:
        if not reqinfo.get_overwrite():
            raise DAVError('412 Precondition Failed: Would overwrite')
        elif os.path.isdir(real_dest):
            shutil.rmtree(real_dest)
        else:
            os.unlink(real_dest)

    if reqinfo.environ['REQUEST_METHOD'] == 'COPY':
        if os.path.isdir(real_source):
            if depth == 0:
                os.mkdir(real_dest)
                shutil.copystat(real_source, real_dest)
            else:
                shutil.copytree(real_source, real_dest, symlinks=True)
        else:
            shutil.copy2(real_source, real_dest)
    else:
        real_source = reqinfo.get_request_path('wd')
        shutil.move(real_source, real_dest)
        purge_locks(reqinfo.lockmanager, real_source)

    if new_resource:
        start_response('201 Created', [])
    else:
        start_response('204 No Content', [])
    return ""
Ejemplo n.º 5
0
def main(environ, start_response):
    '''Main WSGI program to handle requests. Calls handlers from
    request_handlers.
    '''
    try:
        logging.info(
            environ.get('REMOTE_ADDR') + ' ' + environ.get('REQUEST_METHOD') +
            ' ' + environ.get('PATH_INFO'))

        request_method = environ.get('REQUEST_METHOD', '').upper()

        if environ.get('HTTP_EXPECT') and __name__ == '__main__':
            # Expect should work with fcgi etc., but not with the simple_server
            # that is used for testing.
            start_response('400 Bad Request: Expect not supported', [])
            return ""

        environ['wsgi.input'] = WSGIInputWrapper(environ)

        try:
            reqinfo = RequestInfo(environ)
            if request_handlers.has_key(request_method):
                return request_handlers[request_method](reqinfo,
                                                        start_response)
            else:
                raise DAVError('501 Not Implemented')
        except DAVError, e:
            environ['wsgi.input'].read()  # Discard request body
            if not e.body:
                logging.warn(e.httpstatus)
                start_response(e.httpstatus, [('Content-Type', 'text/plain')])
                return [e.httpstatus]
            else:
                logging.warn(e.httpstatus + ' ' + e.body)
                start_response(e.httpstatus, [('Content-Type', 'text/xml')])
                return [e.body]
    except:
        import traceback

        exc = traceback.format_exc()
        logging.error('Request handler crashed', exc_info=1)

        if isinstance(environ['wsgi.input'], WSGIInputWrapper):
            environ['wsgi.input'].read()

        try:
            start_response('500 Internal Server Error',
                           [('Content-Type', 'text/plain')])
        except AssertionError:
            # Ignore duplicate start_response
            pass

        return [exc]
Ejemplo n.º 6
0
def handle_unlock(reqinfo, start_response):
    '''Remove an existing lock.'''
    real_path = reqinfo.get_request_path('r')
    rel_path = davutils.get_relpath(real_path, config.root_dir)
    urn = reqinfo.environ.get('HTTP_LOCK_TOKEN', '').strip(' <>')

    if not reqinfo.lockmanager:
        raise DAVError('501 Not implemented: Lock support disabled')

    reqinfo.lockmanager.release_lock(rel_path, urn)
    start_response('204 No Content', [])
    return ""
Ejemplo n.º 7
0
def handle_mkcol(reqinfo, start_response):
    '''Create a new directory.'''
    reqinfo.assert_nobody()
    real_path = reqinfo.get_request_path('w')

    if os.path.exists(real_path):
        raise DAVError('405 Method Not Allowed: Collection already exists')

    os.mkdir(real_path)

    start_response('201 Created', [])
    return ""
Ejemplo n.º 8
0
def proppatch_verify_instruction(real_path, instruction):
    '''Verify that the property can be set on the file, or throw a DAVError.
    Used to verify instructions before they are executed.
    '''
    command, propname, propelement = instruction

    if command == 'set':
        if propelement.getchildren():
            raise DAVError(
                '409 Conflict: XML property values are not supported')

        if not property_handlers.has_key(propname):
            raise DAVError('403 Forbidden: No such property')

        if property_handlers[propname][1] is None:
            raise DAVError('403 Forbidden',
                           '<DAV:cannot-modify-protected-property/>')

    elif command == 'remove':
        # No properties to remove so far.
        raise DAVError('403 Forbidden: Properties cannot be removed')
Ejemplo n.º 9
0
    def release_lock(self, rel_path, urn):
        '''Remove a lock from database. The rel_path must match a lock
        with the specified urn.
        '''

        self._sql_query('BEGIN IMMEDIATE TRANSACTION')
        try:
            if not self.validate_lock(rel_path, urn):
                raise DAVError('409 Conflict',
                               '<DAV:lock-token-matches-request-uri/>')

            self._sql_query('DELETE FROM locks WHERE urn=?', (urn, ))
            self._sql_query('END TRANSACTION')
        except:
            self._sql_query('ROLLBACK')
            raise
Ejemplo n.º 10
0
def handle_delete(reqinfo, start_response):
    '''Delete a file or a directory.'''
    reqinfo.assert_nobody()
    real_path = reqinfo.get_request_path('wd')

    # Locks on parent directory prohibit deletion of members.
    reqinfo.assert_locks(os.path.dirname(real_path))

    if not os.path.exists(real_path):
        raise DAVError('404 Not Found')

    if os.path.isdir(real_path):
        shutil.rmtree(real_path)
    else:
        os.unlink(real_path)

    purge_locks(reqinfo.lockmanager, real_path)

    start_response('204 No Content', [])
    return ""
Ejemplo n.º 11
0
    def refresh_lock(self, rel_path, urn, timeout):
        '''Refresh the given lock and return new Lock object.'''
        timeout = min(timeout, config.lock_max_time) or config.lock_max_time
        valid_until = datetime.datetime.utcnow()
        valid_until += datetime.timedelta(seconds=timeout)

        self._sql_query('BEGIN IMMEDIATE TRANSACTION')
        try:
            if not self.validate_lock(rel_path, urn):
                raise DAVError('412 Precondition Failed',
                               '<DAV:lock-token-matches-request-uri/>')

            self._sql_query('UPDATE locks SET valid_until=? WHERE urn=?',
                            (valid_until, urn))
            self._sql_query('END TRANSACTION')
        except:
            self._sql_query('ROLLBACK')
            raise

        self._sql_query('SELECT * FROM locks WHERE urn=?', (urn, ))
        return Lock(self.db_cursor.fetchone())
Ejemplo n.º 12
0
    def create_lock(self, rel_path, shared, owner, depth, timeout):
        '''Create a lock for the resource defined by rel_path. Arguments
        are as follows:
        rel_path: full path to the resource in local file system
        shared: True for shared lock, False for exclusive lock
        owner: client-provided <DAV:owner> xml string describing the owner of the lock
        depth: -1 for infinite, 0 otherwise
        timeout: Client-requested lock expiration time in seconds from now.
                 Configuration may limit actual timeout.
        
        Returns a Lock object.
        '''
        assert depth in [-1, 0]
        assert not rel_path.startswith('/')

        urn = uuid4().urn
        timeout = min(timeout, config.lock_max_time) or config.lock_max_time
        valid_until = datetime.datetime.utcnow()
        valid_until += datetime.timedelta(seconds=timeout)

        self._sql_query('BEGIN IMMEDIATE TRANSACTION')

        try:
            for lock in self.get_locks(rel_path, depth == -1):
                if not lock.shared or not shared:
                    # Allow only one exclusive lock
                    raise DAVError('423 Locked')

            self._sql_query(
                'INSERT INTO locks VALUES (?,?,?,?,?,?)',
                (urn, rel_path, bool(shared), owner, depth == -1, valid_until))
            self._sql_query('END TRANSACTION')
        except:
            self._sql_query('ROLLBACK')
            raise

        self._sql_query('SELECT * FROM locks WHERE urn=?', (urn, ))
        return Lock(self.db_cursor.fetchone())
Ejemplo n.º 13
0
def handle_get(reqinfo, start_response):
    '''Download a single file or show directory index.'''
    reqinfo.assert_nobody()
    real_path = reqinfo.get_request_path('r')

    if os.path.isdir(real_path):
        return handle_dirindex(reqinfo, start_response)

    etag = davutils.create_etag(real_path)
    if not reqinfo.check_ifmatch(etag):
        raise DAVError('412 Precondition Failed')

    start_response('200 OK', [
        ('Content-Type', davutils.get_mimetype(real_path)), ('Etag', etag),
        ('Content-Length', str(os.path.getsize(real_path))),
        ('Last-Modified', davutils.get_rfcformat(os.path.getmtime(real_path)))
    ])

    if reqinfo.environ['REQUEST_METHOD'] == 'HEAD':
        return ''

    infile = open(real_path, 'rb')
    return davutils.read_blocks(infile)
Ejemplo n.º 14
0
def handle_post(reqinfo, start_response):
    '''Handle a POST request.
    Used for file uploads and deletes in the HTML GUI.
    '''
    if 'w' not in config.html_interface:
        raise DAVError('403 HTML interface is configured as read-only')

    fields = cgi.FieldStorage(fp=reqinfo.wsgi_input, environ=reqinfo.environ)
    real_path = reqinfo.get_request_path('r')
    message = ""

    if fields.getfirst('file'):
        f = fields['file']
        dest_path = os.path.join(real_path, f.filename)
        reqinfo.assert_write(dest_path)

        if os.path.isdir(dest_path):
            raise DAVError('405 Method Not Allowed: Overwriting directory')

        if os.path.exists(dest_path):
            os.unlink(dest_path)

        outfile = open(dest_path, 'wb')
        davutils.write_blocks(outfile, davutils.read_blocks(f.file))

        message = "Successfully uploaded " + f.filename + "."

    if fields.getfirst('btn_remove'):
        filenames = fields.getlist('select')

        for f in filenames:
            rm_path = os.path.join(real_path, f)
            reqinfo.assert_write(rm_path)

            if os.path.isdir(rm_path):
                shutil.rmtree(rm_path)
            else:
                os.unlink(rm_path)

        message = "Successfully removed " + str(len(filenames)) + " files."

    if fields.getfirst('btn_download'):
        filenames = fields.getlist('select')
        datafile = tempfile.TemporaryFile()
        zipobj = zipfile.ZipFile(datafile, 'w', zipfile.ZIP_DEFLATED, True)

        def check_read(path):
            '''Callback function for zipping to verify that each file in
            the zip has access rights.'''
            try:
                reqinfo.assert_read(path)
                return True
            except DAVError:
                return False

        for f in filenames:
            file_path = os.path.join(real_path, f)
            reqinfo.assert_read(file_path)
            davutils.add_to_zip_recursively(zipobj, file_path, config.root_dir,
                                            check_read)

        zipobj.close()

        start_response('200 OK', [('Content-Type', 'application/zip'),
                                  ('Content-Length', str(datafile.tell()))])

        datafile.seek(0)
        return davutils.read_blocks(datafile)

    return handle_dirindex(reqinfo, start_response, message)