filipebatista
2/27/2013 - 12:02 AM

Collection of code to use the Client interface of Retrofit to use HttpUrlConnection with SharedPreferences persistent cookies.

Collection of code to use the Client interface of Retrofit to use HttpUrlConnection with SharedPreferences persistent cookies.

// Sets the AndroidCookieStore as the CookieStore to use for managing magic-cookies
final CookieManager cookie_manager = new CookieManager(cookie_jar, CookiePolicy.ACCEPT_ORIGINAL_SERVER);
CookieHandler.setDefault(new CookieManager());

// Configures the RestAdapter to use RecommendedAndroidClient instead of AndroidHttpClient
RestAdapter rest_adapter = new RestAdapter.Builder().setServer("http://www.example.com)
                .setClient(http_client).build();
import android.os.PatternMatcher;
import com.github.kevinsawicki.http.HttpRequest;
import com.keepandshare.android.utils.CircularByteBuffer;
import com.keepandshare.android.utils.Ln;
import org.apache.http.protocol.HttpContext;
import retrofit.http.Header;
import retrofit.http.client.Client;
import retrofit.http.client.Request;
import retrofit.http.client.Response;
import retrofit.http.mime.TypedInput;
import retrofit.http.mime.TypedOutput;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.google.common.collect.Maps.newHashMap;

/**
 * Custom implementation of {@link Client} that can utilize an {@link HttpContext}.
 * @author Dandré Allison
 */
public class RecommendedAndroidClient implements Client {

    @Override
    public Response execute(Request request) throws IOException {
        Ln.v("Request: %s '%s'", request.getMethod(), _sanitize_passwords.reset(request.getUrl()).replaceAll("p=***"));
        // Creates the request
        final HttpRequest http_response = new HttpRequest(request.getUrl(), request.getMethod())
                // Requests compressed responses
                .acceptGzipEncoding()
                // Automatically uncompresses response
                .uncompress(true);
        prepareRequest(http_response);

        final List<Header> headers = request.getHeaders();
        if (headers != null)
            http_response.headers(prepareHeaders(request.getHeaders()));

        final TypedOutput body = request.getBody();
        if (body != null) {
            final CircularByteBuffer byte_buffer = new CircularByteBuffer(CircularByteBuffer.INFINITE_SIZE);
            request.getBody().writeTo(byte_buffer.getOutputStream());
            http_response.send(byte_buffer.getInputStream());
        }

        // Creates the response
        return parseResponse(http_response);
    }

    Response parseResponse(HttpRequest response) throws IOException {
        final InputStream stream = response.buffer();
        final int status = response.code();
        final String reason = response.message();

        final List<Header> headers = new ArrayList<Header>();
        String content_type = "application/octet-stream";
        for (Map.Entry<String, List<String>> header : response.headers().entrySet()) {
            final String name = header.getKey();
            final String value = header.getValue().get(0);
            if (_content_type_header.match(name))
                content_type = value;
            headers.add(new Header(name, value));
        }

        prepareResponse(response, stream);

        final TypedInputStream body = stream == null? null : new TypedInputStream(content_type, stream);

        return new Response(status, reason, headers, body);
    }

    /** Callback for additional preparation of the request before execution. */
    protected void prepareRequest(HttpRequest request) { }

    /** Callback for additional preparation of the response before parsing. */
    protected void prepareResponse(HttpRequest response, InputStream body) { }

    static class TypedInputStream implements TypedInput {

        public TypedInputStream(String mime_type, InputStream in) {
            _mime_type = mime_type;
            _in = new BufferedInputStream(in);
        }

        @Override
        public String mimeType() {
            return _mime_type;
        }

        @Override
        public long length() {
            return -1;
        }

        @Override
        public InputStream in() throws IOException {
            return _in;
        }

        private final String _mime_type;
        public final InputStream _in;
    }

    private Map<String, String> prepareHeaders(List<Header> headers) {
        if (headers == null) return null;
        final Map<String, String> header_map = newHashMap();
        for (Header header : headers)
            header_map.put(header.getName(), header.getValue());
        return header_map;
    }

    private static final PatternMatcher _content_type_header = new PatternMatcher(HttpRequest.HEADER_CONTENT_TYPE, PatternMatcher.PATTERN_LITERAL);
    private static final Matcher _sanitize_passwords = Pattern.compile("(p=)[^&]*").matcher("");
}
import android.annotation.TargetApi;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.PatternMatcher;
import com.keepandshare.android.utils.CryptographyUtil;
import com.keepandshare.android.utils.Ln;

import javax.inject.Singleton;
import java.net.CookieStore;
import java.net.HttpCookie;
import java.net.URI;
import java.util.List;

import static com.google.common.collect.Lists.newArrayList;
import static com.keepandshare.android.utils.SharedPreferencesCompat.apply;

/**
 * A simple {@linkplain CookieStore cookie jar} that only cares to store the {@link #_COOKIE_AUTH_TOKEN}.
 * It persists the cookie through app launches and maintains it securely.
 * @author Dandré Allison
 */
@Singleton
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
public class AndroidCookieStore implements CookieStore {

/* Constructor */
    public AndroidCookieStore(SharedPreferences settings, CryptographyUtil crypto) {
        _settings = settings;
        _crypto = crypto;
    }

/* Cookie Store */
    @Override
    public void add(URI _, HttpCookie cookie) {
        Ln.v("Adding Cookie: %s", cookie);

        // Stores the cookie in shared preferences
        if (_auth_token_pattern.match(cookie.getName())) {
            final SharedPreferences.Editor editor = _settings.edit();
            try {
                editor.putString(_KEY_AUTH_TOKEN, _crypto.encrypt(cookie.getValue()));
            } catch (Exception error) {
                Ln.e(error.getCause());
            }
            apply(editor);
        }
    }

    @Override
    public List<HttpCookie> getCookies() {
        return get(null);
    }

    @Override
    public List<HttpCookie> get(URI _) {
        // Loads in cookies from shared preferences
        final String auth_token = _settings.getString(_KEY_AUTH_TOKEN, null);
        if (auth_token != null)
            try {
                final HttpCookie cookie = new HttpCookie(_COOKIE_AUTH_TOKEN, _crypto.decrypt(auth_token));
                cookie.setPath("/");
                cookie.setVersion(0);
                Ln.v("Retrieved Cookie: %s", cookie);
                return newArrayList(cookie);
            } catch (Exception error) {
                Ln.e(error.getCause());
            }
        return newArrayList();
    }

    @Override
    public List<URI> getURIs() {
        return newArrayList();
    }

    @Override
    public boolean remove(URI _, HttpCookie __) {
        final boolean had_auth_token = _settings.getString(_KEY_AUTH_TOKEN, null) != null;
        final SharedPreferences.Editor editor = _settings.edit();
        apply(editor.remove(_KEY_AUTH_TOKEN));
        return had_auth_token;
    }

    @Override
    public boolean removeAll() {
        return remove(null,null);
    }

    private static final String _KEY_AUTH_TOKEN = "AUTH_TOKEN";
    private static final String _COOKIE_AUTH_TOKEN = "PHPSESSID";
    private static final PatternMatcher _auth_token_pattern = new PatternMatcher(_COOKIE_AUTH_TOKEN, PatternMatcher.PATTERN_LITERAL);
    private final SharedPreferences _settings;
    private final CryptographyUtil _crypto;
}