Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save luciofm/82617fbcd59b193ead9d8e07a516a122 to your computer and use it in GitHub Desktop.
Save luciofm/82617fbcd59b193ead9d8e07a516a122 to your computer and use it in GitHub Desktop.
Creates okhttp3.Requests for uploading files to an Amazon S3 storage bucket. Implements https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-authentication-HTTPPOST.html
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import okio.BufferedSource;
import okio.ByteString;
import okio.Okio;
public final class AmazonS3RequestFactory {
private static final Charset UTF_8 = Charset.forName("UTF-8");
private static final ThreadLocal<DateFormat> AWS_DATE_FORMAT = new ThreadLocal<DateFormat>() {
@Override protected DateFormat initialValue() {
SimpleDateFormat format =
new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss 'GMT+00:00'", Locale.US);
format.setTimeZone(TimeZone.getTimeZone("GMT"));
return format;
}
};
private final HttpUrl base;
private final String accessKeyId;
private final String secretKey;
public AmazonS3RequestFactory(String bucket, String accessKeyId, String secretKey) {
base = new HttpUrl.Builder().scheme("https")
.host("s3.amazonaws.com")
.addPathSegment(bucket)
.build();
this.accessKeyId = accessKeyId;
this.secretKey = secretKey;
}
public Request request(String relativeUrlPath, File media) {
if (media == null) {
throw new NullPointerException("media == null");
}
if (!media.exists()) {
throw new IllegalArgumentException(
String.format(Locale.US, "File %1$s does not exist.", media));
}
if (relativeUrlPath == null) {
throw new NullPointerException("relativeUrlPath == null");
}
HttpUrl url =
base.newBuilder().addPathSegments(relativeUrlPath).addPathSegment(media.getName()).build();
String extensionName;
try {
extensionName = fileExtensionName(media);
} catch (IOException e) {
extensionName = "jpeg";
}
String mime = "image/" + extensionName;
String date = AWS_DATE_FORMAT.get().format(new Date());
String stringToSign = "PUT\n\n" + mime + "\n" + date + "\n" + url.encodedPath();
return new Request.Builder().url(url)
.put(RequestBody.create(MediaType.parse(mime), media))
.header("Date", date)
.header("Host", "s3.amazonaws.com")
.header("Authorization", "AWS " + accessKeyId + ":" + awsSignature(secretKey, stringToSign))
.build();
}
// https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-authentication-HTTPPOST.html
private static String awsSignature(String secret, String stringToSign) {
return ByteString.of(stringToSign.getBytes(UTF_8))
.hmacSha1(ByteString.of(secret.getBytes(UTF_8)))
.base64();
}
private static final ByteString PNG_HEADER = ByteString.decodeHex("89504e470d0a1a0a");
private static final ByteString JPEG_HEADER = ByteString.decodeHex("ffd8ff");
private static final ByteString GIF_HEADER_87 = ByteString.decodeHex("474946383761");
private static final ByteString GIF_HEADER_89 = ByteString.decodeHex("474946383961");
private static final ByteString TIFF_HEADER_LITTLE_ENDIAN = ByteString.decodeHex("49492A00");
private static final ByteString TIFF_HEADER_BIG_ENDIAN = ByteString.decodeHex("4D4D002A");
private static String fileExtensionName(File media) throws IOException {
BufferedSource mediaSource = Okio.buffer(Okio.source(media));
try {
if (mediaSource.rangeEquals(0, PNG_HEADER)) {
return "png";
}
if (mediaSource.rangeEquals(0, JPEG_HEADER)) {
return "jpeg";
}
if (mediaSource.rangeEquals(0, GIF_HEADER_87)) {
return "gif";
}
if (mediaSource.rangeEquals(0, GIF_HEADER_89)) {
return "gif";
}
if (mediaSource.rangeEquals(0, TIFF_HEADER_LITTLE_ENDIAN)) {
return "tiff";
}
if (mediaSource.rangeEquals(0, TIFF_HEADER_BIG_ENDIAN)) {
return "tiff";
}
throw new IOException(
String.format(Locale.US, "Unsupported file type. File name: %1$s. Hex: %2$s.",
media.getPath(), mediaSource.readByteString().hex()));
} finally {
try {
mediaSource.close();
} catch (IOException ignored) {
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment