def _GET_container(self, path=None):
        """
        GET a representation of the container in the appropriate (content negotiated) format as identified by
        the supplied id
        Args:
        - id:   The ID of the container as supplied in the request URL
        Returns a representation of the container: SSS will return either the Atom Entry identical to the one supplied
        as a deposit receipt or the pure RDF/XML Statement depending on the Accept header
        """
        ssslog.debug(
            "GET on Container (retrieve deposit receipt or statement); Incoming HTTP headers: "
            + str(request.environ))

        # authenticate
        try:
            auth = self.authenticate()

            ss = SwordServer(config, auth)

            # first thing we need to do is check that there is an object to return, because otherwise we may throw a
            # 415 Unsupported Media Type without looking first to see if there is even any media to content negotiate for
            # which would be weird from a client perspective
            if not ss.container_exists(path):
                return self.manage_error(SwordError(status=404, empty=True))

            # get the content negotiation headers
            accept_header = request.environ.get("HTTP_ACCEPT")
            accept_packaging_header = request.environ.get(
                "HTTP_ACCEPT_PACKAGING")

            # do the negotiation
            default_accept_parameters, acceptable = config.get_container_formats(
            )
            cn = ContentNegotiator(default_accept_parameters, acceptable)
            accept_parameters = cn.negotiate(accept=accept_header)
            ssslog.info("Container requested in format: " +
                        str(accept_parameters))

            # did we successfully negotiate a content type?
            if accept_parameters is None:
                raise SwordError(error_uri=Error.content,
                                 status=415,
                                 empty=True)

            # now actually get hold of the representation of the container and send it to the client
            cont = ss.get_container(path, accept_parameters)
            ssslog.info("Returning " + response.status + " from request on " +
                        inspect.stack()[0][3])
            if cont is not None:
                response.headers["Content-Type"] = str(
                    accept_parameters.content_type.mimetype())
            return cont

        except SwordError as e:
            return self.manage_error(e)
Beispiel #2
0
    def DELETE(self, path):
        """
        DELETE the contents of an object in the store (but not the object's container), leaving behind an empty
        container for further use
        Args:
        - id:   the ID of the object to have its content removed as per the requested URI
        Return a Deposit Receipt
        """
        ssslog.debug("DELETE on Media Resource (remove content, leave container); Incoming HTTP headers: " + str(web.ctx.environ))
        
        # find out if delete is allowed
        if not config.allow_delete:
            error = SwordError(error_uri=Errors.method_not_allowed, msg="Delete operations not currently permitted")
            return self.manage_error(error)

        # authenticate
        try:
            auth = self.http_basic_authenticate(web)
            
            # check the validity of the request
            self.validate_delete_request(web, "6.6")
            
            # parse the delete request out of the HTTP request
            delete = self.get_delete(web, auth)
            
            # carry out the delete
            ss = SwordServer(config, auth)
            result = ss.delete_content(path, delete)
            
            # just return, no need to give any more feedback
            web.ctx.status = "204 No Content" # No Content
            return
            
        except SwordError as e:
            return self.manage_error(e)
Beispiel #3
0
 def DELETE(self, path):
     """
     DELETE the container (and everything in it) from the store, as identified by the supplied id
     Args:
     - id:   the ID of the container
     Returns nothing, as there is nothing to return (204 No Content)
     """
     ssslog.debug("DELETE on Container (remove); Incoming HTTP headers: " + str(web.ctx.environ))
     
     try:
         # find out if update is allowed
         if not config.allow_delete:
             raise SwordError(error_uri=Errors.method_not_allowed, msg="Delete operations not currently permitted")
         
         # authenticate
         auth = self.http_basic_authenticate(web)
         
         # check the validity of the request
         self.validate_delete_request(web, "6.8")
         
         # get the delete request
         delete = self.get_delete(web, auth)
        
         # do the delete
         ss = SwordServer(config, auth)
         result = ss.delete_container(path, delete)
         
         # no need to return any content
         web.ctx.status = "204 No Content"
         return
         
     except SwordError as e:
         return self.manage_error(e)
    def _POST_container(self, path=None):
        """
        POST some new content into the container identified by the supplied id,
        or complete an existing deposit (using the In-Progress header)
        Args:
        - id:    The ID of the container as contained in the URL
        Returns a Deposit Receipt
        """
        ssslog.debug(
            "POST to Container (add new content and metadata); Incoming HTTP headers: "
            + str(request.environ))

        # find out if update is allowed
        if not config.allow_update:
            error = SwordError(error_uri=Errors.method_not_allowed,
                               msg="Update operations not currently permitted")
            return self.manage_error(error)

        try:
            # authenticate
            auth = self.authenticate()

            # check the validity of the request
            self.validate_deposit_request("6.7.2",
                                          None,
                                          "6.7.3",
                                          "9.3",
                                          allow_empty=True)

            deposit = self.get_deposit(auth)

            ss = SwordServer(config, auth)
            result = ss.deposit_existing(path, deposit)

            # NOTE: spec says 201 Created for multipart and 200 Ok for metadata only
            # we have implemented 200 OK across the board, in the understanding that
            # in this case the spec is incorrect (correction need to be implemented
            # asap)

            response.headers["Location"] = str(
                result.location)  # explict cast to str
            response.status_int = 200
            response.status = "200 OK"
            if config.return_deposit_receipt:
                response.content_type = "application/atom+xml;type=entry"
                ssslog.info("Returning Deposit Receipt")
                ssslog.info("Returning " + response.status +
                            " from request on " + inspect.stack()[0][3])
                return result.receipt
            else:
                ssslog.info("Omitting Deposit Receipt")
                ssslog.info("Returning " + response.status +
                            " from request on " + inspect.stack()[0][3])
                return

        except SwordError as e:
            return self.manage_error(e)
    def _GET_media_resource(self, path=None):
        """
        GET the media resource content in the requested format (web request will include content negotiation via
        Accept header)
        Args:
        - id:   the ID of the object in the store
        Returns the content in the requested format
        """
        ssslog.debug("GET on MediaResource; Incoming HTTP headers: " +
                     str(request.environ))

        # NOTE: this method is not authenticated - we imagine sharing this URL with end-users who will just want
        # to retrieve the content.
        ss = SwordServer(config, None)

        # first thing we need to do is check that there is an object to return, because otherwise we may throw a
        # 406 Not Acceptable without looking first to see if there is even any media to content negotiate for
        # which would be weird from a client perspective
        if not ss.media_resource_exists(path):
            return self.manage_error(SwordError(status=404, empty=True))

        # get the content negotiation headers
        accept_header = request.environ.get("HTTP_ACCEPT")
        accept_packaging_header = request.environ.get("HTTP_ACCEPT_PACKAGING")

        # do the negotiation
        default_accept_parameters, acceptable = config.get_media_resource_formats(
        )
        cn = ContentNegotiator(default_accept_parameters, acceptable)
        accept_parameters = cn.negotiate(
            accept=accept_header, accept_packaging=accept_packaging_header)

        ssslog.info("Conneg format: " + str(accept_parameters))

        try:
            # can get hold of the media resource
            media_resource = ss.get_media_resource(path, accept_parameters)
        except SwordError as e:
            return self.manage_error(e)

        # either send the client a redirect, or stream the content out
        if media_resource.redirect:
            redirect(media_resource.url, _code=302)  # FOUND (not SEE OTHER)
            return
        else:
            response.content_type = media_resource.content_type
            if media_resource.packaging is not None:
                response.headers["Packaging"] = str(media_resource.packaging)
            f = open(media_resource.filepath, "r")
            response.status_int = 200
            response.status = "200 OK"
            ssslog.info("Returning " + response.status + " from request on " +
                        inspect.stack()[0][3])
            return f.read()
    def http_basic_authenticate(self):
        ssslog.debug("Attempting HTTP Basic Authentication")

        # extract the appropriate HTTP headers
        auth_header = request.environ.get('HTTP_AUTHORIZATION')
        obo = request.environ.get(HEADER_MAP[HttpHeaders.on_behalf_of])

        # if we're not supplied with an auth header, bounce
        if auth_header is None:
            ssslog.info(
                "No auth header supplied; will return 401 with SSS realm")
            response.headers['WWW-Authenticate'] = 'Basic realm="SSS"'
            raise SwordError(status=401, empty=True)

        # deconstruct the BASIC auth header
        try:
            auth_header = re.sub('^Basic ', '', auth_header)
            username, password = base64.decodestring(auth_header).split(':')
            ssslog.debug("successfully interpreted Basic Auth header")
        except Exception as e:
            # could be exceptions in either decoding the header or in doing a split
            ssslog.error("unable to interpret authentication header: " +
                         auth_header)
            raise SwordError(error_uri=Errors.bad_request,
                             msg="unable to interpret authentication header")

        ssslog.info("Authentication details: " + str(username) +
                    ":[**password**]; On Behalf Of: " + str(obo))

        authenticator = Authenticator(config)
        try:
            auth = authenticator.basic_authenticate(username, password, obo)
        except AuthException as e:
            if e.authentication_failed:
                raise SwordError(status=401, empty=True)
            elif e.target_owner_unknown:
                raise SwordError(error_uri=Errors.target_owner_unknown,
                                 msg="unknown user " + str(obo) +
                                 " as on behalf of user")

        return auth
