/* * Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering). * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/gpl.html */ package at.bitfire.davdroid; import android.accounts.Account; import android.content.Context; import android.database.sqlite.SQLiteOpenHelper; import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import java.io.IOException; import java.net.InetSocketAddress; import java.net.Proxy; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import at.bitfire.davdroid.model.ServiceDB; import at.bitfire.davdroid.model.Settings; import okhttp3.Interceptor; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import okhttp3.logging.HttpLoggingInterceptor; public class HttpClient { private static final OkHttpClient client = new OkHttpClient(); private static final UserAgentInterceptor userAgentInterceptor = new UserAgentInterceptor(); private static final String userAgent; static { String date = new SimpleDateFormat("yyyy/MM/dd", Locale.US).format(new Date(BuildConfig.buildTime)); userAgent = "DAVdroid/" + BuildConfig.VERSION_NAME + " (" + date + "; okhttp3) Android/" + Build.VERSION.RELEASE; } private HttpClient() { } public static OkHttpClient create(@Nullable Context context, @NonNull Account account, @NonNull final Logger logger) throws InvalidAccountException { OkHttpClient.Builder builder = defaultBuilder(context, logger); // use account settings for authentication AccountSettings settings = new AccountSettings(context, account); builder = addAuthentication(builder, null, settings.getAuthToken()); return builder.build(); } public static OkHttpClient create(@NonNull Context context, @NonNull Logger logger) { return defaultBuilder(context, logger).build(); } public static OkHttpClient create(@NonNull Context context, @NonNull Account account) throws InvalidAccountException { return create(context, account, App.log); } public static OkHttpClient create(@Nullable Context context) { return create(context, App.log); } private static OkHttpClient.Builder defaultBuilder(@Nullable Context context, @NonNull final Logger logger) { OkHttpClient.Builder builder = client.newBuilder(); // use MemorizingTrustManager to manage self-signed certificates if (context != null) { App app = (App) context.getApplicationContext(); if (App.getSslSocketFactoryCompat() != null && app.getCertManager() != null) builder.sslSocketFactory(App.getSslSocketFactoryCompat(), app.getCertManager()); if (App.getHostnameVerifier() != null) builder.hostnameVerifier(App.getHostnameVerifier()); } // set timeouts builder.connectTimeout(30, TimeUnit.SECONDS); builder.writeTimeout(30, TimeUnit.SECONDS); builder.readTimeout(120, TimeUnit.SECONDS); // custom proxy support if (context != null) { SQLiteOpenHelper dbHelper = new ServiceDB.OpenHelper(context); try { Settings settings = new Settings(dbHelper.getReadableDatabase()); if (settings.getBoolean(App.OVERRIDE_PROXY, false)) { InetSocketAddress address = new InetSocketAddress( settings.getString(App.OVERRIDE_PROXY_HOST, App.OVERRIDE_PROXY_HOST_DEFAULT), settings.getInt(App.OVERRIDE_PROXY_PORT, App.OVERRIDE_PROXY_PORT_DEFAULT) ); Proxy proxy = new Proxy(Proxy.Type.HTTP, address); builder.proxy(proxy); App.log.log(Level.INFO, "Using proxy", proxy); } } catch (IllegalArgumentException | NullPointerException e) { App.log.log(Level.SEVERE, "Can't set proxy, ignoring", e); } finally { dbHelper.close(); } } // add User-Agent to every request builder.addNetworkInterceptor(userAgentInterceptor); // add network logging, if requested if (logger.isLoggable(Level.FINEST)) { HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() { @Override public void log(String message) { logger.finest(message); } }); loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BASIC); builder.addInterceptor(loggingInterceptor); } return builder; } private static OkHttpClient.Builder addAuthentication(@NonNull OkHttpClient.Builder builder, @Nullable String host, @NonNull String token) { TokenAuthenticator authHandler = new TokenAuthenticator(host, token); return builder.addNetworkInterceptor(authHandler); } private static class TokenAuthenticator implements Interceptor { protected static final String HEADER_AUTHORIZATION = "Authorization"; // FIXME: Host is not used final String host, token; private TokenAuthenticator(String host, String token) { this.host = host; this.token = token; } @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); if ((token != null) && (request.header(HEADER_AUTHORIZATION) == null)) { request = request.newBuilder() .header(HEADER_AUTHORIZATION, "Token " + token) .build(); } return chain.proceed(request); } } static class UserAgentInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Locale locale = Locale.getDefault(); Request request = chain.request().newBuilder() .header("User-Agent", userAgent) .header("Accept-Language", locale.getLanguage() + "-" + locale.getCountry() + ", " + locale.getLanguage() + ";q=0.7, *;q=0.5") .build(); return chain.proceed(request); } } }