diff --git a/cloudbridge/deploy-cloud-bridge.sh b/cloudbridge/deploy-cloud-bridge.sh
old mode 100644
new mode 100755
diff --git a/cloudbridge/src/com/cloud/bridge/service/S3RestServlet.java b/cloudbridge/src/com/cloud/bridge/service/S3RestServlet.java
index 324ed8d1867..b21afd41f8a 100644
--- a/cloudbridge/src/com/cloud/bridge/service/S3RestServlet.java
+++ b/cloudbridge/src/com/cloud/bridge/service/S3RestServlet.java
@@ -311,7 +311,7 @@ public class S3RestServlet extends HttpServlet {
if (info == null) throw new PermissionDeniedException("Unable to authenticate access key: " + AWSAccessKey);
try {
- if (auth.verifySignature( request.getMethod(), info.getSecretKey(), signature )) {
+ if (auth.verifySignature( request.getMethod(), info.getSecretKey(), signature )) {
UserContext.current().initContext(AWSAccessKey, info.getSecretKey(), AWSAccessKey, info.getDescription(), request);
return;
}
diff --git a/cloudbridge/src/com/cloud/bridge/service/controller/s3/S3BucketAction.java b/cloudbridge/src/com/cloud/bridge/service/controller/s3/S3BucketAction.java
index dfb21e9ccf9..55b5429fd2a 100644
--- a/cloudbridge/src/com/cloud/bridge/service/controller/s3/S3BucketAction.java
+++ b/cloudbridge/src/com/cloud/bridge/service/controller/s3/S3BucketAction.java
@@ -604,19 +604,19 @@ public class S3BucketAction implements ServletAction {
public void executeGetBucketLogging(HttpServletRequest request, HttpServletResponse response) throws IOException {
// TODO -- this is a beta feature of S3
- response.setStatus(501);
+ response.setStatus(405);
}
public void executeGetBucketLocation(HttpServletRequest request, HttpServletResponse response) throws IOException {
- response.setStatus(501);
+ response.setStatus(405);
}
public void executeGetBucketWebsite(HttpServletRequest request, HttpServletResponse response) throws IOException {
- response.setStatus(501);
+ response.setStatus(405);
}
public void executeDeleteBucketWebsite(HttpServletRequest request, HttpServletResponse response) throws IOException {
- response.setStatus(501);
+ response.setStatus(405);
}
public void executePutBucket(HttpServletRequest request, HttpServletResponse response) throws IOException
@@ -757,8 +757,11 @@ public class S3BucketAction implements ServletAction {
}
public void executePutBucketWebsite(HttpServletRequest request, HttpServletResponse response) throws IOException {
- // TODO -- HiPri - Implement Put Bucket Website
- response.setStatus(501);
+ // TODO -- HiPri - Undertake checks on Put Bucket Website
+ // Tested using configuration \nAllowOverride FileInfo AuthConfig Limit... 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
diff --git a/cloudbridge/src/com/cloud/bridge/service/controller/s3/S3ObjectAction.java b/cloudbridge/src/com/cloud/bridge/service/controller/s3/S3ObjectAction.java
index ea85d508d06..542859c0e8a 100644
--- a/cloudbridge/src/com/cloud/bridge/service/controller/s3/S3ObjectAction.java
+++ b/cloudbridge/src/com/cloud/bridge/service/controller/s3/S3ObjectAction.java
@@ -105,7 +105,7 @@ public class S3ObjectAction implements ServletAction {
throws IOException, XMLStreamException
{
String method = request.getMethod();
- String queryString = request.getQueryString();
+ String queryString = request.getQueryString();
String copy = null;
response.addHeader( "x-amz-request-id", UUID.randomUUID().toString());
@@ -143,9 +143,9 @@ public class S3ObjectAction implements ServletAction {
}
else executeDeleteObject(request, response);
}
- else if (method.equalsIgnoreCase( "HEAD" ))
- {
- executeHeadObject(request, response);
+ else if (method.equalsIgnoreCase( "HEAD" ))
+ {
+ executeHeadObject(request, response);
}
else if (method.equalsIgnoreCase( "POST" ))
{
@@ -302,7 +302,8 @@ public class S3ObjectAction implements ServletAction {
private void executeGetObject(HttpServletRequest request, HttpServletResponse response) throws IOException
{
String bucket = (String) request.getAttribute(S3Constants.BUCKET_ATTR_KEY);
- String key = (String) request.getAttribute(S3Constants.OBJECT_ATTR_KEY);
+ String key = (String) request.getAttribute(S3Constants.OBJECT_ATTR_KEY);
+
S3GetObjectRequest engineRequest = new S3GetObjectRequest();
engineRequest.setBucketName(bucket);
@@ -713,8 +714,10 @@ public class S3ObjectAction implements ServletAction {
String cannedAccess = null;
int uploadId = -1;
- // -> Amazon defines to keep connection alive by sending whitespace characters until done
+ // AWS S3 specifies that the keep alive connection is by sending whitespace characters until done
+ // Therefore the XML version prolog is prepended to the stream in advance
OutputStream os = response.getOutputStream();
+ os.write("".getBytes());
String temp = request.getParameter("uploadId");
if (null != temp) uploadId = Integer.parseInt( temp );
@@ -781,7 +784,8 @@ public class S3ObjectAction implements ServletAction {
xml.append( "" ).append( key ).append( "" );
xml.append( "\"" ).append( engineResponse.getETag()).append( "\"" );
xml.append( "" );
- os.write( xml.toString().getBytes());
+ String xmlString = xml.toString().replaceAll("^\\s+", ""); // Remove leading whitespace characters
+ os.write( xmlString.getBytes());
os.close();
}
else returnErrorXML( result, null, os );
diff --git a/cloudbridge/src/com/cloud/bridge/util/RestAuth.java b/cloudbridge/src/com/cloud/bridge/util/RestAuth.java
index 5941f44c266..b289b5f1933 100755
--- a/cloudbridge/src/com/cloud/bridge/util/RestAuth.java
+++ b/cloudbridge/src/com/cloud/bridge/util/RestAuth.java
@@ -48,14 +48,33 @@ public class RestAuth {
protected boolean amzDateSet = false;
protected boolean useSubDomain = false;
+ protected Set allowedQueryParams;
+
public RestAuth() {
// these must be lexicographically sorted
AmazonHeaders = new TreeMap();
+ allowedQueryParams = new HashSet() {{
+ add("acl");
+ add("lifecycle");
+ add("location");
+ add("logging");
+ add("notification");
+ add("partNumber");
+ add("policy");
+ add("requestPayment");
+ add("torrent");
+ add("uploadId");
+ add("uploads");
+ add("versionId");
+ add("versioning");
+ add("versions");
+ add("website");
+ }};
}
public RestAuth(boolean useSubDomain) {
- // these must be lexicographically sorted
- AmazonHeaders = new TreeMap();
+ //invoke the other constructor
+ this();
this.useSubDomain = useSubDomain;
}
@@ -140,22 +159,28 @@ public class RestAuth {
return;
}
- query = new String( "?" + query.trim());
+ // Sub-resources (i.e.: query params) must be lex sorted
+ Set subResources = new TreeSet();
- // -> only interested in this subset of parameters
- if (query.startsWith( "?acl") || query.startsWith( "?lifecycle" ) || query.startsWith( "?location" ) ||
- query.startsWith( "?logging" ) || query.startsWith( "?notification" ) || query.startsWith( "?partNumber" ) ||
- query.startsWith( "?policy" ) || query.startsWith( "?requestPayment" ) || query.startsWith( "?torrent" ) ||
- query.startsWith( "?uploadId" ) || query.startsWith( "?uploads" ) || query.startsWith( "?versionId" ) ||
- query.startsWith( "?versioning" ) || query.startsWith( "?versions" ) || query.startsWith( "?website" )
- )
- {
-
- // The query string includes the value given to the start parameter (using =) but ignores all other arguments after &
- int offset = query.indexOf( "&" );
- if ( -1 != offset ) query = query.substring( 0, offset );
- this.queryString = query;
+ String [] queryParams = query.split("&");
+ StringBuffer builtQuery= new StringBuffer();
+ for (String queryParam:queryParams) {
+ // lookup parameter name
+ String paramName = queryParam.split("=")[0];
+ if (allowedQueryParams.contains(paramName)) {
+ subResources.add(queryParam);
+ }
}
+ for (String subResource:subResources) {
+ builtQuery.append(subResource + "&");
+ }
+ // If anything inside the string buffer, add a "?" at the beginning,
+ // and then remove the last '&'
+ if (builtQuery.length() > 0) {
+ builtQuery.insert(0, "?");
+ builtQuery.deleteCharAt(builtQuery.length()-1);
+ }
+ this.queryString = builtQuery.toString();
}
@@ -235,7 +260,7 @@ public class RestAuth {
throws SignatureException, UnsupportedEncodingException {
if (null == httpVerb || null == secretKey || null == signature) return false;
-
+
httpVerb = httpVerb.trim();
secretKey = secretKey.trim();
signature = signature.trim();
@@ -243,7 +268,6 @@ public class RestAuth {
// First calculate the StringToSign after the caller has initialized all the header values
String StringToSign = genStringToSign( httpVerb );
String calSig = calculateRFC2104HMAC( StringToSign, secretKey );
-
// Was the passed in signature URL encoded? (it must be base64 encoded)
int offset = signature.indexOf( "%" );
if (-1 != offset) signature = URLDecoder.decode( signature, "UTF-8" );
@@ -273,14 +297,12 @@ public class RestAuth {
StringBuffer canonicalized = new StringBuffer();
String temp = null;
String canonicalizedResourceElement = genCanonicalizedResourceElement();
-
-
canonicalized.append( httpVerb ).append( "\n" );
- if ( (null != this.contentMD5) && (null == canonicalizedResourceElement) )
+ if ( (null != this.contentMD5) )
canonicalized.append( this.contentMD5 );
canonicalized.append( "\n" );
- if ( (null != this.contentType) && (null == canonicalizedResourceElement) )
+ if ( (null != this.contentType) )
canonicalized.append( this.contentType );
canonicalized.append( "\n" );
@@ -356,7 +378,6 @@ public class RestAuth {
*/
private String calculateRFC2104HMAC( String signIt, String secretKey )
throws SignatureException {
-
String result = null;
try {
SecretKeySpec key = new SecretKeySpec( secretKey.getBytes(), "HmacSHA1" );