Beispiel #7
0
 def validate_delete_request(self, web, section):
     h = HttpHeaders()
     
     # map the headers to standard http
     mapped_headers = self._map_webpy_headers(web.ctx.environ)
     ssslog.debug("Validating on header dictionary: " + str(mapped_headers))
     
     try:
         # now validate the http headers
         h.validate(mapped_headers, section)
     except ValidationError as e:
         raise SwordError(error_uri=Errors.bad_request, msg=e.message)
    def _GET_part(self, path):
        ss = SwordServer(config, None)

        # if we did, we can get hold of the media resource
        fh = ss.get_part(path)

        if fh is None:
            return self.manage_error(SwordError(status=404, empty=True))

        response.content_type = "application/octet-stream"  # FIXME: we're not keeping track of content types
        response.status_int = 200
        response.status = "200 OK"
        return fh.read()
    def _POST_media_resource(self, path=None):
        """
        POST a simple package into the specified media resource
        Args:
        - id:   The ID of the media resource as specified in the requested URL
        Returns a Deposit Receipt
        """
        ssslog.debug(
            "POST to Media Resource (add new file); Incoming HTTP headers: " +
            str(request.environ))

        # find out if update is allowed
        if not config.allow_update:
            error = SwordError(error_uri=Errors.method_not_allowed,
                               msg="Update operations not currently permitted")
            return self.manage_error(error)

        # authenticate
        try:
            auth = self.authenticate()

            # check the validity of the request
            self.validate_deposit_request(None,
                                          "6.7.1",
                                          None,
                                          allow_multipart=False)

            deposit = self.get_deposit(auth)

            # if we get here authentication was successful and we carry on
            ss = SwordServer(config, auth)
            result = ss.add_content(path, deposit)

            response.content_type = "application/atom+xml;type=entry"
            response.headers["Location"] = str(
                result.location)  # explict cast to str
            response.status_int = 201
            response.status = "201 Created"
            if config.return_deposit_receipt:
                ssslog.info("Returning Receipt")
                ssslog.info("Returning " + response.status +
                            " from request on " + inspect.stack()[0][3])
                return result.receipt
            else:
                ssslog.info("Omitting Receipt")
                ssslog.info("Returning " + response.status +
                            " from request on " + inspect.stack()[0][3])
                return

        except SwordError as e:
            return self.manage_error(e)
    def repoze_who_authenticate(self):
        ssslog.debug("Authentication handled by repoze.who")

        # get the auth details
        identity = request.environ.get("repoze.who.identity")
        obo = request.environ.get(HEADER_MAP[HttpHeaders.on_behalf_of])

        ssslog.info("Authentication details: " +
                    str(identity["repoze.who.userid"]) + "; On Behalf Of: " +
                    str(obo))

        authenticator = Authenticator(config)
        try:
            auth = authenticator.repoze_who_authenticate(identity, obo)
        except AuthException as e:
            if e.authentication_failed:
                raise SwordError(status=401, empty=True)
            elif e.target_owner_unknown:
                raise SwordError(error_uri=Errors.target_owner_unknown,
                                 msg="unknown user " + str(obo) +
                                 " as on behalf of user")

        return auth
Beispiel #11
0
    def validate_deposit_request(self, web, entry_section=None, binary_section=None, multipart_section=None, empty_section=None, allow_multipart=True, allow_empty=False):
        h = HttpHeaders()

        # map the headers to standard http
        mapped_headers = self._map_webpy_headers(web.ctx.environ)
        ssslog.debug("Validating on header dictionary: " + str(mapped_headers))
  
        # run the validation
        try:
            # there must be both an "atom" and "payload" input or data in web.data()
            webin = web.input()
            if len(webin) != 2 and len(webin) > 0:
                raise ValidationException("Multipart request does not contain exactly 2 parts")
            if len(webin) >= 2 and not webin.has_key("atom") and not webin.has_key("payload"):
                raise ValidationException("Multipart request must contain Content-Dispositions with names 'atom' and 'payload'")
            if len(webin) > 0 and not allow_multipart:
                raise ValidationException("Multipart request not permitted in this context")

            # if we get to here then we have a valid multipart or no multipart
            is_multipart = False
            is_empty = False
            if len(webin) != 2: # if it is not multipart
                if web.data() is None or web.data().strip() == "": # FIXME: this does not look safe to scale
                    if allow_empty:
                        ssslog.info("Validating an empty deposit (could be a control operation)")
                        is_empty = True
                    else:
                        raise ValidationException("No content sent to the server")
            else:
                ssslog.info("Validating a multipart deposit")
                is_multipart = True
            
            is_entry = False
            content_type = mapped_headers.get("CONTENT-TYPE")
            if content_type is not None and content_type.startswith("application/atom+xml"):
                ssslog.info("Validating an atom-only deposit")
                is_entry = True
            
            if not is_entry and not is_multipart and not is_empty:
                ssslog.info("Validating a binary deposit")
            
            section = entry_section if is_entry else multipart_section if is_multipart else empty_section if is_empty else binary_section
            
            # now validate the http headers
            h.validate(mapped_headers, section)
            
        except ValidationException as e:
            raise SwordError(error_uri=Errors.bad_request, msg=e.message)
    def _PUT_container(self, path=None):
        """
        PUT a new Entry over the existing entry, or a multipart request over
        both the existing metadata and the existing content
        """
        ssslog.debug("PUT on Container (replace); Incoming HTTP headers: " +
                     str(request.environ))

        # find out if update is allowed
        if not config.allow_update:
            error = SwordError(error_uri=Errors.method_not_allowed,
                               msg="Update operations not currently permitted")
            return self.manage_error(error)

        try:
            # authenticate
            auth = self.authenticate()

            # check the validity of the request
            self.validate_deposit_request("6.5.2", None, "6.5.3")

            # get the deposit object
            deposit = self.get_deposit(auth)

            ss = SwordServer(config, auth)
            result = ss.replace(path, deposit)

            response.headers["Location"] = str(
                result.location)  # explicit cast to str
            if config.return_deposit_receipt:
                response.content_type = "application/atom+xml;type=entry"
                response.status_int = 200
                response.status = "200 OK"
                ssslog.info("Returning Deposit Receipt")
                ssslog.info("Returning " + response.status +
                            " from request on " + inspect.stack()[0][3])
                return result.receipt
            else:
                response.status_int = 204
                response.status = "204 No Content"
                ssslog.info("Omitting Deposit Receipt")
                ssslog.info("Returning " + response.status +
                            " from request on " + inspect.stack()[0][3])
                return

        except SwordError as e:
            return self.manage_error(e)
    def _PUT_media_resource(self, path=None):
        """
        PUT a new package onto the object identified by the supplied id
        Args:
        - id:   the ID of the media resource as specified in the URL
        Returns a Deposit Receipt
        """
        ssslog.debug(
            "PUT on Media Resource (replace); Incoming HTTP headers: " +
            str(request.environ))

        # find out if update is allowed
        if not config.allow_update:
            error = SwordError(error_uri=Errors.method_not_allowed,
                               msg="Update operations not currently permitted")
            return self.manage_error(error)

        # authenticate
        try:
            auth = self.authenticate()

            # check the validity of the request (note that multipart requests
            # and atom-only are not permitted in this method)
            self.validate_deposit_request(None,
                                          "6.5.1",
                                          None,
                                          allow_multipart=False)

            # get a deposit object.  The PUT operation only supports a single binary deposit, not an Atom Multipart one
            # so if the deposit object has an atom part we should return an error
            deposit = self.get_deposit(auth)

            # now replace the content of the container
            ss = SwordServer(config, auth)
            result = ss.replace(path, deposit)

            # replaced
            ssslog.info("Content replaced")
            response.status_int = 204
            response.status = "204 No Content"  # notice that this is different from the POST as per AtomPub
            ssslog.info("Returning " + response.status + " from request on " +
                        inspect.stack()[0][3])
            return

        except SwordError as e:
            return self.manage_error(e)
