Httpclient redirect handler - android

I'm trying to call a web server that is using relative URL redirects for some of the calls. This of course isn't working with DefaultHttpClient as it isn't treating it as a relative URL. I've gotten as far as implementing a RedirectHandler in an attempt to catch the redirect and add in the base call but I can't work out how to get the location of the redirect.
With the following method how do I go about finding out where I am being redirected to? I can't find any fields on either response or context that have what I need and I don't know where else to look.
public URI getLocationURI(HttpResponse response, HttpContext context)

Have a look here: HttpClient 4 - how to capture last redirect URL
I would try getStatusLine() for start.

My solution is to read location headers and follow them. This helped me:
if (statusCode != HttpStatus.SC_OK) {
Header[] headers = response.getHeaders("Location");
if (headers != null && headers.length != 0) {
String newUrl = headers[headers.length - 1].getValue();
// call again the same downloading method with new URL
return downloadBitmap(newUrl);
} else {
return null;
}
}
More in my post - Follow 302 redirects with AndroidHttpClient

Related

Android Intercept a WebView request properly

My current code for intercepting a request in webview is
#Override
public WebResourceResponse shouldInterceptRequest(WebView view,
String url) {
String ext = MimeTypeMap.getFileExtensionFromUrl(url);
String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext);
if (mime == null) {
return super.shouldInterceptRequest(view, url);
} else {
HttpURLConnection conn = (HttpURLConnection) new URL(
url).openConnection();
conn.setRequestProperty("User-Agent", userAgent);
return new WebResourceResponse(mime, "UTF-8",
conn.getInputStream());
}
}
I got this code from
The best way to intercept a WebView request in Android.
However, whenever I try to perform authentication, let's say I am loading facebook in my webview.
mWebView.loadUrl("https://www.facebook.com/");
Nothing is happening, what I noticed is that, the request headers are incomplete and also the response. Also, there are no cookies in the Sources. (I saw this when I remotely debugged the webview through Chrome).
Please correct me if I'm wrong, but I think that the incomplete headers and missing cookies is what causing the login request to fail.
Is there a way where I can modify the request and set its headers? Also for the response, should I do it too? And finally, how will I be able to have the cookies.
This question hasn't been answered for 6 months, so I don't know whether you will still need this, but maybe someone else has a similar question.
request headers are incomplete
When using HttpURLConnection you will be responsible to set any request headers, you might need, but it is as simple as setting the User-Agent, which you already did: conn.setRequestHeader(header, value) or if you want to add and not overwrite a header value: conn.addRequestHeader(header, value)
Alternatively, you could use okhttp, a HTTP client, which should add default values for headers, that are commonly expected.
there are no cookies in the Sources
When intercepting the request, you will also be in charge for handling cookies. You could store the cookie manually, by parsing the headers from the response e.g.
public WebResourceResponse shouldInterceptRequest(WebView view,
String url) {
// do your stuff
conn.connect(); // required to tell that the connection should be established
String cookie = getCookieFromConnection(conn);
// do more stuff and return WebResourceResponse
}
/**
* iterates all headers, and finds "cookie" headers
* (there could be more than one)
* #return cookie (concatenated value of all the found cookies)
* or null if no cookie has been found
*/
private String getCookieFromConnection(HttpURLConnection connection) {
String cookie = "";
Map<String, List<String>> header = connection.getHeaderFields();
List<String> cookies = header.get(COOKIE_HEADER);
if (cookies != null) {
for (String c : cookies) {
if (c != null) cookie += c + ";";
}
}
if (cookie.isEmpty()) return null;
return cookie;
}
or you could use a CookieManager, which would handle everything for you:
cookieManager = new CookieManager();
CookieHandler.setDefault(cookieManager);
cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL);
You could will also need to handle your cookies, when using okhttp, but again you could use the CookieManager as stated above. See this docu for more details, or this stackoverflow question.
Please correct me if I'm wrong, but I think that the incomplete headers and missing cookies is what causing the login request to fail.
There is another problem, when intercepting requests in a WebView: it somehow stops loading and evaluating javascript. I found this blog by Artem Zinnatullin online, who describes this behavior, and I experienced the same behavior.
If anyone would has a solution for this, I would be very happy.

Https with self-signed certificate in Android over Restlet

