| // 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; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.io.UnsupportedEncodingException; |
| import java.security.SignatureException; |
| import java.sql.SQLException; |
| import java.util.Enumeration; |
| |
| import javax.inject.Inject; |
| import javax.servlet.ServletConfig; |
| import javax.servlet.ServletException; |
| import javax.servlet.http.HttpServlet; |
| 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 org.apache.axis2.AxisFault; |
| import org.apache.log4j.Logger; |
| import org.springframework.web.context.support.SpringBeanAutowiringSupport; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.NamedNodeMap; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| |
| import com.cloud.bridge.io.MultiPartDimeInputStream; |
| import com.cloud.bridge.model.SAcl; |
| import com.cloud.bridge.model.UserCredentialsVO; |
| import com.cloud.bridge.persist.dao.CloudStackConfigurationDao; |
| import com.cloud.bridge.persist.dao.UserCredentialsDao; |
| import com.cloud.bridge.service.controller.s3.S3BucketAction; |
| import com.cloud.bridge.service.controller.s3.S3ObjectAction; |
| import com.cloud.bridge.service.controller.s3.ServiceProvider; |
| import com.cloud.bridge.service.controller.s3.ServletAction; |
| import com.cloud.bridge.service.core.s3.S3AccessControlList; |
| import com.cloud.bridge.service.core.s3.S3AuthParams; |
| import com.cloud.bridge.service.core.s3.S3Engine; |
| import com.cloud.bridge.service.core.s3.S3Grant; |
| import com.cloud.bridge.service.core.s3.S3MetaDataEntry; |
| import com.cloud.bridge.service.core.s3.S3PutObjectRequest; |
| import com.cloud.bridge.service.core.s3.S3PutObjectResponse; |
| import com.cloud.bridge.service.exception.InvalidBucketName; |
| import com.cloud.bridge.service.exception.PermissionDeniedException; |
| import com.cloud.bridge.util.ConfigurationHelper; |
| import com.cloud.bridge.util.HeaderParam; |
| import com.cloud.bridge.util.RestAuth; |
| import com.cloud.bridge.util.S3SoapAuth; |
| import com.cloud.utils.db.DB; |
| import com.cloud.utils.db.Transaction; |
| import com.cloud.utils.db.TransactionLegacy; |
| |
| public class S3RestServlet extends HttpServlet { |
| private static final long serialVersionUID = -6168996266762804877L; |
| public static final String ENABLE_S3_API = "enable.s3.api"; |
| private static boolean isS3APIEnabled = false; |
| |
| public static final Logger logger = Logger.getLogger(S3RestServlet.class); |
| @Inject |
| CloudStackConfigurationDao csDao; |
| @Inject |
| UserCredentialsDao ucDao; |
| |
| @Override |
| protected void doGet(HttpServletRequest req, HttpServletResponse resp) { |
| processRequest(req, resp, "GET"); |
| } |
| |
| @Override |
| protected void doPost(HttpServletRequest req, HttpServletResponse resp) { |
| // -> DIME requests are authenticated via the SOAP auth mechanism |
| String type = req.getHeader("Content-Type"); |
| if (null != type && type.equalsIgnoreCase("application/dime")) |
| processDimeRequest(req, resp); |
| else |
| processRequest(req, resp, "POST"); |
| } |
| |
| @Override |
| protected void doPut(HttpServletRequest req, HttpServletResponse resp) { |
| processRequest(req, resp, "PUT"); |
| } |
| |
| @Override |
| protected void doHead(HttpServletRequest req, HttpServletResponse resp) { |
| processRequest(req, resp, "HEAD"); |
| } |
| |
| @Override |
| protected void doOptions(HttpServletRequest req, HttpServletResponse resp) { |
| processRequest(req, resp, "OPTIONS"); |
| } |
| |
| @Override |
| protected void doDelete(HttpServletRequest req, HttpServletResponse resp) { |
| processRequest(req, resp, "DELETE"); |
| } |
| |
| @Override |
| public void init(ServletConfig config) throws ServletException { |
| try { |
| SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(this, config.getServletContext()); |
| |
| ConfigurationHelper.preConfigureConfigPathFromServletContext(config.getServletContext()); |
| // check if API is enabled |
| String value = csDao.getConfigValue(ENABLE_S3_API); |
| if (value != null) { |
| isS3APIEnabled = Boolean.valueOf(value); |
| } |
| logger.info("S3Engine :: Configuration value is : " + value); |
| |
| } catch (Exception e) { |
| throw new ServletException("Error initializing awsapi: " + e.getMessage()); |
| } |
| |
| } |
| |
| /** |
| * POST requests do not get authenticated on entry. The associated |
| * access key and signature headers are embedded in the message not encoded |
| * as HTTP headers. |
| */ |
| private void processRequest(HttpServletRequest request, HttpServletResponse response, String method) { |
| TransactionLegacy txn = TransactionLegacy.open("cloudbridge", TransactionLegacy.AWSAPI_DB, true); |
| try { |
| logRequest(request); |
| |
| // Our extensions to the S3 REST API for simple management actions |
| // are conveyed with Request parameter "Action". |
| // The present extensions are either to set up the user credentials |
| // (see the cloud-bridge-register script for more detail) or |
| // to report our version of this capability. |
| // -> unauthenticated calls, should still be done over HTTPS |
| String cloudAction = request.getParameter("Action"); |
| |
| if (!isS3APIEnabled) { |
| throw new RuntimeException("Amazon S3 API is disabled."); |
| } |
| |
| if (null != cloudAction) { |
| if (cloudAction.equalsIgnoreCase("SetUserKeys")) { |
| setUserKeys(request, response); |
| return; |
| } |
| |
| if (cloudAction.equalsIgnoreCase("SetCertificate")) |
| // At present a noop |
| return; |
| |
| if (cloudAction.equalsIgnoreCase("CloudS3Version")) { |
| cloudS3Version(request, response); |
| return; |
| } |
| } |
| |
| txn.start(); |
| // -> authenticated calls |
| if (!((method.equalsIgnoreCase("POST") && !(request.getQueryString().equalsIgnoreCase("delete"))))) { |
| S3AuthParams params = extractRequestHeaders(request); |
| authenticateRequest(request, params); |
| } |
| |
| ServletAction action = routeRequest(request); |
| if (action != null) { |
| action.execute(request, response); |
| } else { |
| response.setStatus(404); |
| endResponse(response, "File not found"); |
| } |
| txn.close(); |
| } catch (InvalidBucketName e) { |
| logger.error("Unexpected exception " + e.getMessage(), e); |
| response.setStatus(400); |
| endResponse(response, "Invalid Bucket Name - " + e.toString()); |
| } catch (PermissionDeniedException e) { |
| logger.error("Unexpected exception " + e.getMessage(), e); |
| response.setStatus(403); |
| endResponse(response, "Access denied - " + e.toString()); |
| } catch (Throwable e) { |
| logger.error("Unexpected exception " + e.getMessage(), e); |
| response.setStatus(404); |
| endResponse(response, "Bad request"); |
| |
| } finally { |
| |
| try { |
| response.flushBuffer(); |
| } catch (IOException e) { |
| logger.error("Unexpected exception " + e.getMessage(), e); |
| } |
| } |
| } |
| |
| /** |
| * Provide an easy way to determine the version of the implementation running. |
| * |
| * This is an unauthenticated REST call. |
| */ |
| private void cloudS3Version(HttpServletRequest request, HttpServletResponse response) { |
| String version = new String("<?xml version=\"1.0\" encoding=\"utf-8\"?><CloudS3Version>1.04</CloudS3Version>"); |
| response.setStatus(200); |
| endResponse(response, version); |
| } |
| |
| /** |
| * This request registers the user Cloud.com account holder to the S3 service. The Cloud.com |
| * account holder saves his API access and secret keys with the S3 service so that |
| * each rest call he makes can be verified was originated from him. The given API access |
| * and secret key are saved into the "usercredentials" database table. |
| * |
| * This is an unauthenticated REST call. The only required parameters are 'accesskey' and |
| * 'secretkey'. |
| * |
| * To verify that the given keys represent an existing account they are used to execute the |
| * Cloud.com's listAccounts API function. If the keys do not represent a valid account the |
| * listAccounts function will fail. |
| * |
| * A user can call this REST function any number of times, on each call the Cloud.com secret |
| * key is simply over writes any previously stored value. |
| * |
| * As with all REST calls HTTPS should be used to ensure their security. |
| */ |
| @DB |
| private void setUserKeys(HttpServletRequest request, HttpServletResponse response) { |
| String[] accessKey = null; |
| String[] secretKey = null; |
| |
| try { |
| // -> both these parameters are required |
| accessKey = request.getParameterValues("accesskey"); |
| if (null == accessKey || 0 == accessKey.length) { |
| response.sendError(530, "Missing accesskey parameter"); |
| return; |
| } |
| |
| secretKey = request.getParameterValues("secretkey"); |
| if (null == secretKey || 0 == secretKey.length) { |
| response.sendError(530, "Missing secretkey parameter"); |
| return; |
| } |
| } catch (Exception e) { |
| logger.error("SetUserKeys exception " + e.getMessage(), e); |
| response.setStatus(500); |
| endResponse(response, "SetUserKeys exception " + e.getMessage()); |
| return; |
| } |
| |
| try { |
| // -> use the keys to see if the account actually exists |
| //ServiceProvider.getInstance().getEC2Engine().validateAccount( accessKey[0], secretKey[0] ); |
| //UserCredentialsDaoImpl credentialDao = new UserCredentialsDao(); |
| TransactionLegacy txn = TransactionLegacy.open(TransactionLegacy.AWSAPI_DB); |
| txn.start(); |
| UserCredentialsVO user = new UserCredentialsVO(accessKey[0], secretKey[0]); |
| user = ucDao.persist(user); |
| txn.commit(); |
| txn.close(); |
| //credentialDao.setUserKeys( accessKey[0], secretKey[0] ); |
| |
| } catch (Exception e) { |
| logger.error("SetUserKeys " + e.getMessage(), e); |
| response.setStatus(401); |
| endResponse(response, e.toString()); |
| return; |
| } |
| response.setStatus(200); |
| endResponse(response, "User keys set successfully"); |
| } |
| |
| /** |
| * We are using the S3AuthParams class to hide where the header values are coming |
| * from so that the authenticateRequest call can be made from several places. |
| */ |
| public static S3AuthParams extractRequestHeaders(HttpServletRequest request) { |
| S3AuthParams params = new S3AuthParams(); |
| |
| Enumeration headers = request.getHeaderNames(); |
| if (null != headers) { |
| while (headers.hasMoreElements()) { |
| HeaderParam oneHeader = new HeaderParam(); |
| String headerName = (String)headers.nextElement(); |
| oneHeader.setName(headerName); |
| oneHeader.setValue(request.getHeader(headerName)); |
| params.addHeader(oneHeader); |
| } |
| } |
| return params; |
| } |
| |
| public static void authenticateRequest(HttpServletRequest request, S3AuthParams params) throws InstantiationException, IllegalAccessException, ClassNotFoundException, |
| SQLException { |
| RestAuth auth = new RestAuth(ServiceProvider.getInstance().getUseSubDomain()); |
| String AWSAccessKey = null; |
| String signature = null; |
| String authorization = null; |
| |
| // [A] Is it an annonymous request? |
| if (null == (authorization = params.getHeader("Authorization"))) { |
| UserContext.current().initContext(); |
| return; |
| } |
| |
| // [B] Is it an authenticated request? |
| int offset = authorization.indexOf("AWS"); |
| if (-1 != offset) { |
| String temp = authorization.substring(offset + 3).trim(); |
| offset = temp.indexOf(":"); |
| AWSAccessKey = temp.substring(0, offset); |
| signature = temp.substring(offset + 1); |
| } |
| |
| // [C] Calculate the signature from the request's headers |
| auth.setDateHeader(request.getHeader("Date")); |
| auth.setContentTypeHeader(request.getHeader("Content-Type")); |
| auth.setContentMD5Header(request.getHeader("Content-MD5")); |
| auth.setHostHeader(request.getHeader("Host")); |
| auth.setQueryString(request.getQueryString()); |
| auth.addUriPath(request.getRequestURI()); |
| |
| // -> are their any Amazon specific (i.e. 'x-amz-' ) headers? |
| HeaderParam[] headers = params.getHeaders(); |
| for (int i = 0; null != headers && i < headers.length; i++) { |
| String headerName = headers[i].getName(); |
| String ignoreCase = headerName.toLowerCase(); |
| if (ignoreCase.startsWith("x-amz-")) |
| auth.addAmazonHeader(headerName + ":" + headers[i].getValue()); |
| } |
| |
| UserInfo info = ServiceProvider.getInstance().getUserInfo(AWSAccessKey); |
| if (info == null) |
| throw new PermissionDeniedException("Unable to authenticate access key: " + AWSAccessKey); |
| |
| try { |
| if (auth.verifySignature(request.getMethod(), info.getSecretKey(), signature)) { |
| UserContext.current().initContext(AWSAccessKey, info.getSecretKey(), AWSAccessKey, info.getDescription(), request); |
| return; |
| } |
| |
| } catch (SignatureException e) { |
| throw new PermissionDeniedException(e); |
| |
| } catch (UnsupportedEncodingException e) { |
| throw new PermissionDeniedException(e); |
| } |
| throw new PermissionDeniedException("Invalid signature"); |
| } |
| |
| private ServletAction routeRequest(HttpServletRequest request) { |
| // URL routing for S3 REST calls. |
| String pathInfo = request.getPathInfo(); |
| String bucketName = null; |
| String key = null; |
| |
| String serviceEndpoint = ServiceProvider.getInstance().getServiceEndpoint(); |
| String host = request.getHeader("Host"); |
| |
| // Check for unrecognized forms of URI information in request |
| |
| if ((pathInfo == null) || (pathInfo.indexOf('/') != 0)) |
| if ("POST".equalsIgnoreCase(request.getMethod())) |
| // Case where request is POST operation with no pathinfo |
| // This is the POST alternative to PUT described at s3.amazonaws.com API doc page 141 |
| { |
| return routePlainPostRequest(request); |
| } |
| |
| // Irrespective of whether the requester is using subdomain or full host naming of path expressions |
| // to buckets, wherever the request is made up of a service endpoint followed by a /, in AWS S3 this always |
| // conveys a ListAllMyBuckets command |
| |
| if ((serviceEndpoint.equalsIgnoreCase(host)) && (pathInfo.equalsIgnoreCase("/"))) { |
| request.setAttribute(S3Constants.BUCKET_ATTR_KEY, "/"); |
| return new S3BucketAction(); // for ListAllMyBuckets |
| } |
| |
| // Because there is a leading / at position 0 of pathInfo, now subtract this to process the remainder |
| pathInfo = pathInfo.substring(1); |
| |
| if (ServiceProvider.getInstance().getUseSubDomain()) |
| |
| { |
| // -> verify the format of the bucket name |
| int endPos = host.indexOf(ServiceProvider.getInstance().getMasterDomain()); |
| if (endPos > 0) { |
| bucketName = host.substring(0, endPos); |
| S3Engine.verifyBucketName(bucketName, false); |
| request.setAttribute(S3Constants.BUCKET_ATTR_KEY, bucketName); |
| } else |
| request.setAttribute(S3Constants.BUCKET_ATTR_KEY, ""); |
| |
| if (pathInfo == null || pathInfo.equalsIgnoreCase("/")) { |
| return new S3BucketAction(); |
| } else { |
| String objectKey = pathInfo.substring(1); |
| request.setAttribute(S3Constants.OBJECT_ATTR_KEY, objectKey); |
| return new S3ObjectAction(); |
| } |
| } |
| |
| else |
| |
| { |
| |
| int endPos = pathInfo.indexOf('/'); // Subsequent / character? |
| |
| if (endPos < 1) { |
| bucketName = pathInfo; |
| S3Engine.verifyBucketName(bucketName, false); |
| request.setAttribute(S3Constants.BUCKET_ATTR_KEY, bucketName); |
| return new S3BucketAction(); |
| } else { |
| bucketName = pathInfo.substring(0, endPos); |
| key = pathInfo.substring(endPos + 1); |
| S3Engine.verifyBucketName(bucketName, false); |
| |
| if (!key.isEmpty()) { |
| request.setAttribute(S3Constants.BUCKET_ATTR_KEY, bucketName); |
| request.setAttribute(S3Constants.OBJECT_ATTR_KEY, pathInfo.substring(endPos + 1)); |
| return new S3ObjectAction(); |
| } else { |
| request.setAttribute(S3Constants.BUCKET_ATTR_KEY, bucketName); |
| return new S3BucketAction(); |
| } |
| } |
| } |
| } |
| |
| public static void endResponse(HttpServletResponse response, String content) { |
| try { |
| byte[] data = content.getBytes(); |
| response.setContentLength(data.length); |
| OutputStream os = response.getOutputStream(); |
| os.write(data); |
| os.close(); |
| } catch (Throwable e) { |
| logger.error("Unexpected exception " + e.getMessage(), e); |
| } |
| } |
| |
| public static void writeResponse(HttpServletResponse response, String content) throws IOException { |
| byte[] data = content.getBytes(); |
| OutputStream os = response.getOutputStream(); |
| os.write(data); |
| } |
| |
| public static void writeResponse(HttpServletResponse response, InputStream is) throws IOException { |
| byte[] data = new byte[4096]; |
| int length = 0; |
| while ((length = is.read(data)) > 0) { |
| response.getOutputStream().write(data, 0, length); |
| } |
| } |
| |
| // Route for the case where request is POST operation with no pathinfo |
| // This is the POST alternative to PUT described at s3.amazonaws.com API doc, Amazon Simple |
| // Storage Service API Reference API Version 2006-03-01 page 141. |
| // The purpose of the plain POST operation is to add an object to a specified bucket using HTML forms. |
| |
| private S3ObjectAction routePlainPostRequest(HttpServletRequest request) { |
| // TODO - Remove the unnecessary fields below |
| // Obtain the mandatory fields from the HTML form or otherwise fail with a logger message |
| String keyString = request.getParameter("key"); |
| String metatagString = request.getParameter("x-amz-meta-tag"); |
| String bucketString = request.getParameter("Bucket"); |
| String aclString = request.getParameter("acl"); |
| String fileString = request.getParameter("file"); |
| |
| String accessKeyString = request.getParameter("AWSAccessKeyId"); |
| String signatureString = request.getParameter("Signature"); |
| |
| // Obtain the discretionary fields from the HTML form |
| String policyKeyString = request.getParameter("Policy"); |
| String metauuidString = request.getParameter("x-amz-meta-uuid"); |
| String redirectString = request.getParameter("redirect"); |
| |
| // if none of the above are null then ... |
| request.setAttribute(S3Constants.BUCKET_ATTR_KEY, bucketString); |
| request.setAttribute(S3Constants.OBJECT_ATTR_KEY, keyString); |
| request.setAttribute(S3Constants.PLAIN_POST_ACCESS_KEY, accessKeyString); |
| request.setAttribute(S3Constants.PLAIN_POST_SIGNATURE, signatureString); |
| |
| // -> authenticated calls |
| try { |
| // S3AuthParams params = extractRequestHeaders( request ); |
| S3AuthParams params = new S3AuthParams(); |
| HeaderParam headerParam1 = new HeaderParam("accessKey", accessKeyString); |
| params.addHeader(headerParam1); |
| HeaderParam headerParam2 = new HeaderParam("secretKey", signatureString); |
| params.addHeader(headerParam2); |
| authenticateRequest(request, params); |
| } catch (Exception e) { |
| logger.warn("Authentication details insufficient"); |
| } |
| |
| return new S3ObjectAction(); |
| |
| } |
| |
| /** |
| * A DIME request is really a SOAP request that we are dealing with, and so its |
| * authentication is the SOAP authentication approach. Since Axis2 does not handle |
| * DIME messages we deal with them here. |
| * |
| * @param request |
| * @param response |
| */ |
| private void processDimeRequest(HttpServletRequest request, HttpServletResponse response) { |
| S3PutObjectRequest putRequest = null; |
| S3PutObjectResponse putResponse = null; |
| int bytesRead = 0; |
| |
| S3Engine engine = new S3Engine(); |
| |
| try { |
| logRequest(request); |
| |
| MultiPartDimeInputStream ds = new MultiPartDimeInputStream(request.getInputStream()); |
| |
| // -> the first stream MUST be the SOAP party |
| if (ds.nextInputStream()) { |
| //logger.debug( "DIME msg [" + ds.getStreamType() + "," + ds.getStreamTypeFormat() + "," + ds.getStreamId() + "]" ); |
| byte[] buffer = new byte[8192]; |
| bytesRead = ds.read(buffer, 0, 8192); |
| //logger.debug( "DIME SOAP Bytes read: " + bytesRead ); |
| ByteArrayInputStream bis = new ByteArrayInputStream(buffer, 0, bytesRead); |
| putRequest = toEnginePutObjectRequest(bis); |
| } |
| |
| // -> we only need to support a DIME message with two bodyparts |
| if (null != putRequest && ds.nextInputStream()) { |
| InputStream is = ds.getInputStream(); |
| putRequest.setData(is); |
| } |
| |
| // -> need to do SOAP level auth here, on failure return the SOAP fault |
| StringBuffer xml = new StringBuffer(); |
| String AWSAccessKey = putRequest.getAccessKey(); |
| UserInfo info = ServiceProvider.getInstance().getUserInfo(AWSAccessKey); |
| try { |
| S3SoapAuth.verifySignature(putRequest.getSignature(), "PutObject", putRequest.getRawTimestamp(), AWSAccessKey, info.getSecretKey()); |
| |
| } catch (AxisFault e) { |
| String reason = e.toString(); |
| int start = reason.indexOf(".AxisFault:"); |
| if (-1 != start) |
| reason = reason.substring(start + 11); |
| |
| xml.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>"); |
| xml.append("<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" >\n"); |
| xml.append("<soap:Body>\n"); |
| xml.append("<soap:Fault>\n"); |
| xml.append("<faultcode>").append(e.getFaultCode().toString()).append("</faultcode>\n"); |
| xml.append("<faultstring>").append(reason).append("</faultstring>\n"); |
| xml.append("</soap:Fault>\n"); |
| xml.append("</soap:Body></soap:Envelope>"); |
| |
| endResponse(response, xml.toString()); |
| return; |
| } |
| |
| // -> PutObject S3 Bucket Policy would be done in the engine.handleRequest() call |
| UserContext.current().initContext(AWSAccessKey, info.getSecretKey(), AWSAccessKey, "S3 DIME request", request); |
| putResponse = engine.handleRequest(putRequest); |
| |
| xml.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>"); |
| xml.append("<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:tns=\"http://s3.amazonaws.com/doc/2006-03-01/\">"); |
| xml.append("<soap:Body>"); |
| xml.append("<tns:PutObjectResponse>"); |
| xml.append("<tns:PutObjectResponse>"); |
| xml.append("<tns:ETag>\"").append(putResponse.getETag()).append("\"</tns:ETag>"); |
| xml.append("<tns:LastModified>").append(DatatypeConverter.printDateTime(putResponse.getLastModified())).append("</tns:LastModified>"); |
| xml.append("</tns:PutObjectResponse></tns:PutObjectResponse>"); |
| xml.append("</soap:Body></soap:Envelope>"); |
| |
| endResponse(response, xml.toString()); |
| } catch (PermissionDeniedException e) { |
| logger.error("Unexpected exception " + e.getMessage(), e); |
| response.setStatus(403); |
| endResponse(response, "Access denied"); |
| } catch (Throwable e) { |
| logger.error("Unexpected exception " + e.getMessage(), e); |
| } finally { |
| } |
| } |
| |
| /** |
| * Convert the SOAP XML we extract from the DIME message into our local object. |
| * Here Axis2 is not parsing the SOAP for us. I tried to use the Amazon PutObject |
| * parser but it keep throwing exceptions. |
| * |
| * @param putObjectInline |
| * @return |
| * @throws Exception |
| */ |
| public static S3PutObjectRequest toEnginePutObjectRequest(InputStream is) throws Exception { |
| DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); |
| dbf.setNamespaceAware(true); |
| |
| DocumentBuilder db = dbf.newDocumentBuilder(); |
| Document doc = db.parse(is); |
| Node parent = null; |
| Node contents = null; |
| NodeList children = null; |
| String temp = null; |
| String element = null; |
| int count = 0; |
| |
| S3PutObjectRequest request = new S3PutObjectRequest(); |
| |
| // [A] Pull out the simple nodes first |
| NodeList part = getElement(doc, "http://s3.amazonaws.com/doc/2006-03-01/", "Bucket"); |
| if (null != part) { |
| if (null != (contents = part.item(0))) |
| request.setBucketName(contents.getFirstChild().getNodeValue()); |
| } |
| part = getElement(doc, "http://s3.amazonaws.com/doc/2006-03-01/", "Key"); |
| if (null != part) { |
| if (null != (contents = part.item(0))) |
| request.setKey(contents.getFirstChild().getNodeValue()); |
| } |
| part = getElement(doc, "http://s3.amazonaws.com/doc/2006-03-01/", "ContentLength"); |
| if (null != part) { |
| if (null != (contents = part.item(0))) { |
| String length = contents.getFirstChild().getNodeValue(); |
| if (null != length) |
| request.setContentLength(Long.decode(length)); |
| } |
| } |
| part = getElement(doc, "http://s3.amazonaws.com/doc/2006-03-01/", "AWSAccessKeyId"); |
| if (null != part) { |
| if (null != (contents = part.item(0))) |
| request.setAccessKey(contents.getFirstChild().getNodeValue()); |
| } |
| part = getElement(doc, "http://s3.amazonaws.com/doc/2006-03-01/", "Signature"); |
| if (null != part) { |
| if (null != (contents = part.item(0))) |
| request.setSignature(contents.getFirstChild().getNodeValue()); |
| } |
| part = getElement(doc, "http://s3.amazonaws.com/doc/2006-03-01/", "Timestamp"); |
| if (null != part) { |
| if (null != (contents = part.item(0))) |
| request.setRawTimestamp(contents.getFirstChild().getNodeValue()); |
| } |
| part = getElement(doc, "http://s3.amazonaws.com/doc/2006-03-01/", "StorageClass"); |
| if (null != part) { |
| if (null != (contents = part.item(0))) |
| request.setStorageClass(contents.getFirstChild().getNodeValue()); |
| } |
| part = getElement(doc, "http://s3.amazonaws.com/doc/2006-03-01/", "Credential"); |
| if (null != part) { |
| if (null != (contents = part.item(0))) |
| request.setCredential(contents.getFirstChild().getNodeValue()); |
| } |
| |
| // [B] Get a list of all 'Metadata' elements |
| part = getElement(doc, "http://s3.amazonaws.com/doc/2006-03-01/", "Metadata"); |
| if (null != part) { |
| count = part.getLength(); |
| S3MetaDataEntry[] metaEntry = new S3MetaDataEntry[count]; |
| |
| for (int i = 0; i < count; i++) { |
| parent = part.item(i); |
| metaEntry[i] = new S3MetaDataEntry(); |
| |
| // -> get a list of all the children elements of the 'Metadata' parent element |
| if (null != (children = parent.getChildNodes())) { |
| int numChildren = children.getLength(); |
| for (int j = 0; j < numChildren; j++) { |
| contents = children.item(j); |
| element = contents.getNodeName().trim(); |
| if (element.endsWith("Name")) { |
| temp = contents.getFirstChild().getNodeValue(); |
| if (null != temp) |
| metaEntry[i].setName(temp); |
| } else if (element.endsWith("Value")) { |
| temp = contents.getFirstChild().getNodeValue(); |
| if (null != temp) |
| metaEntry[i].setValue(temp); |
| } |
| } |
| } |
| } |
| request.setMetaEntries(metaEntry); |
| } |
| |
| // [C] Get a list of all Grant elements in an AccessControlList |
| part = getElement(doc, "http://s3.amazonaws.com/doc/2006-03-01/", "Grant"); |
| if (null != part) { |
| S3AccessControlList engineAcl = new S3AccessControlList(); |
| |
| count = part.getLength(); |
| for (int i = 0; i < count; i++) { |
| parent = part.item(i); |
| S3Grant engineGrant = new S3Grant(); |
| |
| // -> get a list of all the children elements of the 'Grant' parent element |
| if (null != (children = parent.getChildNodes())) { |
| int numChildren = children.getLength(); |
| for (int j = 0; j < numChildren; j++) { |
| contents = children.item(j); |
| element = contents.getNodeName().trim(); |
| if (element.endsWith("Grantee")) { |
| NamedNodeMap attbs = contents.getAttributes(); |
| if (null != attbs) { |
| Node type = attbs.getNamedItemNS("http://www.w3.org/2001/XMLSchema-instance", "type"); |
| if (null != type) |
| temp = type.getFirstChild().getNodeValue().trim(); |
| else |
| temp = null; |
| |
| if (null != temp && temp.equalsIgnoreCase("CanonicalUser")) { |
| engineGrant.setGrantee(SAcl.GRANTEE_USER); |
| engineGrant.setCanonicalUserID(getChildNodeValue(contents, "ID")); |
| } else |
| throw new UnsupportedOperationException("Missing http://www.w3.org/2001/XMLSchema-instance:type value"); |
| } |
| } else if (element.endsWith("Permission")) { |
| temp = contents.getFirstChild().getNodeValue().trim(); |
| if (temp.equalsIgnoreCase("READ")) |
| engineGrant.setPermission(SAcl.PERMISSION_READ); |
| else if (temp.equalsIgnoreCase("WRITE")) |
| engineGrant.setPermission(SAcl.PERMISSION_WRITE); |
| else if (temp.equalsIgnoreCase("READ_ACP")) |
| engineGrant.setPermission(SAcl.PERMISSION_READ_ACL); |
| else if (temp.equalsIgnoreCase("WRITE_ACP")) |
| engineGrant.setPermission(SAcl.PERMISSION_WRITE_ACL); |
| else if (temp.equalsIgnoreCase("FULL_CONTROL")) |
| engineGrant.setPermission(SAcl.PERMISSION_FULL); |
| else |
| throw new UnsupportedOperationException("Unsupported permission: " + temp); |
| } |
| } |
| engineAcl.addGrant(engineGrant); |
| } |
| } |
| request.setAcl(engineAcl); |
| } |
| return request; |
| } |
| |
| /** |
| * Have to deal with XML with and without namespaces. |
| */ |
| public static NodeList getElement(Document doc, String namespace, String tagName) { |
| NodeList part = doc.getElementsByTagNameNS(namespace, tagName); |
| if (null == part || 0 == part.getLength()) |
| part = doc.getElementsByTagName(tagName); |
| |
| return part; |
| } |
| |
| /** |
| * Looking for the value of a specific child of the given parent node. |
| * |
| * @param parent |
| * @param childName |
| * @return |
| */ |
| private static String getChildNodeValue(Node parent, String childName) { |
| NodeList children = null; |
| Node element = null; |
| |
| if (null != (children = parent.getChildNodes())) { |
| int numChildren = children.getLength(); |
| for (int i = 0; i < numChildren; i++) { |
| if (null != (element = children.item(i))) { |
| // -> name may have a namespace on it |
| String name = element.getNodeName().trim(); |
| if (name.endsWith(childName)) { |
| String value = element.getFirstChild().getNodeValue(); |
| if (null != value) |
| value = value.trim(); |
| return value; |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| private void logRequest(HttpServletRequest request) { |
| if (logger.isInfoEnabled()) { |
| logger.info("Request method: " + request.getMethod()); |
| logger.info("Request contextPath: " + request.getContextPath()); |
| logger.info("Request pathInfo: " + request.getPathInfo()); |
| logger.info("Request pathTranslated: " + request.getPathTranslated()); |
| logger.info("Request queryString: " + request.getQueryString()); |
| logger.info("Request requestURI: " + request.getRequestURI()); |
| logger.info("Request requestURL: " + request.getRequestURL()); |
| logger.info("Request servletPath: " + request.getServletPath()); |
| Enumeration headers = request.getHeaderNames(); |
| if (headers != null) { |
| while (headers.hasMoreElements()) { |
| Object headerName = headers.nextElement(); |
| logger.info("Request header " + headerName + ":" + request.getHeader((String)headerName)); |
| } |
| } |
| |
| Enumeration params = request.getParameterNames(); |
| if (params != null) { |
| while (params.hasMoreElements()) { |
| Object paramName = params.nextElement(); |
| logger.info("Request parameter " + paramName + ":" + request.getParameter((String)paramName)); |
| } |
| } |
| logger.info("- End of request -"); |
| } |
| } |
| } |