2 * Copyright (C) 2010 Felix Bechstein
4 * This file is part of WebSMS.
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
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
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
;
47 * AsyncTask to manage IO to O2 API.
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";
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
>();
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
));
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
);
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();
166 connectorSpec
.setStatus(ConnectorSpec
.STATUS_ENABLED
);
169 connectorSpec
.setStatus(ConnectorSpec
.STATUS_INACTIVE
);
171 return connectorSpec
;
175 * Extract _flowExecutionKey from HTML output.
179 * @return _flowExecutionKey
181 private static String
getFlowExecutionkey(final String html
) {
183 int i
= html
.indexOf("name=\"_flowExecutionKey\" value=\"");
185 int j
= html
.indexOf("\"", i
+ 35);
187 ret
= html
.substring(i
+ 32, j
);
194 * Load captcha and wait for user input to solve it.
202 * @return true if captcha was solved
203 * @throws IOException
205 * @throws MalformedCookieException
206 * MalformedCookieException
207 * @throws URISyntaxException
209 * @throws WebSMSException
212 private boolean solveCaptcha(final Context context
,
213 final ArrayList
<Cookie
> cookies
, final String flow
)
214 throws IOException
, MalformedCookieException
, URISyntaxException
,
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()
225 final Intent intent
= new Intent(Connector
.ACTION_CAPTCHA_REQUEST
);
226 intent
.putExtra(Connector
.EXTRA_CAPTCHA_DRAWABLE
, captcha
.getBitmap());
227 context
.sendBroadcast(intent
);
229 synchronized (CAPTCHA_SYNC
) {
232 } catch (InterruptedException e
) {
236 // got user response, try to solve captcha
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()
252 if (mHtmlText
.indexOf(CHECK_WRONGCAPTCHA
) > 0) {
253 throw new WebSMSException(context
, R
.string
.error_wrongcaptcha
);
269 * @return true if logged in
270 * @throws IOException
272 * @throws MalformedCookieException
273 * MalformedCookieException
274 * @throws URISyntaxException
276 * @throws 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
{
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
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
);
307 if (htmlText
.indexOf("captcha") > 0) {
308 // final String newFlow = getFlowExecutionkey(htmlText);
310 // FIXME: !this.solveCaptcha(newFlow)) {
311 throw new WebSMSException("you have to solve a captcha,"
312 + "\nplease contact the developer");
315 throw new WebSMSException(context
, R
.string
.error_pw
);
322 * Format values from calendar to minimum 2 digits.
328 * @return value as string
330 private static String
getTwoDigitsFromCal(final Calendar cal
, final int f
) {
332 if (f
== Calendar
.MONTH
) {
348 * {@link ConnectorCommand}
352 * html source of previous site
353 * @throws IOException
355 * @throws MalformedCookieException
356 * MalformedCookieException
357 * @throws URISyntaxException
359 * @throws 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"));
378 postData
.add(new BasicNameValuePair("FlagAnonymous", "0"));
379 postData
.add(new BasicNameValuePair("FlagDefSender", "1"));
381 postData
.add(new BasicNameValuePair("FlagDefSender", "0"));
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"));
390 postData
.add(new BasicNameValuePair("FlagFlash", "0"));
392 String url
= URL_SEND
;
393 final long sendLater
= command
.getSendLater();
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")) {
432 postData
.add(new BasicNameValuePair(subst
[1], subst
[3]));
438 HttpResponse response
= Utils
.getHttpClient(url
, cookies
, postData
,
439 TARGET_AGENT
, URL_PRESEND
);
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
;
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
);
464 * {@link ConnectorCommand}
465 * @param reuseSession
466 * try to reuse existing session
467 * @throws 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
>();
479 cookies
= (ArrayList
<Cookie
>) staticCookies
.clone();
484 HttpResponse response
;
486 if (!reuseSession
|| cookies
.size() == 0) {
487 // clear session data
489 Log
.d(TAG
, "init session");
491 response
= Utils
.getHttpClient(URL_PRELOGIN
, cookies
, null,
493 resp
= response
.getStatusLine().getStatusCode();
494 if (resp
!= HttpURLConnection
.HTTP_OK
) {
495 throw new WebSMSException(context
, R
.string
.error_http
, ""
498 Utils
.updateCookies(cookies
, response
.getAllHeaders(),
500 String htmlText
= Utils
501 .stream2str(response
.getEntity().getContent(),
502 STRIP_PRELOGIN_START
, STRIP_PRELOGIN_END
);
503 final String flowExecutionKey
= ConnectorO2
504 .getFlowExecutionkey(htmlText
);
508 if (!this.login(context
, command
, cookies
, flowExecutionKey
)) {
509 throw new WebSMSException(context
, R
.string
.error
);
513 response
= Utils
.getHttpClient(URL_SMSCENTER
, cookies
, null,
514 TARGET_AGENT
, URL_LOGIN
);
515 resp
= response
.getStatusLine().getStatusCode();
516 if (resp
!= HttpURLConnection
.HTTP_OK
) {
518 // try again with clear session
519 this.sendData(context
, command
, false);
522 throw new WebSMSException(context
, R
.string
.error_http
, ""
525 Utils
.updateCookies(cookies
, response
.getAllHeaders(),
530 response
= Utils
.getHttpClient(URL_PRESEND
, cookies
, null,
531 TARGET_AGENT
, URL_SMSCENTER
);
532 resp
= response
.getStatusLine().getStatusCode();
533 if (resp
!= HttpURLConnection
.HTTP_OK
) {
535 // try again with clear session
536 this.sendData(context
, command
, false);
539 throw new WebSMSException(context
, R
.string
.error_http
, ""
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
);
547 int j
= htmlText
.indexOf(CHECK_WEB2SMS
, i
);
549 this.getSpec(context
)
551 htmlText
.substring(i
+ 9, j
).trim().split(
553 } else if (reuseSession
) {
554 // try again with clear session
555 this.sendData(context
, command
, false);
561 final String text
= command
.getText();
562 if (text
!= null && text
.length() > 0) {
563 this.sendToO2(context
, command
, cookies
, htmlText
);
566 } catch (IOException e
) {
568 throw new WebSMSException(e
.toString());
569 } catch (URISyntaxException e
) {
571 throw new WebSMSException(e
.toString());
572 } catch (MalformedCookieException e
) {
574 throw new WebSMSException(e
.toString());
576 staticCookies
= cookies
;
583 protected final void doUpdate(final Context context
, final Intent intent
)
584 throws WebSMSException
{
585 this.sendData(context
, new ConnectorCommand(intent
), true);
592 protected final void doSend(final Context context
, final Intent intent
)
593 throws WebSMSException
{
594 this.sendData(context
, new ConnectorCommand(intent
), true);
600 protected final void gotSolvedCaptcha(final Context context
,
601 final String solvedCaptcha
) {
602 captchaSolve
= solvedCaptcha
;
603 CAPTCHA_SYNC
.notify();