I'm trying to implement simple Androd client with RestLet for my thesis.Evrything works well over HTTP but I have to get it to work over HTTPS with self-signed certificate and this is were I have problems.
I was unable to find any helpful articles or anything similar about how to implement this on Android with RestLet. I know how to do this with code from Android Developers but I need to do this with Restlet classes.
I managed to implement some code which will execute GET method ( though very slow, it takes about 20-30 seconds to get data from server) but i have problems with PUT method.
I used the following code to implement HTTPS client
private static ClientResource makeInstance(String url, Context con) {
Reference reference = new Reference(url);
Log.d("URL", url);
String keyStoreFileAbsolutePath = PreferenceManager
.getDefaultSharedPreferences(con).getString(
Constants.PREF_KEY_KEYSTORE_FILE_PATH, "");
System.setProperty("javax.net.ssl.trustStore",keyStoreFileAbsolutePath);
System.setProperty("javax.net.ssl.trustStorePassword", "ks090278d");
System.setProperty("ssl.TrustManagerFactory.algorithm",
javax.net.ssl.KeyManagerFactory.getDefaultAlgorithm());
org.restlet.Context context = new org.restlet.Context();
context.getAttributes().put("hostnameVerifier", new HostnameVerifier() {
#Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
});
ClientResource resource = new ClientResource(context, reference);
Engine.getInstance().getRegisteredClients().clear();
Engine.getInstance().getRegisteredClients()
.add(new HttpClientHelper(null));
resource.release();
return resource;
}
and I'm using it like this
ClientResource request = new ClientResource(url);
request.setRetryAttempts(0);
Series<Header> responseHeaders = (Series<Header>) request.getResponse()
.getAttributes().get("org.restlet.http.headers");
if (responseHeaders == null) {
responseHeaders = new Series(Header.class);
request.getRequestAttributes().put("org.restlet.http.headers",
responseHeaders);
}
responseHeaders.add(new Header("Accept", "application/json;"));
responseHeaders.add(new Header("Content-Type", "application/json;"));
responseHeaders.add(new Header("Authorization",
"Basic ZXN0dWRlbnQ6YW5kcm9pZA==;"));
try {
request.put(new JsonRepresentation(jsonString));
Response response = request.getResponse();
if (response != null) {
Log.d("Response", "HTTP Response: " + response.toString());
ResponseHandler
.handleResponse(response);
}
} catch (Exception e) {
}
When I execute this request I'm getting "Internal server error - 500". I'm using basically the same code for GET method only insted of
request.put(new JsonRepresentation(jsonString));
I'm doing
request.get();
and it works (though very slow as I mentioned above).
I checked if this is due to some error with the server but I think is not because I managed to get appropriate response when I'm using code similar to the code from Android Developers and also when I'm trying to execute PUT to the same URL from RestClient in Chrome.
Can anyone give me any suggestions how to get this to work? I'm not sure if my aproach is good to begin with, may be that I'm gooing in the wrong direction from the start.
Thnaks in advance.

FileNotFoundException when using the offline cache of HttpResponsecache

