mirror of https://github.com/apache/cloudstack.git
1153 lines
50 KiB
Java
1153 lines
50 KiB
Java
// Licensed to the Apache Software Foundation (ASF) under one
|
|
// or more contributor license agreements. See the NOTICE file
|
|
// distributed with this work for additional information
|
|
// regarding copyright ownership. The ASF licenses this file
|
|
// to you under the Apache License, Version 2.0 (the
|
|
// "License"); you may not use this file except in compliance
|
|
// with the License. You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing,
|
|
// software distributed under the License is distributed on an
|
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
// KIND, either express or implied. See the License for the
|
|
// specific language governing permissions and limitations
|
|
// under the License.
|
|
package com.cloud.bridge.service.controller.s3;
|
|
|
|
import java.io.BufferedReader;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.InputStreamReader;
|
|
import java.io.OutputStream;
|
|
import java.io.Reader;
|
|
import java.io.StringWriter;
|
|
import java.io.Writer;
|
|
import java.text.SimpleDateFormat;
|
|
import java.util.Calendar;
|
|
|
|
import javax.inject.Inject;
|
|
import javax.servlet.http.HttpServletRequest;
|
|
import javax.servlet.http.HttpServletResponse;
|
|
import javax.xml.bind.DatatypeConverter;
|
|
import javax.xml.parsers.DocumentBuilder;
|
|
import javax.xml.parsers.DocumentBuilderFactory;
|
|
import javax.xml.stream.XMLStreamException;
|
|
|
|
import org.apache.log4j.Logger;
|
|
import org.json.simple.parser.ParseException;
|
|
import org.w3c.dom.Document;
|
|
import org.w3c.dom.Element;
|
|
import org.w3c.dom.Node;
|
|
import org.w3c.dom.NodeList;
|
|
|
|
import com.amazon.s3.GetBucketAccessControlPolicyResponse;
|
|
import com.amazon.s3.ListAllMyBucketsResponse;
|
|
import com.amazon.s3.ListBucketResponse;
|
|
import com.cloud.bridge.io.MTOMAwareResultStreamWriter;
|
|
import com.cloud.bridge.model.BucketPolicyVO;
|
|
import com.cloud.bridge.model.SAcl;
|
|
import com.cloud.bridge.model.SAclVO;
|
|
import com.cloud.bridge.model.SBucketVO;
|
|
import com.cloud.bridge.persist.dao.BucketPolicyDao;
|
|
import com.cloud.bridge.persist.dao.MultipartLoadDao;
|
|
import com.cloud.bridge.persist.dao.SBucketDao;
|
|
import com.cloud.bridge.service.S3Constants;
|
|
import com.cloud.bridge.service.S3RestServlet;
|
|
import com.cloud.bridge.service.UserContext;
|
|
import com.cloud.bridge.service.core.s3.S3AccessControlList;
|
|
import com.cloud.bridge.service.core.s3.S3AccessControlPolicy;
|
|
import com.cloud.bridge.service.core.s3.S3BucketPolicy;
|
|
import com.cloud.bridge.service.core.s3.S3BucketPolicy.PolicyAccess;
|
|
import com.cloud.bridge.service.core.s3.S3CanonicalUser;
|
|
import com.cloud.bridge.service.core.s3.S3CreateBucketConfiguration;
|
|
import com.cloud.bridge.service.core.s3.S3CreateBucketRequest;
|
|
import com.cloud.bridge.service.core.s3.S3CreateBucketResponse;
|
|
import com.cloud.bridge.service.core.s3.S3DeleteBucketRequest;
|
|
import com.cloud.bridge.service.core.s3.S3DeleteObjectRequest;
|
|
import com.cloud.bridge.service.core.s3.S3Engine;
|
|
import com.cloud.bridge.service.core.s3.S3GetBucketAccessControlPolicyRequest;
|
|
import com.cloud.bridge.service.core.s3.S3Grant;
|
|
import com.cloud.bridge.service.core.s3.S3ListAllMyBucketsEntry;
|
|
import com.cloud.bridge.service.core.s3.S3ListAllMyBucketsRequest;
|
|
import com.cloud.bridge.service.core.s3.S3ListAllMyBucketsResponse;
|
|
import com.cloud.bridge.service.core.s3.S3ListBucketObjectEntry;
|
|
import com.cloud.bridge.service.core.s3.S3ListBucketRequest;
|
|
import com.cloud.bridge.service.core.s3.S3ListBucketResponse;
|
|
import com.cloud.bridge.service.core.s3.S3MultipartUpload;
|
|
import com.cloud.bridge.service.core.s3.S3PolicyAction.PolicyActions;
|
|
import com.cloud.bridge.service.core.s3.S3PolicyCondition.ConditionKeys;
|
|
import com.cloud.bridge.service.core.s3.S3PolicyContext;
|
|
import com.cloud.bridge.service.core.s3.S3Response;
|
|
import com.cloud.bridge.service.core.s3.S3SetBucketAccessControlPolicyRequest;
|
|
import com.cloud.bridge.service.exception.InvalidRequestContentException;
|
|
import com.cloud.bridge.service.exception.NetworkIOException;
|
|
import com.cloud.bridge.service.exception.NoSuchObjectException;
|
|
import com.cloud.bridge.service.exception.ObjectAlreadyExistsException;
|
|
import com.cloud.bridge.service.exception.PermissionDeniedException;
|
|
import com.cloud.bridge.util.Converter;
|
|
import com.cloud.bridge.util.OrderedPair;
|
|
import com.cloud.bridge.util.PolicyParser;
|
|
import com.cloud.bridge.util.StringHelper;
|
|
import com.cloud.bridge.util.XSerializer;
|
|
import com.cloud.bridge.util.XSerializerXmlAdapter;
|
|
import com.cloud.bridge.util.XmlHelper;
|
|
import com.cloud.utils.db.Transaction;
|
|
|
|
|
|
public class S3BucketAction implements ServletAction {
|
|
protected final static Logger logger = Logger.getLogger(S3BucketAction.class);
|
|
@Inject BucketPolicyDao bPolicyDao;
|
|
@Inject SBucketDao bucketDao;
|
|
|
|
private DocumentBuilderFactory dbf = null;
|
|
public S3BucketAction() {
|
|
dbf = DocumentBuilderFactory.newInstance();
|
|
dbf.setNamespaceAware( true );
|
|
|
|
}
|
|
|
|
@Override
|
|
public void execute(HttpServletRequest request, HttpServletResponse response)
|
|
throws IOException, XMLStreamException
|
|
{
|
|
String method = request.getMethod();
|
|
String queryString = request.getQueryString();
|
|
|
|
if ( method.equalsIgnoreCase("PUT"))
|
|
{
|
|
if ( queryString != null && queryString.length() > 0 )
|
|
{
|
|
if ( queryString.startsWith("acl")) {
|
|
executePutBucketAcl(request, response);
|
|
return;
|
|
}
|
|
else if (queryString.startsWith("versioning")) {
|
|
executePutBucketVersioning(request, response);
|
|
return;
|
|
}
|
|
else if (queryString.startsWith("policy")) {
|
|
executePutBucketPolicy(request, response);
|
|
return;
|
|
}
|
|
else if (queryString.startsWith("logging")) {
|
|
executePutBucketLogging(request, response);
|
|
return;
|
|
}
|
|
else if (queryString.startsWith("website")) {
|
|
executePutBucketWebsite(request, response);
|
|
return;
|
|
}
|
|
}
|
|
executePutBucket(request, response);
|
|
}
|
|
else if(method.equalsIgnoreCase("GET") || method.equalsIgnoreCase("HEAD"))
|
|
{
|
|
if (queryString != null && queryString.length() > 0)
|
|
{
|
|
if ( queryString.startsWith("acl")) {
|
|
executeGetBucketAcl(request, response);
|
|
return;
|
|
}
|
|
else if (queryString.startsWith("versioning")) {
|
|
executeGetBucketVersioning(request, response);
|
|
return;
|
|
}
|
|
else if (queryString.contains("versions")) {
|
|
executeGetBucketObjectVersions(request, response);
|
|
return;
|
|
}
|
|
else if (queryString.startsWith("location")) {
|
|
executeGetBucketLocation(request, response);
|
|
return;
|
|
}
|
|
else if (queryString.startsWith("uploads")) {
|
|
executeListMultipartUploads(request, response);
|
|
return;
|
|
}
|
|
else if (queryString.startsWith("policy")) {
|
|
executeGetBucketPolicy(request, response);
|
|
return;
|
|
}
|
|
else if (queryString.startsWith("logging")) {
|
|
executeGetBucketLogging(request, response);
|
|
return;
|
|
}
|
|
else if (queryString.startsWith("website")) {
|
|
executeGetBucketWebsite(request, response);
|
|
return;
|
|
}
|
|
}
|
|
|
|
String bucketAtr = (String)request.getAttribute(S3Constants.BUCKET_ATTR_KEY);
|
|
if ( bucketAtr.equals( "/" ))
|
|
executeGetAllBuckets(request, response);
|
|
else executeGetBucket(request, response);
|
|
}
|
|
else if (method.equalsIgnoreCase("DELETE"))
|
|
{
|
|
if (queryString != null && queryString.length() > 0)
|
|
{
|
|
if ( queryString.startsWith("policy")) {
|
|
executeDeleteBucketPolicy(request, response);
|
|
return;
|
|
}
|
|
else if (queryString.startsWith("website")) {
|
|
executeDeleteBucketWebsite(request, response);
|
|
return;
|
|
}
|
|
|
|
}
|
|
executeDeleteBucket(request, response);
|
|
}
|
|
else if ( (method.equalsIgnoreCase("POST")) && (queryString.equalsIgnoreCase("delete")) )
|
|
{
|
|
executeMultiObjectDelete(request, response);
|
|
}
|
|
else throw new IllegalArgumentException("Unsupported method in REST request");
|
|
}
|
|
|
|
|
|
|
|
private void executeMultiObjectDelete(HttpServletRequest request, HttpServletResponse response) throws IOException{
|
|
|
|
int contentLength = request.getContentLength();
|
|
StringBuffer xmlDeleteResponse = null;
|
|
boolean quite = true;
|
|
|
|
if(contentLength > 0)
|
|
{
|
|
InputStream is = null;
|
|
String versionID =null;
|
|
try {
|
|
is = request.getInputStream();
|
|
String xml = StringHelper.stringFromStream(is);
|
|
String elements[] = {"Key","VersionId"};
|
|
Document doc = XmlHelper.parse(xml);
|
|
Node node = XmlHelper.getRootNode(doc);
|
|
|
|
if(node == null) {
|
|
System.out.println("Invalid XML document, no root element");
|
|
return;
|
|
}
|
|
|
|
xmlDeleteResponse = new StringBuffer("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
|
|
"<DeleteResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">");
|
|
|
|
String bucket = (String)request.getAttribute(S3Constants.BUCKET_ATTR_KEY);
|
|
|
|
S3DeleteObjectRequest engineRequest = new S3DeleteObjectRequest();
|
|
engineRequest.setBucketName( bucket );
|
|
is.close();
|
|
|
|
doc.getDocumentElement().normalize();
|
|
NodeList qList = doc.getElementsByTagName("Quiet");
|
|
|
|
if (qList.getLength() == 1 ) {
|
|
Node qNode= qList.item(0);
|
|
if ( qNode.getFirstChild().getNodeValue().equalsIgnoreCase("true") == false )
|
|
quite = false;
|
|
|
|
logger.debug("Quite value :" + qNode.getFirstChild().getNodeValue());
|
|
}
|
|
|
|
NodeList objList = doc.getElementsByTagName("Object");
|
|
|
|
for (int i = 0; i < objList.getLength(); i++) {
|
|
|
|
Node key = objList.item(i);
|
|
NodeList key_data = key.getChildNodes();
|
|
|
|
if (key.getNodeType() == Node.ELEMENT_NODE) {
|
|
Element eElement = (Element) key;
|
|
String key_name = getTagValue(elements[0], eElement);
|
|
engineRequest.setBucketName(bucket);
|
|
engineRequest.setKey(key_name);
|
|
|
|
if (key_data.getLength() == 2) {
|
|
versionID = getTagValue(elements[1], eElement);
|
|
engineRequest.setVersion(versionID);
|
|
}
|
|
|
|
S3Response engineResponse = ServiceProvider.getInstance().getS3Engine().handleRequest( engineRequest );
|
|
int resultCode = engineResponse.getResultCode();
|
|
String resutlDesc = engineResponse.getResultDescription();
|
|
if(resultCode == 204) {
|
|
if (quite) { // show response depending on quite/verbose
|
|
xmlDeleteResponse.append("<Deleted><Key>"+key_name+"</Key>");
|
|
if (resutlDesc != null)
|
|
xmlDeleteResponse.append(resutlDesc);
|
|
xmlDeleteResponse.append("</Deleted>");
|
|
}
|
|
}
|
|
else {
|
|
logger.debug("Error in delete ::" + key_name + " eng response:: " + engineResponse.getResultDescription());
|
|
xmlDeleteResponse.append("<Error><Key>"+key_name+"</Key>" );
|
|
if (resutlDesc != null)
|
|
xmlDeleteResponse.append(resutlDesc);
|
|
xmlDeleteResponse.append("</Error>");
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
|
|
String version = engineRequest.getVersion();
|
|
if (null != version) response.addHeader( "x-amz-version-id", version );
|
|
|
|
|
|
} catch (IOException e) {
|
|
logger.error("Unable to read request data due to " + e.getMessage(), e);
|
|
throw new NetworkIOException(e);
|
|
|
|
} finally {
|
|
if(is != null) is.close();
|
|
}
|
|
|
|
xmlDeleteResponse.append("</DeleteResult>");
|
|
|
|
}
|
|
response.setStatus(200);
|
|
response.setContentType("text/xml; charset=UTF-8");
|
|
S3RestServlet.endResponse(response, xmlDeleteResponse.toString());
|
|
|
|
}
|
|
|
|
private String getTagValue(String sTag, Element eElement) {
|
|
|
|
NodeList nlList = eElement.getElementsByTagName(sTag).item(0).getChildNodes();
|
|
Node nValue = nlList.item(0);
|
|
return nValue.getNodeValue();
|
|
}
|
|
|
|
|
|
/**
|
|
* In order to support a policy on the "s3:CreateBucket" action we must be able to set and get
|
|
* policies before a bucket is actually created.
|
|
*
|
|
* @param request
|
|
* @param response
|
|
* @throws IOException
|
|
*/
|
|
private void executePutBucketPolicy(HttpServletRequest request, HttpServletResponse response) throws IOException
|
|
{
|
|
String bucketName = (String)request.getAttribute(S3Constants.BUCKET_ATTR_KEY);
|
|
String policy = streamToString( request.getInputStream());
|
|
|
|
// [A] Is there an owner of an existing policy or bucket?
|
|
SBucketVO bucket = bucketDao.getByName( bucketName );
|
|
String owner = null;
|
|
|
|
if ( null != bucket )
|
|
{
|
|
owner = bucket.getOwnerCanonicalId();
|
|
}
|
|
else
|
|
{ try {
|
|
owner = bPolicyDao.getByName(bucketName).getOwnerCanonicalID();
|
|
}
|
|
catch( Exception e ) {}
|
|
}
|
|
|
|
|
|
// [B] "The bucket owner by default has permissions to attach bucket policies to their buckets using PUT Bucket policy."
|
|
// -> the bucket owner may want to restrict the IP address from where this can be executed
|
|
String client = UserContext.current().getCanonicalUserId();
|
|
S3PolicyContext context = new S3PolicyContext(
|
|
PolicyActions.PutBucketPolicy, bucketName);
|
|
|
|
switch (S3Engine.verifyPolicy(context)) {
|
|
case ALLOW:
|
|
break;
|
|
|
|
case DEFAULT_DENY:
|
|
if (null != owner && !client.equals(owner)) {
|
|
response.setStatus(405);
|
|
return;
|
|
}
|
|
break;
|
|
case DENY:
|
|
response.setStatus(403);
|
|
return;
|
|
}
|
|
Transaction txn = Transaction.open(Transaction.AWSAPI_DB);
|
|
// [B] Place the policy into the database over writting an existing policy
|
|
try {
|
|
// -> first make sure that the policy is valid by parsing it
|
|
PolicyParser parser = new PolicyParser();
|
|
S3BucketPolicy sbp = parser.parse( policy, bucketName );
|
|
bPolicyDao.deletePolicy(bucketName);
|
|
|
|
if (null != policy && !policy.isEmpty()) {
|
|
BucketPolicyVO bpolicy = new BucketPolicyVO(bucketName, client, policy);
|
|
bpolicy = bPolicyDao.persist(bpolicy);
|
|
//policyDao.addPolicy( bucketName, client, policy );
|
|
}
|
|
|
|
if (null != sbp) ServiceProvider.getInstance().setBucketPolicy( bucketName, sbp );
|
|
response.setStatus(200);
|
|
txn.commit();
|
|
txn.close();
|
|
}
|
|
catch( PermissionDeniedException e ) {
|
|
logger.error("Put Bucket Policy failed due to " + e.getMessage(), e);
|
|
throw e;
|
|
}
|
|
catch( ParseException e ) {
|
|
logger.error("Put Bucket Policy failed due to " + e.getMessage(), e);
|
|
throw new PermissionDeniedException( e.toString());
|
|
}
|
|
catch( Exception e ) {
|
|
logger.error("Put Bucket Policy failed due to " + e.getMessage(), e);
|
|
response.setStatus(500);
|
|
}
|
|
}
|
|
|
|
private void executeGetBucketPolicy(HttpServletRequest request, HttpServletResponse response)
|
|
{
|
|
String bucketName = (String)request.getAttribute(S3Constants.BUCKET_ATTR_KEY);
|
|
|
|
// [A] Is there an owner of an existing policy or bucket?
|
|
SBucketVO bucket = bucketDao.getByName(bucketName);
|
|
String owner = null;
|
|
|
|
if (null != bucket) {
|
|
owner = bucket.getOwnerCanonicalId();
|
|
} else {
|
|
try {
|
|
owner = bPolicyDao.getByName(bucketName).getOwnerCanonicalID();
|
|
} catch (Exception e) {
|
|
}
|
|
}
|
|
|
|
// [B]
|
|
// "The bucket owner by default has permissions to retrieve bucket policies using GET Bucket policy."
|
|
// -> the bucket owner may want to restrict the IP address from where
|
|
// this can be executed
|
|
String client = UserContext.current().getCanonicalUserId();
|
|
S3PolicyContext context = new S3PolicyContext(
|
|
PolicyActions.GetBucketPolicy, bucketName);
|
|
switch (S3Engine.verifyPolicy(context)) {
|
|
case ALLOW:
|
|
break;
|
|
|
|
case DEFAULT_DENY:
|
|
if (null != owner && !client.equals(owner)) {
|
|
response.setStatus(405);
|
|
return;
|
|
}
|
|
break;
|
|
|
|
case DENY:
|
|
response.setStatus(403);
|
|
return;
|
|
}
|
|
|
|
// [B] Pull the policy from the database if one exists
|
|
try {
|
|
String policy = bPolicyDao.getByName(bucketName).getPolicy();
|
|
if (null == policy) {
|
|
response.setStatus(404);
|
|
} else {
|
|
response.setStatus(200);
|
|
response.setContentType("application/json");
|
|
S3RestServlet.endResponse(response, policy);
|
|
}
|
|
} catch (Exception e) {
|
|
logger.error("Get Bucket Policy failed due to " + e.getMessage(), e);
|
|
response.setStatus(500);
|
|
}
|
|
}
|
|
|
|
private void executeDeleteBucketPolicy(HttpServletRequest request,
|
|
HttpServletResponse response) {
|
|
String bucketName = (String) request
|
|
.getAttribute(S3Constants.BUCKET_ATTR_KEY);
|
|
|
|
SBucketVO bucket = bucketDao.getByName(bucketName);
|
|
if (bucket != null) {
|
|
String client = UserContext.current().getCanonicalUserId();
|
|
if (!client.equals(bucket.getOwnerCanonicalId())) {
|
|
response.setStatus(405);
|
|
return;
|
|
}
|
|
}
|
|
|
|
try {
|
|
|
|
String policy = bPolicyDao.getByName(bucketName).getPolicy();
|
|
if (null == policy) {
|
|
response.setStatus(204);
|
|
} else {
|
|
ServiceProvider.getInstance().deleteBucketPolicy(bucketName);
|
|
bPolicyDao.deletePolicy(bucketName);
|
|
response.setStatus(200);
|
|
}
|
|
} catch (Exception e) {
|
|
logger.error(
|
|
"Delete Bucket Policy failed due to " + e.getMessage(), e);
|
|
response.setStatus(500);
|
|
}
|
|
}
|
|
|
|
public void executeGetAllBuckets(HttpServletRequest request,
|
|
HttpServletResponse response) throws IOException,
|
|
XMLStreamException {
|
|
Calendar cal = Calendar.getInstance();
|
|
cal.set(1970, 1, 1);
|
|
S3ListAllMyBucketsRequest engineRequest = new S3ListAllMyBucketsRequest();
|
|
engineRequest.setAccessKey(UserContext.current().getAccessKey());
|
|
engineRequest.setRequestTimestamp(cal);
|
|
engineRequest.setSignature("");
|
|
|
|
S3ListAllMyBucketsResponse engineResponse = ServiceProvider
|
|
.getInstance().getS3Engine().handleRequest(engineRequest);
|
|
|
|
// To allow the all buckets list to be serialized via Axiom classes
|
|
ListAllMyBucketsResponse allBuckets = S3SerializableServiceImplementation
|
|
.toListAllMyBucketsResponse(engineResponse);
|
|
|
|
OutputStream outputStream = response.getOutputStream();
|
|
response.setStatus(200);
|
|
response.setContentType("application/xml");
|
|
// The content-type literally should be "application/xml; charset=UTF-8"
|
|
// but any compliant JVM supplies utf-8 by default
|
|
|
|
// MTOMAwareResultStreamWriter resultWriter = new
|
|
// MTOMAwareResultStreamWriter ("ListAllMyBucketsResult", outputStream
|
|
// );
|
|
// resultWriter.startWrite();
|
|
// resultWriter.writeout(allBuckets);
|
|
// resultWriter.stopWrite();
|
|
StringBuffer xml = new StringBuffer();
|
|
xml.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
|
|
xml.append("<ListAllMyBucketsResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">");
|
|
xml.append("<Owner><ID>");
|
|
xml.append(engineResponse.getOwner().getID()).append("</ID>");
|
|
xml.append("<DisplayName>")
|
|
.append(engineResponse.getOwner().getDisplayName())
|
|
.append("</DisplayName>");
|
|
xml.append("</Owner>").append("<Buckets>");
|
|
SimpleDateFormat sdf = new SimpleDateFormat(
|
|
"yyyy-MM-dd'T'HH:mm:ss.SSSZ");
|
|
for (S3ListAllMyBucketsEntry entry : engineResponse.getBuckets()) {
|
|
xml.append("<Bucket>").append("<Name>").append(entry.getName())
|
|
.append("</Name>");
|
|
xml.append("<CreationDate>")
|
|
.append(sdf.format(entry.getCreationDate().getTime()))
|
|
.append("</CreationDate>");
|
|
xml.append("</Bucket>");
|
|
}
|
|
xml.append("</Buckets>").append("</ListAllMyBucketsResult>");
|
|
response.setStatus(200);
|
|
response.setContentType("text/xml; charset=UTF-8");
|
|
S3RestServlet.endResponse(response, xml.toString());
|
|
|
|
}
|
|
|
|
public void executeGetBucket(HttpServletRequest request, HttpServletResponse response)
|
|
throws IOException, XMLStreamException
|
|
{
|
|
S3ListBucketRequest engineRequest = new S3ListBucketRequest();
|
|
engineRequest.setBucketName((String) request
|
|
.getAttribute(S3Constants.BUCKET_ATTR_KEY));
|
|
engineRequest.setDelimiter(request.getParameter("delimiter"));
|
|
engineRequest.setMarker(request.getParameter("marker"));
|
|
engineRequest.setPrefix(request.getParameter("prefix"));
|
|
|
|
int maxKeys = Converter.toInt(request.getParameter("max-keys"), 1000);
|
|
engineRequest.setMaxKeys(maxKeys);
|
|
try {
|
|
S3ListBucketResponse engineResponse = ServiceProvider.getInstance()
|
|
.getS3Engine().listBucketContents(engineRequest, false);
|
|
|
|
// To allow the all list buckets result to be serialized via Axiom
|
|
// classes
|
|
ListBucketResponse oneBucket = S3SerializableServiceImplementation
|
|
.toListBucketResponse(engineResponse);
|
|
|
|
OutputStream outputStream = response.getOutputStream();
|
|
response.setStatus(200);
|
|
response.setContentType("application/xml");
|
|
// The content-type literally should be
|
|
// "application/xml; charset=UTF-8"
|
|
// but any compliant JVM supplies utf-8 by default;
|
|
|
|
MTOMAwareResultStreamWriter resultWriter = new MTOMAwareResultStreamWriter(
|
|
"ListBucketResult", outputStream);
|
|
resultWriter.startWrite();
|
|
resultWriter.writeout(oneBucket);
|
|
resultWriter.stopWrite();
|
|
} catch (NoSuchObjectException nsoe) {
|
|
response.setStatus(404);
|
|
response.setContentType("application/xml");
|
|
|
|
StringBuffer xmlError = new StringBuffer();
|
|
xmlError.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>")
|
|
.append("<Error><Code>NoSuchBucket</Code><Message>The specified bucket does not exist</Message>")
|
|
.append("<BucketName>")
|
|
.append((String) request
|
|
.getAttribute(S3Constants.BUCKET_ATTR_KEY))
|
|
.append("</BucketName>")
|
|
.append("<RequestId>1DEADBEEF9</RequestId>") // TODO
|
|
.append("<HostId>abCdeFgHiJ1k2LmN3op4q56r7st89</HostId>") // TODO
|
|
.append("</Error>");
|
|
S3RestServlet.endResponse(response, xmlError.toString());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
public void executeGetBucketAcl(HttpServletRequest request, HttpServletResponse response)
|
|
throws IOException, XMLStreamException
|
|
{
|
|
S3GetBucketAccessControlPolicyRequest engineRequest = new S3GetBucketAccessControlPolicyRequest();
|
|
Calendar cal = Calendar.getInstance();
|
|
cal.set( 1970, 1, 1 );
|
|
engineRequest.setAccessKey(UserContext.current().getAccessKey());
|
|
engineRequest.setRequestTimestamp( cal );
|
|
engineRequest.setSignature( "" ); // TODO - Consider providing signature in a future release which allows additional user description
|
|
engineRequest.setBucketName((String)request.getAttribute(S3Constants.BUCKET_ATTR_KEY));
|
|
|
|
S3AccessControlPolicy engineResponse = ServiceProvider.getInstance().getS3Engine().handleRequest(engineRequest);
|
|
|
|
// To allow the bucket acl policy result to be serialized via Axiom classes
|
|
GetBucketAccessControlPolicyResponse onePolicy = S3SerializableServiceImplementation.toGetBucketAccessControlPolicyResponse( engineResponse );
|
|
|
|
OutputStream outputStream = response.getOutputStream();
|
|
response.setStatus(200);
|
|
response.setContentType("application/xml");
|
|
// The content-type literally should be "application/xml; charset=UTF-8"
|
|
// but any compliant JVM supplies utf-8 by default;
|
|
|
|
MTOMAwareResultStreamWriter resultWriter = new MTOMAwareResultStreamWriter ("GetBucketAccessControlPolicyResult", outputStream );
|
|
resultWriter.startWrite();
|
|
resultWriter.writeout(onePolicy);
|
|
resultWriter.stopWrite();
|
|
|
|
|
|
}
|
|
|
|
public void executeGetBucketVersioning(HttpServletRequest request, HttpServletResponse response) throws IOException
|
|
{
|
|
// [A] Does the bucket exist?
|
|
String bucketName = (String)request.getAttribute(S3Constants.BUCKET_ATTR_KEY);
|
|
String versioningStatus = null;
|
|
|
|
if (null == bucketName) {
|
|
logger.error( "executeGetBucketVersioning - no bucket name given" );
|
|
response.setStatus( 400 );
|
|
return;
|
|
}
|
|
|
|
SBucketVO sbucket = bucketDao.getByName( bucketName );
|
|
if (sbucket == null) {
|
|
response.setStatus( 404 );
|
|
return;
|
|
}
|
|
|
|
// [B] The owner may want to restrict the IP address at which this can be performed
|
|
String client = UserContext.current().getCanonicalUserId();
|
|
if (!client.equals( sbucket.getOwnerCanonicalId()))
|
|
throw new PermissionDeniedException( "Access Denied - only the owner can read bucket versioning" );
|
|
|
|
S3PolicyContext context = new S3PolicyContext( PolicyActions.GetBucketVersioning, bucketName );
|
|
if (PolicyAccess.DENY == S3Engine.verifyPolicy( context )) {
|
|
response.setStatus(403);
|
|
return;
|
|
}
|
|
|
|
|
|
// [C]
|
|
switch( sbucket.getVersioningStatus()) {
|
|
default:
|
|
case 0: versioningStatus = ""; break;
|
|
case 1: versioningStatus = "Enabled"; break;
|
|
case 2: versioningStatus = "Suspended"; break;
|
|
}
|
|
|
|
StringBuffer xml = new StringBuffer();
|
|
xml.append( "<?xml version=\"1.0\" encoding=\"utf-8\"?>" );
|
|
xml.append( "<VersioningConfiguration xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">" );
|
|
if (0 < versioningStatus.length()) xml.append( "<Status>" ).append( versioningStatus ).append( "</Status>" );
|
|
xml.append( "</VersioningConfiguration>" );
|
|
|
|
response.setStatus(200);
|
|
response.setContentType("text/xml; charset=UTF-8");
|
|
S3RestServlet.endResponse(response, xml.toString());
|
|
}
|
|
|
|
public void executeGetBucketObjectVersions(HttpServletRequest request, HttpServletResponse response) throws IOException
|
|
{
|
|
S3ListBucketRequest engineRequest = new S3ListBucketRequest();
|
|
String keyMarker = request.getParameter("key-marker");
|
|
String versionIdMarker = request.getParameter("version-id-marker");
|
|
|
|
engineRequest.setBucketName((String)request.getAttribute(S3Constants.BUCKET_ATTR_KEY));
|
|
engineRequest.setDelimiter(request.getParameter("delimiter"));
|
|
engineRequest.setMarker( keyMarker );
|
|
engineRequest.setPrefix(request.getParameter("prefix"));
|
|
engineRequest.setVersionIdMarker( versionIdMarker );
|
|
|
|
int maxKeys = Converter.toInt(request.getParameter("max-keys"), 1000);
|
|
engineRequest.setMaxKeys(maxKeys);
|
|
S3ListBucketResponse engineResponse = ServiceProvider.getInstance().getS3Engine().listBucketContents( engineRequest, true );
|
|
|
|
// -> the SOAP version produces different XML
|
|
StringBuffer xml = new StringBuffer();
|
|
xml.append( "<?xml version=\"1.0\" encoding=\"utf-8\"?>" );
|
|
xml.append( "<ListVersionsResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">" );
|
|
xml.append( "<Name>" ).append( engineResponse.getBucketName()).append( "</Name>" );
|
|
|
|
if ( null == keyMarker )
|
|
xml.append( "<KeyMarker/>" );
|
|
else xml.append( "<KeyMarker>" ).append( keyMarker ).append( "</KeyMarker" );
|
|
|
|
if ( null == versionIdMarker )
|
|
xml.append( "<VersionIdMarker/>" );
|
|
else xml.append( "<VersionIdMarker>" ).append( keyMarker ).append( "</VersionIdMarker" );
|
|
|
|
xml.append( "<MaxKeys>" ).append( engineResponse.getMaxKeys()).append( "</MaxKeys>" );
|
|
xml.append( "<IsTruncated>" ).append( engineResponse.isTruncated()).append( "</IsTruncated>" );
|
|
|
|
S3ListBucketObjectEntry[] versions = engineResponse.getContents();
|
|
for( int i=0; null != versions && i < versions.length; i++ )
|
|
{
|
|
S3CanonicalUser owner = versions[i].getOwner();
|
|
boolean isDeletionMarker = versions[i].getIsDeletionMarker();
|
|
String displayName = owner.getDisplayName();
|
|
String id = owner.getID();
|
|
|
|
if ( isDeletionMarker )
|
|
{
|
|
xml.append( "<DeleteMarker>" );
|
|
xml.append( "<Key>" ).append( versions[i].getKey()).append( "</Key>" );
|
|
xml.append( "<VersionId>" ).append( versions[i].getVersion()).append( "</VersionId>" );
|
|
xml.append( "<IsLatest>" ).append( versions[i].getIsLatest()).append( "</IsLatest>" );
|
|
xml.append( "<LastModified>" ).append( DatatypeConverter.printDateTime( versions[i].getLastModified())).append( "</LastModified>" );
|
|
}
|
|
else
|
|
{ xml.append( "<Version>" );
|
|
xml.append( "<Key>" ).append( versions[i].getKey()).append( "</Key>" );
|
|
xml.append( "<VersionId>" ).append( versions[i].getVersion()).append( "</VersionId>" );
|
|
xml.append( "<IsLatest>" ).append( versions[i].getIsLatest()).append( "</IsLatest>" );
|
|
xml.append( "<LastModified>" ).append( DatatypeConverter.printDateTime( versions[i].getLastModified())).append( "</LastModified>" );
|
|
xml.append( "<ETag>" ).append( versions[i].getETag()).append( "</ETag>" );
|
|
xml.append( "<Size>" ).append( versions[i].getSize()).append( "</Size>" );
|
|
xml.append( "<StorageClass>" ).append( versions[i].getStorageClass()).append( "</StorageClass>" );
|
|
}
|
|
|
|
xml.append( "<Owner>" );
|
|
xml.append( "<ID>" ).append( id ).append( "</ID>" );
|
|
if ( null == displayName )
|
|
xml.append( "<DisplayName/>" );
|
|
else xml.append( "<DisplayName>" ).append( owner.getDisplayName()).append( "</DisplayName>" );
|
|
xml.append( "</Owner>" );
|
|
|
|
if ( isDeletionMarker )
|
|
xml.append( "</DeleteMarker>" );
|
|
else xml.append( "</Version>" );
|
|
}
|
|
xml.append( "</ListVersionsResult>" );
|
|
|
|
response.setStatus(200);
|
|
response.setContentType("text/xml; charset=UTF-8");
|
|
S3RestServlet.endResponse(response, xml.toString());
|
|
}
|
|
|
|
public void executeGetBucketLogging(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
|
// TODO -- Review this in future. Currently this is a beta feature of S3
|
|
response.setStatus(405);
|
|
}
|
|
|
|
public void executeGetBucketLocation(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
|
// TODO - This is a fakery! We don't actually store location in backend
|
|
StringBuffer xml = new StringBuffer();
|
|
xml.append( "<?xml version=\"1.0\" encoding=\"utf-8\"?>" );
|
|
xml.append( "<LocationConstraint xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">" );
|
|
// This is the real fakery
|
|
xml.append( "us-west-2" );
|
|
xml.append( "</LocationConstraint>" );
|
|
response.setStatus(200);
|
|
response.setContentType("text/xml; charset=UTF-8");
|
|
S3RestServlet.endResponse(response, xml.toString());
|
|
}
|
|
|
|
public void executeGetBucketWebsite(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
|
response.setStatus(405);
|
|
}
|
|
|
|
public void executeDeleteBucketWebsite(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
|
response.setStatus(405);
|
|
}
|
|
|
|
public void executePutBucket(HttpServletRequest request, HttpServletResponse response) throws IOException
|
|
{
|
|
int contentLength = request.getContentLength();
|
|
Object objectInContent = null;
|
|
|
|
if(contentLength > 0)
|
|
{
|
|
InputStream is = null;
|
|
try {
|
|
is = request.getInputStream();
|
|
String xml = StringHelper.stringFromStream(is);
|
|
Class.forName("com.cloud.bridge.service.core.s3.S3CreateBucketConfiguration");
|
|
XSerializer serializer = new XSerializer(new XSerializerXmlAdapter());
|
|
objectInContent = serializer.serializeFrom(xml);
|
|
if(objectInContent != null && !(objectInContent instanceof S3CreateBucketConfiguration)) {
|
|
throw new InvalidRequestContentException("Invalid request content in create-bucket: " + xml);
|
|
}
|
|
is.close();
|
|
|
|
} catch (IOException e) {
|
|
logger.error("Unable to read request data due to " + e.getMessage(), e);
|
|
throw new NetworkIOException(e);
|
|
|
|
} catch (ClassNotFoundException e) {
|
|
logger.error("In a normal world this should never never happen:" + e.getMessage(), e);
|
|
throw new RuntimeException("A required class was not found in the classpath:" + e.getMessage());
|
|
}
|
|
finally {
|
|
if(is != null) is.close();
|
|
}
|
|
}
|
|
|
|
S3CreateBucketRequest engineRequest = new S3CreateBucketRequest();
|
|
engineRequest.setBucketName((String)request.getAttribute(S3Constants.BUCKET_ATTR_KEY));
|
|
engineRequest.setConfig((S3CreateBucketConfiguration)objectInContent);
|
|
try {
|
|
S3CreateBucketResponse engineResponse = ServiceProvider.getInstance().getS3Engine().handleRequest(engineRequest);
|
|
response.addHeader("Location", "/" + engineResponse.getBucketName());
|
|
response.setContentLength(0);
|
|
response.setStatus(200);
|
|
response.flushBuffer();
|
|
} catch (ObjectAlreadyExistsException oaee) {
|
|
response.setStatus(409);
|
|
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?> <Error><Code>OperationAborted</Code><Message>A conflicting conditional operation is currently in progress against this resource. Please try again..</Message>";
|
|
response.setContentType("text/xml; charset=UTF-8");
|
|
S3RestServlet.endResponse(response, xml.toString());
|
|
}
|
|
}
|
|
|
|
public void executePutBucketAcl(HttpServletRequest request, HttpServletResponse response) throws IOException
|
|
{
|
|
// [A] Determine that there is an applicable bucket which might have an ACL set
|
|
|
|
String bucketName = (String) request
|
|
.getAttribute(S3Constants.BUCKET_ATTR_KEY);
|
|
SBucketVO bucket = bucketDao.getByName(bucketName);
|
|
String owner = null;
|
|
if (null != bucket)
|
|
owner = bucket.getOwnerCanonicalId();
|
|
if (null == owner) {
|
|
logger.error("ACL update failed since " + bucketName
|
|
+ " does not exist");
|
|
throw new IOException("ACL update failed");
|
|
}
|
|
|
|
// [B] Obtain the grant request which applies to the acl request string.
|
|
// This latter is supplied as the value of the x-amz-acl header.
|
|
|
|
S3SetBucketAccessControlPolicyRequest engineRequest = new S3SetBucketAccessControlPolicyRequest();
|
|
S3Grant grantRequest = new S3Grant();
|
|
S3AccessControlList aclRequest = new S3AccessControlList();
|
|
|
|
String aclRequestString = request.getHeader("x-amz-acl");
|
|
OrderedPair<Integer, Integer> accessControlsForBucketOwner = SAclVO.getCannedAccessControls(aclRequestString, "SBucket");
|
|
grantRequest.setPermission(accessControlsForBucketOwner.getFirst());
|
|
grantRequest.setGrantee(accessControlsForBucketOwner.getSecond());
|
|
grantRequest.setCanonicalUserID(owner);
|
|
aclRequest.addGrant(grantRequest);
|
|
engineRequest.setAcl(aclRequest);
|
|
engineRequest.setBucketName(bucketName);
|
|
|
|
// [C] Allow an S3Engine to handle the
|
|
// S3SetBucketAccessControlPolicyRequest
|
|
S3Response engineResponse = ServiceProvider.getInstance().getS3Engine()
|
|
.handleRequest(engineRequest);
|
|
response.setStatus(engineResponse.getResultCode());
|
|
|
|
}
|
|
|
|
public void executePutBucketVersioning(HttpServletRequest request, HttpServletResponse response) throws IOException
|
|
{
|
|
String bucketName = (String) request.getAttribute(S3Constants.BUCKET_ATTR_KEY);
|
|
String versioningStatus = null;
|
|
Node item = null;
|
|
|
|
if (null == bucketName) {
|
|
logger.error("executePutBucketVersioning - no bucket name given");
|
|
response.setStatus(400);
|
|
return;
|
|
}
|
|
|
|
// -> is the XML as defined?
|
|
try {
|
|
DocumentBuilder db = dbf.newDocumentBuilder();
|
|
Document restXML = db.parse(request.getInputStream());
|
|
NodeList match = S3RestServlet.getElement(restXML,
|
|
"http://s3.amazonaws.com/doc/2006-03-01/", "Status");
|
|
if (0 < match.getLength()) {
|
|
item = match.item(0);
|
|
versioningStatus = new String(item.getFirstChild()
|
|
.getNodeValue());
|
|
} else {
|
|
logger.error("executePutBucketVersioning - cannot find Status tag in XML body");
|
|
response.setStatus(400);
|
|
return;
|
|
}
|
|
} catch (Exception e) {
|
|
logger.error(
|
|
"executePutBucketVersioning - failed to parse XML due to "
|
|
+ e.getMessage(), e);
|
|
response.setStatus(400);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Irrespective of what the ACLs say only the owner can turn on
|
|
// versioning on a bucket.
|
|
// The bucket owner may want to restrict the IP address from which
|
|
// this can occur.
|
|
|
|
SBucketVO sbucket = bucketDao.getByName(bucketName);
|
|
|
|
String client = UserContext.current().getCanonicalUserId();
|
|
if (!client.equals(sbucket.getOwnerCanonicalId()))
|
|
throw new PermissionDeniedException(
|
|
"Access Denied - only the owner can turn on versioing on a bucket");
|
|
|
|
S3PolicyContext context = new S3PolicyContext(
|
|
PolicyActions.PutBucketVersioning, bucketName);
|
|
if (PolicyAccess.DENY == S3Engine.verifyPolicy(context)) {
|
|
response.setStatus(403);
|
|
return;
|
|
}
|
|
|
|
if (versioningStatus.equalsIgnoreCase("Enabled"))
|
|
sbucket.setVersioningStatus(1);
|
|
else if (versioningStatus.equalsIgnoreCase("Suspended"))
|
|
sbucket.setVersioningStatus(2);
|
|
else {
|
|
logger.error("executePutBucketVersioning - unknown state: ["
|
|
+ versioningStatus + "]");
|
|
response.setStatus(400);
|
|
return;
|
|
}
|
|
bucketDao.update(sbucket.getId(), sbucket);
|
|
|
|
} catch (PermissionDeniedException e) {
|
|
logger.error(
|
|
"executePutBucketVersioning - failed due to "
|
|
+ e.getMessage(), e);
|
|
throw e;
|
|
|
|
} catch (Exception e) {
|
|
logger.error(
|
|
"executePutBucketVersioning - failed due to "
|
|
+ e.getMessage(), e);
|
|
response.setStatus(500);
|
|
return;
|
|
}
|
|
response.setStatus(200);
|
|
}
|
|
|
|
public void executePutBucketLogging(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
|
// TODO -- Review this in future. Currently this is a S3 beta feature
|
|
response.setStatus(501);
|
|
}
|
|
|
|
public void executePutBucketWebsite(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
|
// TODO -- LoPri - Undertake checks on Put Bucket Website
|
|
// Tested using configuration <Directory /Users/john1/S3-Mount>\nAllowOverride FileInfo AuthConfig Limit...</Directory> in httpd.conf
|
|
// Need some way of using AllowOverride to allow use of .htaccess and then pushing .httaccess file to bucket subdirectory of mount point
|
|
// Currently has noop effect in the sense that a running apachectl process sees the directory contents without further action
|
|
response.setStatus(200);
|
|
}
|
|
|
|
public void executeDeleteBucket(HttpServletRequest request, HttpServletResponse response) throws IOException
|
|
{
|
|
S3DeleteBucketRequest engineRequest = new S3DeleteBucketRequest();
|
|
engineRequest.setBucketName((String)request.getAttribute(S3Constants.BUCKET_ATTR_KEY));
|
|
S3Response engineResponse = ServiceProvider.getInstance().getS3Engine().handleRequest(engineRequest);
|
|
response.setStatus(engineResponse.getResultCode());
|
|
response.flushBuffer();
|
|
}
|
|
|
|
/**
|
|
* Multipart upload is a complex operation with all the options defined by Amazon. Part of the functionality is
|
|
* provided by the query done against the database. The CommonPrefixes functionality is done the same way
|
|
* as done in the listBucketContents function (i.e., by iterating though the list to decide which output
|
|
* element each key is placed).
|
|
*
|
|
* @param request
|
|
* @param response
|
|
* @throws IOException
|
|
*/
|
|
public void executeListMultipartUploads(HttpServletRequest request, HttpServletResponse response) throws IOException
|
|
{
|
|
// [A] Obtain parameters and do basic bucket verification
|
|
String bucketName = (String) request
|
|
.getAttribute(S3Constants.BUCKET_ATTR_KEY);
|
|
String delimiter = request.getParameter("delimiter");
|
|
String keyMarker = request.getParameter("key-marker");
|
|
String prefix = request.getParameter("prefix");
|
|
int maxUploads = 1000;
|
|
int nextUploadId = 0;
|
|
String nextKey = null;
|
|
boolean isTruncated = false;
|
|
S3MultipartUpload[] uploads = null;
|
|
S3MultipartUpload onePart = null;
|
|
String temp = request.getParameter("max-uploads");
|
|
if (null != temp) {
|
|
maxUploads = Integer.parseInt(temp);
|
|
if (maxUploads > 1000 || maxUploads < 0)
|
|
maxUploads = 1000;
|
|
}
|
|
|
|
// -> upload-id-marker is ignored unless key-marker is also specified
|
|
String uploadIdMarker = request.getParameter("upload-id-marker");
|
|
if (null == keyMarker)
|
|
uploadIdMarker = null;
|
|
|
|
// -> does the bucket exist, we may need it to verify access permissions
|
|
SBucketVO bucket = bucketDao.getByName(bucketName);
|
|
if (bucket == null) {
|
|
logger.error("listMultipartUpload failed since " + bucketName
|
|
+ " does not exist");
|
|
response.setStatus(404);
|
|
return;
|
|
}
|
|
|
|
S3PolicyContext context = new S3PolicyContext(
|
|
PolicyActions.ListBucketMultipartUploads, bucketName);
|
|
context.setEvalParam(ConditionKeys.Prefix, prefix);
|
|
context.setEvalParam(ConditionKeys.Delimiter, delimiter);
|
|
S3Engine.verifyAccess(context, "SBucket", bucket.getId(),
|
|
SAcl.PERMISSION_READ);
|
|
|
|
// [B] Query the multipart table to get the list of current uploads
|
|
try {
|
|
MultipartLoadDao uploadDao = new MultipartLoadDao();
|
|
OrderedPair<S3MultipartUpload[], Boolean> result = uploadDao
|
|
.getInitiatedUploads(bucketName, maxUploads, prefix,
|
|
keyMarker, uploadIdMarker);
|
|
uploads = result.getFirst();
|
|
isTruncated = result.getSecond().booleanValue();
|
|
} catch (Exception e) {
|
|
logger.error(
|
|
"List Multipart Uploads failed due to " + e.getMessage(), e);
|
|
response.setStatus(500);
|
|
}
|
|
|
|
StringBuffer xml = new StringBuffer();
|
|
xml.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
|
|
xml.append("<ListMultipartUploadsResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">");
|
|
xml.append("<Bucket>").append(bucketName).append("</Bucket>");
|
|
xml.append("<KeyMarker>").append((null == keyMarker ? "" : keyMarker))
|
|
.append("</KeyMarker>");
|
|
xml.append("<UploadIdMarker>")
|
|
.append((null == uploadIdMarker ? "" : uploadIdMarker))
|
|
.append("</UploadIdMarker>");
|
|
|
|
// [C] Construct the contents of the <Upload> element
|
|
StringBuffer partsList = new StringBuffer();
|
|
for (int i = 0; i < uploads.length; i++) {
|
|
onePart = uploads[i];
|
|
if (null == onePart)
|
|
break;
|
|
|
|
if (delimiter != null && !delimiter.isEmpty()) {
|
|
// -> is this available only in the CommonPrefixes element?
|
|
if (StringHelper.substringInBetween(onePart.getKey(), prefix,
|
|
delimiter) != null)
|
|
continue;
|
|
}
|
|
|
|
nextKey = onePart.getKey();
|
|
nextUploadId = onePart.getId();
|
|
partsList.append("<Upload>");
|
|
partsList.append("<Key>").append(nextKey).append("</Key>");
|
|
partsList.append("<UploadId>").append(nextUploadId)
|
|
.append("</UploadId>");
|
|
partsList.append("<Initiator>");
|
|
partsList.append("<ID>").append(onePart.getAccessKey())
|
|
.append("</ID>");
|
|
partsList.append("<DisplayName></DisplayName>");
|
|
partsList.append("</Initiator>");
|
|
partsList.append("<Owner>");
|
|
partsList.append("<ID>").append(onePart.getAccessKey())
|
|
.append("</ID>");
|
|
partsList.append("<DisplayName></DisplayName>");
|
|
partsList.append("</Owner>");
|
|
partsList.append("<StorageClass>STANDARD</StorageClass>");
|
|
partsList
|
|
.append("<Initiated>")
|
|
.append(DatatypeConverter.printDateTime(onePart
|
|
.getLastModified())).append("</Initiated>");
|
|
partsList.append("</Upload>");
|
|
}
|
|
|
|
// [D] Construct the contents of the <CommonPrefixes> elements (if any)
|
|
for (int i = 0; i < uploads.length; i++) {
|
|
onePart = uploads[i];
|
|
if (null == onePart)
|
|
break;
|
|
|
|
if (delimiter != null && !delimiter.isEmpty()) {
|
|
String subName = StringHelper.substringInBetween(
|
|
onePart.getKey(), prefix, delimiter);
|
|
if (subName != null) {
|
|
partsList.append("<CommonPrefixes>");
|
|
partsList.append("<Prefix>");
|
|
if (prefix != null && prefix.length() > 0)
|
|
partsList.append(prefix + delimiter + subName);
|
|
else
|
|
partsList.append(subName);
|
|
partsList.append("</Prefix>");
|
|
partsList.append("</CommonPrefixes>");
|
|
}
|
|
}
|
|
}
|
|
|
|
// [D] Finish off the response
|
|
xml.append("<NextKeyMarker>").append((null == nextKey ? "" : nextKey))
|
|
.append("</NextKeyMarker>");
|
|
xml.append("<NextUploadIdMarker>")
|
|
.append((0 == nextUploadId ? "" : nextUploadId))
|
|
.append("</NextUploadIdMarker>");
|
|
xml.append("<MaxUploads>").append(maxUploads).append("</MaxUploads>");
|
|
xml.append("<IsTruncated>").append(isTruncated)
|
|
.append("</IsTruncated>");
|
|
|
|
xml.append(partsList.toString());
|
|
xml.append("</ListMultipartUploadsResult>");
|
|
|
|
response.setStatus(200);
|
|
response.setContentType("text/xml; charset=UTF-8");
|
|
S3RestServlet.endResponse(response, xml.toString());
|
|
}
|
|
|
|
private String streamToString( InputStream is ) throws IOException
|
|
{
|
|
int n = 0;
|
|
|
|
if ( null != is )
|
|
{
|
|
Writer writer = new StringWriter();
|
|
char[] buffer = new char[1024];
|
|
try {
|
|
Reader reader = new BufferedReader( new InputStreamReader(is, "UTF-8"));
|
|
while ((n = reader.read(buffer)) != -1) writer.write(buffer, 0, n);
|
|
}
|
|
finally {
|
|
is.close();
|
|
}
|
|
return writer.toString();
|
|
}
|
|
else return null;
|
|
}
|
|
}
|