is it possible to access HTML form data POSTed through a WebView?
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();
}
精彩评论