content: /*
   * Copyright (C) 2015 Actor LLC. <https://actor.im>
   */
  
  package im.actor.runtime.android;
  
  import android.content.res.Resources;
  
  import com.squareup.okhttp.Callback;
  import com.squareup.okhttp.MediaType;
  import com.squareup.okhttp.OkHttpClient;
  import com.squareup.okhttp.Request;
  import com.squareup.okhttp.RequestBody;
  import com.squareup.okhttp.Response;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.security.GeneralSecurityException;
  import java.security.KeyStore;
  import java.security.SecureRandom;
  import java.security.cert.Certificate;
  import java.security.cert.CertificateFactory;
  import java.util.Collection;
  
  import javax.net.ssl.HostnameVerifier;
  import javax.net.ssl.KeyManagerFactory;
  import javax.net.ssl.SSLContext;
  import javax.net.ssl.SSLSession;
  import javax.net.ssl.TrustManagerFactory;
  
  import im.actor.runtime.HttpRuntime;
  import im.actor.runtime.Log;
  import im.actor.runtime.http.HTTPError;
  import im.actor.runtime.http.HTTPResponse;
  import im.actor.runtime.promise.Promise;
  import okio.Buffer;
  
  public class AndroidHttpProvider implements HttpRuntime {
  
      private static final String TAG = "AndroidHTTP";
  
      private final OkHttpClient client = new OkHttpClient();
  
      private final MediaType MEDIA_TYPE = MediaType.parse("application/octet-stream");
  
      public AndroidHttpProvider() {
          Resources resources = AndroidContext.getContext().getResources();
          try {
              String cert = resources.getString(resources.getIdentifier("trusted_pem", "string", AndroidContext.getContext().getPackageName()));
              SSLContext sslContext = sslContextForTrustedCertificates(new Buffer()
                      .writeUtf8(cert)
                      .inputStream());
              client.setSslSocketFactory(sslContext.getSocketFactory());
          } catch (Resources.NotFoundException e) {
              // Just Ignore
          }
  
          try {
              final String trustHostname = resources.getString(resources.getIdentifier("trusted_hostname", "string", AndroidContext.getContext().getPackageName()));
              client.setHostnameVerifier((hostname, session) -> hostname.equals(trustHostname));
          } catch (Resources.NotFoundException e) {
              // Just Ignore
          }
      }
  
      public SSLContext sslContextForTrustedCertificates(InputStream in) {
          try {
              CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
              Collection<? extends Certificate> certificates = certificateFactory.generateCertificates(in);
              if (certificates.isEmpty()) {
                  throw new IllegalArgumentException("expected non-empty set of trusted certificates");
              }
  
              // Put the certificates a key store.
              char[] password = "password".toCharArray(); // Any password will work.
              KeyStore keyStore = newEmptyKeyStore(password);
              int index = 0;
              for (Certificate certificate : certificates) {
                  String certificateAlias = Integer.toString(index++);
                  keyStore.setCertificateEntry(certificateAlias, certificate);
              }
  
              // Wrap it up in an SSL context.
              KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(
                      KeyManagerFactory.getDefaultAlgorithm());
              keyManagerFactory.init(keyStore, password);
              TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
                      TrustManagerFactory.getDefaultAlgorithm());
              trustManagerFactory.init(keyStore);
              SSLContext sslContext = SSLContext.getInstance("TLS");
              sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(),
                      new SecureRandom());
              return sslContext;
          } catch (GeneralSecurityException e) {
              throw new RuntimeException(e);
          }
      }
  
      private KeyStore newEmptyKeyStore(char[] password) throws GeneralSecurityException {
          try {
              KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
              InputStream in = null; // By convention, 'null' creates an empty key store.
              keyStore.load(in, password);
              return keyStore;
          } catch (IOException e) {
              throw new AssertionError(e);
          }
      }
  
      @Override
      public Promise<HTTPResponse> getMethod(String url, int startOffset, int size, int totalSize) {
          return new Promise<>(resolver -> {
              final Request request = new Request.Builder()
                      .url(url)
                      .addHeader("Range", "bytes=" + startOffset + "-" + (startOffset + size))
                      .build();
              Log.d(TAG, "Downloading part: " + request.toString());
              client.newCall(request).enqueue(new Callback() {
                  @Override
                  public void onFailure(Request request, IOException e) {
                      Log.d(TAG, "Downloading part error: " + request.toString());
                      e.printStackTrace();
                      // TODO: Better error?
                      resolver.error(new HTTPError(0));
                  }
  
                  @Override
                  public void onResponse(Response response) throws IOException {
                      Log.d(TAG, "Downloading part response: " + request.toString() + " -> " + response.toString());
                      if (response.code() >= 200 && response.code() < 300) {
                          resolver.result(new HTTPResponse(response.code(), response.body().bytes()));
                      } else {
                          resolver.error(new HTTPError(response.code()));
                      }
                  }
              });
          });
      }
  
      @Override
      public Promise<HTTPResponse> putMethod(String url, byte[] contents) {
          return new Promise<>(resolver -> {
              final Request request = new Request.Builder()
                      .url(url)
                      .method("PUT", RequestBody.create(MEDIA_TYPE, contents))
                      .build();
              Log.d(TAG, "Uploading part: " + request.toString());
              client.newCall(request).enqueue(new Callback() {
                  @Override
                  public void onFailure(Request request, IOException e) {
                      Log.d(TAG, "Uploading part error: " + request.toString());
                      e.printStackTrace();
                      // TODO: Better error?
                      resolver.error(new HTTPError(0));
                  }
  
                  @Override
                  public void onResponse(Response response) throws IOException {
                      Log.d(TAG, "Upload part response: " + request.toString() + " -> " + response.toString());
                      if (response.code() >= 200 && response.code() < 300) {
                          resolver.result(new HTTPResponse(response.code(), null));
                      } else {
                          resolver.error(new HTTPError(response.code()));
                      }
                  }
              });
          });
      }
  }
  