/* * Copyright (C) 2013-2017 microG Project Team * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.microg.gms.common; import android.net.Uri; import android.util.Log; import java.io.IOException; import java.io.OutputStream; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Field; import java.net.HttpURLConnection; import java.net.URL; import java.util.List; import java.util.Map; public class HttpFormClient { private static final String TAG = "GmsHttpFormClient"; public static T request(String url, Request request, Class tClass) throws IOException { HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection(); connection.setRequestMethod("POST"); connection.setDoInput(true); connection.setDoOutput(true); connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); StringBuilder content = new StringBuilder(); request.prepare(); for (Field field : request.getClass().getDeclaredFields()) { try { field.setAccessible(true); Object objVal = field.get(request); if (field.isAnnotationPresent(RequestContentDynamic.class)) { Map contentParams = (Map) objVal; for (Map.Entry param : contentParams.entrySet()) { appendParam(content, param.getKey(), param.getValue()); } continue; } String value = objVal != null ? String.valueOf(objVal) : null; Boolean boolVal = null; if (field.getType().equals(boolean.class)) { boolVal = field.getBoolean(request); } if (field.isAnnotationPresent(RequestHeader.class)) { RequestHeader annotation = field.getAnnotation(RequestHeader.class); value = valueFromBoolVal(value, boolVal, annotation.truePresent(), annotation.falsePresent()); if (value != null || annotation.nullPresent()) { for (String key : annotation.value()) { connection.setRequestProperty(key, String.valueOf(value)); } } } if (field.isAnnotationPresent(RequestContent.class)) { RequestContent annotation = field.getAnnotation(RequestContent.class); value = valueFromBoolVal(value, boolVal, annotation.truePresent(), annotation.falsePresent()); if (value != null || annotation.nullPresent()) { for (String key : annotation.value()) { appendParam(content, key, value); } } } } catch (Exception ignored) { } } Log.d(TAG, "-- Request --\n" + content); OutputStream os = connection.getOutputStream(); os.write(content.toString().getBytes()); os.close(); if (connection.getResponseCode() != 200) { String error = connection.getResponseMessage(); try { error = new String(Utils.readStreamToEnd(connection.getErrorStream())); } catch (IOException e) { // Ignore } throw new IOException(error); } String result = new String(Utils.readStreamToEnd(connection.getInputStream())); Log.d(TAG, "-- Response --\n" + result); return parseResponse(tClass, connection, result); } private static String valueFromBoolVal(String value, Boolean boolVal, boolean truePresent, boolean falsePresent) { if (boolVal != null) { if (boolVal && truePresent) { return "1"; } else if (!boolVal && falsePresent) { return "0"; } else { return null; } } else { return value; } } private static void appendParam(StringBuilder content, String key, String value) { if (content.length() > 0) content.append("&"); content.append(Uri.encode(key)).append("=").append(Uri.encode(String.valueOf(value))); } private static T parseResponse(Class tClass, HttpURLConnection connection, String result) throws IOException { Map> headerFields = connection.getHeaderFields(); T response; try { response = tClass.getConstructor().newInstance(); } catch (Exception e) { return null; } String[] entries = result.split("\n"); for (String s : entries) { String[] keyValuePair = s.split("=", 2); String key = keyValuePair[0].trim(); String value = keyValuePair[1].trim(); try { for (Field field : tClass.getDeclaredFields()) { if (field.isAnnotationPresent(ResponseField.class) && key.equals(field.getAnnotation(ResponseField.class).value())) { if (field.getType().equals(String.class)) { field.set(response, value); } else if (field.getType().equals(boolean.class)) { field.setBoolean(response, value.equals("1")); } else if (field.getType().equals(long.class)) { field.setLong(response, Long.parseLong(value)); } else if (field.getType().equals(int.class)) { field.setInt(response, Integer.parseInt(value)); } } } } catch (Exception e) { Log.w(TAG, e); } } for (Field field : tClass.getDeclaredFields()) { if (field.isAnnotationPresent(ResponseHeader.class)) { List strings = headerFields.get(field.getAnnotation(ResponseHeader.class).value()); if (strings == null || strings.size() != 1) continue; String value = strings.get(0); try { if (field.getType().equals(String.class)) { field.set(response, value); } else if (field.getType().equals(boolean.class)) { field.setBoolean(response, value.equals("1")); } else if (field.getType().equals(long.class)) { field.setLong(response, Long.parseLong(value)); } else if (field.getType().equals(int.class)) { field.setInt(response, Integer.parseInt(value)); } } catch (Exception e) { Log.w(TAG, e); } } if (field.isAnnotationPresent(ResponseStatusCode.class) && field.getType() == int.class) { try { field.setInt(response, connection.getResponseCode()); } catch (IllegalAccessException e) { Log.w(TAG, e); } } if (field.isAnnotationPresent(ResponseStatusText.class) && field.getType() == String.class) { try { field.set(response, connection.getResponseMessage()); } catch (IllegalAccessException e) { Log.w(TAG, e); } } } return response; } public static void requestAsync(final String url, final Request request, final Class tClass, final Callback callback) { new Thread(() -> { try { callback.onResponse(request(url, request, tClass)); } catch (Exception e) { callback.onException(e); } }).start(); } public static abstract class Request { protected void prepare() { } } public interface Callback { void onResponse(T response); void onException(Exception exception); } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface RequestHeader { String[] value(); boolean truePresent() default true; boolean falsePresent() default false; boolean nullPresent() default false; } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface RequestContent { String[] value(); boolean truePresent() default true; boolean falsePresent() default false; boolean nullPresent() default false; } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface RequestContentDynamic { } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface ResponseField { String value(); } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface ResponseHeader { String value(); } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface ResponseStatusCode { } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface ResponseStatusText { } }