PageRenderTime 28ms CodeModel.GetById 27ms RepoModel.GetById 1ms app.codeStats 0ms

/main/src/cgeo/geocaching/connector/gc/Login.java

https://github.com/MichielK/c-geo-opensource
Java | 468 lines | 340 code | 77 blank | 51 comment | 69 complexity | caa6262954c2a30ff86ad2d64ab4e5b7 MD5 | raw file
Possible License(s): Apache-2.0
  1. package cgeo.geocaching.connector.gc;
  2. import cgeo.geocaching.R;
  3. import cgeo.geocaching.Settings;
  4. import cgeo.geocaching.cgeoapplication;
  5. import cgeo.geocaching.enumerations.StatusCode;
  6. import cgeo.geocaching.network.Cookies;
  7. import cgeo.geocaching.network.HtmlImage;
  8. import cgeo.geocaching.network.Network;
  9. import cgeo.geocaching.network.Parameters;
  10. import cgeo.geocaching.utils.BaseUtils;
  11. import cgeo.geocaching.utils.Log;
  12. import cgeo.geocaching.utils.MatcherWrapper;
  13. import ch.boye.httpclientandroidlib.HttpResponse;
  14. import org.apache.commons.lang3.ArrayUtils;
  15. import org.apache.commons.lang3.StringUtils;
  16. import org.apache.commons.lang3.tuple.ImmutablePair;
  17. import android.graphics.drawable.BitmapDrawable;
  18. import java.text.ParseException;
  19. import java.text.SimpleDateFormat;
  20. import java.util.Collections;
  21. import java.util.Date;
  22. import java.util.HashMap;
  23. import java.util.Locale;
  24. import java.util.Map;
  25. public abstract class Login {
  26. private final static String ENGLISH = "<a href=\"#\">English&#9660;</a>";
  27. // false = not logged in
  28. private static boolean actualLoginStatus = false;
  29. private static String actualUserName = "";
  30. private static int actualCachesFound = -1;
  31. private static String actualStatus = "";
  32. private final static Map<String, SimpleDateFormat> gcCustomDateFormats;
  33. public static final String LANGUAGE_CHANGE_URI = "http://www.geocaching.com/my/souvenirs.aspx";
  34. static {
  35. final String[] formats = new String[] {
  36. "MM/dd/yyyy",
  37. "yyyy-MM-dd",
  38. "yyyy/MM/dd",
  39. "dd/MMM/yyyy",
  40. "MMM/dd/yyyy",
  41. "dd MMM yy",
  42. "dd/MM/yyyy"
  43. };
  44. Map<String, SimpleDateFormat> map = new HashMap<String, SimpleDateFormat>();
  45. for (String format : formats) {
  46. map.put(format, new SimpleDateFormat(format, Locale.ENGLISH));
  47. }
  48. gcCustomDateFormats = Collections.unmodifiableMap(map);
  49. }
  50. public static StatusCode login() {
  51. return login(true);
  52. }
  53. private static StatusCode login(boolean retry) {
  54. final ImmutablePair<String, String> login = Settings.getLogin();
  55. if (login == null || StringUtils.isEmpty(login.left) || StringUtils.isEmpty(login.right)) {
  56. Login.setActualStatus(cgeoapplication.getInstance().getString(R.string.err_login));
  57. Log.e("Login.login: No login information stored");
  58. return StatusCode.NO_LOGIN_INFO_STORED;
  59. }
  60. Login.setActualStatus(cgeoapplication.getInstance().getString(R.string.init_login_popup_working));
  61. HttpResponse loginResponse = Network.getRequest("https://www.geocaching.com/login/default.aspx");
  62. String loginData = Network.getResponseData(loginResponse);
  63. if (loginResponse != null && loginResponse.getStatusLine().getStatusCode() == 503 && BaseUtils.matches(loginData, GCConstants.PATTERN_MAINTENANCE)) {
  64. return StatusCode.MAINTENANCE;
  65. }
  66. if (StringUtils.isBlank(loginData)) {
  67. Log.e("Login.login: Failed to retrieve login page (1st)");
  68. return StatusCode.CONNECTION_FAILED; // no loginpage
  69. }
  70. if (Login.getLoginStatus(loginData)) {
  71. Log.i("Already logged in Geocaching.com as " + login.left + " (" + Settings.getMemberStatus() + ')');
  72. Login.switchToEnglish(loginData);
  73. return StatusCode.NO_ERROR; // logged in
  74. }
  75. Cookies.clearCookies();
  76. Settings.setCookieStore(null);
  77. final Parameters params = new Parameters(
  78. "__EVENTTARGET", "",
  79. "__EVENTARGUMENT", "",
  80. "ctl00$ContentBody$tbUsername", login.left,
  81. "ctl00$ContentBody$tbPassword", login.right,
  82. "ctl00$ContentBody$cbRememberMe", "on",
  83. "ctl00$ContentBody$btnSignIn", "Login");
  84. final String[] viewstates = Login.getViewstates(loginData);
  85. if (isEmpty(viewstates)) {
  86. Log.e("Login.login: Failed to find viewstates");
  87. return StatusCode.LOGIN_PARSE_ERROR; // no viewstates
  88. }
  89. Login.putViewstates(params, viewstates);
  90. loginResponse = Network.postRequest("https://www.geocaching.com/login/default.aspx", params);
  91. loginData = Network.getResponseData(loginResponse);
  92. if (StringUtils.isBlank(loginData)) {
  93. Log.e("Login.login: Failed to retrieve login page (2nd)");
  94. // FIXME: should it be CONNECTION_FAILED to match the first attempt?
  95. return StatusCode.COMMUNICATION_ERROR; // no login page
  96. }
  97. if (Login.getLoginStatus(loginData)) {
  98. Log.i("Successfully logged in Geocaching.com as " + login.left + " (" + Settings.getMemberStatus() + ')');
  99. Login.switchToEnglish(loginData);
  100. Settings.setCookieStore(Cookies.dumpCookieStore());
  101. return StatusCode.NO_ERROR; // logged in
  102. }
  103. if (loginData.contains("Your username/password combination does not match.")) {
  104. Log.i("Failed to log in Geocaching.com as " + login.left + " because of wrong username/password");
  105. return StatusCode.WRONG_LOGIN_DATA; // wrong login
  106. }
  107. if (loginData.contains("You must validate your account before you can log in.")) {
  108. Log.i("Failed to log in Geocaching.com as " + login.left + " because account needs to be validated first");
  109. return StatusCode.UNVALIDATED_ACCOUNT;
  110. }
  111. Log.i("Failed to log in Geocaching.com as " + login.left + " for some unknown reason");
  112. if (retry) {
  113. Login.switchToEnglish(loginData);
  114. return login(false);
  115. }
  116. return StatusCode.UNKNOWN_ERROR; // can't login
  117. }
  118. public static StatusCode logout() {
  119. HttpResponse logoutResponse = Network.getRequest("https://www.geocaching.com/login/default.aspx?RESET=Y&redir=http%3a%2f%2fwww.geocaching.com%2fdefault.aspx%3f");
  120. String logoutData = Network.getResponseData(logoutResponse);
  121. if (logoutResponse != null && logoutResponse.getStatusLine().getStatusCode() == 503 && BaseUtils.matches(logoutData, GCConstants.PATTERN_MAINTENANCE)) {
  122. return StatusCode.MAINTENANCE;
  123. }
  124. Cookies.clearCookies();
  125. Settings.setCookieStore(null);
  126. return StatusCode.NO_ERROR;
  127. }
  128. static void setActualCachesFound(final int found) {
  129. actualCachesFound = found;
  130. }
  131. public static String getActualStatus() {
  132. return actualStatus;
  133. }
  134. private static void setActualStatus(final String status) {
  135. actualStatus = status;
  136. }
  137. public static boolean isActualLoginStatus() {
  138. return actualLoginStatus;
  139. }
  140. private static void setActualLoginStatus(boolean loginStatus) {
  141. actualLoginStatus = loginStatus;
  142. }
  143. public static String getActualUserName() {
  144. return actualUserName;
  145. }
  146. private static void setActualUserName(String userName) {
  147. actualUserName = userName;
  148. }
  149. public static int getActualCachesFound() {
  150. return actualCachesFound;
  151. }
  152. /**
  153. * Check if the user has been logged in when he retrieved the data.
  154. *
  155. * @param page
  156. * @return <code>true</code> if user is logged in, <code>false</code> otherwise
  157. */
  158. public static boolean getLoginStatus(final String page) {
  159. if (StringUtils.isBlank(page)) {
  160. Log.e("Login.checkLogin: No page given");
  161. return false;
  162. }
  163. setActualStatus(cgeoapplication.getInstance().getString(R.string.init_login_popup_ok));
  164. // on every page except login page
  165. setActualLoginStatus(BaseUtils.matches(page, GCConstants.PATTERN_LOGIN_NAME));
  166. if (isActualLoginStatus()) {
  167. setActualUserName(BaseUtils.getMatch(page, GCConstants.PATTERN_LOGIN_NAME, true, "???"));
  168. setActualCachesFound(Integer.parseInt(BaseUtils.getMatch(page, GCConstants.PATTERN_CACHES_FOUND, true, "0").replaceAll("[,.]", "")));
  169. Settings.setMemberStatus(BaseUtils.getMatch(page, GCConstants.PATTERN_MEMBER_STATUS, true, null));
  170. if ( page.contains(GCConstants.MEMBER_STATUS_RENEW) ) {
  171. Settings.setMemberStatus(GCConstants.MEMBER_STATUS_PM);
  172. }
  173. return true;
  174. }
  175. // login page
  176. setActualLoginStatus(BaseUtils.matches(page, GCConstants.PATTERN_LOGIN_NAME_LOGIN_PAGE));
  177. if (isActualLoginStatus()) {
  178. setActualUserName(Settings.getUsername());
  179. // number of caches found is not part of this page
  180. return true;
  181. }
  182. setActualStatus(cgeoapplication.getInstance().getString(R.string.init_login_popup_failed));
  183. return false;
  184. }
  185. private static void switchToEnglish(String previousPage) {
  186. if (previousPage != null && previousPage.contains(ENGLISH)) {
  187. Log.i("Geocaching.com language already set to English");
  188. // get find count
  189. getLoginStatus(Network.getResponseData(Network.getRequest("http://www.geocaching.com/email/")));
  190. } else {
  191. final String page = Network.getResponseData(Network.getRequest(LANGUAGE_CHANGE_URI));
  192. getLoginStatus(page);
  193. if (page == null) {
  194. Log.e("Failed to read viewstates to set geocaching.com language");
  195. }
  196. final Parameters params = new Parameters(
  197. "__EVENTTARGET", "ctl00$uxLocaleList$uxLocaleList$ctl00$uxLocaleItem", // switch to english
  198. "__EVENTARGUMENT", "");
  199. Login.transferViewstates(page, params);
  200. final HttpResponse response = Network.postRequest(LANGUAGE_CHANGE_URI, params, new Parameters("Referer", LANGUAGE_CHANGE_URI));
  201. if (Network.isSuccess(response)) {
  202. Log.i("changed language on geocaching.com to English");
  203. } else {
  204. Log.e("Failed to set geocaching.com language to English");
  205. }
  206. }
  207. }
  208. public static BitmapDrawable downloadAvatarAndGetMemberStatus() {
  209. try {
  210. final String profile = BaseUtils.replaceWhitespace(Network.getResponseData(Network.getRequest("http://www.geocaching.com/my/")));
  211. Settings.setMemberStatus(BaseUtils.getMatch(profile, GCConstants.PATTERN_MEMBER_STATUS, true, null));
  212. if (profile.contains(GCConstants.MEMBER_STATUS_RENEW)) {
  213. Settings.setMemberStatus(GCConstants.MEMBER_STATUS_PM);
  214. }
  215. setActualCachesFound(Integer.parseInt(BaseUtils.getMatch(profile, GCConstants.PATTERN_CACHES_FOUND, true, "-1").replaceAll("[,.]", "")));
  216. final String avatarURL = BaseUtils.getMatch(profile, GCConstants.PATTERN_AVATAR_IMAGE_PROFILE_PAGE, false, null);
  217. if (null != avatarURL) {
  218. final HtmlImage imgGetter = new HtmlImage("", false, 0, false);
  219. return imgGetter.getDrawable(avatarURL);
  220. }
  221. // No match? There may be no avatar set by user.
  222. Log.d("No avatar set for user");
  223. } catch (Exception e) {
  224. Log.w("Error when retrieving user avatar", e);
  225. }
  226. return null;
  227. }
  228. /**
  229. * Detect user date settings on geocaching.com
  230. */
  231. public static void detectGcCustomDate() {
  232. final String result = Network.getResponseData(Network.getRequest("http://www.geocaching.com/account/ManagePreferences.aspx"));
  233. if (null == result) {
  234. Log.w("Login.detectGcCustomDate: result is null");
  235. return;
  236. }
  237. String customDate = BaseUtils.getMatch(result, GCConstants.PATTERN_CUSTOMDATE, true, null);
  238. if (null != customDate) {
  239. Settings.setGcCustomDate(customDate);
  240. }
  241. }
  242. public static Date parseGcCustomDate(final String input, final String format) throws ParseException {
  243. if (StringUtils.isBlank(input)) {
  244. throw new ParseException("Input is null", 0);
  245. }
  246. final String trimmed = input.trim();
  247. if (gcCustomDateFormats.containsKey(format)) {
  248. try {
  249. return gcCustomDateFormats.get(format).parse(trimmed);
  250. } catch (ParseException e) {
  251. }
  252. }
  253. for (SimpleDateFormat sdf : gcCustomDateFormats.values()) {
  254. try {
  255. return sdf.parse(trimmed);
  256. } catch (ParseException e) {
  257. }
  258. }
  259. throw new ParseException("No matching pattern", 0);
  260. }
  261. public static Date parseGcCustomDate(final String input) throws ParseException {
  262. return parseGcCustomDate(input, Settings.getGcCustomDate());
  263. }
  264. /**
  265. * checks if an Array of Strings is empty or not. Empty means:
  266. * - Array is null
  267. * - or all elements are null or empty strings
  268. */
  269. public static boolean isEmpty(String[] a) {
  270. if (a == null) {
  271. return true;
  272. }
  273. for (String s : a) {
  274. if (StringUtils.isNotEmpty(s)) {
  275. return false;
  276. }
  277. }
  278. return true;
  279. }
  280. /**
  281. * read all viewstates from page
  282. *
  283. * @return String[] with all view states
  284. */
  285. public static String[] getViewstates(String page) {
  286. // Get the number of viewstates.
  287. // If there is only one viewstate, __VIEWSTATEFIELDCOUNT is not present
  288. if (page == null) { // no network access
  289. return null;
  290. }
  291. int count = 1;
  292. final MatcherWrapper matcherViewstateCount = new MatcherWrapper(GCConstants.PATTERN_VIEWSTATEFIELDCOUNT, page);
  293. if (matcherViewstateCount.find()) {
  294. try {
  295. count = Integer.parseInt(matcherViewstateCount.group(1));
  296. } catch (NumberFormatException e) {
  297. Log.e("getViewStates", e);
  298. }
  299. }
  300. String[] viewstates = new String[count];
  301. // Get the viewstates
  302. final MatcherWrapper matcherViewstates = new MatcherWrapper(GCConstants.PATTERN_VIEWSTATES, page);
  303. while (matcherViewstates.find()) {
  304. String sno = matcherViewstates.group(1); // number of viewstate
  305. int no;
  306. if (StringUtils.isEmpty(sno)) {
  307. no = 0;
  308. } else {
  309. try {
  310. no = Integer.parseInt(sno);
  311. } catch (NumberFormatException e) {
  312. Log.e("getViewStates", e);
  313. no = 0;
  314. }
  315. }
  316. viewstates[no] = matcherViewstates.group(2);
  317. }
  318. if (viewstates.length != 1 || viewstates[0] != null) {
  319. return viewstates;
  320. }
  321. // no viewstates were present
  322. return null;
  323. }
  324. /**
  325. * put viewstates into request parameters
  326. */
  327. public static void putViewstates(final Parameters params, final String[] viewstates) {
  328. if (ArrayUtils.isEmpty(viewstates)) {
  329. return;
  330. }
  331. params.put("__VIEWSTATE", viewstates[0]);
  332. if (viewstates.length > 1) {
  333. for (int i = 1; i < viewstates.length; i++) {
  334. params.put("__VIEWSTATE" + i, viewstates[i]);
  335. }
  336. params.put("__VIEWSTATEFIELDCOUNT", String.valueOf(viewstates.length));
  337. }
  338. }
  339. /**
  340. * transfers the viewstates variables from a page (response) to parameters
  341. * (next request)
  342. */
  343. public static void transferViewstates(final String page, final Parameters params) {
  344. putViewstates(params, getViewstates(page));
  345. }
  346. /**
  347. * POST HTTP request. Do the request a second time if the user is not logged in
  348. *
  349. * @param uri
  350. * @return
  351. */
  352. public static String postRequestLogged(final String uri, final Parameters params) {
  353. HttpResponse response = Network.postRequest(uri, params);
  354. String data = Network.getResponseData(response);
  355. if (!getLoginStatus(data)) {
  356. if (login() == StatusCode.NO_ERROR) {
  357. response = Network.postRequest(uri, params);
  358. data = Network.getResponseData(response);
  359. } else {
  360. Log.i("Working as guest.");
  361. }
  362. }
  363. return data;
  364. }
  365. /**
  366. * GET HTTP request. Do the request a second time if the user is not logged in
  367. *
  368. * @param uri
  369. * @param params
  370. * @return
  371. */
  372. public static String getRequestLogged(final String uri, final Parameters params) {
  373. final String data = Network.getResponseData(Network.getRequest(uri, params));
  374. if (getLoginStatus(data)) {
  375. return data;
  376. }
  377. if (login() == StatusCode.NO_ERROR) {
  378. return Network.getResponseData(Network.getRequest(uri, params));
  379. }
  380. Log.w("Working as guest.");
  381. return data;
  382. }
  383. /** Get user session & session token from the Live Map. Needed for following requests */
  384. public static String[] getMapTokens() {
  385. final HttpResponse response = Network.getRequest(GCConstants.URL_LIVE_MAP);
  386. final String data = Network.getResponseData(response);
  387. final String userSession = BaseUtils.getMatch(data, GCConstants.PATTERN_USERSESSION, "");
  388. final String sessionToken = BaseUtils.getMatch(data, GCConstants.PATTERN_SESSIONTOKEN, "");
  389. return new String[] { userSession, sessionToken };
  390. }
  391. }