Подтвердить что ты не робот

Возможно ли получить доступ к данным формы HTML, отправленным через WebView?

Я начал писать приложение, которое предоставляет пользователю форму HTML через WebView. Поскольку форма не находится под моим контролем, заполненные данные могут быть отправлены как GET, так и POST-запрос. Мое приложение необходимо для захвата данных переносимой формы, то есть для того, чтобы удержать то, что было введено в поля формы.

Используя адекватный обратный вызов из WebViewClient, такой как onPageLoaded(), легко получить данные формы из запроса GET. Тем не менее, я не могу найти подходящий метод, позволяющий делать то же самое для POSTED-данных, то есть иметь возможность доступа к телу сообщения HTTP POST, содержащему данные формы. Я пропустил соответствующий обратный вызов здесь или нет простого способа достижения указанной цели с данным API (даже на последнем уровне 8)?

Предполагая, что это невозможно, я рассмотрел возможность переопределения и расширения частей android.webkit, чтобы ввести новый крючок обратного вызова, который каким-то образом передается POST. Таким образом, мое приложение может быть отправлено с помощью настроенного браузера /WebViewClient, который выполняет желаемую функцию. Тем не менее, я не мог найти хорошего места для начала в коде и был бы рад любым намекам в этом отношении (в случае, если подход выглядит перспективным вообще).

Спасибо заранее!

4b9b3361

Ответ 1

Как указано в моем собственном комментарии к исходному вопросу, работает подход JavaScript-инъекции. В основном, что вам нужно сделать, это добавить часть кода JavaScript в событие DOM onsubmit, проанализировать поля формы и вернуть результат обратно к зарегистрированной Java функции.

Пример кода:

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 + "})()");
        }
}

Неформально, это означает, что пользовательский код JavaScript (как обработчик onsubmit) вводит каждый раз, когда страница заканчивает загрузку. При представлении формы Javascript будет анализировать данные формы и передавать ее обратно на Землю Java через объект JavaScriptInterface.

Чтобы разобрать поля формы, код Javascript добавляет обработчики формы onsubmit и onclick. Первый может обрабатывать представления канонической формы через обычную кнопку отправки, в то время как последний имеет дело с пользовательскими кнопками отправки, то есть с кнопками, которые делают некоторую дополнительную магию JavaScript перед вызовом form.submit().

Имейте в виду, что код Javascript может быть не идеальным: могут быть другие методы отправки формы, которую мой код не может быть уловлен. Тем не менее, я убежден, что введенный код можно обновить, чтобы справиться с такими возможностями.

Ответ 2

Предоставленный ответ дает ошибку, поэтому я решил сделать более простую реализацию, в которой также был хорошо структурированный JavaScript (это означает, что JS находится в файле):

В папке с вашими файлами создайте файл с именем inject.js со следующим кодом внутри:

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;
};

Это код javascript, который мы будем использовать для инъекций, вы можете отключить операторы if, как вы считаете нужным, и использовать типы вместо или имена. Обратный вызов для Android - это строка: window.AndroidInterface.processHTML(str);

Затем ваша активность/фрагмент должна выглядеть так:

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();
    }