Java tutorial
/* * Copyright (C) 2010 Silas Graffy * * This file is part of MeinBMW Connector for WebSMS by Felix Bechstein. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 3 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program; If not, see <http://www.gnu.org/licenses/>. */ package info.graffy.android.websms.connector.meinbmw; import java.io.IOException; import java.util.ArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.StatusLine; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.preference.PreferenceManager; import de.ub0r.android.websms.connector.common.Connector; import de.ub0r.android.websms.connector.common.ConnectorCommand; import de.ub0r.android.websms.connector.common.ConnectorSpec; import de.ub0r.android.websms.connector.common.ConnectorSpec.SubConnectorSpec; import de.ub0r.android.websms.connector.common.Utils; import de.ub0r.android.websms.connector.common.WebSMSException; public class ConnectorMeinBMW extends Connector { private final static String ROOT_PAGE_URL = "https://www.meinbmw.de"; private final static String SMS_PAGE_URL = "https://www.meinbmw.de/tabid/80/Default.aspx"; private final static String REFERER_URL = "https://www.meinbmw.de/Home/tabid/36/ctl/Login/Default.aspx"; private final static String USER_AGENT = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.52 Safari/536.5"; private final static String ENCODING = "UTF-8"; private final static String[] SSL_FINGERPRINTS = { "b4:2b:76:39:ec:53:ee:83:39:02:a3:70:c1:fc:a9:a5:f7:24:d5:2a", "0f:9e:67:16:e7:e0:98:02:22:49:d6:a0:74:9b:76:11:7e:c6:e1:e9", "df:27:77:83:2d:7b:41:02:61:0d:52:25:f4:c3:a0:0c:6c:3c:3e:1c", "bd:54:a8:bc:61:9d:ec:70:dd:a9:05:39:ea:b6:cc:18:3d:0c:8a:8d" }; private final static String SESSION_INPUT_VALUE_REGEXP = ".*<input type=\"hidden\" name=\"__VIEWSTATE_CACHEKEY\" id=\"__VIEWSTATE_CACHEKEY\" value=\"(VS_[a-z\\d]+_\\d+)\" />.*"; private final static String LOGIN_POST_DESTINATION_REGEXP = ".*<form name=\"Form\" method=\"post\" action=\"(/ServiceCenter/.+/SMSService/tabid/80/ctl/Login/Default.aspx\\?returnurl=.+\\.aspx)\".*id=\"Form\" enctype=\"multipart/form-data\" .*"; private final static String SEND_SUCCESS_SUBSTRING = "verschickt."; private final static String SMS_PAGE_LOAD_SUCCESS_SUBSTRING = "MeinBMW.de - Service-Center - Mein BMW Services - SMS Service"; private final static String WRONG_LOGIN_MESSAGE_SUBSTRING = "Ihre E-Mail-Adresse oder Ihr Passwort ist leider nicht korrekt. Bitte berprfen Sie Ihre Eingaben."; // TODO changed private final static String TODAYS_SMS_EXPIRED_SUBSTRING = "heute ist erreicht. Bitte nutzen Sie diesen Service wieder ab morgen"; private static String currentHtmlResultPage; private static Context currentContext; private final static Pattern sessionInputValueExtractPattern = Pattern.compile(SESSION_INPUT_VALUE_REGEXP, Pattern.DOTALL); private final static Pattern postUrlExtractPattern = Pattern.compile(LOGIN_POST_DESTINATION_REGEXP, Pattern.DOTALL); @Override public final ConnectorSpec initSpec(final Context context) { setCurrentContext(context); return createConnectorSpec(); } @Override public final ConnectorSpec updateSpec(final Context context, final ConnectorSpec connectorSpec) { setCurrentContext(context); return setConnectorStatus(connectorSpec); } @Override protected final void doSend(final Context context, final Intent intent) { setCurrentContext(context); tryToLoadSmsPage(); if (!isSmsPageLoaded()) { final String user = getUserName(); final String pass = getPassword(); doLogin(user, pass); } final String phonenumber = getPhonenumber(intent); final String text = getMessageText(intent); sendSms(phonenumber, text); } private final void setCurrentContext(final Context context) { currentContext = context; } private final ConnectorSpec setConnectorStatus(final ConnectorSpec connectorSpec) { final SharedPreferences p = getDefaultSharedPreferences(); if (p.getBoolean(Preferences.PREFS_ENABLED, false)) if (getUserName().length() > 0 && getPassword().length() > 0) connectorSpec.setReady(); else connectorSpec.setStatus(ConnectorSpec.STATUS_ENABLED); else connectorSpec.setStatus(ConnectorSpec.STATUS_INACTIVE); return connectorSpec; } private final SharedPreferences getDefaultSharedPreferences() { return PreferenceManager.getDefaultSharedPreferences(currentContext); } private final String getUserName() { final SharedPreferences p = getDefaultSharedPreferences(); return p.getString(Preferences.PREFS_USER, ""); } private final String getPassword() { final SharedPreferences p = getDefaultSharedPreferences(); return p.getString(Preferences.PREFS_PASSWORD, ""); } private final ConnectorSpec createConnectorSpec() { final String name = getStringResource(R.string.connector_meinbmw_name); final ConnectorSpec c = new ConnectorSpec(name); c.setAuthor(getStringResource(R.string.connector_meinbmw_author)); c.setBalance(null); c.setCapabilities(ConnectorSpec.CAPABILITIES_SEND | ConnectorSpec.CAPABILITIES_PREFS); c.addSubConnector("0", "", SubConnectorSpec.FEATURE_NONE); return c; } private final String getStringResource(final int resourceStringID) { return currentContext.getString(resourceStringID); } private final String getPhonenumber(final Intent intent) { final ConnectorCommand cc = new ConnectorCommand(intent); return Utils.getRecipientsNumber(cc.getRecipients()[0]); } private final String getMessageText(final Intent intent) { final ConnectorCommand cc = new ConnectorCommand(intent); return cc.getText(); } private final void tryToLoadSmsPage() { StatusLine httpResult; httpResult = performHttpRequestForStatusLine(SMS_PAGE_URL); if (httpResult.getStatusCode() != HttpStatus.SC_OK) throw new WebSMSException(getStringResource(R.string.error_sms_page_result) + httpResult.toString()); } private final boolean isSmsPageLoaded() { return currentHtmlResultPage.indexOf(SMS_PAGE_LOAD_SUCCESS_SUBSTRING) > -1; } private final void doLogin(final String user, final String password) { StatusLine httpResult; final String sessionInputValueLogin = extractSessionInputValue(); final String postDestination = ROOT_PAGE_URL + extractRelativePostUrl(); httpResult = performRawLogin(user, password, postDestination, sessionInputValueLogin); if (httpResult.getStatusCode() != HttpStatus.SC_OK) throw new WebSMSException(getStringResource(R.string.error_login_form_result) + httpResult.toString()); if (wasLoginOrPasswordWrong()) throw new WebSMSException(getStringResource(R.string.error_mail)); if (!isSmsPageLoaded()) throw new WebSMSException(getStringResource(R.string.error_login_sms_unknown)); } private final void sendSms(String phonenumber, final String message) { phonenumber = Utils.international2oldformat(phonenumber); if (message.length() > 160) throw new WebSMSException(getStringResource(R.string.error_length)); if (areSmsExiredForToday()) throw new WebSMSException(getStringResource(R.string.error_sms_expired)); final String sessionInputValueSms = extractSessionInputValue(); StatusLine httpResult; httpResult = performRawSendSms(phonenumber, message, sessionInputValueSms); if (httpResult.getStatusCode() != HttpStatus.SC_OK) throw new WebSMSException(getStringResource(R.string.error_sms_form_result) + httpResult.toString()); if (sendingFailed()) throw new WebSMSException(getStringResource(R.string.error_sending_sms_unknown)); } private final StatusLine performRawLogin(final String user, final String password, final String postDestinationUrl, final String sessionInputValueLogin) { final ArrayList<BasicNameValuePair> nameValuePairs = new ArrayList<BasicNameValuePair>(); nameValuePairs.add(new BasicNameValuePair("dnn$ctr$Login$Login_DNN$txtUsername", user)); nameValuePairs.add(new BasicNameValuePair("dnn$ctr$Login$Login_DNN$txtPassword", password)); nameValuePairs.add(new BasicNameValuePair("dnn$ctr$Login$Login_DNN$cmdLogin", "Login")); nameValuePairs.add(new BasicNameValuePair("__EVENTTARGET", "")); nameValuePairs.add(new BasicNameValuePair("__EVENTARGUMENT", "")); nameValuePairs.add(new BasicNameValuePair("__VIEWSTATE_CACHEKEY", sessionInputValueLogin)); nameValuePairs.add(new BasicNameValuePair("__VIEWSTATE", "")); return performHttpRequestForStatusLine(postDestinationUrl, nameValuePairs); } private final StatusLine performRawSendSms(final String phonenumber, final String message, final String sessionInputValueSms) { final ArrayList<BasicNameValuePair> nameValuePairs = new ArrayList<BasicNameValuePair>(); nameValuePairs.add(new BasicNameValuePair("dnn$ctr493$SfiSms_View$phone", phonenumber)); nameValuePairs.add(new BasicNameValuePair("dnn$ctr493$SfiSms_View$subject", message)); nameValuePairs.add(new BasicNameValuePair("dnn$ctr493$SfiSms_View$sendData", "Senden")); nameValuePairs.add(new BasicNameValuePair("__EVENTTARGET", "")); nameValuePairs.add(new BasicNameValuePair("__EVENTARGUMENT", "")); nameValuePairs.add(new BasicNameValuePair("__VIEWSTATE_CACHEKEY", sessionInputValueSms)); nameValuePairs.add(new BasicNameValuePair("__VIEWSTATE", "")); return performHttpRequestForStatusLine(SMS_PAGE_URL, nameValuePairs); } private final StatusLine performHttpRequestForStatusLine(final String url) { return performHttpRequestForStatusLine(url, null); } private final StatusLine performHttpRequestForStatusLine(final String url, final ArrayList<BasicNameValuePair> postData) { try { final HttpResponse response = performHttpRequestForStatusLineForResponse(url, postData); final HttpEntity entity = response.getEntity(); if (entity != null) { currentHtmlResultPage = EntityUtils.toString(entity, ENCODING); entity.consumeContent(); } return response.getStatusLine(); } catch (IOException e) { throw new WebSMSException(e.toString()); } } private final HttpResponse performHttpRequestForStatusLineForResponse(final String url, final ArrayList<BasicNameValuePair> postData) throws IOException { try { return performHttpRequestForStatusLineUtils(url, postData); } catch (IOException e) { //HACK: This fails regularly with "SSL shutdown failed: I/O error during system call, Broken pipe", // see https://issues.apache.org/jira/browse/HTTPCLIENT-951?focusedCommentId=12901563&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#action_12901563 // But in this case the second try usually works ... return performHttpRequestForStatusLineUtils(url, postData); } } private final HttpResponse performHttpRequestForStatusLineUtils(final String url, final ArrayList<BasicNameValuePair> postData) throws IOException { Utils.HttpOptions o = new Utils.HttpOptions(ENCODING); o.url = url; o.userAgent = USER_AGENT; o.referer = REFERER_URL; o.knownFingerprints = SSL_FINGERPRINTS; o.addFormParameter(postData); return Utils.getHttpClient(o); } private final String extractRelativePostUrl() { final Matcher matcher = postUrlExtractPattern.matcher(currentHtmlResultPage); if (matcher.matches()) return matcher.group(1); throw new WebSMSException(getStringResource(R.string.error_no_post_url_found)); } private final String extractSessionInputValue() { final Matcher matcher = sessionInputValueExtractPattern.matcher(currentHtmlResultPage); if (matcher.matches()) return matcher.group(1); throw new WebSMSException(getStringResource(R.string.error_no_session_input_value_found)); } private final boolean wasLoginOrPasswordWrong() { return currentHtmlResultPage.indexOf(WRONG_LOGIN_MESSAGE_SUBSTRING) > -1; } private final boolean areSmsExiredForToday() { return currentHtmlResultPage.indexOf(TODAYS_SMS_EXPIRED_SUBSTRING) > -1; } private final boolean sendingFailed() { return currentHtmlResultPage.indexOf(SEND_SUCCESS_SUBSTRING) == -1; } }