package com.mediafire.sdk; import com.mediafire.sdk.response_models.MediaFireApiResponse; import com.mediafire.sdk.response_models.user.UserGetActionTokenResponse; import com.mediafire.sdk.response_models.user.UserGetSessionTokenResponse; import com.mediafire.sdk.util.TextUtils; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.logging.Logger; public class MFClient implements MediaFireClient { private static final String UTF8 = "UTF-8"; private static final int SESSION_TOKEN_VERSION = 2; private final String apiVersion; private final MediaFireHttpRequester requester; private final MediaFireSessionStore sessionStore; private final MediaFireCredentialsStore credentialsStore; private final MediaFireHasher hasher; private final MediaFireApiResponseParser parser; private final String applicationId; private final String apiKey; private final Object storeLock = new Object(); private MFClient(Builder builder) { this.apiVersion = builder.apiVersion; this.requester = builder.requester; this.sessionStore = builder.sessionStore; this.credentialsStore = builder.credentialsStore; this.hasher = builder.hasher; this.parser = builder.parser; this.applicationId = builder.applicationId; this.apiKey = builder.apiKey; } @Override public <T extends MediaFireApiResponse> T noAuthRequest(MediaFireApiRequest request, Class<T> classOfT) throws MediaFireException { Map<String, Object> query = new LinkedHashMap<>(); query.put("response_format", getResponseParser().getResponseFormat()); if (request.getQueryParameters() != null) { query.putAll(request.getQueryParameters()); } byte[] payload = makeQueryStringFromMap(query, true).getBytes(); Map<String, Object> headers = new HashMap<>(); headers.put("Content-Type", "application/x-www-form-urlencoded;charset=" + UTF8); headers.put("Content-Length", payload.length); headers.put("Accept-Charset", "UTF-8"); String baseUrl = "https://www.mediafire.com"; StringBuilder url = new StringBuilder(); url.append(baseUrl).append("/api"); if (!TextUtils.isEmpty(getApiVersion())) { url.append("/").append(getApiVersion()); } url.append(request.getPath()); MediaFireHttpRequest mediaFireHttpRequest = new MFHttpRequest(url.toString(), payload, headers); MediaFireHttpResponse mediaFireHttpResponse = requester.post(mediaFireHttpRequest); return getResponseParser().parseResponse(mediaFireHttpResponse, classOfT); } @Override public MediaFireHttpResponse conversionServerRequest(String hash, Map<String, Object> requestParameters) throws MediaFireException { String baseUrl = "https://www.mediafire.com"; StringBuilder url = new StringBuilder(); url.append(baseUrl); url.append("/conversion_server.php?"); if (hash == null || hash.length() < 4) { throw new MediaFireException("invalid hash passed in conversion request"); } url.append(hash.substring(0, 4)); url.append("&"); MediaFireActionToken mediaFireActionToken; synchronized (getSessionStore()) { if (!getSessionStore().isActionTokenAvailable(MediaFireActionToken.TYPE_IMAGE)) { mediaFireActionToken = requestNewActionToken(MediaFireActionToken.TYPE_IMAGE); if (mediaFireActionToken == null) { throw new MediaFireException("could not request action token type " + MediaFireActionToken.TYPE_IMAGE); } getSessionStore().store(mediaFireActionToken); } else { mediaFireActionToken = getSessionStore().getActionToken(MediaFireActionToken.TYPE_IMAGE); } } if (mediaFireActionToken == null) { throw new MediaFireException("could not get action token type " + MediaFireActionToken.TYPE_IMAGE + " from store"); } requestParameters.put("session_token", mediaFireActionToken.getSessionToken()); String encodedQuery = makeQueryStringFromMap(requestParameters, true); url.append(encodedQuery); MediaFireHttpRequest mediaFireHttpRequest = new MFHttpRequest(url.toString(), null, null); return getHttpRequester().get(mediaFireHttpRequest); } @Override public <T extends MediaFireApiResponse> T uploadRequest(MediaFireApiRequest request, Class<T> classOfT) throws MediaFireException { Map<String, Object> query = new LinkedHashMap<>(); query.put("response_format", getResponseParser().getResponseFormat()); query.putAll(request.getQueryParameters()); MediaFireActionToken mediaFireActionToken; synchronized (storeLock) { if (!getSessionStore().isActionTokenAvailable(MediaFireActionToken.TYPE_UPLOAD)) { mediaFireActionToken = requestNewActionToken(MediaFireActionToken.TYPE_UPLOAD); if (mediaFireActionToken == null) { throw new MediaFireException("could not request action token type " + MediaFireActionToken.TYPE_IMAGE); } getSessionStore().store(mediaFireActionToken); } else { mediaFireActionToken = getSessionStore().getActionToken(MediaFireActionToken.TYPE_UPLOAD); } } if (mediaFireActionToken == null) { throw new MediaFireException("could not get action token type " + MediaFireActionToken.TYPE_UPLOAD + " from store"); } query.put("session_token", mediaFireActionToken.getSessionToken()); Map<String, Object> headers = new HashMap<>(); headers.put("Content-Type", "application/octet-stream"); headers.put("Content-Length", request.getPayload().length); headers.put("Accept-Charset", "UTF-8"); if (request.getHeaders() != null) { headers.putAll(request.getHeaders()); } String baseUrl = "https://www.mediafire.com"; StringBuilder url = new StringBuilder(); url.append(baseUrl).append("/api"); if (!TextUtils.isEmpty(getApiVersion())) { url.append("/").append(getApiVersion()); } url.append(request.getPath()).append("?"); String encodedQuery = makeQueryStringFromMap(query, true); url.append(encodedQuery); MediaFireHttpRequest mediaFireHttpRequest = new MFHttpRequest(url.toString(), request.getPayload(), headers); MediaFireHttpResponse mediaFireHttpResponse = getHttpRequester().post(mediaFireHttpRequest); return getResponseParser().parseResponse(mediaFireHttpResponse, classOfT); } @Override public <T extends MediaFireApiResponse> T sessionRequest(MediaFireApiRequest request, Class<T> classOfT) throws MediaFireException { Map<String, Object> query = new LinkedHashMap<>(); query.put("response_format", getResponseParser().getResponseFormat()); if (request.getQueryParameters() != null) { query.putAll(request.getQueryParameters()); } String baseUrl = "https://www.mediafire.com"; StringBuilder url = new StringBuilder(); url.append(baseUrl); StringBuilder uri = new StringBuilder(); uri.append("/api"); if (!TextUtils.isEmpty(getApiVersion())) { uri.append("/").append(getApiVersion()); } uri.append(request.getPath()); MediaFireSessionToken mediaFireSessionToken; synchronized (storeLock) { if (!getSessionStore().isSessionTokenV2Available()) { mediaFireSessionToken = requestNewSessionToken(); } else { mediaFireSessionToken = getSessionStore().getSessionTokenV2(); } } if (mediaFireSessionToken == null) { throw new MediaFireException("could not get session token from store"); } query.put("session_token", mediaFireSessionToken.getSessionToken()); String signature = createSignatureForAuthenticatedRequest(mediaFireSessionToken.getSecretKey(), mediaFireSessionToken.getTime(), uri.toString(), query); query.put("signature", signature); String encodedQuery = makeQueryStringFromMap(query, true); Map<String, Object> headers = createHeadersUsingQueryAsPostBody(encodedQuery); url.append(uri); MediaFireHttpRequest mediaFireHttpRequest = new MFHttpRequest(url.toString(), encodedQuery.getBytes(), headers); MediaFireHttpResponse mediaFireHttpResponse = getHttpRequester().post(mediaFireHttpRequest); T response = getResponseParser().parseResponse(mediaFireHttpResponse, classOfT); if (!response.hasError() && response.needNewKey()) { mediaFireSessionToken.update(); getSessionStore().store(mediaFireSessionToken); } return response; } @Override public <T extends MediaFireApiResponse> T authenticationRequest(Class<T> classOfT) throws MediaFireException { int credentialType = getCredentialStore().getTypeStored(); Map<String, Object> query = new LinkedHashMap<>(); query.put("response_format", getResponseParser().getResponseFormat()); query.put("token_version", SESSION_TOKEN_VERSION); query.put("application_id", getApplicationId()); StringBuilder unhashedSignature = new StringBuilder(); switch (credentialType) { case MediaFireCredentialsStore.TYPE_EMAIL: MediaFireCredentialsStore.EmailCredentials emailCredentials = getCredentialStore().getEmailCredentials(); query.put("email", emailCredentials.getEmail()); query.put("password", emailCredentials.getPassword()); unhashedSignature.append(emailCredentials.getEmail()).append(emailCredentials.getPassword()); break; case MediaFireCredentialsStore.TYPE_EKEY: MediaFireCredentialsStore.EkeyCredentials ekeyCredentials = getCredentialStore().getEkeyCredentials(); query.put("ekey", ekeyCredentials.getEkey()); query.put("password", ekeyCredentials.getPassword()); unhashedSignature.append(ekeyCredentials.getEkey()).append(ekeyCredentials.getPassword()); break; case MediaFireCredentialsStore.TYPE_FACEBOOK: MediaFireCredentialsStore.FacebookCredentials facebookCredentials = getCredentialStore().getFacebookCredentials(); query.put("fb_access_token", facebookCredentials.getFacebookAccessToken()); unhashedSignature.append(facebookCredentials.getFacebookAccessToken()); break; case MediaFireCredentialsStore.TYPE_TWITTER: MediaFireCredentialsStore.TwitterCredentials twitterCredentials = getCredentialStore().getTwitterCredentials(); query.put("tw_oauth_token", twitterCredentials.getTwitterOauthToken()); query.put("tw_oauth_token_secret", twitterCredentials.getTwitterOauthTokenSecret()); unhashedSignature.append(twitterCredentials.getTwitterOauthToken()).append(twitterCredentials.getTwitterOauthTokenSecret()); break; case MediaFireCredentialsStore.TYPE_NONE: default: throw new MediaFireException("no credentials stored, cannot authenticate"); } unhashedSignature.append(getApplicationId()); if (!TextUtils.isEmpty(getApiKey())) { unhashedSignature.append(getApiKey()); } String hashedSignature = getHasher().sha1(unhashedSignature.toString()); query.put("signature", hashedSignature); byte[] payload = makeQueryStringFromMap(query, true).getBytes(); Map<String, Object> headers = new HashMap<>(); headers.put("Content-Type", "application/x-www-form-urlencoded;charset=" + UTF8); headers.put("Content-Length", payload.length); headers.put("Accept-Charset", "UTF-8"); String baseUrl = "https://www.mediafire.com"; StringBuilder url = new StringBuilder(); url.append(baseUrl).append("/api"); if (!TextUtils.isEmpty(getApiVersion())) { url.append("/").append(getApiVersion()); } url.append("/user/get_session_token.php"); MediaFireHttpRequest mediaFireHttpRequest = new MFHttpRequest(url.toString(), payload, headers); MediaFireHttpResponse mediaFireHttpResponse = getHttpRequester().post(mediaFireHttpRequest); return getResponseParser().parseResponse(mediaFireHttpResponse, classOfT); } @Override public String getApplicationId() { return applicationId; } @Override public String getApiKey() { return apiKey; } @Override public String getApiVersion() { return apiVersion; } @Override public MediaFireHttpRequester getHttpRequester() { return requester; } @Override public MediaFireSessionStore getSessionStore() { return sessionStore; } @Override public MediaFireCredentialsStore getCredentialStore() { return credentialsStore; } @Override public MediaFireHasher getHasher() { return hasher; } @Override public MediaFireApiResponseParser getResponseParser() { return parser; } public MediaFireActionToken requestNewActionToken(int type) throws MediaFireException { Map<String, Object> query = new LinkedHashMap<>(); query.put("response_format", getResponseParser().getResponseFormat()); int lifespan; switch (type) { case MediaFireActionToken.TYPE_IMAGE: lifespan = 60; query.put("type", "image"); break; case MediaFireActionToken.TYPE_UPLOAD: lifespan = 360; query.put("type", "upload"); break; default: throw new MediaFireException("invalid action token type passed: " + type); } query.put("lifespan", lifespan); MediaFireApiRequest request = new MFApiRequest("/user/get_action_token.php", query, null, null); UserGetActionTokenResponse response = sessionRequest(request, UserGetActionTokenResponse.class); if (response.hasError()) { return null; } String sessionToken = response.getActionToken(); return new MFActionToken(sessionToken, type, System.currentTimeMillis(), lifespan); } public MediaFireSessionToken requestNewSessionToken() throws MediaFireException { UserGetSessionTokenResponse response = authenticationRequest(UserGetSessionTokenResponse.class); if (response.hasError()) { return null; } String sessionToken = response.getSessionToken(); String time = response.getTime(); long secretKey = response.getSecretKey(); String pkey = response.getPkey(); String ekey = response.getEkey(); MFSessionToken token = new MFSessionToken(sessionToken, time, secretKey, pkey, ekey); return token; } private Map<String, Object> createHeadersUsingQueryAsPostBody(String encodedQuery) { Map<String, Object> headers = new HashMap<>(); headers.put("Accept-Charset", "UTF-8"); headers.put("Content-Length", encodedQuery.length()); headers.put("Content-Type", "application/x-www-form-urlencoded;charset=" + UTF8); return headers; } private String makeQueryStringFromMap(Map<String, Object> query, boolean encoded) throws MediaFireException { StringBuilder sb = new StringBuilder(); for (String key : query.keySet()) { sb.append(constructQueryKVPair(key, query.get(key), encoded)); } return sb.toString().replaceFirst("&", ""); } private String constructQueryKVPair(String key, Object value, boolean encoded) throws MediaFireException { if (encoded) { try { return "&" + key + "=" + URLEncoder.encode(String.valueOf(value), UTF8); } catch (UnsupportedEncodingException e) { throw new MediaFireException("could not encode string using " + UTF8, e); } } else { return "&" + key + "=" + value; } } private String createSignatureForAuthenticatedRequest(long secretKey, String time, String uri, Map<String, Object> query) throws MediaFireException { long secretKeyMod256 = secretKey % 256; String queryMap = makeQueryStringFromMap(query, false); String hashTarget = secretKeyMod256 + time + uri + "?" + queryMap; String signature = getHasher().md5(hashTarget); return signature; } public static class Builder { private static final MediaFireHttpRequester DEFAULT_REQUESTER = new MFHttpRequester(45000, 45000); private static final MediaFireSessionStore DEFAULT_SESSION_STORE = new MFSessionStore(); private static final MediaFireCredentialsStore DEFAULT_CREDENTIALS_STORE = new MFCredentialsStore(); private static final MediaFireHasher DEFAULT_HASHER = new MFHasher(); private static final MediaFireApiResponseParser DEFAULT_PARSER = new MFApiResponseParser(); private final String applicationId; private final String apiKey; private String apiVersion; private MediaFireHttpRequester requester = DEFAULT_REQUESTER; private MediaFireSessionStore sessionStore = DEFAULT_SESSION_STORE; private MediaFireCredentialsStore credentialsStore = DEFAULT_CREDENTIALS_STORE; private MediaFireHasher hasher = DEFAULT_HASHER; private MediaFireApiResponseParser parser = DEFAULT_PARSER; public Builder(String applicationId, String apiKey) { this.applicationId = applicationId; this.apiKey = apiKey; } public Builder parser(MediaFireApiResponseParser parser) { this.parser = parser; return this; } public Builder hasher(MediaFireHasher hasher) { this.hasher = hasher; return this; } public Builder credentialStore(MediaFireCredentialsStore credentialsStore) { this.credentialsStore = credentialsStore; return this; } public Builder sessionStore(MediaFireSessionStore sessionStore) { this.sessionStore = sessionStore; return this; } public Builder httpRequester(MediaFireHttpRequester requester) { this.requester = requester; return this; } public Builder apiVersion(String apiVersion) { this.apiVersion = apiVersion; return this; } public MFClient build() { return new MFClient(this); } } }