Beispiel #14
0
    def POST(self, path):
        """
        POST some new content into the container identified by the supplied id,
        or complete an existing deposit (using the In-Progress header)
        Args:
        - id:    The ID of the container as contained in the URL
        Returns a Deposit Receipt
        """
        ssslog.debug("POST to Container (add new content and metadata); Incoming HTTP headers: " + str(web.ctx.environ))
        
        # find out if update is allowed
        if not config.allow_update:
            error = SwordError(error_uri=Errors.method_not_allowed, msg="Update operations not currently permitted")
            return self.manage_error(error)

        try:
             # authenticate
            auth = self.http_basic_authenticate(web)
            
            # check the validity of the request
            self.validate_deposit_request(web, "6.7.2", None, "6.7.3", "9.3", allow_empty=True)
            
            deposit = self.get_deposit(web, auth)
            
            ss = SwordServer(config, auth)
            result = ss.deposit_existing(path, deposit)
            
            # NOTE: spec says 201 Created for multipart and 200 Ok for metadata only
            # we have implemented 200 OK across the board, in the understanding that
            # in this case the spec is incorrect (correction need to be implemented
            # asap)
            
            web.header("Location", result.location)
            web.ctx.status = "200 OK"
            if config.return_deposit_receipt:
                web.header("Content-Type", "application/atom+xml;type=entry")
                return result.receipt
            else:
                return
            
        except SwordError as e:
            return self.manage_error(e)
Beispiel #15
0
    def PUT(self, path):
        """
        PUT a new package onto the object identified by the supplied id
        Args:
        - id:   the ID of the media resource as specified in the URL
        Returns a Deposit Receipt
        """
        ssslog.debug("PUT on Media Resource (replace); Incoming HTTP headers: " + str(web.ctx.environ))
        
        # find out if update is allowed
        if not config.allow_update:
            error = SwordError(error_uri=Errors.method_not_allowed, msg="Update operations not currently permitted")
            return self.manage_error(error)

        # authenticate
        try:
            auth = self.http_basic_authenticate(web)
            
            # check the validity of the request (note that multipart requests 
            # and atom-only are not permitted in this method)
            self.validate_deposit_request(web, None, "6.5.1", None, allow_multipart=False)
            
            # get a deposit object.  The PUT operation only supports a single binary deposit, not an Atom Multipart one
            # so if the deposit object has an atom part we should return an error
            deposit = self.get_deposit(web, auth)
            
            # set in_progress to None, since this is not relevant to this kind of deposit
            # the repository can then use that as a cue that it should leave the state as-is
            deposit.in_progress = None
            
            # now replace the content of the container
            ss = SwordServer(config, auth)
            result = ss.replace(path, deposit)
            
            # replaced
            ssslog.info("Content replaced")
            web.ctx.status = "204 No Content" # notice that this is different from the POST as per AtomPub
            return
            
        except SwordError as e:
            return self.manage_error(e)
    def _DELETE_container(self, path=None):
        """
        DELETE the container (and everything in it) from the store, as identified by the supplied id
        Args:
        - id:   the ID of the container
        Returns nothing, as there is nothing to return (204 No Content)
        """
        ssslog.debug("DELETE on Container (remove); Incoming HTTP headers: " +
                     str(request.environ))

        try:
            # find out if update is allowed
            if not config.allow_delete:
                raise SwordError(
                    error_uri=Errors.method_not_allowed,
                    msg="Delete operations not currently permitted")

            # authenticate
            auth = self.authenticate()

            # check the validity of the request
            self.validate_delete_request("6.8")

            # get the delete request
            delete = self.get_delete(auth)

            # do the delete
            ss = SwordServer(config, auth)
            result = ss.delete_container(path, delete)

            # no need to return any content
            response.status_int = 204
            response.status = "204 No Content"
            ssslog.info("Returning " + response.status + " from request on " +
                        inspect.stack()[0][3])
            return

        except SwordError as e:
            return self.manage_error(e)
