-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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