kopia lustrzana https://github.com/gaul/s3proxy
rodzic
1797992e02
commit
2207dfbd21
5
pom.xml
5
pom.xml
|
@ -281,6 +281,11 @@
|
|||
<version>4.12</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-fileupload</groupId>
|
||||
<artifactId>commons-fileupload</artifactId>
|
||||
<version>1.3.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.jclouds</groupId>
|
||||
<artifactId>jclouds-allblobstore</artifactId>
|
||||
|
|
|
@ -18,6 +18,7 @@ package org.gaul.s3proxy;
|
|||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
@ -74,6 +75,7 @@ import com.google.common.io.ByteStreams;
|
|||
import com.google.common.net.HostAndPort;
|
||||
import com.google.common.net.HttpHeaders;
|
||||
|
||||
import org.apache.commons.fileupload.MultipartStream;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||
import org.jclouds.blobstore.BlobRequestSigner;
|
||||
|
@ -287,7 +289,8 @@ final class S3ProxyHandler extends AbstractHandler {
|
|||
}
|
||||
|
||||
// anonymous request
|
||||
if ((method.equals("GET") || method.equals("HEAD")) &&
|
||||
if ((method.equals("GET") || method.equals("HEAD") ||
|
||||
method.equals("POST")) &&
|
||||
request.getHeader(HttpHeaders.AUTHORIZATION) == null &&
|
||||
request.getParameter("AWSAccessKeyId") == null &&
|
||||
defaultBlobStore != null) {
|
||||
|
@ -592,6 +595,12 @@ final class S3ProxyHandler extends AbstractHandler {
|
|||
return;
|
||||
}
|
||||
break;
|
||||
case "POST":
|
||||
if (path.length <= 2 || path[2].isEmpty()) {
|
||||
handlePostBlob(request, response, blobStore, path[1]);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -1345,6 +1354,110 @@ final class S3ProxyHandler extends AbstractHandler {
|
|||
}
|
||||
}
|
||||
|
||||
private void handlePostBlob(HttpServletRequest request,
|
||||
HttpServletResponse response, BlobStore blobStore,
|
||||
String containerName)
|
||||
throws IOException, S3Exception {
|
||||
String boundaryHeader = request.getHeader(HttpHeaders.CONTENT_TYPE);
|
||||
if (boundaryHeader == null ||
|
||||
!boundaryHeader.startsWith("multipart/form-data; boundary=")) {
|
||||
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
|
||||
return;
|
||||
}
|
||||
String boundary =
|
||||
boundaryHeader.substring(boundaryHeader.indexOf('=') + 1);
|
||||
|
||||
String blobName = null;
|
||||
String contentType = null;
|
||||
String identity = null;
|
||||
// TODO: handle policy
|
||||
byte[] policy = null;
|
||||
String signature = null;
|
||||
byte[] payload = null;
|
||||
try (InputStream is = request.getInputStream()) {
|
||||
MultipartStream multipartStream = new MultipartStream(is,
|
||||
boundary.getBytes(StandardCharsets.UTF_8), 4096, null);
|
||||
boolean nextPart = multipartStream.skipPreamble();
|
||||
while (nextPart) {
|
||||
String header = multipartStream.readHeaders();
|
||||
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
|
||||
multipartStream.readBodyData(baos);
|
||||
if (startsWithIgnoreCase(header,
|
||||
"Content-Disposition: form-data;" +
|
||||
" name=\"acl\"")) {
|
||||
// TODO: acl
|
||||
} else if (startsWithIgnoreCase(header,
|
||||
"Content-Disposition: form-data;" +
|
||||
" name=\"AWSAccessKeyId\"")) {
|
||||
identity = new String(baos.toByteArray());
|
||||
} else if (startsWithIgnoreCase(header,
|
||||
"Content-Disposition: form-data;" +
|
||||
" name=\"Content-Type\"")) {
|
||||
contentType = new String(baos.toByteArray());
|
||||
} else if (startsWithIgnoreCase(header,
|
||||
"Content-Disposition: form-data;" +
|
||||
" name=\"file\"")) {
|
||||
// TODO: buffers entire payload
|
||||
payload = baos.toByteArray();
|
||||
} else if (startsWithIgnoreCase(header,
|
||||
"Content-Disposition: form-data;" +
|
||||
" name=\"key\"")) {
|
||||
blobName = new String(baos.toByteArray());
|
||||
} else if (startsWithIgnoreCase(header,
|
||||
"Content-Disposition: form-data;" +
|
||||
" name=\"policy\"")) {
|
||||
policy = baos.toByteArray();
|
||||
} else if (startsWithIgnoreCase(header,
|
||||
"Content-Disposition: form-data;" +
|
||||
" name=\"signature\"")) {
|
||||
signature = new String(baos.toByteArray());
|
||||
}
|
||||
}
|
||||
nextPart = multipartStream.readBoundary();
|
||||
}
|
||||
}
|
||||
|
||||
if (identity == null || signature == null || blobName == null ||
|
||||
policy == null) {
|
||||
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
|
||||
return;
|
||||
}
|
||||
|
||||
Map.Entry<String, BlobStore> provider =
|
||||
blobStoreLocator.locateBlobStore(identity, null, null);
|
||||
if (provider == null) {
|
||||
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
|
||||
return;
|
||||
}
|
||||
String credential = provider.getKey();
|
||||
|
||||
Mac mac;
|
||||
try {
|
||||
mac = Mac.getInstance("HmacSHA1");
|
||||
mac.init(new SecretKeySpec(credential.getBytes(
|
||||
StandardCharsets.UTF_8), "HmacSHA1"));
|
||||
} catch (InvalidKeyException | NoSuchAlgorithmException e) {
|
||||
throw Throwables.propagate(e);
|
||||
}
|
||||
String expectedSignature = BaseEncoding.base64().encode(
|
||||
mac.doFinal(policy));
|
||||
if (!signature.equals(expectedSignature)) {
|
||||
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
|
||||
return;
|
||||
}
|
||||
|
||||
BlobBuilder.PayloadBlobBuilder builder = blobStore
|
||||
.blobBuilder(blobName)
|
||||
.payload(payload);
|
||||
if (contentType != null) {
|
||||
builder.contentType(contentType);
|
||||
}
|
||||
Blob blob = builder.build();
|
||||
blobStore.putBlob(containerName, blob);
|
||||
|
||||
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
|
||||
}
|
||||
|
||||
private void handleInitiateMultipartUpload(HttpServletRequest request,
|
||||
HttpServletResponse response, BlobStore blobStore,
|
||||
String containerName, String blobName) throws IOException {
|
||||
|
@ -2120,4 +2233,8 @@ final class S3ProxyHandler extends AbstractHandler {
|
|||
}
|
||||
return eTag;
|
||||
}
|
||||
|
||||
private static boolean startsWithIgnoreCase(String string, String prefix) {
|
||||
return string.toLowerCase().startsWith(prefix.toLowerCase());
|
||||
}
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue