/*
* Copyright (C) 2014 Stichting Akvo (Akvo Foundation)
*
* This file is part of Akvo FLOW.
*
* Akvo FLOW is free software: you can redistribute it and modify it under the terms of
* the GNU Affero General Public License (AGPL) as published by the Free Software Foundation,
* either version 3 of the License or any later version.
*
* Akvo FLOW is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero General Public License included below for more details.
*
* The full license text can also be seen at <http://www.gnu.org/licenses/agpl.html>.
*/
package com.gallatinsystems.common.util;
import static com.gallatinsystems.common.Constants.AWS_ACCESS_ID;
import static com.gallatinsystems.common.Constants.AWS_SECRET_KEY;
import static com.gallatinsystems.common.Constants.CONNECTION_TIMEOUT;
import static com.gallatinsystems.common.Constants.READ_TIMEOUT;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
public class S3Util {
private static Logger log = Logger.getLogger(S3Util.class.getName());
private static final long EXPIRE_DATE = 2145916800; // 2038-01-01 00:00:00 +000
private static final String S3_URL = "https://%s.s3.amazonaws.com/%s";
private static final String GET_PAYLOAD = "GET\n\n\n%s\n/%s/%s";
private static final String BROWSER_GET_PAYLOAD = "GET\n\n\n" + EXPIRE_DATE + "\n/%s/%s";
private static final String PUT_PAYLOAD_PUBLIC = "PUT\n%s\n%s\n%s\nx-amz-acl:public-read\n/%s/%s";
private static final String PUT_PAYLOAD_PRIVATE = "PUT\n%s\n%s\n%s\n/%s/%s";
private static final String PUT_PAYLOAD_ACL = "PUT\n\n\n%s\nx-amz-acl:%s\n/%s/%s?acl";
private static final String GET_PAYLOAD_ACL = "GET\n\n\n%s\n/%s/%s?acl";
public enum ACL {
PRIVATE("private"), PUBLIC_READ("public-read");
private String val;
ACL(String v) {
this.val = v;
}
@Override
public String toString() {
return val;
}
}
public static URLConnection getConnection(String bucketName, String objectKey)
throws IOException {
return getConnection(bucketName, objectKey, PropertyUtil.getProperty(AWS_ACCESS_ID),
PropertyUtil.getProperty(AWS_SECRET_KEY));
}
public static URLConnection getConnection(String bucketName, String objectKey,
String awsAccessKeyId,
String awsAccessSecret) throws IOException {
final String date = getDate();
final String payload = String.format(GET_PAYLOAD, date, bucketName, objectKey);
final String signature = MD5Util.generateHMAC(payload, awsAccessSecret);
final URL url = new URL(String.format(S3_URL, bucketName, objectKey));
final URLConnection conn = url.openConnection();
conn.setConnectTimeout(CONNECTION_TIMEOUT);
conn.setReadTimeout(READ_TIMEOUT);
conn.addRequestProperty("Cache-Control", "no-cache,max-age=0");
conn.setRequestProperty("Date", date);
conn.setRequestProperty("Authorization", "AWS " + awsAccessKeyId + ":" + signature);
return conn;
}
public static boolean put(String bucketName, String objectKey, byte[] data, String contentType,
boolean isPublic) throws IOException {
final String awsAccessId = PropertyUtil.getProperty(AWS_ACCESS_ID);
final String awsSecretKey = PropertyUtil.getProperty(AWS_SECRET_KEY);
return put(bucketName, objectKey, data, contentType, isPublic, awsAccessId, awsSecretKey);
}
public static boolean put(String bucketName, String objectKey, byte[] data, String contentType,
boolean isPublic, String awsAccessId, String awsSecretKey) throws IOException {
final byte[] md5Raw = MD5Util.md5(data);
final String md5Base64 = Base64.encodeBase64String(md5Raw).trim();
final String md5Hex = MD5Util.toHex(md5Raw);
final String date = getDate();
final String payloadStr = isPublic ? PUT_PAYLOAD_PUBLIC : PUT_PAYLOAD_PRIVATE;
final String payload = String.format(payloadStr, md5Base64, contentType, date, bucketName,
objectKey);
final String signature = MD5Util.generateHMAC(payload, awsSecretKey);
final URL url = new URL(String.format(S3_URL, bucketName, objectKey));
OutputStream out = null;
HttpURLConnection conn = null;
try {
conn = (HttpURLConnection) url.openConnection();
conn.setDoOutput(true);
conn.setRequestMethod("PUT");
conn.setRequestProperty("Content-MD5", md5Base64);
conn.setRequestProperty("Content-Type", contentType);
conn.setRequestProperty("Date", date);
if (isPublic) {
// If we don't send this header, the object will be private by default
conn.setRequestProperty("x-amz-acl", "public-read");
}
conn.setRequestProperty("Authorization", "AWS " + awsAccessId + ":" + signature);
out = new BufferedOutputStream(conn.getOutputStream());
IOUtils.copy(new ByteArrayInputStream(data), out);
out.flush();
int status = conn.getResponseCode();
if (status != 200 && status != 201) {
log.severe("Error uploading file: " + url.toString());
log.severe(IOUtils.toString(conn.getInputStream()));
return false;
}
String etag = conn.getHeaderField("ETag");
etag = etag != null ? etag.replaceAll("\"", "") : null;// Remove quotes
if (!md5Hex.equals(etag)) {
log.severe("ETag comparison failed. Response ETag: " + etag +
"Locally computed MD5: " + md5Hex);
return false;
}
return true;
} finally {
if (conn != null) {
conn.disconnect();
}
IOUtils.closeQuietly(out);
}
}
public static String getBrowserLink(String bucketName, String objectKey) {
final String awsAccessId = PropertyUtil.getProperty(AWS_ACCESS_ID);
final String awsSecretKey = PropertyUtil.getProperty(AWS_SECRET_KEY);
return getBrowserLink(bucketName, objectKey, awsAccessId, awsSecretKey);
}
public static String getBrowserLink(String bucketName, String objectKey, String awsAccessId,
String awsSecretKey) {
final String payload = String.format(BROWSER_GET_PAYLOAD, bucketName, objectKey);
final String signature = MD5Util.generateHMAC(payload, awsSecretKey);
final StringBuffer sb = new StringBuffer(String.format(S3_URL, bucketName, objectKey));
sb.append("?AWSAccessKeyId=").append(awsAccessId);
sb.append("&Expires=").append(EXPIRE_DATE);
try {
sb.append("&Signature=").append(URLEncoder.encode(signature, "UTF-8"));
} catch (UnsupportedEncodingException e) {
log.log(Level.SEVERE, "Error generating signature for browser URL: " + e.getMessage());
}
return sb.toString();
}
public static boolean putObjectAcl(String bucketName, String objectKey, ACL acl)
throws IOException {
final String awsAccessId = PropertyUtil.getProperty(AWS_ACCESS_ID);
final String awsSecretKey = PropertyUtil.getProperty(AWS_SECRET_KEY);
return putObjectAcl(bucketName, objectKey, acl, awsAccessId, awsSecretKey);
}
public static boolean putObjectAcl(String bucketName, String objectKey, ACL acl,
String awsAccessId, String awsSecretKey) throws IOException {
final String date = getDate();
final URL url = new URL(String.format(S3_URL, bucketName, objectKey) + "?acl");
final String payload = String.format(PUT_PAYLOAD_ACL, date, acl.toString(), bucketName,
objectKey);
final String signature = MD5Util.generateHMAC(payload, awsSecretKey);
HttpURLConnection conn = null;
try {
conn = (HttpURLConnection) url.openConnection();
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setRequestMethod("PUT");
conn.setRequestProperty("Date", date);
conn.setRequestProperty("x-amz-acl", acl.toString());
conn.setRequestProperty("Authorization", "AWS " + awsAccessId + ":" + signature);
int status = conn.getResponseCode();
if (status != 200 && status != 201) {
log.severe("Error setting ACL for: " + url.toString());
log.severe(IOUtils.toString(conn.getInputStream()));
return false;
}
return true;
} finally {
if (conn != null) {
conn.disconnect();
}
}
}
public static String getObjectAcl(String bucketName, String objectKey) throws IOException {
final String awsAccessId = PropertyUtil.getProperty(AWS_ACCESS_ID);
final String awsSecretKey = PropertyUtil.getProperty(AWS_SECRET_KEY);
return getObjectAcl(bucketName, objectKey, awsAccessId, awsSecretKey);
}
public static String getObjectAcl(String bucketName, String objectKey, String awsAccessId,
String awsSecretKey) throws IOException {
final String date = getDate();
final URL url = new URL(String.format(S3_URL, bucketName, objectKey) + "?acl");
final String payload = String.format(GET_PAYLOAD_ACL, date, bucketName, objectKey);
final String signature = MD5Util.generateHMAC(payload, awsSecretKey);
InputStream in = null;
HttpURLConnection conn = null;
try {
conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("Date", date);
conn.setRequestProperty("Authorization", "AWS " + awsAccessId + ":" + signature);
in = new BufferedInputStream(conn.getInputStream());
return IOUtils.toString(in);
} catch (Exception e) {
log.log(Level.SEVERE, "Error getting ACL for : " + url.toString(), e);
return null;
} finally {
if (conn != null) {
conn.disconnect();
}
IOUtils.closeQuietly(in);
}
}
private static String getDate() {
final DateFormat df = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss ", Locale.US);
df.setTimeZone(TimeZone.getTimeZone("GMT"));
return df.format(new Date()) + "GMT";
}
}