Java tutorial
/* * This file is part of Ermete SMS. * * Ermete SMS 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. * * Ermete SMS 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 Ermete SMS. If not, see <http://www.gnu.org/licenses/>. * */ package com.googlecode.esms.provider; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.Scanner; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.net.ssl.SSLException; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.message.BasicNameValuePair; import org.apache.http.protocol.HTTP; import com.googlecode.esms.account.Account; import com.googlecode.esms.account.AccountConnector; import com.googlecode.esms.message.Receiver; import com.googlecode.esms.message.SMS; public class Tim extends Account { private static final long serialVersionUID = 1L; static final String PROVIDER = "TIM"; static final String DO_LOGIN = "https://www.tim.it/authfe/login.do"; static final String CHECK_USER = "https://www.119selfservice.tim.it/area-clienti-119/privata/opzioni"; static final String DO_LOGOUT = "https://www.tim.it/authfe/logout.do"; static final String ADD_DISPATCH_NEW = "https://smsweb.tim.it/sms-web/adddispatch?start=new"; static final String ADD_DISPATCH_FORM = "https://smsweb.tim.it/sms-web/adddispatch.adddispatchform"; static final String VALIDATE_CAPTCHA_IMAGE = "https://smsweb.tim.it/sms-web/validatecaptcha:image/false?t:ac=Dispatch"; static final String VALIDATE_CAPTCHA_FORM = "https://smsweb.tim.it/sms-web/validatecaptcha.validatecaptchaform"; static final String RE_SENDER_LIST = "<option\\s+([^>]*\\s+)?value=\"[^0-9]*([0-9]{9,10})[^0-9]*\"\\s*([^>]*)?>"; static final Pattern PATTERN_LOGIN = Pattern.compile("(" + RE_SENDER_LIST + ")|(" + DO_LOGOUT + ")"); static final String RE_LIMIT_ERROR = "<div\\s+([^>]*\\s+)?class=\"errore\"\\s*([^>]*)?>([^<]*)<br/>"; static final String RE_GENERIC_ERROR = "<span\\s+([^>]*\\s+)?class=\"errore\"\\s*([^>]*)?>([^<]*)</span>"; static final String RE_MESSAGE_COUNT = "<img\\s+([^>]*\\s+)?alt=\"SMS inviati\"\\s*([^>]*)?>" + "<div\\s*([^>]*)?>\\s*([0-5])\\s*</div>"; static final String RE_FORM_DATA = "<input\\s+([^>]*\\s+)?name=\"t:formdata\"\\s+([^>]*\\s+)?" + "value=\"([^\"]*)\"\\s*([^>]*)?>"; static final String RE_RESULTS = "<span\\s*([^>]*)?>([0-9]{9,10})</span>" + "<span\\s*([^>]*)?>-</span>" + "<span\\s*([^>]*)?>([^<]*)?</span>"; static final String RE_BLOCKING_ERROR = "<div\\s+([^>]*\\s+)?class=\"t-error\"\\s*([^>]*)?>" + "<div>Attenzione:</div><ul><li>([^<]*)</li></ul></div>"; static final Pattern PATTERN_ADD_DISPATCH_NEW = Pattern.compile("(" + RE_LIMIT_ERROR + ")|(" + RE_GENERIC_ERROR + ")|" + "(" + RE_MESSAGE_COUNT + ")|(" + RE_FORM_DATA + ")"); static final Pattern PATTERN_ADD_DISPATCH_FORM = Pattern.compile("(" + RE_FORM_DATA + ")"); static final Pattern PATTERN_VALIDATE_CAPTCHA_FORM = Pattern .compile("(" + RE_GENERIC_ERROR + ")|(" + RE_BLOCKING_ERROR + ")|(" + RE_RESULTS + ")"); static final String LIMIT_ERROR = "Oggi hai raggiunto il numero massimo di SMS gratis"; static final String RECEIVER_ERROR = "I seguenti numeri non sono corretti"; static final String CAPTCHA_ERROR = "Le lettere che hai inserito non corrispondono " + "a quelle presenti nell'immagine"; boolean loggedIn; String senderCurrent; List<String> senderList; String formData, separateFreeNumbers; public Tim(AccountConnector connector) { super(connector); label = PROVIDER; provider = PROVIDER; limit = 5; loggedIn = false; senderList = new LinkedList<String>(); } @Override protected void initAccountConnector() { httpClient.getParams().setParameter("http.protocol.allow-circular-redirects", true); httpClient.getParams().setParameter("http.useragent", "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)"); } @Override public int calcRemaining(int length) { if (length == 0) { return 0; } else { return 640 - length; } } @Override public int calcFragments(int length) { if (length > 0 && length <= 640) { return 1; } else { return 0; } } @Override protected void updateCount() { Calendar prev = Calendar.getInstance(); prev.setTime(countDate); Calendar curr = Calendar.getInstance(); curr.setTime(new Date()); if (prev.get(Calendar.YEAR) < curr.get(Calendar.YEAR) || prev.get(Calendar.MONTH) < curr.get(Calendar.MONTH) || prev.get(Calendar.DATE) < curr.get(Calendar.DATE)) { count = 0; countDate = new Date(); } } @Override public Result login() { try { if (loggedIn) { if (senderList.contains(username)) return Result.SUCCESSFUL; else if (!doLogout()) return Result.LOGOUT_ERROR; } if (doLogin()) { return Result.SUCCESSFUL; } else { return Result.LOGIN_ERROR; } } catch (SSLException s) { s.printStackTrace(); return Result.PROVIDER_ERROR; } catch (Exception e) { e.printStackTrace(); return Result.NETWORK_ERROR; } } @Override public Result logout() { try { if (!loggedIn) { return Result.SUCCESSFUL; } if (doLogout()) { return Result.SUCCESSFUL; } else { return Result.LOGOUT_ERROR; } } catch (Exception e) { e.printStackTrace(); return Result.NETWORK_ERROR; } } @Override public List<String> getSenderList() { if (loggedIn) return senderList; else return null; } @Override public List<Result> send(SMS sms) { List<Result> results = new LinkedList<Result>(); for (int r = 0; r < sms.getReceivers().size(); ++r) results.add(Result.UNKNOWN_ERROR); try { Result login = login(); if (login != Result.SUCCESSFUL) { results.set(0, login); return results; } // TODO swap SIM here if (sms.getCaptchaText() == null || sms.getCaptchaText() == "") { Result addDispatchNew = addDispatchNew(); if (addDispatchNew == Result.LOGIN_ERROR) { Result relogin = login(); if (relogin != Result.SUCCESSFUL) { results.set(0, relogin); return results; } addDispatchNew = addDispatchNew(); } if (addDispatchNew != Result.SUCCESSFUL) { results.set(0, addDispatchNew); return results; } results.set(0, addDispatchForm(sms)); return results; } else { boolean validateCaptchaForm = validateCaptchaForm(sms, results); if (!validateCaptchaForm) return results; updateCount(); List<Receiver> sortedReceivers = new LinkedList<Receiver>(); for (int r = 0; r < sms.getReceivers().size(); ++r) if (results.get(r).equals(Result.SUCCESSFUL)) { sortedReceivers.add(sms.getReceivers().get(r)); ++count; } for (int r = 0; r < sms.getReceivers().size(); ++r) if (!results.get(r).equals(Result.SUCCESSFUL)) sortedReceivers.add(sms.getReceivers().get(r)); sms.setReceivers(sortedReceivers); } } catch (Exception e) { e.printStackTrace(); results.set(0, Result.NETWORK_ERROR); } return results; } private boolean doLogin() throws ClientProtocolException, IOException { HttpPost request = new HttpPost(DO_LOGIN); List<NameValuePair> requestData = new ArrayList<NameValuePair>(); requestData.add(new BasicNameValuePair("portale", "timPortale")); requestData.add(new BasicNameValuePair("urlOk", CHECK_USER)); requestData.add(new BasicNameValuePair("login", username)); requestData.add(new BasicNameValuePair("password", password)); request.setEntity(new UrlEncodedFormEntity(requestData, HTTP.UTF_8)); HttpResponse response = httpClient.execute(request, httpContext); response.getEntity().consumeContent(); return checkUser(); } private boolean checkUser() throws ClientProtocolException, IOException { HttpGet request = new HttpGet(CHECK_USER); HttpResponse response = httpClient.execute(request, httpContext); List<String> strings = findPattern(response.getEntity().getContent(), PATTERN_LOGIN); response.getEntity().consumeContent(); loggedIn = strings.contains(DO_LOGOUT); if (loggedIn) { strings.remove(DO_LOGOUT); Pattern number = Pattern.compile("([0-9]{9,10})"); for (String s : strings) { Matcher m = number.matcher(s); if (m.find()) { // senderList.add(m.group()); // TODO support other SIM if (s.contains("class=\"whoo\"")) { senderList.add(m.group()); senderCurrent = m.group(); break; } } } } return loggedIn; } private boolean doLogout() throws ClientProtocolException, IOException { // HttpGet request = new HttpGet(DO_LOGOUT); // HttpResponse response = httpClient.execute(request, httpContext); // response.getEntity().consumeContent(); cookieStore.clear(); return true; } private Result addDispatchNew() { try { HttpGet request = new HttpGet(ADD_DISPATCH_NEW); HttpResponse response = httpClient.execute(request, httpContext); List<String> strings = findPattern(response.getEntity().getContent(), PATTERN_ADD_DISPATCH_NEW); response.getEntity().consumeContent(); Pattern noMore = Pattern.compile(RE_LIMIT_ERROR); for (String s : strings) { Matcher m = noMore.matcher(s); if (m.find() && m.group(3).contains(LIMIT_ERROR)) return Result.LIMIT_ERROR; } Pattern error = Pattern.compile(RE_GENERIC_ERROR); for (String s : strings) { Matcher m = error.matcher(s); if (m.find()) return Result.LOGIN_ERROR; } Pattern counter = Pattern.compile(RE_MESSAGE_COUNT); for (String s : strings) { Matcher m = counter.matcher(s); if (m.find()) count = Integer.parseInt(m.group(4)); } if (count == limit) return Result.LIMIT_ERROR; formData = null; separateFreeNumbers = null; Pattern data = Pattern.compile(RE_FORM_DATA); for (String s : strings) { Matcher m = data.matcher(s); if (m.find()) { if (s.contains("id=")) { if (s.contains("seperateFreeNumbers:hidden")) separateFreeNumbers = m.group(3); } else { formData = m.group(3); } } } if (formData == null || separateFreeNumbers == null) return Result.PROVIDER_ERROR; return Result.SUCCESSFUL; } catch (Exception e) { e.printStackTrace(); return Result.NETWORK_ERROR; } } private Result addDispatchForm(SMS sms) { List<Receiver> receivers = sms.getReceivers(); String freeNumbers = stripPrefix(receivers.get(0).getNumber()); for (int r = 1; r < receivers.size(); ++r) freeNumbers += "," + stripPrefix(receivers.get(r).getNumber()); try { HttpPost request = new HttpPost(ADD_DISPATCH_FORM); List<NameValuePair> requestData = new ArrayList<NameValuePair>(); requestData.add(new BasicNameValuePair("t:formdata", formData)); requestData.add(new BasicNameValuePair("recipientType", "FREE_NUMBERS")); requestData.add(new BasicNameValuePair("t:formdata", separateFreeNumbers)); requestData.add(new BasicNameValuePair("freeNumbers", freeNumbers)); requestData.add(new BasicNameValuePair("t:formdata", "")); requestData.add(new BasicNameValuePair("contactListId", "")); requestData.add(new BasicNameValuePair("t:formdata", "")); requestData.add(new BasicNameValuePair("contactsIdString", "")); requestData.add(new BasicNameValuePair("deliverySmsClass", "STANDARD")); requestData.add(new BasicNameValuePair("textAreaStandard", sms.getMessage())); requestData.add(new BasicNameValuePair("textAreaFlash", "")); requestData.add(new BasicNameValuePair("modelsSelect", "")); request.setEntity(new UrlEncodedFormEntity(requestData, HTTP.UTF_8)); HttpResponse response = httpClient.execute(request, httpContext); List<String> strings = findPattern(response.getEntity().getContent(), PATTERN_ADD_DISPATCH_FORM); response.getEntity().consumeContent(); formData = null; Pattern data = Pattern.compile(RE_FORM_DATA); for (String s : strings) { Matcher m = data.matcher(s); if (m.find()) formData = m.group(3); } if (formData == null) return Result.PROVIDER_ERROR; Result validateCaptchaImage = validateCaptchaImage(sms); if (validateCaptchaImage == Result.SUCCESSFUL) validateCaptchaImage = Result.CAPTCHA_NEEDED; return validateCaptchaImage; } catch (Exception e) { e.printStackTrace(); return Result.NETWORK_ERROR; } } private Result validateCaptchaImage(SMS sms) throws ClientProtocolException, IOException { HttpGet request = new HttpGet(VALIDATE_CAPTCHA_IMAGE); HttpResponse response = httpClient.execute(request, httpContext); if (response.getStatusLine().getStatusCode() != 200) return Result.PROVIDER_ERROR; sms.setCaptchaText(""); sms.setCaptchaArray(toByteArray(response.getEntity().getContent())); response.getEntity().consumeContent(); return Result.SUCCESSFUL; } private boolean validateCaptchaForm(SMS sms, List<Result> results) { try { HttpPost request = new HttpPost(VALIDATE_CAPTCHA_FORM); List<NameValuePair> requestData = new ArrayList<NameValuePair>(); requestData.add(new BasicNameValuePair("t:ac", "Dispatch")); requestData.add(new BasicNameValuePair("t:formdata", formData)); requestData.add(new BasicNameValuePair("verificationCode", sms.getCaptchaText())); request.setEntity(new UrlEncodedFormEntity(requestData, HTTP.UTF_8)); HttpResponse response = httpClient.execute(request, httpContext); List<String> strings = findPattern(response.getEntity().getContent(), PATTERN_VALIDATE_CAPTCHA_FORM); response.getEntity().consumeContent(); Pattern error = Pattern.compile(RE_GENERIC_ERROR); for (String s : strings) { Matcher m = error.matcher(s); if (m.find()) { // Result.LIMIT_ERROR ? results.set(0, Result.UNKNOWN_ERROR); return false; } } Pattern captcha = Pattern.compile(RE_BLOCKING_ERROR); for (String s : strings) { Matcher m = captcha.matcher(s); if (m.find()) { String errorString = m.group(3); if (errorString.contains(RECEIVER_ERROR)) { results.set(0, Result.RECEIVER_ERROR); } else if (errorString.contains(CAPTCHA_ERROR)) { Result validateCaptchaImage = validateCaptchaImage(sms); if (validateCaptchaImage == Result.SUCCESSFUL) validateCaptchaImage = Result.CAPTCHA_ERROR; results.set(0, validateCaptchaImage); } else { results.set(0, Result.UNKNOWN_ERROR); } return false; } } boolean successful = false; Pattern completed = Pattern.compile(RE_RESULTS); for (String s : strings) { Matcher m = completed.matcher(s); if (m.find()) { successful = true; String number = m.group(2); for (int r = 0; r < sms.getReceivers().size(); ++r) { String receiver = sms.getReceivers().get(r).getNumber(); if (receiver.contains(number)) { String what = m.group(5); if (what.equals("SMS inviato")) { results.set(r, Result.SUCCESSFUL); } else if (what.contains("il numero non TIM")) { results.set(r, Result.RECEIVER_ERROR); } } } } } if (successful) return true; results.set(0, Result.UNKNOWN_ERROR); return false; } catch (Exception e) { e.printStackTrace(); results.set(0, Result.NETWORK_ERROR); return false; } } /** * Remove country code prefix, if present. * @param receiver Phone number with or without CC prefix. * @return Phone number without CC prefix. */ private String stripPrefix(String receiver) { final String PREFIX = "39"; receiver = receiver.replaceAll("[^0-9\\+]*", ""); int lNumber = receiver.length(); String pPlus = "+" + PREFIX; int lPlus = pPlus.length(); if (lNumber > lPlus && receiver.substring(0, lPlus).equals(pPlus)) { return receiver.substring(lPlus); } String pZero = "00" + PREFIX; int lZero = pZero.length(); if (lNumber > lZero && receiver.substring(0, lZero).equals(pZero)) { return receiver.substring(lZero); } return receiver; } /** * Search for a pattern inside a stream. * @param source The stream to search into. * @param pattern The pattern to match. * @return List of matches found. */ private List<String> findPattern(InputStream source, Pattern pattern) { List<String> results = new ArrayList<String>(); Scanner scanner = new Scanner(source, "UTF-8"); String match = ""; while (match != null) { match = scanner.findWithinHorizon(pattern, 0); if (match != null) results.add(match); } scanner.close(); return results; } /** * Perform stream to array conversion. * @param is Stream to be converted. * @return An array of bytes. */ private byte[] toByteArray(InputStream is) throws IOException { int read; byte[] data = new byte[16 * 1024]; // 16 kB ByteArrayOutputStream buffer = new ByteArrayOutputStream(); while ((read = is.read(data, 0, data.length)) != -1) buffer.write(data, 0, read); buffer.flush(); return buffer.toByteArray(); } }