de.ub0r.android.websms.connector.o2.ConnectorO2.java Source code

Java tutorial

Introduction

Here is the source code for de.ub0r.android.websms.connector.o2.ConnectorO2.java

Source

/*
 * Copyright (C) 2010 Felix Bechstein
 * 
 * This file is part of WebSMS.
 * 
 * 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 de.ub0r.android.websms.connector.o2;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.Calendar;

import org.apache.http.HttpResponse;
import org.apache.http.message.BasicNameValuePair;

import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.drawable.BitmapDrawable;
import android.preference.PreferenceManager;
import android.text.format.DateFormat;
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.Log;
import de.ub0r.android.websms.connector.common.Utils;
import de.ub0r.android.websms.connector.common.WebSMSException;
import de.ub0r.android.websms.connector.common.ConnectorSpec.SubConnectorSpec;

/**
 * AsyncTask to manage IO to O2 API.
 * 
 * @author flx
 */
public class ConnectorO2 extends Connector {
    /** Tag for output. */
    private static final String TAG = "o2";

    /** Google's ad unit id. */
    private static final String AD_UNITID = "a14dd555326b466";

    /** Used encoding. */
    private static final String ENCODING = "ISO-8859-15";

    /** Custom Dateformater. */
    private static final String DATEFORMAT = "yyyy,MM,dd,kk,mm,00";

    /** URL before login. */
    private static final String URL_PRELOGIN = "https://login.o2online.de" + "/loginRegistration/loginAction.do"
            + "?_flowId=login&o2_type=asp&o2_label=login/" + "comcenter-login&scheme=http&port=80&server=email"
            + ".o2online.de&url=%2Fssomanager.osp%3FAPIID%3D" + "AUTH-WEBSSO%26TargetApp%3D%2Fsmscenter_new.osp"
            + "%253f%26o2_type%3Durl%26o2_label%3Dweb2sms-o2online";
    /** URL for login. */
    private static final String URL_LOGIN = "https://login.o2online.de" + "/loginRegistration/loginAction.do";
    /** URL of captcha. */
    private static final String URL_CAPTCHA = "https://login.o2online.de" + "/loginRegistration/jcaptchaReg";
    /** URL for solving captcha. */
    private static final String URL_SOLVECAPTCHA = URL_LOGIN;
    /** URL for sms center. */
    private static final String URL_SMSCENTER = "http://email.o2online.de:80"
            + "/ssomanager.osp?APIID=AUTH-WEBSSO&TargetApp=/smscenter_new.osp"
            + "?&o2_type=url&o2_label=web2sms-o2online";
    /** URL before sending. */
    private static final String URL_PRESEND = "https://email.o2online.de"
            + "/smscenter_new.osp?Autocompletion=1&MsgContentID=-1";
    /** URL for sending. */
    private static final String URL_SEND = "https://email.o2online.de" + "/smscenter_send.osp";
    /** URL for sending later. */
    private static final String URL_SCHEDULE = "https://email.o2online.de" + "/smscenter_schedule.osp";

    /** Check for free sms. */
    private static final String CHECK_FREESMS = "Frei-SMS: ";
    /** Check for web2sms. */
    private static final String CHECK_WEB2SMS = "Web2SMS";
    /** Check if message was sent. */
    private static final String CHECK_SENT = // .
            "Ihre SMS wurde erfolgreich versendet.";
    // private static final String CHECK_SENT = "/app_pic/ico_mail_send_ok.gif";
    /** Check if message was scheduled. */
    private static final String CHECK_SCHED = "Ihre Web2SMS ist geplant";
    /** Check if captcha was solved wrong. */
    private static final String CHECK_WRONGCAPTCHA = // .
            "Sie haben einen falschen Code eingegeben.";
    /** Check for _flowExecutionKey. */
    private static final String CHECK_FLOW = // .
            "name=\"_flowExecutionKey\" value=\"";

    /** Stip bytes from stream: prelogin. */
    private static final int STRIP_PRELOGIN_START = 8000;
    /** Stip bytes from stream: prelogin. */
    private static final int STRIP_PRELOGIN_END = 11000;
    /** Stip bytes from stream: presend. */
    private static final int STRIP_PRESEND_START = 56000;
    /** Stip bytes from stream: presend. */
    private static final int STRIP_PRESEND_END = 62000;
    /** Stip bytes from stream: send. */
    private static final int STRIP_SEND_START = 2000;

    /** HTTP Useragent. */
    private static final String TARGET_AGENT = "Mozilla/5.0 (Windows; U;"
            + " Windows NT 5.1; de; rv:1.9.0.9) Gecko/2009040821" + " Firefox/3.0.9 (.NET CLR 3.5.30729)";

