cleanup
[andGMXsms.git] / connectors / o2 / src / de / ub0r / android / websms / connector / o2 / ConnectorO2.java
blob46c8192d4620f47f67ee1f98fe3b8a939cff457d
1 /*
2 * Copyright (C) 2010 Felix Bechstein
3 *
4 * This file is part of WebSMS.
5 *
6 * This program is free software; you can redistribute it and/or modify it under
7 * the terms of the GNU General Public License as published by the Free Software
8 * Foundation; either version 3 of the License, or (at your option) any later
9 * version.
11 * This program is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14 * details.
16 * You should have received a copy of the GNU General Public License along with
17 * this program; If not, see <http://www.gnu.org/licenses/>.
19 package de.ub0r.android.websms.connector.o2;
21 import java.io.IOException;
22 import java.net.HttpURLConnection;
23 import java.net.URISyntaxException;
24 import java.util.ArrayList;
25 import java.util.Calendar;
27 import org.apache.http.HttpResponse;
28 import org.apache.http.cookie.Cookie;
29 import org.apache.http.cookie.MalformedCookieException;
30 import org.apache.http.message.BasicNameValuePair;
32 import android.content.Context;
33 import android.content.Intent;
34 import android.content.SharedPreferences;
35 import android.graphics.drawable.BitmapDrawable;
36 import android.preference.PreferenceManager;
37 import android.text.format.DateFormat;
38 import android.util.Log;
39 import de.ub0r.android.websms.connector.common.Connector;
40 import de.ub0r.android.websms.connector.common.ConnectorCommand;
41 import de.ub0r.android.websms.connector.common.ConnectorSpec;
42 import de.ub0r.android.websms.connector.common.Utils;
43 import de.ub0r.android.websms.connector.common.WebSMSException;
44 import de.ub0r.android.websms.connector.common.ConnectorSpec.SubConnectorSpec;
46 /**
47 * AsyncTask to manage IO to O2 API.
49 * @author flx
51 public class ConnectorO2 extends Connector {
52 /** Tag for output. */
53 private static final String TAG = "WebSMS.o2";
55 /** Preferences intent action. */
56 private static final String PREFS_INTENT_ACTION = "de.ub0r.android."
57 + "websms.connectors.o2.PREFS";
59 /** Custom Dateformater. */
60 private static final String DATEFORMAT = "yyyy,MM,dd,kk,mm,00";
62 /** URL before login. */
63 private static final String URL_PRELOGIN = "https://login.o2online.de"
64 + "/loginRegistration/loginAction.do"
65 + "?_flowId=login&o2_type=asp&o2_label=login/"
66 + "comcenter-login&scheme=http&port=80&server=email"
67 + ".o2online.de&url=%2Fssomanager.osp%3FAPIID%3D"
68 + "AUTH-WEBSSO%26TargetApp%3D%2Fsmscenter_new.osp"
69 + "%253f%26o2_type" + "%3Durl%26o2_label%3Dweb2sms-o2online";
70 /** URL for login. */
71 private static final String URL_LOGIN = "https://login.o2online.de"
72 + "/loginRegistration/loginAction.do";
73 /** URL of captcha. */
74 private static final String URL_CAPTCHA = "https://login.o2online.de"
75 + "/loginRegistration/jcaptcha";
76 /** URL for solving captcha. */
77 private static final String URL_SOLVECAPTCHA = "https://login.o2online.de"
78 + "/loginRegistration" + "/loginAction.do";
79 /** URL for sms center. */
80 private static final String URL_SMSCENTER = "http://email.o2online.de:80"
81 + "/ssomanager.osp?APIID=AUTH-WEBSSO&TargetApp=/smscenter_new.osp"
82 + "?&o2_type=url&o2_label=web2sms-o2online";
83 /** URL before sending. */
84 private static final String URL_PRESEND = "https://email.o2online.de"
85 + "/smscenter_new.osp?Autocompletion=1&MsgContentID=-1";
86 /** URL for sending. */
87 private static final String URL_SEND = "https://email.o2online.de"
88 + "/smscenter_send.osp";
89 /** URL for sending later. */
90 private static final String URL_SCHEDULE = "https://email.o2online.de"
91 + "/smscenter_schedule.osp";
93 /** Check for free sms. */
94 private static final String CHECK_FREESMS = "Frei-SMS: ";
95 /** Check for web2sms. */
96 private static final String CHECK_WEB2SMS = "Web2SMS";
97 /** Check if message was sent. */
98 private static final String CHECK_SENT = // .
99 "Ihre SMS wurde erfolgreich versendet.";
100 // private static final String CHECK_SENT = "/app_pic/ico_mail_send_ok.gif";
101 /** Check if message was scheduled. */
102 private static final String CHECK_SCHED = "Ihre Web2SMS ist geplant.";
103 /** Check if captcha was solved wrong. */
104 private static final String CHECK_WRONGCAPTCHA = // .
105 "Sie haben einen falschen Code eingegeben.";
107 /** Stip bytes from stream: prelogin. */
108 private static final int STRIP_PRELOGIN_START = 8000;
109 /** Stip bytes from stream: prelogin. */
110 private static final int STRIP_PRELOGIN_END = 11000;
111 /** Stip bytes from stream: presend. */
112 private static final int STRIP_PRESEND_START = 58000;
113 /** Stip bytes from stream: presend. */
114 private static final int STRIP_PRESEND_END = 62000;
115 /** Stip bytes from stream: send. */
116 private static final int STRIP_SEND_START = 2000;
117 /** Stip bytes from stream: send. */
118 private static final int STRIP_SEND_END = 4000;
120 /** HTTP Useragent. */
121 private static final String TARGET_AGENT = "Mozilla/5.0 (Windows; U;"
122 + " Windows NT 5.1; de; rv:1.9.0.9) Gecko/2009040821"
123 + " Firefox/3.0.9 (.NET CLR 3.5.30729)";
125 /** Solved Captcha. */
126 static String captchaSolve = null;
127 /** Object to sync with. */
128 static final Object CAPTCHA_SYNC = new Object();
130 /** Static cookies. */
131 private static ArrayList<Cookie> staticCookies = new ArrayList<Cookie>();
134 * {@inheritDoc}
136 @Override
137 public final ConnectorSpec initSpec(final Context context) {
138 final String name = context.getString(R.string.connector_o2_name);
139 ConnectorSpec c = new ConnectorSpec(TAG, name);
140 c.setAuthor(context.getString(R.string.connector_o2_author));
141 c.setBalance(null);
142 c.setPrefsIntent(PREFS_INTENT_ACTION);
143 c.setPrefsTitle(context.getString(R.string.connector_o2_preferences));
144 c.setCapabilities(ConnectorSpec.CAPABILITIES_UPDATE
145 | ConnectorSpec.CAPABILITIES_SEND);
146 c.addSubConnector(c.getID(), c.getName(),
147 SubConnectorSpec.FEATURE_CUSTOMSENDER
148 | SubConnectorSpec.FEATURE_SENDLATER
149 | SubConnectorSpec.FEATURE_SENDLATER_QUARTERS
150 | SubConnectorSpec.FEATURE_FLASHSMS);
151 return c;
155 * {@inheritDoc}
157 @Override
158 public final ConnectorSpec updateSpec(final Context context,
159 final ConnectorSpec connectorSpec) {
160 final SharedPreferences p = PreferenceManager
161 .getDefaultSharedPreferences(context);
162 if (p.getBoolean(Preferences.PREFS_ENABLED, false)) {
163 if (p.getString(Preferences.PREFS_PASSWORD, "").length() > 0) {
164 connectorSpec.setReady();
165 } else {
166 connectorSpec.setStatus(ConnectorSpec.STATUS_ENABLED);
168 } else {
169 connectorSpec.setStatus(ConnectorSpec.STATUS_INACTIVE);
171 return connectorSpec;
175 * Extract _flowExecutionKey from HTML output.
177 * @param html
178 * input
179 * @return _flowExecutionKey
181 private static String getFlowExecutionkey(final String html) {
182 String ret = "";
183 int i = html.indexOf("name=\"_flowExecutionKey\" value=\"");
184 if (i > 0) {
185 int j = html.indexOf("\"", i + 35);
186 if (j >= 0) {
187 ret = html.substring(i + 32, j);
190 return ret;
194 * Load captcha and wait for user input to solve it.
196 * @param context
197 * {@link Context}
198 * @param cookies
199 * {@link Cookie}s
200 * @param flow
201 * _flowExecutionKey
202 * @return true if captcha was solved
203 * @throws IOException
204 * IOException
205 * @throws MalformedCookieException
206 * MalformedCookieException
207 * @throws URISyntaxException
208 * URISyntaxException
209 * @throws WebSMSException
210 * WebSMSException
212 private boolean solveCaptcha(final Context context,
213 final ArrayList<Cookie> cookies, final String flow)
214 throws IOException, MalformedCookieException, URISyntaxException,
215 WebSMSException {
216 HttpResponse response = Utils.getHttpClient(URL_CAPTCHA, cookies, null,
217 TARGET_AGENT, URL_LOGIN);
218 int resp = response.getStatusLine().getStatusCode();
219 if (resp != HttpURLConnection.HTTP_OK) {
220 throw new WebSMSException(context, R.string.error_http, "" + resp);
222 Utils.updateCookies(cookies, response.getAllHeaders(), URL_CAPTCHA);
223 BitmapDrawable captcha = new BitmapDrawable(response.getEntity()
224 .getContent());
225 final Intent intent = new Intent(Connector.ACTION_CAPTCHA_REQUEST);
226 intent.putExtra(Connector.EXTRA_CAPTCHA_DRAWABLE, captcha.getBitmap());
227 context.sendBroadcast(intent);
228 try {
229 synchronized (CAPTCHA_SYNC) {
230 CAPTCHA_SYNC.wait();
232 } catch (InterruptedException e) {
233 Log.e(TAG, null, e);
234 return false;
236 // got user response, try to solve captcha
237 captcha = null;
238 final ArrayList<BasicNameValuePair> postData = // .
239 new ArrayList<BasicNameValuePair>(3);
240 postData.add(new BasicNameValuePair("_flowExecutionKey", flow));
241 postData.add(new BasicNameValuePair("_eventId", "submit"));
242 postData.add(new BasicNameValuePair("riddleValue", captchaSolve));
243 response = Utils.getHttpClient(URL_SOLVECAPTCHA, cookies, postData,
244 TARGET_AGENT, URL_LOGIN);
245 resp = response.getStatusLine().getStatusCode();
246 if (resp != HttpURLConnection.HTTP_OK) {
247 throw new WebSMSException(context, R.string.error_http, "" + resp);
249 Utils.updateCookies(cookies, response.getAllHeaders(), URL_CAPTCHA);
250 final String mHtmlText = Utils.stream2str(response.getEntity()
251 .getContent());
252 if (mHtmlText.indexOf(CHECK_WRONGCAPTCHA) > 0) {
253 throw new WebSMSException(context, R.string.error_wrongcaptcha);
255 return true;
259 * Login to O2.
261 * @param context
262 * Context
263 * @param command
264 * ConnectorCommand
265 * @param cookies
266 * {@link Cookie}s
267 * @param flow
268 * _flowExecutionKey
269 * @return true if logged in
270 * @throws IOException
271 * IOException
272 * @throws MalformedCookieException
273 * MalformedCookieException
274 * @throws URISyntaxException
275 * URISyntaxException
276 * @throws WebSMSException
277 * WebSMSException
279 private boolean login(final Context context,
280 final ConnectorCommand command, final ArrayList<Cookie> cookies,
281 final String flow) throws IOException, MalformedCookieException,
282 URISyntaxException, WebSMSException {
283 // post data
284 final ArrayList<BasicNameValuePair> postData = // .
285 new ArrayList<BasicNameValuePair>(4);
286 postData.add(new BasicNameValuePair("_flowExecutionKey", flow));
287 postData.add(new BasicNameValuePair("loginName", Utils
288 .international2national(command.getDefPrefix(), command
289 .getDefSender())));
290 final SharedPreferences p = PreferenceManager
291 .getDefaultSharedPreferences(context);
292 postData.add(new BasicNameValuePair("password", p.getString(
293 Preferences.PREFS_PASSWORD, "")));
294 postData.add(new BasicNameValuePair("_eventId", "login"));
295 HttpResponse response = Utils.getHttpClient(URL_LOGIN, cookies,
296 postData, TARGET_AGENT, URL_PRELOGIN);
297 int resp = response.getStatusLine().getStatusCode();
298 if (resp != HttpURLConnection.HTTP_OK) {
299 throw new WebSMSException(context, R.string.error_http, "" + resp);
301 resp = cookies.size();
302 Utils.updateCookies(cookies, response.getAllHeaders(), URL_LOGIN);
303 if (resp == cookies.size()) {
304 String htmlText = Utils.stream2str(response.getEntity()
305 .getContent(), STRIP_PRELOGIN_START, STRIP_PRELOGIN_END);
306 response = null;
307 if (htmlText.indexOf("captcha") > 0) {
308 // final String newFlow = getFlowExecutionkey(htmlText);
309 htmlText = null;
310 // FIXME: !this.solveCaptcha(newFlow)) {
311 throw new WebSMSException("you have to solve a captcha,"
312 + "\nplease contact the developer");
313 // }
314 } else {
315 throw new WebSMSException(context, R.string.error_pw);
318 return true;
322 * Format values from calendar to minimum 2 digits.
324 * @param cal
325 * calendar
326 * @param f
327 * field
328 * @return value as string
330 private static String getTwoDigitsFromCal(final Calendar cal, final int f) {
331 int r = cal.get(f);
332 if (f == Calendar.MONTH) {
333 ++r;
335 if (r < 10) {
336 return "0" + r;
337 } else {
338 return "" + r;
343 * Send SMS.
345 * @param context
346 * {@link Context}
347 * @param command
348 * {@link ConnectorCommand}
349 * @param cookies
350 * {@link Cookie}s
351 * @param htmlText
352 * html source of previous site
353 * @throws IOException
354 * IOException
355 * @throws MalformedCookieException
356 * MalformedCookieException
357 * @throws URISyntaxException
358 * URISyntaxException
359 * @throws WebSMSException
360 * WebSMSException
362 private void sendToO2(final Context context,
363 final ConnectorCommand command, final ArrayList<Cookie> cookies,
364 final String htmlText) throws IOException,
365 MalformedCookieException, URISyntaxException, WebSMSException {
366 ArrayList<BasicNameValuePair> postData = // .
367 new ArrayList<BasicNameValuePair>();
368 postData.add(new BasicNameValuePair("SMSTo", Utils
369 .national2international(command.getDefPrefix(), Utils
370 .getRecipientsNumber(command.getRecipients()[0]))));
371 postData.add(new BasicNameValuePair("SMSText", command.getText()));
372 final String customSender = command.getCustomSender();
373 if (customSender != null) {
374 postData.add(new BasicNameValuePair("SMSFrom", customSender));
375 if (customSender.length() == 0) {
376 postData.add(new BasicNameValuePair("FlagAnonymous", "1"));
377 } else {
378 postData.add(new BasicNameValuePair("FlagAnonymous", "0"));
379 postData.add(new BasicNameValuePair("FlagDefSender", "1"));
381 postData.add(new BasicNameValuePair("FlagDefSender", "0"));
382 } else {
383 postData.add(new BasicNameValuePair("SMSFrom", ""));
384 postData.add(new BasicNameValuePair("FlagDefSender", "1"));
386 postData.add(new BasicNameValuePair("Frequency", "5"));
387 if (command.getFlashSMS()) {
388 postData.add(new BasicNameValuePair("FlagFlash", "1"));
389 } else {
390 postData.add(new BasicNameValuePair("FlagFlash", "0"));
392 String url = URL_SEND;
393 final long sendLater = command.getSendLater();
394 if (sendLater > 0) {
395 url = URL_SCHEDULE;
396 final Calendar cal = Calendar.getInstance();
397 cal.setTimeInMillis(sendLater);
398 postData.add(new BasicNameValuePair("StartDateDay",
399 getTwoDigitsFromCal(cal, Calendar.DAY_OF_MONTH)));
400 postData.add(new BasicNameValuePair("StartDateMonth",
401 getTwoDigitsFromCal(cal, Calendar.MONTH)));
402 postData.add(new BasicNameValuePair("StartDateYear",
403 getTwoDigitsFromCal(cal, Calendar.YEAR)));
404 postData.add(new BasicNameValuePair("StartDateHour",
405 getTwoDigitsFromCal(cal, Calendar.HOUR_OF_DAY)));
406 postData.add(new BasicNameValuePair("StartDateMin",
407 getTwoDigitsFromCal(cal, Calendar.MINUTE)));
408 postData.add(new BasicNameValuePair("EndDateDay",
409 getTwoDigitsFromCal(cal, Calendar.DAY_OF_MONTH)));
410 postData.add(new BasicNameValuePair("EndDateMonth",
411 getTwoDigitsFromCal(cal, Calendar.MONTH)));
412 postData.add(new BasicNameValuePair("EndDateYear",
413 getTwoDigitsFromCal(cal, Calendar.YEAR)));
414 postData.add(new BasicNameValuePair("EndDateHour",
415 getTwoDigitsFromCal(cal, Calendar.HOUR_OF_DAY)));
416 postData.add(new BasicNameValuePair("EndDateMin",
417 getTwoDigitsFromCal(cal, Calendar.MINUTE)));
418 final String s = DateFormat.format(DATEFORMAT, cal).toString();
419 postData.add(new BasicNameValuePair("RepeatStartDate", s));
420 postData.add(new BasicNameValuePair("RepeatEndDate", s));
421 postData.add(new BasicNameValuePair("RepeatType", "5"));
422 postData.add(new BasicNameValuePair("RepeatEndType", "0"));
424 String[] st = htmlText.split("<input type=\"Hidden\" ");
425 for (String s : st) {
426 if (s.startsWith("name=")) {
427 String[] subst = s.split("\"", 5);
428 if (subst.length >= 4) {
429 if (sendLater > 0 && subst[1].startsWith("Repeat")) {
430 continue;
432 postData.add(new BasicNameValuePair(subst[1], subst[3]));
436 st = null;
438 HttpResponse response = Utils.getHttpClient(url, cookies, postData,
439 TARGET_AGENT, URL_PRESEND);
440 postData = null;
441 int resp = response.getStatusLine().getStatusCode();
442 if (resp != HttpURLConnection.HTTP_OK) {
443 throw new WebSMSException(context, R.string.error_http, "" + resp);
445 final String htmlText1 = Utils.stream2str(response.getEntity()
446 .getContent(), STRIP_SEND_START, STRIP_SEND_END);
447 String check = CHECK_SENT;
448 if (sendLater > 0) {
449 check = CHECK_SCHED;
451 if (htmlText1.indexOf(check) < 0) {
452 // check output html for success message
453 Log.d(TAG, htmlText1);
454 throw new WebSMSException(context, R.string.error);
459 * Send data.
461 * @param context
462 * {@link Context}
463 * @param command
464 * {@link ConnectorCommand}
465 * @param reuseSession
466 * try to reuse existing session
467 * @throws WebSMSException
468 * WebSMSException
470 @SuppressWarnings("unchecked")
471 private void sendData(final Context context,
472 final ConnectorCommand command, final boolean reuseSession)
473 throws WebSMSException {
474 Log.d(TAG, "sendData(" + reuseSession + ")");
475 ArrayList<Cookie> cookies;
476 if (staticCookies == null) {
477 cookies = new ArrayList<Cookie>();
478 } else {
479 cookies = (ArrayList<Cookie>) staticCookies.clone();
481 // do IO
482 try {
483 // get Connection
484 HttpResponse response;
485 int resp;
486 if (!reuseSession || cookies.size() == 0) {
487 // clear session data
488 cookies.clear();
489 Log.d(TAG, "init session");
490 // pre-login
491 response = Utils.getHttpClient(URL_PRELOGIN, cookies, null,
492 TARGET_AGENT, null);
493 resp = response.getStatusLine().getStatusCode();
494 if (resp != HttpURLConnection.HTTP_OK) {
495 throw new WebSMSException(context, R.string.error_http, ""
496 + resp);
498 Utils.updateCookies(cookies, response.getAllHeaders(),
499 URL_PRELOGIN);
500 String htmlText = Utils
501 .stream2str(response.getEntity().getContent(),
502 STRIP_PRELOGIN_START, STRIP_PRELOGIN_END);
503 final String flowExecutionKey = ConnectorO2
504 .getFlowExecutionkey(htmlText);
505 htmlText = null;
507 // login
508 if (!this.login(context, command, cookies, flowExecutionKey)) {
509 throw new WebSMSException(context, R.string.error);
512 // sms-center
513 response = Utils.getHttpClient(URL_SMSCENTER, cookies, null,
514 TARGET_AGENT, URL_LOGIN);
515 resp = response.getStatusLine().getStatusCode();
516 if (resp != HttpURLConnection.HTTP_OK) {
517 if (reuseSession) {
518 // try again with clear session
519 this.sendData(context, command, false);
520 return;
522 throw new WebSMSException(context, R.string.error_http, ""
523 + resp);
525 Utils.updateCookies(cookies, response.getAllHeaders(),
526 URL_SMSCENTER);
529 // pre-send
530 response = Utils.getHttpClient(URL_PRESEND, cookies, null,
531 TARGET_AGENT, URL_SMSCENTER);
532 resp = response.getStatusLine().getStatusCode();
533 if (resp != HttpURLConnection.HTTP_OK) {
534 if (reuseSession) {
535 // try again with clear session
536 this.sendData(context, command, false);
537 return;
539 throw new WebSMSException(context, R.string.error_http, ""
540 + resp);
542 Utils.updateCookies(cookies, response.getAllHeaders(), URL_PRESEND);
543 String htmlText = Utils.stream2str(response.getEntity()
544 .getContent(), STRIP_PRESEND_START, STRIP_PRESEND_END);
545 int i = htmlText.indexOf(CHECK_FREESMS);
546 if (i > 0) {
547 int j = htmlText.indexOf(CHECK_WEB2SMS, i);
548 if (j > 0) {
549 this.getSpec(context)
550 .setBalance(
551 htmlText.substring(i + 9, j).trim().split(
552 " ", 2)[0]);
553 } else if (reuseSession) {
554 // try again with clear session
555 this.sendData(context, command, false);
556 return;
560 // send
561 final String text = command.getText();
562 if (text != null && text.length() > 0) {
563 this.sendToO2(context, command, cookies, htmlText);
565 htmlText = null;
566 } catch (IOException e) {
567 Log.e(TAG, null, e);
568 throw new WebSMSException(e.toString());
569 } catch (URISyntaxException e) {
570 Log.e(TAG, null, e);
571 throw new WebSMSException(e.toString());
572 } catch (MalformedCookieException e) {
573 Log.e(TAG, null, e);
574 throw new WebSMSException(e.toString());
576 staticCookies = cookies;
580 * {@inheritDoc}
582 @Override
583 protected final void doUpdate(final Context context, final Intent intent)
584 throws WebSMSException {
585 this.sendData(context, new ConnectorCommand(intent), true);
589 * {@inheritDoc}
591 @Override
592 protected final void doSend(final Context context, final Intent intent)
593 throws WebSMSException {
594 this.sendData(context, new ConnectorCommand(intent), true);
598 * {@inheritDoc}
600 protected final void gotSolvedCaptcha(final Context context,
601 final String solvedCaptcha) {
602 captchaSolve = solvedCaptcha;
603 CAPTCHA_SYNC.notify();