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.
Related
I'm trying to append the cors headers to a request made in an Android webview by a JavaScript fetch command in order to bypass cors. To do this I'm intercepting requests made in the WebView with the shouldInterceptRequest member function.
The code I have seems to be working except that the stream I'm getting from the HttpURLConnection doesn't seem to work with the stream required by the WebResourceResponse. The relevant code (inside shouldInterceptRequest function):
val url = URL("https://www.android.com")
val urlConnection: HttpURLConnection = url.openConnection() as HttpURLConnection
try {
var headers =
urlConnection.headerFields.mapValues { it.value.joinToString() }
headers = headers.toMutableMap()
headers["Access-Control-Allow-Origin"] = "*"
headers["Access-Control-Allow-Headers"] = "*"
headers["Access-Control-Allow-Credentials"] = "true"
// val inStream: InputStream = BufferedInputStream(urlConnection.inputStream) // Doesn't work
// val string = "hello world :)"
// val inStream = string.byteInputStream(charset = Charset.forName("UTF-8")) // Works when used as stream, sends "hello world :)" as response
return WebResourceResponse(
headers["Content-Type"],
if (urlConnection.contentEncoding === null) "UTF-8" else urlConnection.contentEncoding,
urlConnection.responseCode,
"OK",
headers,
urlConnection.inputStream
)
} finally {
urlConnection.disconnect()
}
The last argument of the WebResourceResponse needs to be a stream which will be sent to the Webview page. When using the urlConnection.inputStream I can see in Chrome devtools that the headers are set properly on the response, but the data fails to load.
When I replace the response stream with a string the solution works fine, and the webview receives the string correctly. My question is then why doesn't the stream from the HttpURLConnection work when given to the response object, and how do I make it work.
The solution I'm looking for should not only support text, but also binary data and streams.
As a side note I've also tried implementing the requestIntercept asynchronously, as mentioned here: How to get WebViewClient.shouldInterceptRequest invoked asynchronously, which changes the stream given to the response object, however this resulted in a very slow request followed by an android crash after the data had loaded and the request was done.
This isn't a duplicate of WebResourceResponse can't read full inputstream from HttpConnection (Android), the problem is different and the solution wouldn't work.
Entire code file: https://gist.github.com/RuurdBijlsma/2b52d80a4d74460ac2837ee55b0b933c in case that's helpful.
I have created an Android application, in that I want to get Session from webview.
How to make it possible ?
Thanks.
I use this method for getting session cookies from a webview:
public static String getCookieFromAppCookieManager(String url) throws MalformedURLException {
CookieManager cookieManager = CookieManager.getInstance();
if (cookieManager == null)
return null;
String rawCookieHeader = null;
URL parsedURL = new URL(url);
rawCookieHeader = cookieManager.getCookie(parsedURL.getHost());
if (rawCookieHeader == null)
return null;
return rawCookieHeader;
}
There are two ways:
If a developer has made a httpclient, and makes an api auth call and store the cookie. Then you sync the httpclient's cookie with webview and maintain a session natively.
If user has used a webview to make an auth call and the cookie resides in the webview.
First one is your code and simply making a getter will return instance of DefaultHTTPClient. The instance will have access to cookies too. you can make async calls to auth api to get correct cookie in the instance. Make sure to keep HttpClient and Webview in sync.
For retrieving cookie in second method, you would use CookieManager object and the url which user is logged into and you need cookie for, example twitter.com See the second post here for implementation details.
I track my visitors' usernames and I want to pass the username data from webpage to my activity method. What is the proper way to pass this data ?
(Putting username data inside HTML page's hidden div and parsing webpage is my last option, I don't prefer it)
I use Webview in my Android application. I load URLs like this:
public boolean shouldOverrideUrlLoading(WebView view, String url) {
...
String url = "http://example.com";
webView.postUrl(url, EncodingUtils.getBytes(myPostData, "base64"));
...
}
There is some custom headers inside my page response. Response headers have this:
header('User name: michael');
If page is successfully loaded, I need to get that headers from the response and use it inside my Google Analytics as custom dimension.
EasyTracker easyTracker = EasyTracker.getInstance();
easyTracker.send(MapBuilder
.createAppView("Home screen")
.set(Fields.customDimension(1), "michael");
.build()
);
My problem is what is the proper way for this ? Is it possible to get it from onPageFinished and set this header inside Analytics code or is there a better way ? I'm open to any method/solution that is compatible with this structure.
Note that I override these methods for my activity works:
WebViewClient::onPageStarted(WebView view, String url, Bitmap favicon)
WebViewClient::onPageFinished(WebView view, String url)
WebViewClient::shouldOverrideUrlLoading(WebView view, String url)
WebViewClient::onLoadResource(WebView view, String url)
WebViewClient::onReceivedError(WebView view, int errorCode, String description, String failingUrl)
WebChromeClient::onProgressChanged(WebView view, int newProgress)
Edit:
There is a similar answer here which is 3 years old: https://stackoverflow.com/a/3134609/429938
But It offers to catch headers in shouldOverrideUrlLoading. Is it not possible to catch it in onPageFinished ? Or how can I use it with Webview.postUrl()
Edit2:
Another solution is to use addJavascriptInterface and set user name from the javascript code and set google analytics code inside activity. But this is a Android specific solution. I would prefer a common solution that can be used in IOS etc.
the most cross platform solution is a javascript bridge ( addJavascriptInterface ). This is what I would recommend you.
The native part can be implemented on iOS too. And the HTML part stay the same.
If not, you need to implement all the logic on the native side using one of the events, like onPageFinished. If the header are not accessible from here, do the request yourself with HTTPURLConnection then loadDataWithBaseURL.
I'm using following code to konw when page is loaded:
webview.setWebChromeClient(new WebChromeClient() {
public void onProgressChanged(WebView view, int progress)
{
MyActivity.setTitle("Loading...");
MyActivity.setProgress(progress * 100);
if (progress == 100) {
//page is successfully loaded and you can get our header.
}
}
});
Try this one: here I'm getting "User name" value from header
HttpURLConnection conn = null;
final HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
try {
URL url = new URL(YOUR_URL);
if (url.getProtocol().toLowerCase().equals("https")) {
trustAllHosts();
HttpsURLConnection https = (HttpsURLConnection) url.openConnection();
https.setHostnameVerifier(DO_NOT_VERIFY);
conn = https;
} else {
conn = (HttpURLConnection) url.openConnection();
}
conn.setInstanceFollowRedirects(false);
conn.connect();
String location = conn.getHeaderField( "User name" );
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.
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