def GET(self, env, start_response): """ Handle GET Service request """ env['QUERY_STRING'] = 'format=json' body_iter = self._app_call(env) status = self._get_status_int() if status != HTTP_OK: if status in (HTTP_UNAUTHORIZED, HTTP_FORBIDDEN): return get_err_response('AccessDenied') else: return get_err_response('InvalidURI') containers = loads(''.join(list(body_iter))) # we don't keep the creation time of a backet (s3cmd doesn't # work without that) so we use something bogus. if containers: owner = containers[0].get('owner', '') else: owner = '' body = '<?xml version="1.0" encoding="UTF-8"?>' \ '<ListAllMyBucketsResult ' \ 'xmlns="http://doc.s3.amazonaws.com/2006-03-01">'\ '<Owner><ID>%s</ID><DisplayName>%s</DisplayName></Owner>'\ '<Buckets>%s</Buckets>' \ '</ListAllMyBucketsResult>' \ % (xml_escape(owner), xml_escape(owner), "".join(['<Bucket><Name>%s</Name><CreationDate>' '2009-02-03T16:45:09.000Z</CreationDate></Bucket>' % xml_escape(i['name']) for i in containers])) resp = Response(status=HTTP_OK, content_type='application/xml', body=body) return resp
def GETorHEAD(self, env, start_response): if env['REQUEST_METHOD'] == 'HEAD': head = True env['REQUEST_METHOD'] = 'GET' else: head = False if 'QUERY_STRING' in env: args = dict(urlparse.parse_qsl(env['QUERY_STRING'], 1)) else: args = {} # Let s3multi handle it. if 'uploadId' in args: return self.app(env, start_response) env['QUERY_STRING'] = '' if 'acl' in args: env['QUERY_STRING'] += 'acl' env['REQUEST_METHOD'] = 'HEAD' if 'versionId' in args: env['QUERY_STRING'] += 'versionId=%s' % args['versionId'] app_iter = self._app_call(env) if head: app_iter = None if 'acl' in args and not head: env['REQUEST_METHOD'] = 'GET' # recover HTTP method status = self._get_status_int() headers = dict(self._response_headers) if is_success(status): if 'QUERY_STRING' in env: args = dict(urlparse.parse_qsl(env['QUERY_STRING'], 1)) else: args = {} if 'acl' in args: resp = get_s3_acl(headers, obj_server.ACL_HEADERS, 'object') return resp new_hdrs = {} for key, val in headers.iteritems(): _key = key.lower() if _key.startswith('x-object-meta-'): new_hdrs['x-amz-meta-' + key[14:]] = val elif _key in ('content-length', 'content-type', 'content-range', 'content-encoding', 'etag', 'last-modified'): new_hdrs[key] = val return Response(status=status, headers=new_hdrs, app_iter=app_iter) elif status in (HTTP_UNAUTHORIZED, HTTP_FORBIDDEN): return get_err_response('AccessDenied') elif status == HTTP_NOT_FOUND: return get_err_response('NoSuchKey') else: return get_err_response('InvalidURI')
def DELETE(self, env, start_response): """ Handle DELETE Object request """ body_iter = self._app_call(env) status = self._get_status_int() if status != HTTP_NO_CONTENT: if status in (HTTP_UNAUTHORIZED, HTTP_FORBIDDEN): return get_err_response('AccessDenied') elif status == HTTP_NOT_FOUND: return get_err_response('NoSuchKey') else: return get_err_response('InvalidURI') resp = Response() resp.status = HTTP_NO_CONTENT return resp
def PUT(self, env, start_response): """ Handle PUT Object and PUT Object (Copy) request """ if 'QUERY_STRING' in env: args = dict(urlparse.parse_qsl(env['QUERY_STRING'], True)) else: args = {} acl = 'acl' in args if acl: res = acp_to_headers(env, 'object') if res: return res env['QUERY_STRING'] = 'acl' env['REQUEST_METHOD'] = 'POST' else: for key, value in env.items(): if key.startswith('HTTP_X_AMZ_META_'): del env[key] env['HTTP_X_OBJECT_META_' + key[16:]] = value elif key == 'HTTP_CONTENT_MD5': if value == '': return get_err_response('InvalidDigest') try: env['HTTP_ETAG'] = value.decode('base64').encode('hex') except: return get_err_response('InvalidDigest') if env['HTTP_ETAG'] == '': return get_err_response('SignatureDoesNotMatch') elif key == 'HTTP_X_AMZ_COPY_SOURCE': env['HTTP_X_COPY_FROM'] = value body_iter = self._app_call(env) status = self._get_status_int() success_status = HTTP_ACCEPTED if acl else HTTP_CREATED if status != success_status: if status in (HTTP_UNAUTHORIZED, HTTP_FORBIDDEN): return get_err_response('AccessDenied') elif status == HTTP_NOT_FOUND: return get_err_response('NoSuchBucket') elif status == HTTP_UNPROCESSABLE_ENTITY: return get_err_response('InvalidDigest') else: return get_err_response('InvalidURI') if not acl and 'HTTP_X_COPY_FROM' in env: body = '<CopyObjectResult>' \ '<ETag>"%s"</ETag>' \ '</CopyObjectResult>' % self._response_header_value('etag') return Response(status=HTTP_OK, body=body) kwargs = {'status': HTTP_OK} if not acl: kwargs['etag'] = self._response_header_value('etag') return Response(**kwargs)
def _delete_multiple_objects(self, env): def _object_key_iter(xml): dom = parseString(xml) delete = dom.getElementsByTagName('Delete')[0] for obj in delete.getElementsByTagName('Object'): key = obj.getElementsByTagName('Key')[0].firstChild.data version = None if obj.getElementsByTagName('VersionId').length > 0: version = obj.getElementsByTagName('VersionId')[0]\ .firstChild.data yield (key, version) def _get_deleted_elem(key): return ' <Deleted>\r\n' \ ' <Key>%s</Key>\r\n' \ ' </Deleted>\r\n' % key def _get_err_elem(key, err_code, message): return ' <Error>\r\n' \ ' <Key>%s</Key>\r\n' \ ' <Code>%s</Code>\r\n' \ ' <Message>%s</Message>\r\n' \ ' </Error>\r\n' % (key, err_code, message) body = '<?xml version="1.0" encoding="UTF-8"?>\r\n' \ '<DeleteResult ' \ 'xmlns="http://doc.s3.amazonaws.com/2006-03-01">\r\n' xml = env['wsgi.input'].read() for key, version in _object_key_iter(xml): if version is not None: # TODO: delete the specific version of the object return get_err_response('Unsupported') tmp_env = dict(env) del tmp_env['QUERY_STRING'] tmp_env['CONTENT_LENGTH'] = '0' tmp_env['REQUEST_METHOD'] = 'DELETE' controller = ObjectController(tmp_env, self.app, self.account_name, env['HTTP_X_AUTH_TOKEN'], self.container_name, key) body_iter = controller._app_call(tmp_env) status = controller._get_status_int() if status == HTTP_NO_CONTENT or status == HTTP_NOT_FOUND: body += _get_deleted_elem(key) else: if status == HTTP_UNAUTHORIZED: body += _get_err_elem(key, 'AccessDenied', 'Access Denied') else: body += _get_err_elem(key, 'InvalidURI', 'Invalid URI') body += '</DeleteResult>\r\n' return Response(status=HTTP_OK, body=body)
def POST(self, env, start_response): """ Handle POST Bucket (Delete/Upload Multiple Objects) request """ if 'QUERY_STRING' in env: args = dict(urlparse.parse_qsl(env['QUERY_STRING'], 1)) else: args = {} if 'delete' in args: return self._delete_multiple_objects(env) if 'uploads' in args: # Pass it through, the s3multi upload helper will handle it. return self.app(env, start_response) if 'uploadId' in args: # Pass it through, the s3multi upload helper will handle it. return self.app(env, start_response) return get_err_response('Unsupported')
def handle_request(self, env, start_response): req = Request(env) self.logger.debug('Calling Swift3 Middleware') self.logger.debug(req.__dict__) if 'AWSAccessKeyId' in req.params: try: req.headers['Date'] = req.params['Expires'] req.headers['Authorization'] = \ 'AWS %(AWSAccessKeyId)s:%(Signature)s' % req.params except KeyError: return get_err_response('InvalidArgument')(env, start_response) if 'Authorization' not in req.headers: return self.app(env, start_response) try: keyword, info = req.headers['Authorization'].split(' ') except: return get_err_response('AccessDenied')(env, start_response) if keyword != 'AWS': return get_err_response('AccessDenied')(env, start_response) try: account, signature = info.rsplit(':', 1) except: return get_err_response('InvalidArgument')(env, start_response) try: controller, path_parts = self.get_controller(env, env['PATH_INFO']) except ValueError: return get_err_response('InvalidURI')(env, start_response) if 'Date' in req.headers: date = email.utils.parsedate(req.headers['Date']) if date is None and 'Expires' in req.params: d = email.utils.formatdate(float(req.params['Expires'])) date = email.utils.parsedate(d) if date is None: return get_err_response('AccessDenied')(env, start_response) d1 = datetime.datetime(*date[0:6]) d2 = datetime.datetime.utcnow() epoch = datetime.datetime(1970, 1, 1, 0, 0, 0, 0) if d1 < epoch: return get_err_response('AccessDenied')(env, start_response) delta = datetime.timedelta(seconds=60 * 5) if d1 - d2 > delta or d2 - d1 > delta: return get_err_response('RequestTimeTooSkewed')(env, start_response) token = base64.urlsafe_b64encode(canonical_string(req)) controller = controller(env, self.app, account, token, conf=self.conf, **path_parts) if hasattr(controller, req.method): res = getattr(controller, req.method)(env, start_response) else: return get_err_response('InvalidURI')(env, start_response) return res(env, start_response)
def POST(self, env, start_response): return get_err_response('AccessDenied')
def PUT(self, env, start_response): """ Handle PUT Bucket request """ if 'CONTENT_LENGTH' in env: try: content_length = int(env['CONTENT_LENGTH']) except (ValueError, TypeError): return get_err_response('InvalidArgument') if content_length < 0: return get_err_response('InvalidArgument') if 'QUERY_STRING' in env: args = dict(urlparse.parse_qsl(env['QUERY_STRING'], 1)) else: args = {} acl = 'acl' in args if acl: res = acp_to_headers(env, 'container') if res: return res env['REQUEST_METHOD'] = 'POST' versioning = 'versioning' in args if versioning: if 'wsgi.input' not in env: return get_err_response( 'IllegalVersioningConfigurationException') versioning_conf = env['wsgi.input'].read() if 'Enabled' in versioning_conf: env['HTTP_X_CONTAINER_VERSIONING'] = 'enabled' elif 'Suspended' in versioning_conf: env['HTTP_X_CONTAINER_VERSIONING'] = 'suspended' else: return get_err_response( 'IllegalVersioningConfigurationException') env['REQUEST_METHOD'] = 'POST' if not acl and not versioning: if 'HTTP_X_AMZ_ACL' in env: amz_acl = env['HTTP_X_AMZ_ACL'] # Translate the Amazon ACL to something that can be # implemented in Swift, 501 otherwise. Swift uses POST # for ACLs, whereas S3 uses PUT. del env['HTTP_X_AMZ_ACL'] if 'QUERY_STRING' in env: del env['QUERY_STRING'] translated_acl = swift_acl_translate(amz_acl) if translated_acl == 'Unsupported': return get_err_response('Unsupported') elif translated_acl == 'InvalidArgument': return get_err_response('InvalidArgument') for header, acl in translated_acl: env[header] = acl if 'CONTENT_LENGTH' in env: content_length = env['CONTENT_LENGTH'] try: content_length = int(content_length) except (ValueError, TypeError): return get_err_response('InvalidArgument') if content_length < 0: return get_err_response('InvalidArgument') if 'QUERY_STRING' in env: args = dict(urlparse.parse_qsl(env['QUERY_STRING'], 1)) if 'acl' in args: # We very likely have an XML-based ACL request. body = env['wsgi.input'].readline().decode() translated_acl = swift_acl_translate(body, xml=True) if translated_acl == 'Unsupported': return get_err_response('Unsupported') elif translated_acl == 'InvalidArgument': return get_err_response('InvalidArgument') for header, acl in translated_acl: env[header] = acl env['REQUEST_METHOD'] = 'POST' body_iter = self._app_call(env) status = self._get_status_int() if status != HTTP_CREATED and status != HTTP_NO_CONTENT: if status in (HTTP_UNAUTHORIZED, HTTP_FORBIDDEN): return get_err_response('AccessDenied') elif status == HTTP_ACCEPTED: return get_err_response('BucketAlreadyExists') else: return get_err_response('InvalidURI') resp = Response() if not versioning: resp.headers['Location'] = self.container_name resp.status = HTTP_OK return resp
def GET(self, env, start_response): """ Handle GET Bucket (List Objects) request """ if 'QUERY_STRING' in env: args = dict(urlparse.parse_qsl(env['QUERY_STRING'], 1)) else: args = {} if 'max-keys' in args: if args.get('max-keys').isdigit() is False: return get_err_response('InvalidArgument') max_keys = min(int(args.get('max-keys', MAX_BUCKET_LISTING)), MAX_BUCKET_LISTING) if 'acl' in args: env['REQUEST_METHOD'] = 'HEAD' else: # acl request sent with format=json etc confuses swift env['QUERY_STRING'] = 'format=json&limit=%s' % (max_keys + 1) if 'versions' in args: env['QUERY_STRING'] += '&versions' if 'marker' in args: env['QUERY_STRING'] += '&marker=%s' % quote(args['marker']) if 'prefix' in args: env['QUERY_STRING'] += '&prefix=%s' % quote(args['prefix']) if 'delimiter' in args: env['QUERY_STRING'] += '&delimiter=%s' % quote(args['delimiter']) body_iter = self._app_call(env) status = self._get_status_int() headers = dict(self._response_headers) if 'acl' in args: env['REQUEST_METHOD'] = 'GET' # recover HTTP method if is_success(status) and 'acl' in args: return get_s3_acl(headers, container_server.ACL_HEADERS, 'container') if status != HTTP_OK: if status in (HTTP_UNAUTHORIZED, HTTP_FORBIDDEN): return get_err_response('AccessDenied') elif status == HTTP_NOT_FOUND: return get_err_response('NoSuchBucket') else: return get_err_response('InvalidURI') if 'location' in args: body = ('<?xml version="1.0" encoding="UTF-8"?>' '<LocationConstraint ' 'xmlns="http://s3.amazonaws.com/doc/2006-03-01/"') if self.location == 'US': body += '/>' else: body += ('>%s</LocationConstraint>' % self.location) return Response(body=body, content_type='application/xml') if 'versioning' in args: vers = self._response_header_value('x-container-versioning') or '' body = ( '<VersioningConfiguration ' 'xmlns="http://s3.amazonaws.com/doc/2006-03-01/">' '<Status>%s</Status></VersioningConfiguration>' % vers.capitalize()) return Response(body=body, content_type='application/xml') if 'logging' in args: # logging disabled body = ('<?xml version="1.0" encoding="UTF-8"?>' '<BucketLoggingStatus ' 'xmlns="http://doc.s3.amazonaws.com/2006-03-01" />') return Response(body=body, content_type='application/xml') objects = loads(''.join(list(body_iter))) if 'versions' in args: obj_list = [] for obj in objects: if 'subdir' not in obj: if obj['deleted']: name = xml_escape(unquote(obj['name'].encode('utf-8'))) obj_list.append( '<DeleteMarker>' '<Key>%s</Key>' '<VersionId>%s</VersionId>' '<IsLatest>%s</IsLatest>' '<LastModified>%s</LastModified>' '</DeleteMarker>' % ( name, obj['version_id'], 'true' if obj['is_latest'] else 'false', obj['last_modified'])) else: name = xml_escape(unquote(obj['name'].encode('utf-8'))) obj_list.append( '<Version>' '<Key>%s</Key>' '<VersionId>%s</VersionId>' '<IsLatest>%s</IsLatest>' '<LastModified>%s</LastModified>' '<ETag>"%s"</ETag>' '<Size>%s</Size>' '<StorageClass>STANDARD</StorageClass>' '<Owner>' '<ID>%s</ID>' '<DisplayName>%s</DisplayName>' '</Owner>' '</Version>' % ( name, obj['version_id'], 'true' if obj['is_latest'] else 'false', obj['last_modified'], obj['hash'], obj['bytes'], obj['owner'], obj['owner'])) body = ('<?xml version="1.0" encoding="UTF-8"?>' '<ListVersionsResult ' 'xmlns="http://s3.amazonaws.com/doc/2006-03-01">' '<Prefix>%s</Prefix>' '<KeyMarker>%s</KeyMarker>' '<VersionIdMarker>%s</VersionIdMarker>' '<Delimiter>%s</Delimiter>' '<IsTruncated>%s</IsTruncated>' '<MaxKeys>%s</MaxKeys>' '<Name>%s</Name>' '%s' '%s' '</ListVersionsResult>' % ( xml_escape(args.get('prefix', '')), xml_escape(args.get('key-marker', '')), xml_escape(args.get('version-id-marker', '')), xml_escape(args.get('delimiter', '')), 'true' if len(objects) == (max_keys + 1) else 'false', max_keys, xml_escape(self.container_name), "".join(obj_list), "".join([ '<CommonPrefixes><Prefix>%s</Prefix></CommonPrefixes>' % xml_escape(i['subdir']) for i in objects[:max_keys] if 'subdir' in i]))) else: obj_list = [] prefixes = [] for i in objects: if 'subdir' in i: name = xml_escape(unquote(i['subdir'].encode('utf-8'))) prefixes.append('<CommonPrefixes>' '<Prefix>%s</Prefix>' '</CommonPrefixes>' % name) else: name = xml_escape(unquote(i['name'].encode('utf-8'))) owner = i.get('owner', self.account_name) obj_list.append( '<Contents>' '<Key>%s</Key>' '<LastModified>%sZ</LastModified>' '<ETag>%s</ETag>' '<Size>%s</Size>' '<StorageClass>STANDARD</StorageClass>' '<Owner>' '<ID>%s</ID>' '<DisplayName>%s</DisplayName>' '</Owner>' '</Contents>' % (name, i['last_modified'], i['hash'], i['bytes'], owner, owner)) body = ('<?xml version="1.0" encoding="UTF-8"?>' '<ListBucketResult ' 'xmlns="http://s3.amazonaws.com/doc/2006-03-01">' '<Prefix>%s</Prefix>' '<Marker>%s</Marker>' '<Delimiter>%s</Delimiter>' '<IsTruncated>%s</IsTruncated>' '<MaxKeys>%s</MaxKeys>' '<Name>%s</Name>' '%s' '%s' '</ListBucketResult>' % ( xml_escape(args.get('prefix', '')), xml_escape(args.get('marker', '')), xml_escape(args.get('delimiter', '')), 'true' if (max_keys > 0 and len(objects) == (max_keys + 1)) else 'false', max_keys, xml_escape(self.container_name), ''.join(obj_list), ''.join(prefixes))) return Response(body=body, content_type='application/xml')