Beispiel #17
0
 def GET(self, path):
     ssslog.debug("GET on Statement (retrieve); Incoming HTTP headers: " + str(web.ctx.environ))
     
     try:
         # authenticate
         auth = self.http_basic_authenticate(web)
         
         ss = SwordServer(config, auth)
         
         # first thing we need to do is check that there is an object to return, because otherwise we may throw a
         # 415 Unsupported Media Type without looking first to see if there is even any media to content negotiate for
         # which would be weird from a client perspective
         if not ss.container_exists(path):
             raise SwordError(status=404, empty=True)
         
         # FIXME: need to include a Content-Type header
         
         # now actually get hold of the representation of the statement and send it to the client
         cont = ss.get_statement(path)
         return cont
         
     except SwordError as e:
         return self.manage_error(e)
Beispiel #18
0
 def POST(self, path):
     """
     POST a simple package into the specified media resource
     Args:
     - id:   The ID of the media resource as specified in the requested URL
     Returns a Deposit Receipt
     """
     ssslog.debug("POST to Media Resource (add new file); Incoming HTTP headers: " + str(web.ctx.environ))
     
     # find out if update is allowed
     if not config.allow_update:
         error = SwordError(error_uri=Errors.method_not_allowed, msg="Update operations not currently permitted")
         return self.manage_error(error)
         
     # authenticate
     try:
         auth = self.http_basic_authenticate(web)
         
         # check the validity of the request
         self.validate_deposit_request(web, None, "6.7.1", None, allow_multipart=False)
         
         deposit = self.get_deposit(web, auth)
         
         # if we get here authentication was successful and we carry on
         ss = SwordServer(config, auth)
         result = ss.add_content(path, deposit)
         
         web.header("Content-Type", "application/atom+xml;type=entry")
         web.header("Location", result.location)
         web.ctx.status = "201 Created"
         if config.return_deposit_receipt:
             return result.receipt
         else:
             return
         
     except SwordError as e:
         return self.manage_error(e)
