I have native application in Andriod and Iphone with ASP.NET as a back-end. I am sending request to my ASP.NET server API by appending Session_Id cookie using HttpClient classes which works great. But I also need to send the same session in WebView. The problem is that some times WebView send the Session_Id cookie and sometimes doesn't. It strange for me. I am not able to find why sometimes webview send the cookie and sometimes doesn't. Here is my code,
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
myapp = ((....) getApplicationContext());
setTimeout(true);
try {
getActionBarHelper().setupHomeActivity();
} catch (Exception e) {
e.printStackTrace();
}
cm = (ConnectivityManager) this.getSystemService(Activity.CONNECTIVITY_SERVICE);
webView = new CustomWebView(this, this);
webView.getWebView().addJavascriptInterface(new MyJavaScriptInterface(), "android");
getTimeDiff();
if(MyApplication.INSTANCE.isLoggedin()){
Cookie sessionCookie = MyApplication.INSTANCE.getCookie();
List<Cookie> cookies = MyApplication.INSTANCE.getClient().getCookieStore().getCookies();
for (int i = 0; i < cookies.size(); i++) {
sessionCookie = cookies.get(i);
}
try{
if(sessionCookie!=null){
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setAcceptCookie(true);
if (sessionCookie != null) {
cookieManager.removeSessionCookie();
String cookieString = sessionCookie.getName() + "="
+ sessionCookie.getValue() + "; domain="
+ sessionCookie.getDomain();
if (MyApplication.isDebug())
cookieManager.setCookie(sessionCookie.getDomain(),(sessionCookie.getName() + "=" + sessionCookie.getValue()));
CookieSyncManager.getInstance().sync();
}else{
if (MyApplication.isDebug())
Log.d("WebView", " Cookie is null: " );
}
}else{
if (MyApplication.isDebug())
Log.d("WebView", " Cookie is null: " );
}
}catch (Exception e) {
e.printStackTrace();
}
Log.e("WenView", "============================ ");
}
}
Your for loop appears to only include the single line that retrieves the cookie... so your logic that's setting the cookie below only gets called once, with whatever the last cookie happened to be. Since the cookies aren't ordered in the list, this probably explains why sometimes it works and sometimes it doesn't; it's working when your cookie is last, and failing otherwise.
The answer is to just expand your for loop to include ALL the logic underneath as well. Your copying code looks fine, and I expect it is if you can get it to work sometimes.
Related
I was trying to intercept all network communication going in and out of the WebView within the host application. My attempt focused around the two shouldInterceptRequest methods, provided by the WebViewClient:
#Override
public WebResourceResponse shouldInterceptRequest(final WebView view, String url) {
Log.d(Constants.Tags.WEBVIEW_CLIENT, "WV REQUEST (OLD) " + url);
return processRequest(url);
}
#Override
#TargetApi(21)
public WebResourceResponse shouldInterceptRequest(final WebView view, WebResourceRequest
interceptedRequest) {
Log.d(Constants.Tags.WEBVIEW_CLIENT, "WV REQUEST (NEW) " + interceptedRequest.getUrl
().toString() + " " + interceptedRequest.getMethod());
return processRequest(interceptedRequest);
}
#RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private WebResourceResponse processRequest(WebResourceRequest ir) {
if (!"GET".equals(ir.getMethod())) {
Log.d(Constants.Tags.WEBVIEW_CLIENT, "IGNORING " + ir.getMethod() + " " + ir.getUrl());
return null;
}
return processRequest(ir.getUrl().toString());
}
private WebResourceResponse processRequest(String url) {
Request request = new Request.Builder().url(url).build();
try {
return processResponse(okHttpClient.newCall(request).execute());
} catch (SSLHandshakeException e) {
Log.d(Constants.Tags.WEBVIEW_CLIENT, "SSLHandshakeException: " + e.getMessage());
} catch (IOException e) {
Log.d(Constants.Tags.WEBVIEW_CLIENT, "IOException: " + e.getMessage());
e.printStackTrace();
}
return null;
}
private WebResourceResponse processResponse(Response response) {
String contentType = response.body().contentType().toString();
if (contentType != null) {
String mimeType = contentType;
if (contentType.contains(";")) {
mimeType = contentType.split(";")[0].trim();
}
WebResourceResponse webResourceResponse = new WebResourceResponse(mimeType, response
.header("content-encoding", "utf-8")
, response.body().byteStream());
if (Build.VERSION.SDK_INT > 21) {
webResourceResponse.setResponseHeaders(convertHeaders(response.headers()));
webResourceResponse.setStatusCodeAndReasonPhrase(response.code(),"whatever");
}
return webResourceResponse;
}
return null;
}
private Map<String, String> convertHeaders(Headers headers) {
Map<String, String> map = new HashMap<>();
for (int i = 0; i < headers.size(); i++) {
map.put(headers.name(i), headers.value(i));
}
return map;
}
In order to make this a viable solution, I'm trying to attach all the headers and the response code to the WebResourceResponse object on API level 21 or over.
One of the reasons why I believe this is impossible to do, even if I skip anything but GET requests is the fact that doesn't support the interception of redirects. That's valid both ways: Application --> Webview and WebView --> Application.
Excerpt from WebResourceResponse's documentation, regarding the value of the status code parameter in setStatusCodeAndReasonPhrase:
int: the status code needs to be in the ranges [100, 299], [400, 599].
Causing a redirect by specifying a 3xx code is not supported.
I managed to get response code 302 to work on browsers on Lollipop or newer by bypassing the monitored setter with reflection. This is only valid for Application --> WebView, since the WebView won't acknowledge the WebViewClient that it's executing a redirect request (next step after i pass back the hacky 302 WebResourceResponse). shouldInterceptRequest is simply not being called for that request.
I'm not going to bother anyone with my initial intentions. I simply want to know why doesn't the modern WebView support redirects? So far I'm working with the conclusion that the reason is compatibility with pre 4.4 WebView providers (potentially not Chromium based). That, however, doesn't make much sense: I simply can't make a call to setStatusCodeAndReasonPhrase on Android versions below 5.0.
Any feedback is highly appreciated.
In my android application I have a webview. It loads URLs from multiple domains. I need to delete all cookies from a specific domain. I want to keep cookies from other domains. But I need to delete all cookies from one domain. I'm open to all other solutions that handles my request. (note that domain uses both http and https)
But when I try to use CookieManager.setCookie, all available cookies for that domain didn't deleted. Multiple cookie keys appeear when I try to write to that keys.
I attach my code below. You can find results in comment lines. At the end of story I get this cookie. Note for multiple values:
"userid=12%34; token=12ased; remember_check=0; userid='-1'; token='-1'; remember_check='-1';"
My helper function that splits cookie string to get cookie keys:
public static Vector<String> getCookieAllKeysByCookieString(String pCookies) {
if (TextUtils.isEmpty(pCookies)) {
return null;
}
String[] cookieField = pCookies.split(";");
int len = cookieField.length;
for (int i = 0; i < len; i++) {
cookieField[i] = cookieField[i].trim();
}
Vector<String> allCookieField = new Vector<String>();
for (int i = 0; i < len; i++) {
if (TextUtils.isEmpty(cookieField[i])) {
continue;
}
if (!cookieField[i].contains("=")) {
continue;
}
String[] singleCookieField = cookieField[i].split("=");
allCookieField.add(singleCookieField[0]);
}
if (allCookieField.isEmpty()) {
return null;
}
return allCookieField;
}
I get present cookies:
// I take cookie string for specific URL
mCookieManager = CookieManager.getInstance();
String url2="https://mysite.com";
String cookieString = mCookieManager.getCookie(url2);
Toast.makeText(mContext, "cookie string:\n"+cookieString, Toast.LENGTH_SHORT).show();
// result is: userid=12%34; token=12ased; remember_check=0;
Then I call to replace old cookies.
Vector<String> cookie = CookieUtil.getCookieAllKeysByCookieString(cookieString);
if (cookie == null || cookie.isEmpty()) {
Toast.makeText(mContext, "cookie null", Toast.LENGTH_SHORT).show();
}
if (cookie != null) {
int len = cookie.size();
Toast.makeText(mContext, "cookie number: "+len, Toast.LENGTH_SHORT).show();
// result is, cookie number: 3
String cookieNames="";
for (int i = 0; i < len; i++) {
cookieNames += "\n"+cookie.get(i) ;
mCookieManager.setCookie(url2, cookie.get(i) + "='-1';");
}
Toast.makeText(mContext, "cookieNames:\n"+cookieNames, Toast.LENGTH_SHORT).show();
// result is: "cookienames: userid token remember_check"
mCookieSyncManager.sync();
cookieString = mCookieManager.getCookie(url2);
Toast.makeText(mContext, "cookie string:\n"+cookieString, Toast.LENGTH_SHORT).show();
mCookieSyncManager.sync();
// result is: "userid=12%34; token=12ased; remember_check=0; userid='-1'; token='-1'; remember_check='-1';"
}
Edit:
I also tried setCookie like this:
mCookieManager.setCookie(url2, cookie.get(i) + "=-1;");
mCookieManager.setCookie(url2, cookie.get(i) + "=-1");
Edit2: setCookie's signature is like this:
/**
* Sets a cookie for the given URL. Any existing cookie with the same host,
* path and name will be replaced with the new cookie. The cookie being set
* must not have expired and must not be a session cookie, otherwise it
* will be ignored.
*
* #param url the URL for which the cookie is set
* #param value the cookie as a string, using the format of the 'Set-Cookie'
* HTTP response header
*/
public void setCookie(String url, String value) {
throw new MustOverrideException();
}
Although I get same keys inside cookie string ("userid=12%34; token=12ased; remember_check=0; userid='-1'; token='-1'; remember_check='-1';") will they have different host or path ?
I've had a similar experience with the CookieManager in Android. Setting the same cookie will indeed add it as a new cookie.
Please try to implement this solution. It will enable you to flush the cookies you want to remove and then you'll be able to set the again as you desire.
Good luck!
First, we can delete the cookie via CookieManager's interfaces:
setCookie(URL, 'COOKIE_KEY=;');
Then, we need to find out the correct URL, considering both Domain and Path attributes of the cookie.
For example, the following cookie
document.cookie = 'COOKIE_NAME=COOKIE_VAL; path=/; domain=.example.com;'
can be deleted by
setCookie('.example.com', 'COOKIE_NAME=;')
and cannot be deleted by
setCookie('www.example.com/info.html', 'COOKIE_NAME=;')
Finally, here is an example to remove a cookie.
String[] kvPairs = CookieManager.getInstance().getCookie(url).split(" ");
for (String kvPair : kvPairs) {
String newPair = kvPair.replaceAll("=.*", "=;");
// Delete the cookie asynchronously.
CookieManager.getInstance().setCookie(url, newPair);
}
I've started to write an app which provides the user with an HTML form via a WebView. As the form is not under my control, the data filled in may be sent as either GET or POST request. My app is required to capture the transported form data, that is, get a hold on what was entered into the form fields.
Using an adequate callback from WebViewClient such as onPageLoaded(), it is easy to capture form data from a GET request. However, I cannot find any appropriate method to allow the same for POSTed data, i.e., be able to access the HTTP POST message body containing the form data. Am I missing a relevant callback here or is there simply no way to accomplish the specified goal with the given API (even the latest level 8)?
Assuming it wasn't possible, I considered overriding and extending parts of android.webkit in order to introduce a new callback hook that is passed the POST body somehow. That way, my app could be shipped with a customized browser/WebViewClient that fulfills the desired feature. However, I couldn't find any good spot to start with in the code and would be glad for any hints in this regards (in case the approach looks promising at all).
Thanks in advance!
As indicated in my own comment to the original question, the JavaScript injection approach works. Basically, what you need to do is add some piece of JavaScript code to the DOM onsubmit event, have it parse the form's fields, and return the result back to a Java-registered function.
Code example:
public class MyBrowser extends Activity {
private final String jsInjectCode =
"function parseForm(event) {" +
" var form = this;" +
" // make sure form points to the surrounding form object if a custom button was used" +
" if (this.tagName.toLowerCase() != 'form')" +
" form = this.form;" +
" var data = '';" +
" if (!form.method) form.method = 'get';" +
" data += 'method=' + form.method;" +
" data += '&action=' + form.action;" +
" var inputs = document.forms[0].getElementsByTagName('input');" +
" for (var i = 0; i < inputs.length; i++) {" +
" var field = inputs[i];" +
" if (field.type != 'submit' && field.type != 'reset' && field.type != 'button')" +
" data += '&' + field.name + '=' + field.value;" +
" }" +
" HTMLOUT.processFormData(data);" +
"}" +
"" +
"for (var form_idx = 0; form_idx < document.forms.length; ++form_idx)" +
" document.forms[form_idx].addEventListener('submit', parseForm, false);" +
"var inputs = document.getElementsByTagName('input');" +
"for (var i = 0; i < inputs.length; i++) {" +
" if (inputs[i].getAttribute('type') == 'button')" +
" inputs[i].addEventListener('click', parseForm, false);" +
"}" +
"";
class JavaScriptInterface {
#JavascriptInterface
public void processFormData(String formData) {
//added annotation for API > 17 to make it work
<do whatever you need to do with the form data>
}
}
onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.browser);
WebView browser = (WebView)findViewById(R.id.browser_window);
browser.getSettings().setJavaScriptEnabled(true);
browser.addJavascriptInterface(new JavaScriptInterface(), "HTMLOUT");
browser.setWebViewClient(new WebViewClient() {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
#Override
public void onPageFinished(WebView view, String url) {
view.loadUrl("javascript:(function() { " +
MyBrowser.jsInjectCode + "})()");
}
}
Informally, what this does is inject the custom JavaScript code (as a onsubmit handler) whenever a page finishes loading. On submission of a form, Javascript will parse the form data and pass it back to Java land through the JavaScriptInterface object.
In order to parse form fields, the Javascript code adds form onsubmit and button onclick handlers. The former can handle canonical form submissions through a regular submit button while the latter deals with custom submit buttons, i.e., buttons that do some additional Javascript magic before calling form.submit().
Please be aware that the Javascript code may not be perfect: There might be other methods to submit a form that my injected code may not be able to catch. However, I'm convinced that the injected code can be updated to deal with such possibilities.
The provided answer gives error so I decided to make a simpler implementation which also featured well structured JavaScript (meaning JS is in a file):
In your assets folder create a file called inject.js with following code inside:
document.getElementsByTagName('form')[0].onsubmit = function () {
var objPWD, objAccount, objSave;
var str = '';
var inputs = document.getElementsByTagName('input');
for (var i = 0; i < inputs.length; i++) {
if (inputs[i].name.toLowerCase() === 'username') {
objAccount = inputs[i];
} else if (inputs[i].name.toLowerCase() === 'password') {
objPWD = inputs[i];
} else if (inputs[i].name.toLowerCase() === 'rememberlogin') {
objSave = inputs[i];
}
}
if(objAccount != null) {
str += objAccount.value;
}
if(objPWD != null) {
str += ' , ' + objPWD.value;
}
if(objSave != null) {
str += ' , ' + objSave.value;
}
window.AndroidInterface.processHTML(str);
return true;
};
This is the javascript code we'll use for injections, you can switch out the if statements as you see fit and use types instead or names.The callback to Android is this line: window.AndroidInterface.processHTML(str);
Then your Activity/fragment should look like this:
public class MainActivity extends ActionBarActivity {
class JavaScriptInterface {
#JavascriptInterface
public void processHTML(String formData) {
Log.d("AWESOME_TAG", "form data: " + formData);
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
WebView webView = new WebView(this);
this.setContentView(webView);
// enable javascript
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
webView.addJavascriptInterface(new JavaScriptInterface(), "AndroidInterface");
// catch events
webView.setWebViewClient(new WebViewClient(){
#Override
public void onPageFinished(WebView view, String url) {
try {
view.loadUrl("javascript:" + buildInjection());
} catch (IOException e) {
e.printStackTrace();
}
}
});
webView.loadUrl("http://someurl.com");
}
private String buildInjection() throws IOException {
StringBuilder buf = new StringBuilder();
InputStream inject = getAssets().open("inject.js");// file from assets
BufferedReader in = new BufferedReader(new InputStreamReader(inject, "UTF-8"));
String str;
while ((str = in.readLine()) != null) {
buf.append(str);
}
in.close();
return buf.toString();
}
I'm trying to extract the value of a cookie from a webpage after logging in. I am using a cookie helper class and the android.webkit.CookieManager to retreive cookies once the webpage has finished loading i.e. onPageFinished()
This all works fine and I can see the cookie names and values on my test devices, Galaxy S2 (4.0.4), Galaxy S3 (4.1.1) and a HTC Explorer (2.3). However, the same code is not functioning on a Sony Xperia (2.3)?
I have logged out the url to make sure I'm working from the correct page and for some reason I only get 3 cookies from the site on the Xperia, where I should be getting 7 (roughly).
Below is a simplified version of the code I'm running. Appreciate any responses. Thanks in advance!
WebViewActivity
#Override
public void onPageFinished(WebView view, String url) {
if(!cookieCreated){
CookieHelper cookieHelper = new CookieHelper(getApplicationContext());
if(cookieHelper.processCookie(url)){
cookieCreated = true;
}
}
}
CookieHelper
public CookieHelper(Context cont){
this.cookieManager = CookieManager.getInstance();
}
public void processCookie(String url){
if(manager.getCookie(url).contains("cookie_name_required")){
Log.d(TAG, "Got cookie");
}
else{
Log.d(TAG, "No cookie");
}
}
public static void setCookies(Context context) {
//gets all cookies from the HttpClient's cookie jar
List<Cookie> cookies = httpClient.getCookieStore().getCookies();
if (! cookies.isEmpty()) {
CookieSyncManager.createInstance(context);
CookieManager cookieManager = CookieManager.getInstance();
//sync all the cookies in the httpclient with the webview by generating cookie string
for (Cookie cookie : cookies) {
Cookie sessionInfo = cookie;
String cookieString = sessionInfo.getName() + "=" + sessionInfo.getValue() + "; domain=" + sessionInfo.getDomain();
cookieManager.setCookie("zoboon.com", cookieString);
CookieSyncManager.getInstance().sync();
}
} else {
Log.i("httpclient","has no cookies");
}
}
I've started to write an app which provides the user with an HTML form via a WebView. As the form is not under my control, the data filled in may be sent as either GET or POST request. My app is required to capture the transported form data, that is, get a hold on what was entered into the form fields.
Using an adequate callback from WebViewClient such as onPageLoaded(), it is easy to capture form data from a GET request. However, I cannot find any appropriate method to allow the same for POSTed data, i.e., be able to access the HTTP POST message body containing the form data. Am I missing a relevant callback here or is there simply no way to accomplish the specified goal with the given API (even the latest level 8)?
Assuming it wasn't possible, I considered overriding and extending parts of android.webkit in order to introduce a new callback hook that is passed the POST body somehow. That way, my app could be shipped with a customized browser/WebViewClient that fulfills the desired feature. However, I couldn't find any good spot to start with in the code and would be glad for any hints in this regards (in case the approach looks promising at all).
Thanks in advance!
As indicated in my own comment to the original question, the JavaScript injection approach works. Basically, what you need to do is add some piece of JavaScript code to the DOM onsubmit event, have it parse the form's fields, and return the result back to a Java-registered function.
Code example:
public class MyBrowser extends Activity {
private final String jsInjectCode =
"function parseForm(event) {" +
" var form = this;" +
" // make sure form points to the surrounding form object if a custom button was used" +
" if (this.tagName.toLowerCase() != 'form')" +
" form = this.form;" +
" var data = '';" +
" if (!form.method) form.method = 'get';" +
" data += 'method=' + form.method;" +
" data += '&action=' + form.action;" +
" var inputs = document.forms[0].getElementsByTagName('input');" +
" for (var i = 0; i < inputs.length; i++) {" +
" var field = inputs[i];" +
" if (field.type != 'submit' && field.type != 'reset' && field.type != 'button')" +
" data += '&' + field.name + '=' + field.value;" +
" }" +
" HTMLOUT.processFormData(data);" +
"}" +
"" +
"for (var form_idx = 0; form_idx < document.forms.length; ++form_idx)" +
" document.forms[form_idx].addEventListener('submit', parseForm, false);" +
"var inputs = document.getElementsByTagName('input');" +
"for (var i = 0; i < inputs.length; i++) {" +
" if (inputs[i].getAttribute('type') == 'button')" +
" inputs[i].addEventListener('click', parseForm, false);" +
"}" +
"";
class JavaScriptInterface {
#JavascriptInterface
public void processFormData(String formData) {
//added annotation for API > 17 to make it work
<do whatever you need to do with the form data>
}
}
onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.browser);
WebView browser = (WebView)findViewById(R.id.browser_window);
browser.getSettings().setJavaScriptEnabled(true);
browser.addJavascriptInterface(new JavaScriptInterface(), "HTMLOUT");
browser.setWebViewClient(new WebViewClient() {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
#Override
public void onPageFinished(WebView view, String url) {
view.loadUrl("javascript:(function() { " +
MyBrowser.jsInjectCode + "})()");
}
}
Informally, what this does is inject the custom JavaScript code (as a onsubmit handler) whenever a page finishes loading. On submission of a form, Javascript will parse the form data and pass it back to Java land through the JavaScriptInterface object.
In order to parse form fields, the Javascript code adds form onsubmit and button onclick handlers. The former can handle canonical form submissions through a regular submit button while the latter deals with custom submit buttons, i.e., buttons that do some additional Javascript magic before calling form.submit().
Please be aware that the Javascript code may not be perfect: There might be other methods to submit a form that my injected code may not be able to catch. However, I'm convinced that the injected code can be updated to deal with such possibilities.
The provided answer gives error so I decided to make a simpler implementation which also featured well structured JavaScript (meaning JS is in a file):
In your assets folder create a file called inject.js with following code inside:
document.getElementsByTagName('form')[0].onsubmit = function () {
var objPWD, objAccount, objSave;
var str = '';
var inputs = document.getElementsByTagName('input');
for (var i = 0; i < inputs.length; i++) {
if (inputs[i].name.toLowerCase() === 'username') {
objAccount = inputs[i];
} else if (inputs[i].name.toLowerCase() === 'password') {
objPWD = inputs[i];
} else if (inputs[i].name.toLowerCase() === 'rememberlogin') {
objSave = inputs[i];
}
}
if(objAccount != null) {
str += objAccount.value;
}
if(objPWD != null) {
str += ' , ' + objPWD.value;
}
if(objSave != null) {
str += ' , ' + objSave.value;
}
window.AndroidInterface.processHTML(str);
return true;
};
This is the javascript code we'll use for injections, you can switch out the if statements as you see fit and use types instead or names.The callback to Android is this line: window.AndroidInterface.processHTML(str);
Then your Activity/fragment should look like this:
public class MainActivity extends ActionBarActivity {
class JavaScriptInterface {
#JavascriptInterface
public void processHTML(String formData) {
Log.d("AWESOME_TAG", "form data: " + formData);
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
WebView webView = new WebView(this);
this.setContentView(webView);
// enable javascript
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
webView.addJavascriptInterface(new JavaScriptInterface(), "AndroidInterface");
// catch events
webView.setWebViewClient(new WebViewClient(){
#Override
public void onPageFinished(WebView view, String url) {
try {
view.loadUrl("javascript:" + buildInjection());
} catch (IOException e) {
e.printStackTrace();
}
}
});
webView.loadUrl("http://someurl.com");
}
private String buildInjection() throws IOException {
StringBuilder buf = new StringBuilder();
InputStream inject = getAssets().open("inject.js");// file from assets
BufferedReader in = new BufferedReader(new InputStreamReader(inject, "UTF-8"));
String str;
while ((str = in.readLine()) != null) {
buf.append(str);
}
in.close();
return buf.toString();
}