    /** Solved Captcha. */
    private static String captchaSolve = null;
    /** Object to sync with. */
    private static final Object CAPTCHA_SYNC = new Object();
    /** Timeout for entering the captcha. */
    private static final long CAPTCHA_TIMEOUT = 60000;

    /**
     * The current fingerprints of the SSL-certificate used by the https-sites.
     */
    private static final String[] O2_SSL_FINGERPRINTS = //
            { "2c:b4:86:a8:da:87:77:3f:e4:b2:9d:26:6e:11:9e:00:3d:db:85:55",
                    "a6:da:fd:d3:da:4e:29:95:3d:b3:cd:69:49:8f:d1:e7:0e:e5:fa:c7",
                    "b0:36:f6:fd:0b:6f:28:75:ca:3b:5d:4a:91:07:ce:db:d0:0d:71:b0" };

    /**
     * {@inheritDoc}
     */
    @Override
    public final ConnectorSpec initSpec(final Context context) {
        final String name = context.getString(R.string.connector_o2_name);
        ConnectorSpec c = new ConnectorSpec(name);
        c.setAuthor(context.getString(R.string.connector_o2_author));
        c.setAdUnitId(AD_UNITID);
        c.setBalance(null);
        c.setCapabilities(ConnectorSpec.CAPABILITIES_UPDATE | ConnectorSpec.CAPABILITIES_SEND
                | ConnectorSpec.CAPABILITIES_PREFS);
        c.addSubConnector(TAG, c.getName(),
                SubConnectorSpec.FEATURE_CUSTOMSENDER | SubConnectorSpec.FEATURE_SENDLATER
                        | SubConnectorSpec.FEATURE_SENDLATER_QUARTERS | SubConnectorSpec.FEATURE_FLASHSMS);
        return c;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final ConnectorSpec updateSpec(final Context context, final ConnectorSpec connectorSpec) {
        final SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(context);
        if (p.getBoolean(Preferences.PREFS_ENABLED, false)) {
            if (p.getString(Preferences.PREFS_PASSWORD, "").length() > 0) {
                connectorSpec.setReady();
            } else {
                connectorSpec.setStatus(ConnectorSpec.STATUS_ENABLED);
            }
        } else {
            connectorSpec.setStatus(ConnectorSpec.STATUS_INACTIVE);
        }
        return connectorSpec;
    }

    /**
     * Extract _flowExecutionKey from HTML output.
     * 
     * @param html
     *            input
     * @return _flowExecutionKey
     */
    private static String getFlowExecutionkey(final String html) {
        String ret = "";
        int i = html.indexOf(CHECK_FLOW);
        if (i > 0) {
            int j = html.indexOf("\"", i + 35);
            if (j >= 0) {
                ret = html.substring(i + 32, j);
            }
        }
        return ret;
    }

    /**
     * Load captcha and wait for user input to solve it.
     * 
     * @param context
     *            {@link Context}
     * @param flow
     *            _flowExecutionKey
     * @return true if captcha was solved
     * @throws IOException
     *             IOException
     */
    private boolean solveCaptcha(final Context context, final String flow) throws IOException {
        HttpResponse response = Utils.getHttpClient(URL_CAPTCHA, null, null, TARGET_AGENT, URL_LOGIN, ENCODING,
                O2_SSL_FINGERPRINTS);
        int resp = response.getStatusLine().getStatusCode();
        if (resp != HttpURLConnection.HTTP_OK) {
            throw new WebSMSException(context, R.string.error_http, "" + resp);
        }
        BitmapDrawable captcha = new BitmapDrawable(response.getEntity().getContent());
        final Intent intent = new Intent(Connector.ACTION_CAPTCHA_REQUEST);
        intent.putExtra(Connector.EXTRA_CAPTCHA_DRAWABLE, captcha.getBitmap());
        captcha = null;
        this.getSpec(context).setToIntent(intent);
        context.sendBroadcast(intent);
        try {
            synchronized (CAPTCHA_SYNC) {
                CAPTCHA_SYNC.wait(CAPTCHA_TIMEOUT);
            }
        } catch (InterruptedException e) {
            Log.e(TAG, null, e);
            return false;
        }
        if (captchaSolve == null) {
            return false;
        }
        // got user response, try to solve captcha
        Log.d(TAG, "got solved captcha: " + captchaSolve);
        final ArrayList<BasicNameValuePair> postData = // .
                new ArrayList<BasicNameValuePair>(3);
        postData.add(new BasicNameValuePair("_flowExecutionKey", flow));
        postData.add(new BasicNameValuePair("_eventId", "submit"));
        postData.add(new BasicNameValuePair("riddleValue", captchaSolve));
        response = Utils.getHttpClient(URL_SOLVECAPTCHA, null, postData, TARGET_AGENT, URL_LOGIN, ENCODING,
                O2_SSL_FINGERPRINTS);
        Log.d(TAG, postData.toString());
        resp = response.getStatusLine().getStatusCode();
        if (resp != HttpURLConnection.HTTP_OK) {
            throw new WebSMSException(context, R.string.error_http, "" + resp);
        }
        final String mHtmlText = Utils.stream2str(response.getEntity().getContent());
        if (mHtmlText.indexOf(CHECK_WRONGCAPTCHA) > 0) {
            throw new WebSMSException(context, R.string.error_wrongcaptcha);
        }
        return true;
    }

    /**
     * Login to O2.
     * 
     * @param context
     *            Context
     * @param command
     *            ConnectorCommand
     * @param flow
     *            _flowExecutionKey
     * @return true if logged in
     * @throws IOException
     *             IOException
     */
    private boolean login(final Context context, final ConnectorCommand command, final String flow)
            throws IOException {
        // post data
        final ArrayList<BasicNameValuePair> postData = // .
                new ArrayList<BasicNameValuePair>(4);
        postData.add(new BasicNameValuePair("_flowExecutionKey", flow));
        postData.add(new BasicNameValuePair("loginName", Utils.international2national(command.getDefPrefix(),
                Utils.getSenderNumber(context, command.getDefSender()))));
        final SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(context);
        postData.add(new BasicNameValuePair("password", p.getString(Preferences.PREFS_PASSWORD, "")));
        postData.add(new BasicNameValuePair("_eventId", "login"));
        int ccount = Utils.getCookieCount();
        HttpResponse response = Utils.getHttpClient(URL_LOGIN, null, postData, TARGET_AGENT, URL_PRELOGIN, ENCODING,
                O2_SSL_FINGERPRINTS);
        int resp = response.getStatusLine().getStatusCode();
        if (resp != HttpURLConnection.HTTP_OK) {
            throw new WebSMSException(context, R.string.error_http, "" + resp);
        }
        if (ccount == Utils.getCookieCount()) {
            Log.i(TAG, "cooie count: " + ccount);
            Log.d(TAG, Utils.getCookiesAsString());
            String htmlText = null;
            if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean(Preferences.PREFS_TWEAK, false)) {
                htmlText = Utils.stream2str(response.getEntity().getContent(), STRIP_PRELOGIN_START,
                        STRIP_PRELOGIN_END);
            } else {
                htmlText = Utils.stream2str(response.getEntity().getContent());
            }
            response = null;
            if (htmlText != null && htmlText.indexOf("captcha") > 0) {
                final String newFlow = getFlowExecutionkey(htmlText);
                htmlText = null;
                if (!this.solveCaptcha(context, newFlow)) {
                    throw new WebSMSException(context, R.string.error_wrongcaptcha);
                }
            } else {
                Log.d(TAG, htmlText);
                throw new WebSMSException(context, R.string.error_pw);
            }
        }
        return true;
    }

    /**
     * Format values from calendar to minimum 2 digits.
     * 
     * @param cal
     *            calendar
     * @param f
     *            field
     * @return value as string
     */
    private static String getTwoDigitsFromCal(final Calendar cal, final int f) {
        int r = cal.get(f);
        if (f == Calendar.MONTH) {
            ++r;
        }
        if (r < 10) {
            return "0" + r;
        } else {
            return "" + r;
        }
    }

    /**
     * Send SMS.
     * 
     * @param context
     *            {@link Context}
     * @param command
     *            {@link ConnectorCommand}
     * @param htmlText
     *            html source of previous site
     * @throws IOException
     *             IOException
     */
    private void sendToO2(final Context context, final ConnectorCommand command, final String htmlText)
            throws IOException {
        ArrayList<BasicNameValuePair> postData = // .
                new ArrayList<BasicNameValuePair>();
        postData.add(new BasicNameValuePair("SMSTo", Utils.national2international(command.getDefPrefix(),
                Utils.getRecipientsNumber(command.getRecipients()[0]))));
        postData.add(new BasicNameValuePair("SMSText", CharacterTable.encodeString(command.getText())));
        String customSender = command.getCustomSender();
        if (customSender == null) {
            final String sn = Utils.getSenderNumber(context, command.getDefSender());
            final String s = Utils.getSender(context, command.getDefSender());
            if (s != null && !s.equals(sn)) {
                customSender = s;
            }
        }
        if (customSender != null) {
            postData.add(new BasicNameValuePair("SMSFrom", customSender));
            if (customSender.length() == 0) {
                postData.add(new BasicNameValuePair("FlagAnonymous", "1"));
            } else {
                postData.add(new BasicNameValuePair("FlagAnonymous", "0"));
                postData.add(new BasicNameValuePair("FlagDefSender", "1"));
            }
            postData.add(new BasicNameValuePair("FlagDefSender", "0"));
        } else {
            postData.add(new BasicNameValuePair("SMSFrom", ""));
            postData.add(new BasicNameValuePair("FlagDefSender", "1"));
        }
        postData.add(new BasicNameValuePair("Frequency", "5"));
        if (command.getFlashSMS()) {
            postData.add(new BasicNameValuePair("FlagFlash", "1"));
        } else {
            postData.add(new BasicNameValuePair("FlagFlash", "0"));
        }
        String url = URL_SEND;
        final long sendLater = command.getSendLater();
        if (sendLater > 0) {
            url = URL_SCHEDULE;
            final Calendar cal = Calendar.getInstance();
            cal.setTimeInMillis(sendLater);
            postData.add(new BasicNameValuePair("StartDateDay", getTwoDigitsFromCal(cal, Calendar.DAY_OF_MONTH)));
            postData.add(new BasicNameValuePair("StartDateMonth", getTwoDigitsFromCal(cal, Calendar.MONTH)));
            postData.add(new BasicNameValuePair("StartDateYear", getTwoDigitsFromCal(cal, Calendar.YEAR)));
            postData.add(new BasicNameValuePair("StartDateHour", getTwoDigitsFromCal(cal, Calendar.HOUR_OF_DAY)));
            postData.add(new BasicNameValuePair("StartDateMin", getTwoDigitsFromCal(cal, Calendar.MINUTE)));
            postData.add(new BasicNameValuePair("EndDateDay", getTwoDigitsFromCal(cal, Calendar.DAY_OF_MONTH)));
            postData.add(new BasicNameValuePair("EndDateMonth", getTwoDigitsFromCal(cal, Calendar.MONTH)));
            postData.add(new BasicNameValuePair("EndDateYear", getTwoDigitsFromCal(cal, Calendar.YEAR)));
            postData.add(new BasicNameValuePair("EndDateHour", getTwoDigitsFromCal(cal, Calendar.HOUR_OF_DAY)));
            postData.add(new BasicNameValuePair("EndDateMin", getTwoDigitsFromCal(cal, Calendar.MINUTE)));
            final String s = DateFormat.format(DATEFORMAT, cal).toString();
            postData.add(new BasicNameValuePair("RepeatStartDate", s));
            postData.add(new BasicNameValuePair("RepeatEndDate", s));
            postData.add(new BasicNameValuePair("RepeatType", "5"));
            postData.add(new BasicNameValuePair("RepeatEndType", "0"));
        }
        String[] st = htmlText.split("<input type=\"Hidden\" ");
        for (String s : st) {
            if (s.startsWith("name=")) {
                String[] subst = s.split("\"", 5);
                if (subst.length >= 4) {
                    if (sendLater > 0 && subst[1].startsWith("Repeat")) {
                        continue;
                    }
                    postData.add(new BasicNameValuePair(subst[1], subst[3]));
                }
            }
        }
        st = null;

        HttpResponse response = Utils.getHttpClient(url, null, postData, TARGET_AGENT, URL_PRESEND, ENCODING,
                O2_SSL_FINGERPRINTS);
        postData = null;
        int resp = response.getStatusLine().getStatusCode();
        if (resp != HttpURLConnection.HTTP_OK) {
            throw new WebSMSException(context, R.string.error_http, "" + resp);
        }
        String check = CHECK_SENT;
        if (sendLater > 0) {
            check = CHECK_SCHED;
        }
        String htmlText1 = null;
        if (sendLater <= 0 && PreferenceManager.getDefaultSharedPreferences(context)
                .getBoolean(Preferences.PREFS_TWEAK, false)) {
            htmlText1 = Utils.stream2str(response.getEntity().getContent(), STRIP_SEND_START,
                    Utils.ONLY_MATCHING_LINE, check);
        } else {
            htmlText1 = Utils.stream2str(response.getEntity().getContent(), 0, Utils.ONLY_MATCHING_LINE, check);
        }
        if (htmlText1 == null) {
            throw new WebSMSException("error parsing website");
        } else if (htmlText1.indexOf(check) < 0) {
            // check output html for success message
            Log.w(TAG, htmlText1);
            throw new WebSMSException("error parsing website");
        }
    }

    /**
     * Send data.
     * 
     * @param context
     *            {@link Context}
     * @param command
     *            {@link ConnectorCommand}
     * @param reuseSession
     *            try to reuse existing session
     * @throws IOException
     *             IOException
     */
    private void sendData(final Context context, final ConnectorCommand command, final boolean reuseSession)
            throws IOException {
        Log.d(TAG, "sendData(" + reuseSession + ")");

        // get Connection
        HttpResponse response;
        int resp;
        if (!reuseSession) {
            // clear session data
            Utils.clearCookies();
        }
        if (Utils.getCookieCount() == 0) {
            Log.d(TAG, "init session");
            // pre-login
            response = Utils.getHttpClient(URL_PRELOGIN, null, null, TARGET_AGENT, null, ENCODING,
                    O2_SSL_FINGERPRINTS);
            resp = response.getStatusLine().getStatusCode();
            if (resp != HttpURLConnection.HTTP_OK) {
                throw new WebSMSException(context, R.string.error_http, "" + resp);
            }
            String htmlText = Utils.stream2str(response.getEntity().getContent(), 0, Utils.ONLY_MATCHING_LINE,
                    CHECK_FLOW);
            final String flowExecutionKey = ConnectorO2.getFlowExecutionkey(htmlText);
            htmlText = null;

            // login
            if (!this.login(context, command, flowExecutionKey)) {
                throw new WebSMSException(context, R.string.error);
            }

            // sms-center
            response = Utils.getHttpClient(URL_SMSCENTER, null, null, TARGET_AGENT, URL_LOGIN, ENCODING,
                    O2_SSL_FINGERPRINTS);
            resp = response.getStatusLine().getStatusCode();
            if (resp != HttpURLConnection.HTTP_OK) {
                if (reuseSession) {
                    // try again with clear session
                    this.sendData(context, command, false);
                    return;
                }
                throw new WebSMSException(context, R.string.error_http, "" + resp);
            }
        }

        // pre-send
        response = Utils.getHttpClient(URL_PRESEND, null, null, TARGET_AGENT, URL_SMSCENTER, ENCODING,
                O2_SSL_FINGERPRINTS);
        resp = response.getStatusLine().getStatusCode();
        if (resp != HttpURLConnection.HTTP_OK) {
            if (reuseSession) {
                // try again with clear session
                this.sendData(context, command, false);
                return;
            }
            throw new WebSMSException(context, R.string.error_http, "" + resp);
        }
        String htmlText = null;
        if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean(Preferences.PREFS_TWEAK, false)) {
            htmlText = Utils.stream2str(response.getEntity().getContent(), STRIP_PRESEND_START, STRIP_PRESEND_END,
                    CHECK_FREESMS);
        } else {
            htmlText = Utils.stream2str(response.getEntity().getContent(), 0, -1, CHECK_FREESMS);
        }
        if (htmlText == null) {
            if (reuseSession) {
                this.sendData(context, command, false);
                return;
            } else {
                throw new WebSMSException(context, // .
                        R.string.missing_freesms);
            }
        }
        int i = htmlText.indexOf(CHECK_FREESMS);
        if (i > 0) {
            int j = htmlText.indexOf(CHECK_WEB2SMS, i);
            if (j > 0) {
                ConnectorSpec c = this.getSpec(context);
                c.setBalance(htmlText.substring(i + 9, j).trim().split(" ", 2)[0]);
                Log.d(TAG, "balance: " + c.getBalance());
            } else if (reuseSession) {
                // try again with clear session
                this.sendData(context, command, false);
                return;
            } else {
                Log.d(TAG, htmlText);
                throw new WebSMSException(context, // .
                        R.string.missing_freesms);
            }
        } else {
            Log.d(TAG, htmlText);
            throw new WebSMSException(context, R.string.missing_freesms);
        }

        // send
        final String text = command.getText();
        if (text != null && text.length() > 0) {
            this.sendToO2(context, command, htmlText);
        }
        htmlText = null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected final void doUpdate(final Context context, final Intent intent) throws IOException {
        Utils.showUpdateNotification(context, "com.websms.connector.o2");
        this.sendData(context, new ConnectorCommand(intent), true);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected final void doSend(final Context context, final Intent intent) throws IOException {
        Utils.showUpdateNotification(context, "com.websms.connector.o2");
        this.sendData(context, new ConnectorCommand(intent), true);
    }

    /**
     * {@inheritDoc}
     */
    protected final void gotSolvedCaptcha(final Context context, final String solvedCaptcha) {
        captchaSolve = solvedCaptcha;
        synchronized (CAPTCHA_SYNC) {
            CAPTCHA_SYNC.notify();
        }
    }
}