Beispiel #19
0
 def PUT(self, path):
     """
     PUT a new Entry over the existing entry, or a multipart request over
     both the existing metadata and the existing content
     """
     ssslog.debug("PUT on Container (replace); Incoming HTTP headers: " + str(web.ctx.environ))
     
     # find out if update is allowed
     if not config.allow_update:
         error = SwordError(error_uri=Errors.method_not_allowed, msg="Update operations not currently permitted")
         return self.manage_error(error)
     
     try:
         # authenticate
         auth = self.http_basic_authenticate(web)
         
         # check the validity of the request
         self.validate_deposit_request(web, "6.5.2", None, "6.5.3")
         
         # get the deposit object
         deposit = self.get_deposit(web, auth)
         
         ss = SwordServer(config, auth)
         result = ss.replace(path, deposit)
         
         web.header("Location", result.location)
         if config.return_deposit_receipt:
             web.header("Content-Type", "application/atom+xml;type=entry")
             web.ctx.status = "200 OK"
             return result.receipt
         else:
             web.ctx.status = "204 No Content"
             return
             
     except SwordError as e:
         return self.manage_error(e)
    def _GET_statement(self, path=None):
        ssslog.debug("GET on Statement (retrieve); Incoming HTTP headers: " +
                     str(request.environ))

        try:
            # authenticate
            auth = self.authenticate()

            ss = SwordServer(config, auth)

            # first thing we need to do is check that there is an object to return, because otherwise we may throw a
            # 415 Unsupported Media Type without looking first to see if there is even any media to content negotiate for
            # which would be weird from a client perspective
            if not ss.container_exists(path):
                raise SwordError(status=404, empty=True)

            # now actually get hold of the representation of the statement and send it to the client
            cont = ss.get_statement(path)
            ssslog.info("Returning " + response.status + " from request on " +
                        inspect.stack()[0][3])
            return cont

        except SwordError as e:
            return self.manage_error(e)
    def validate_deposit_request(self,
                                 entry_section=None,
                                 binary_section=None,
                                 multipart_section=None,
                                 empty_section=None,
                                 allow_multipart=True,
                                 allow_empty=False):
        h = HttpHeaders()

        # map the headers to standard http
        mapped_headers = self._map_webpy_headers(request.environ)
        ssslog.debug("Validating on header dictionary: " + str(mapped_headers))

        # run the validation
        try:
            # there must be both an "atom" and "payload" input or data in web.data()

            # FIXME: deposit does NOT support multipart
            if request.environ["CONTENT_TYPE"].startswith("multipart"):
                raise SwordError(
                    error_uri=Errors.method_not_allowed,
                    msg=
                    "Pylons implementation does not currently support multipart/related requests"
                )
            """
            # leave this out until we can get multipart sorted (at a later date)
            webin = request.POST
            if len(webin) != 2 and len(webin) > 0:
                raise ValidationException("Multipart request does not contain exactly 2 parts")
            if len(webin) >= 2 and not webin.has_key("atom") and not webin.has_key("payload"):
                raise ValidationException("Multipart request must contain Content-Dispositions with names 'atom' and 'payload'")
            if len(webin) > 0 and not allow_multipart:
                raise ValidationException("Multipart request not permitted in this context")
            """

            # if we get to here then we have a valid multipart or no multipart
            is_multipart = False
            is_empty = False
            #if len(webin) != 2: # if it is not multipart
            # FIXME: this is reading everything in, and should be re-evaluated for performance/scalability
            # wsgi_input = request.environ['wsgi.input']
            f = request.body_file
            f.seek(0, 0)

            # FIXME: can't we just look at the Content-Type header????

            # read a byte in, to see if the body is empty
            if f is not None:
                byte = f.read(1)
                if byte == "" and allow_empty:
                    ssslog.debug(
                        "first byte of deposit request is the empty string")
                    is_empty = True
                else:
                    ssslog.debug("first byte of deposit request is \"" +
                                 str(byte) + "\" ... not an empty request")
                f.seek(0, 0)
            else:
                is_empty = True

            # validate whether we allow an empty deposit
            if is_empty and not allow_empty:
                raise ValidationException("No content sent to the server")
            elif is_empty and allow_empty:
                ssslog.info(
                    "Validating an empty deposit (could be a control operation)"
                )

            #else:
            #    ssslog.info("Validating a multipart deposit")
            #    is_multipart = True

            is_entry = False
            content_type = mapped_headers.get("CONTENT-TYPE")
            if content_type is not None and content_type.startswith(
                    "application/atom+xml"):
                ssslog.info("Validating an atom-only deposit")
                is_entry = True

            if not is_entry and not is_multipart and not is_empty:
                ssslog.info("Validating a binary deposit")

            section = entry_section if is_entry else multipart_section if is_multipart else empty_section if is_empty else binary_section

            # now validate the http headers
            h.validate(mapped_headers, section)

        except ValidationException as e:
            raise SwordError(error_uri=Errors.bad_request, msg=e.message)
    def get_deposit(self, auth=None, atom_only=False):
        # FIXME: this does not deal with the Media Part headers on a multipart deposit
        """
        Take a request object and extract from it the parameters and content required for a SWORD deposit.  This
        includes determining whether this is an Atom Multipart request or not, and extracting the atom/payload where
        appropriate.  It also includes extracting the HTTP headers which are relevant to deposit, and for those not
        supplied providing their defaults in the returned DepositRequest object
        """
        d = DepositRequest()

        # map the webpy headers to something more standard
        mapped_headers = self._map_webpy_headers(request.environ)

        # get the headers that have been provided.  Any headers which have not been provided will
        # will have default values applied
        h = HttpHeaders()
        d.set_from_headers(h.get_sword_headers(mapped_headers))

        if d.content_type.startswith("application/atom+xml"):
            atom_only = True

        empty_request = False
        if d.content_length == 0:
            ssslog.info("Received empty deposit request")
            empty_request = True
        if config.max_upload_size is not None and d.content_length > config.max_upload_size:
            raise SwordError(
                error_uri=Errors.max_upload_size_exceeded,
                msg="Max upload size is " + str(config.max_upload_size) +
                "; incoming content length was " + str(d.content_length))

        # FIXME: this method does NOT support multipart
        # find out if this is a multipart or not
        is_multipart = False

        # FIXME: these headers aren't populated yet, because the webpy api doesn't
        # appear to have a mechanism to retrieve them.  urgh.
        #entry_part_headers = {}
        #media_part_headers = {}
        #webin = request.POST
        #ssslog.debug(webin)
        #if len(webin) == 2:
        #    ssslog.info("Received multipart deposit request")
        #    d.atom = webin['atom']
        # FIXME: this reads the payload into memory, we need to sort that out
        # read the zip file from the base64 encoded string
        #    d.content = base64.decodestring(webin['payload'])
        #    is_multipart = True
        #elif not empty_request:
        if not empty_request:
            # for this section, we have to reset the file pointer in the body_file
            # part of the request back to the start, since it may have
            # already been read once
            f = request.body_file
            f.seek(0, 0)

            # if this wasn't a multipart, and isn't an empty request, then read the
            # data from the body_file
            if atom_only:
                # we don't worry about scalability here - the entries should be
                # generally small
                ssslog.info("Received Entry deposit request")
                d.atom = f.read()
            else:
                ssslog.info("Received Binary deposit request")
                # FIXME: this is reading everything in, and should be re-evaluated for performance/scalability
                d.content_file = f

        if is_multipart:
            d.filename = h.extract_filename(media_part_headers)
        else:
            d.filename = h.extract_filename(mapped_headers)

        # now just attach the authentication data and return
        d.auth = auth
        return d