I'm using HttpResponseCache to enable the response caching (for web requests) in my android app, and the offline cache isn't working.
I'm doing the offline cache as the documentation tells me to do.
In my Application class, at the onCreate method, I'm turning on the the cache with:
try {
long httpCacheSize = 10 * 1024 * 1024; // 10 MiB
File httpCacheDir = new File(getCacheDir(), "http");
Class.forName("android.net.http.HttpResponseCache")
.getMethod("install", File.class, long.class)
.invoke(null, httpCacheDir, httpCacheSize);
} catch (Exception httpResponseCacheNotAvailable) {}
At my HttpConnection class I'm getting the JSON with the method:
private String sendHttpGet(boolean cacheOnly) throws Exception {
URL url = new URL(getUrlCompleta());
HttpURLConnection urlConnection = null;
String retorno = null;
try {
urlConnection = (HttpURLConnection) url.openConnection();
if(urlConnection == null)
throw new Exception("Conn obj is null");
fillHeaders(urlConnection, cacheOnly);
InputStream in = new BufferedInputStream(urlConnection.getInputStream(), 8192);
retorno = convertStream(in);
in.close();
urlConnection.disconnect();
if(retorno != null)
return retorno;
} catch(IOException e) {
throw e;
} finally {
if(urlConnection != null)
urlConnection.disconnect();
}
throw new Exception();
}
Where the convertStream method just parse a InputStream into a String.
The method fillHeaders put an token on the request (for security reasons) and if the parameter cacheOnly is true, then the header "Cache-Control", "only-if-cached" is added to the request header ( with the code: connection.addRequestProperty("Cache-Control", "only-if-cached");)
The cache works 'fine' (with minor strange behaviors) when there is connectivity and the app hit the web server just to see if there is a newer version of the JSON. When the web server answers "nothing changed", the cache works.
The problem is when I have no connectivity and use the header "Cache-Control", "only-if-cached". In this case, I receive a java.io.FileNotFoundException: https://api.example.com/movies.json. That is awkward, because the implementation code of the cache probably stores the response in a file named using a hash function on the request url, and not the url itself.
Does anyone knows what can I do or what is wrong with my implementation?
ps: Above, I said "probably using a hash function", because I was not able to found the implementation of the com.android.okhttp.HttpResponseCache object (the class that android.net.http.HttpResponseCache delegates cache calls). If someone found it, please tell me where to look at :)
ps2: Even when I add a max-stale parameter in the Cache-Control header, it still doesn't work.
ps3: I obviously tested it on api 14+.
ps4: Although I'm accessing an "https://" URL address, the same behavior occurs when the URL is just a normal "http://" address.
It turns out that the problem was with the max-age value of the Cache-control directive in the response given by my web server. It had the following value: Cache-Control: max-age=0, private, must-revalidate. With this directive, my server was saying to the cache that the response could be used from the cache even if it was 0 seconds old. So, my connection wasn't using any cached response.
Knowing that max-age is specified in seconds, all I had to do was change the value to: Cache-Control: max-age=600, private, must-revalidate! There it is, now I have a 10 minute cache.
Edit: If you want to use a stale response, with the max-stale directive of the request, you shouldn't use the must-revalidate directive in the response, as I did in my webserver.

Sharing cookies/session from WebView to HttpClient doesn't work

I know this question has been asked a hundred times, and I've read and tried for 2 hours now, but I can't find my error :-(
I am trying to create a simple webbrowser and therefore have a webview, where I login on a site and get access to a picture area. With help of a DefaultHttpClient, I want to make it possible to download pictures in the secured area.
Therefore I am trying to share the cookies from the webview and pass them on to the HttpClient, so that it is authenticated and able to download. But whatever I try and do, I always get a 403 response back...
Basically the steps are the following:
1) Enter URL, webview loads website
2) Enter login details in a form
3) Navigate to picture and long hold for context menu
4) Retrieve the image URL and pass it on to AsynTask for downloading
Here's the code of the AsyncTask with the Cookie stuff:
protected String doInBackground(String... params) {
//params[0] is the URL of the image
try
{
CookieManager cookieManager = CookieManager.getInstance();
String c = cookieManager.getCookie(new URL(params[0]).getHost());
BasicCookieStore cookieStore = new BasicCookieStore();
BasicHttpContext localContext = new BasicHttpContext();
localContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore);
String[] cookieParts = null;
String cookies[] = null;
cookies = c.split(";");
for(int i=0;i<cookies.length;i++)
{
cookieParts = cookies[i].split("=");
BasicClientCookie sessionCookie = new BasicClientCookie(cookieParts[0].trim(), cookieParts[1].trim());
sessionCookie.setDomain(new URL(params[0]).getHost());
cookieStore.addCookie(sessionCookie);
}
DefaultHttpClient httpClient = new DefaultHttpClient();
httpClient.setCookieStore(cookieStore);
HttpGet pageGet = new HttpGet(new URL(params[0]).toURI());
HttpResponse response = httpClient.execute(pageGet, localContext);
if(response.getStatusLine().getStatusCode() == HttpStatus.SC_OK)
--> NEVER Happens, always get 403
.) One of the problems is that the webview saves some cookies for the host *www.*example.com, but the image-URL to download (params[0]) is *static.*example.com. The line
cookieManager.getCookie(new URL(params[0]).getHost());
returns null, because there is no cookie for static.example.com, but only for www.example.com.
.) When I manually say cookieManager.getCookie("www.example.com"); I get some cookies back, which I add to the HttpClient cookie store:
There are 5 cookies added
- testcookie = 0
- PHPSESSID = 320947238someGibberishSessionId
- email = my#email.net
- pass = 32423te32someEncodedPassGibberish
- user = 345542
So although these cookies, a session ID and other stuff, get added to the HttpClient, it never get's through to download an image. Im totally lost... though I guess that it either has something to do with the cookies domains, or that Im still missing other cookies.
But from where the heck should I know which cookies exist in the webview, when I have to specify a specific URL to get a cookie back?? :-(
Any advice?
I guess we have made it too complicated in above snippet.
Use these easy steps -
1)Retrieve the cookie from webView -wherever your webview is, use this code to re
String cookie = CookieManager.getInstance().getCookie(
url.toString());
Log.d("mytcs", "cookie downloadlistner " + cookie);
2) Pass this in your downloading asyncTask using params -
downloadImageTask = new DownloadImage();
downloadPDFTask.execute(url, cookie);
(I assume you know to retrieve this cookie in asyncTask, you will use params[1],
3) set this cookie in your http request using -
if (cookie != null)
con.setRequestProperty("cookie", cookie);
where con is HttpURLConnection con;
so you can set it to your need, in HttpGet.
You probably figured out the answer already coz it is a pretty late answer. But, just in case...
Try this. When you retrieve the cookie from WebView just use example.com in the domain name. When you set the cookie in BasicClientCookie and set the domain, set the domain name to .example.com. Note the "." in the beginning. Now, i think the session should work across all subdomains in your application.

Better method to store user credentials

Ok, so I am trying to develop a mobile website application for the iPhone and Android. Currently my site uses cURL to log the user into the other site. I have a PHP script that creates a cookie based on the username of the user. cURL then places the info into that cookie. The cookie is stored on my site's host.
Basically this mobile site I am creating is suppose to allow users to log into a forum that I developed this for (site owner would not allow me to create a mobile version on their site so needed to do it on mine). Then once they log in they can read posts and reply to them. When it goes to read a thread needs to load the cookie, as well as when they try to make a post.
How can I get the cookie to save to the users phone rather than my server? The reason I ask is, I'd like it so my host doesn't get filled up with dozens of text files with credentials of users (which I don't want to see so I am not phishing).
I want it so the user signs in, cookie gets saved to the phone. They want to read a post the phone pulls up that cookie. They want to post, phone pulls up the cookie.
I looked into PHP setcookie() function, wasn't sure if that is what I needed.
Any help provided will be appreciated.
When you set a cookie on the server side that cookie gets sent to the client (your phone in this case) via something called HTTP Headers. There is a HTTP Header with the name "Set-Cookie" and a Value of the cookie. When the browser makes a request to the server in the future, its expected to give that value back in a HTTP Header called "Cookie"
So, if you want to set a cookie and use that cookie its a matter of getting the cookie from your request, storing it somewhere safe, and giving it back in future requests.
http://en.wikipedia.org/wiki/HTTP_cookie
Here is a simple Authentication method that takes an url, a username and a password and returns the cookie value.
static public String authenticate(String service_url, String username, String password) throws IOException
{
if (username == null || password == null)
throw new IOException();
String charset = "UTF-8";
URL url = new URL(service_url);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset="+charset);
connection.setDoOutput(true);
connection.setRequestMethod("POST");
connection.setReadTimeout(5000); // 2 second timeout.
String query = String.format("Email=%s&Password=%s",
URLEncoder.encode(username, charset),
URLEncoder.encode(password, charset));
OutputStream output = null;
try {
output = connection.getOutputStream();
output.write(query.getBytes(charset));
} finally {
if (output != null) try { output.close(); } catch (IOException logOrIgnore) {}
}
connection.getInputStream();
List<String> cookies = connection.getHeaderFields().get("Set-Cookie");
if (cookies == null)
throw new IOException();
for (String cookie : cookies)
{
if (cookie.startsWith("authcookie"))
return cookie; // this is the only correct path out.
}
throw new IOException();
}
Example HTTPGET, note the http header to add the cookie value back to requests.
public static InputStream getDataFromHTTP(String url, String authenticationCookie, String mimetype) throws ClientProtocolException, IOException
{
DefaultHttpClient client = getHttpClient();
if (client == null)
throw new IOException("Cant getHttpClient()");
if (url == null)
throw new IOException("URL is null");
HttpGet httpget = new HttpGet(url);
httpget.addHeader("Accept", mimetype);
httpget.addHeader("Cookie", authenticationCookie);
httpget.addHeader("Accept-Encoding", "gzip");
HttpResponse response = client.execute(httpget);
InputStream instream = response.getEntity().getContent();
Header contentEncoding = response.getFirstHeader("Content-Encoding");
if (contentEncoding != null && contentEncoding.getValue().equalsIgnoreCase("gzip")) {
instream = new GZIPInputStream(instream);
}
return instream;
}

Categories

Resources