Beispiel #23
0
 def get_deposit(self, web, auth=None, atom_only=False):
     # FIXME: this reads files into memory, and therefore does not scale
     # FIXME: this does not deal with the Media Part headers on a multipart deposit
     """
     Take a web.py web object and extract from it the parameters and content required for a SWORD deposit.  This
     includes determining whether this is an Atom Multipart request or not, and extracting the atom/payload where
     appropriate.  It also includes extracting the HTTP headers which are relevant to deposit, and for those not
     supplied providing their defaults in the returned DepositRequest object
     """
     d = DepositRequest()
     
     # map the webpy headers to something more standard
     mapped_headers = self._map_webpy_headers(web.ctx.environ)
     
     # get the headers that have been provided.  Any headers which have not been provided will
     # will have default values applied
     h = HttpHeaders()
     d.set_from_headers(h.get_sword_headers(mapped_headers))
     
     if d.content_type.startswith("application/atom+xml"):
         atom_only=True
     
     empty_request = False
     if d.content_length == 0:
         ssslog.info("Received empty deposit request")
         empty_request = True
     if d.content_length > config.max_upload_size:
         raise SwordError(error_uri=Errors.max_upload_size_exceeded, 
                         msg="Max upload size is " + str(config.max_upload_size) + 
                         "; incoming content length was " + str(d.content_length))
     
     # find out if this is a multipart or not
     is_multipart = False
     
     # FIXME: these headers aren't populated yet, because the webpy api doesn't
     # appear to have a mechanism to retrieve them.  urgh.
     entry_part_headers = {}
     media_part_headers = {}
     webin = web.input()
     if len(webin) == 2:
         ssslog.info("Received multipart deposit request")
         d.atom = webin['atom']
         # FIXME: this reads the payload into memory, we need to sort that out
         # read the zip file from the base64 encoded string
         d.content = base64.decodestring(webin['payload'])
         is_multipart = True
     elif not empty_request:
         # if this wasn't a multipart, and isn't an empty request, then the data is in web.data().  This could be a binary deposit or
         # an atom entry deposit - reply on the passed/determined argument to determine which
         if atom_only:
             ssslog.info("Received Entry deposit request")
             d.atom = web.data()
         else:
             ssslog.info("Received Binary deposit request")
             d.content = web.data()
     
     if is_multipart:
         d.filename = h.extract_filename(media_part_headers)
     else:
         d.filename = h.extract_filename(mapped_headers)
     
     # now just attach the authentication data and return
     d.auth = auth
     return d