aboutsummaryrefslogtreecommitdiffstats
path: root/main/src/cgeo/geocaching/connector/gc/GCLogin.java
diff options
context:
space:
mode:
authorBananeweizen <bananeweizen@gmx.de>2013-12-16 15:09:26 +0100
committerBananeweizen <bananeweizen@gmx.de>2013-12-16 15:09:26 +0100
commit3719de7a754c103a3e841ec68ba8af31eda30d76 (patch)
tree0f3d0d2b5fae4d821f3bf8643b0fa7e970703176 /main/src/cgeo/geocaching/connector/gc/GCLogin.java
parent755c21382c08953db22dc7ed958f72eff6760632 (diff)
downloadcgeo-3719de7a754c103a3e841ec68ba8af31eda30d76.zip
cgeo-3719de7a754c103a3e841ec68ba8af31eda30d76.tar.gz
cgeo-3719de7a754c103a3e841ec68ba8af31eda30d76.tar.bz2
rename Login to GCLogin
* easier to differentiate, as there are other login providers now
Diffstat (limited to 'main/src/cgeo/geocaching/connector/gc/GCLogin.java')
-rw-r--r--main/src/cgeo/geocaching/connector/gc/GCLogin.java517
1 files changed, 517 insertions, 0 deletions
diff --git a/main/src/cgeo/geocaching/connector/gc/GCLogin.java b/main/src/cgeo/geocaching/connector/gc/GCLogin.java
new file mode 100644
index 0000000..53be668
--- /dev/null
+++ b/main/src/cgeo/geocaching/connector/gc/GCLogin.java
@@ -0,0 +1,517 @@
+package cgeo.geocaching.connector.gc;
+
+import cgeo.geocaching.CgeoApplication;
+import cgeo.geocaching.R;
+import cgeo.geocaching.enumerations.StatusCode;
+import cgeo.geocaching.network.Cookies;
+import cgeo.geocaching.network.HtmlImage;
+import cgeo.geocaching.network.Network;
+import cgeo.geocaching.network.Parameters;
+import cgeo.geocaching.settings.Settings;
+import cgeo.geocaching.utils.Log;
+import cgeo.geocaching.utils.MatcherWrapper;
+import cgeo.geocaching.utils.TextUtils;
+
+import ch.boye.httpclientandroidlib.HttpResponse;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+
+import android.graphics.drawable.BitmapDrawable;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+public abstract class GCLogin {
+
+ private static final String DEFAULT_CUSTOM_DATE_FORMAT = "MM/dd/yyyy";
+
+ private final static String ENGLISH = "<a href=\"#\">English&#9660;</a>";
+
+ // false = not logged in
+ private static boolean actualLoginStatus = false;
+ private static String actualUserName = StringUtils.EMPTY;
+ private static int actualCachesFound = -1;
+ private static String actualStatus = StringUtils.EMPTY;
+
+ private final static Map<String, SimpleDateFormat> GC_CUSTOM_DATE_FORMATS;
+ public static final String LANGUAGE_CHANGE_URI = "http://www.geocaching.com/my/souvenirs.aspx";
+
+ static {
+ final String[] formats = new String[] {
+ DEFAULT_CUSTOM_DATE_FORMAT,
+ "yyyy-MM-dd",
+ "yyyy/MM/dd",
+ "dd/MMM/yyyy",
+ "MMM/dd/yyyy",
+ "dd MMM yy",
+ "dd/MM/yyyy"
+ };
+
+ final Map<String, SimpleDateFormat> map = new HashMap<String, SimpleDateFormat>();
+
+ for (final String format : formats) {
+ map.put(format, new SimpleDateFormat(format, Locale.ENGLISH));
+ }
+
+ GC_CUSTOM_DATE_FORMATS = Collections.unmodifiableMap(map);
+ }
+
+ public static StatusCode login() {
+ return login(true);
+ }
+
+ private static StatusCode login(boolean retry) {
+ final ImmutablePair<String, String> login = Settings.getGcLogin();
+
+ if (StringUtils.isEmpty(login.left) || StringUtils.isEmpty(login.right)) {
+ clearLoginInfo();
+ Log.e("Login.login: No login information stored");
+ return StatusCode.NO_LOGIN_INFO_STORED;
+ }
+
+ GCLogin.setActualStatus(CgeoApplication.getInstance().getString(R.string.init_login_popup_working));
+ HttpResponse loginResponse = Network.getRequest("https://www.geocaching.com/login/default.aspx");
+ String loginData = Network.getResponseData(loginResponse);
+ if (loginResponse != null && loginResponse.getStatusLine().getStatusCode() == 503 && TextUtils.matches(loginData, GCConstants.PATTERN_MAINTENANCE)) {
+ return StatusCode.MAINTENANCE;
+ }
+
+ if (StringUtils.isBlank(loginData)) {
+ Log.e("Login.login: Failed to retrieve login page (1st)");
+ return StatusCode.CONNECTION_FAILED; // no loginpage
+ }
+
+ if (GCLogin.getLoginStatus(loginData)) {
+ Log.i("Already logged in Geocaching.com as " + login.left + " (" + Settings.getMemberStatus() + ')');
+ GCLogin.switchToEnglish(loginData);
+ return StatusCode.NO_ERROR; // logged in
+ }
+
+ Cookies.clearCookies();
+ Settings.setCookieStore(null);
+
+ final Parameters params = new Parameters(
+ "__EVENTTARGET", "",
+ "__EVENTARGUMENT", "",
+ "ctl00$ContentBody$tbUsername", login.left,
+ "ctl00$ContentBody$tbPassword", login.right,
+ "ctl00$ContentBody$cbRememberMe", "on",
+ "ctl00$ContentBody$btnSignIn", "Login");
+ final String[] viewstates = GCLogin.getViewstates(loginData);
+ if (isEmpty(viewstates)) {
+ Log.e("Login.login: Failed to find viewstates");
+ return StatusCode.LOGIN_PARSE_ERROR; // no viewstates
+ }
+ GCLogin.putViewstates(params, viewstates);
+
+ loginResponse = Network.postRequest("https://www.geocaching.com/login/default.aspx", params);
+ loginData = Network.getResponseData(loginResponse);
+
+ if (StringUtils.isBlank(loginData)) {
+ Log.e("Login.login: Failed to retrieve login page (2nd)");
+ // FIXME: should it be CONNECTION_FAILED to match the first attempt?
+ return StatusCode.COMMUNICATION_ERROR; // no login page
+ }
+ assert loginData != null; // Caught above
+
+ if (GCLogin.getLoginStatus(loginData)) {
+ Log.i("Successfully logged in Geocaching.com as " + login.left + " (" + Settings.getMemberStatus() + ')');
+
+ GCLogin.switchToEnglish(loginData);
+ Settings.setCookieStore(Cookies.dumpCookieStore());
+
+ return StatusCode.NO_ERROR; // logged in
+ }
+
+ if (loginData.contains("Your username/password combination does not match.")) {
+ Log.i("Failed to log in Geocaching.com as " + login.left + " because of wrong username/password");
+ return StatusCode.WRONG_LOGIN_DATA; // wrong login
+ }
+
+ if (loginData.contains("You must validate your account before you can log in.")) {
+ Log.i("Failed to log in Geocaching.com as " + login.left + " because account needs to be validated first");
+ return StatusCode.UNVALIDATED_ACCOUNT;
+ }
+
+ Log.i("Failed to log in Geocaching.com as " + login.left + " for some unknown reason");
+ if (retry) {
+ GCLogin.switchToEnglish(loginData);
+ return login(false);
+ }
+
+ return StatusCode.UNKNOWN_ERROR; // can't login
+ }
+
+ public static StatusCode logout() {
+ final HttpResponse logoutResponse = Network.getRequest("https://www.geocaching.com/login/default.aspx?RESET=Y&redir=http%3a%2f%2fwww.geocaching.com%2fdefault.aspx%3f");
+ final String logoutData = Network.getResponseData(logoutResponse);
+ if (logoutResponse != null && logoutResponse.getStatusLine().getStatusCode() == 503 && TextUtils.matches(logoutData, GCConstants.PATTERN_MAINTENANCE)) {
+ return StatusCode.MAINTENANCE;
+ }
+
+ resetLoginStatus();
+
+ return StatusCode.NO_ERROR;
+ }
+
+ private static void resetLoginStatus() {
+ Cookies.clearCookies();
+ Settings.setCookieStore(null);
+
+ setActualLoginStatus(false);
+ }
+
+ private static void clearLoginInfo() {
+ resetLoginStatus();
+
+ setActualCachesFound(-1);
+ setActualStatus(CgeoApplication.getInstance().getString(R.string.err_login));
+ }
+
+ static void setActualCachesFound(final int found) {
+ actualCachesFound = found;
+ }
+
+ public static String getActualStatus() {
+ return actualStatus;
+ }
+
+ private static void setActualStatus(final String status) {
+ actualStatus = status;
+ }
+
+ public static boolean isActualLoginStatus() {
+ return actualLoginStatus;
+ }
+
+ private static void setActualLoginStatus(boolean loginStatus) {
+ actualLoginStatus = loginStatus;
+ }
+
+ public static String getActualUserName() {
+ return actualUserName;
+ }
+
+ private static void setActualUserName(String userName) {
+ actualUserName = userName;
+ }
+
+ public static int getActualCachesFound() {
+ return actualCachesFound;
+ }
+
+ /**
+ * Check if the user has been logged in when he retrieved the data.
+ *
+ * @param page
+ * @return <code>true</code> if user is logged in, <code>false</code> otherwise
+ */
+ public static boolean getLoginStatus(@Nullable final String page) {
+ if (StringUtils.isBlank(page)) {
+ Log.e("Login.checkLogin: No page given");
+ return false;
+ }
+ assert page != null;
+
+ setActualStatus(CgeoApplication.getInstance().getString(R.string.init_login_popup_ok));
+
+ // on every page except login page
+ setActualLoginStatus(TextUtils.matches(page, GCConstants.PATTERN_LOGIN_NAME));
+ if (isActualLoginStatus()) {
+ setActualUserName(TextUtils.getMatch(page, GCConstants.PATTERN_LOGIN_NAME, true, "???"));
+ int cachesCount = 0;
+ try {
+ cachesCount = Integer.parseInt(TextUtils.getMatch(page, GCConstants.PATTERN_CACHES_FOUND, true, "0").replaceAll("[,.]", ""));
+ } catch (final NumberFormatException e) {
+ Log.e("getLoginStatus: bad cache count", e);
+ }
+ setActualCachesFound(cachesCount);
+ Settings.setMemberStatus(TextUtils.getMatch(page, GCConstants.PATTERN_MEMBER_STATUS, true, null));
+ if ( page.contains(GCConstants.MEMBER_STATUS_RENEW) ) {
+ Settings.setMemberStatus(GCConstants.MEMBER_STATUS_PM);
+ }
+ return true;
+ }
+
+ // login page
+ setActualLoginStatus(TextUtils.matches(page, GCConstants.PATTERN_LOGIN_NAME_LOGIN_PAGE));
+ if (isActualLoginStatus()) {
+ setActualUserName(Settings.getUsername());
+ // number of caches found is not part of this page
+ return true;
+ }
+
+ setActualStatus(CgeoApplication.getInstance().getString(R.string.init_login_popup_failed));
+ return false;
+ }
+
+ private static void switchToEnglish(String previousPage) {
+ if (previousPage != null && previousPage.contains(ENGLISH)) {
+ Log.i("Geocaching.com language already set to English");
+ // get find count
+ getLoginStatus(Network.getResponseData(Network.getRequest("http://www.geocaching.com/email/")));
+ } else {
+ final String page = Network.getResponseData(Network.getRequest(LANGUAGE_CHANGE_URI));
+ getLoginStatus(page);
+ if (page == null) {
+ Log.e("Failed to read viewstates to set geocaching.com language");
+ }
+ final Parameters params = new Parameters(
+ "__EVENTTARGET", "ctl00$uxLocaleList$uxLocaleList$ctl00$uxLocaleItem", // switch to english
+ "__EVENTARGUMENT", "");
+ GCLogin.transferViewstates(page, params);
+ final HttpResponse response = Network.postRequest(LANGUAGE_CHANGE_URI, params, new Parameters("Referer", LANGUAGE_CHANGE_URI));
+ if (Network.isSuccess(response)) {
+ Log.i("changed language on geocaching.com to English");
+ } else {
+ Log.e("Failed to set geocaching.com language to English");
+ }
+ }
+ }
+
+ public static BitmapDrawable downloadAvatarAndGetMemberStatus() {
+ try {
+ final String responseData = StringUtils.defaultString(Network.getResponseData(Network.getRequest("http://www.geocaching.com/my/")));
+ final String profile = TextUtils.replaceWhitespace(responseData);
+
+ Settings.setMemberStatus(TextUtils.getMatch(profile, GCConstants.PATTERN_MEMBER_STATUS, true, null));
+ if (profile.contains(GCConstants.MEMBER_STATUS_RENEW)) {
+ Settings.setMemberStatus(GCConstants.MEMBER_STATUS_PM);
+ }
+
+ setActualCachesFound(Integer.parseInt(TextUtils.getMatch(profile, GCConstants.PATTERN_CACHES_FOUND, true, "-1").replaceAll("[,.]", "")));
+
+ final String avatarURL = TextUtils.getMatch(profile, GCConstants.PATTERN_AVATAR_IMAGE_PROFILE_PAGE, false, null);
+ if (null != avatarURL) {
+ final HtmlImage imgGetter = new HtmlImage("", false, 0, false);
+ return imgGetter.getDrawable(avatarURL);
+ }
+ // No match? There may be no avatar set by user.
+ Log.d("No avatar set for user");
+ } catch (final Exception e) {
+ Log.w("Error when retrieving user avatar", e);
+ }
+ return null;
+ }
+
+ /**
+ * Detect user date settings on geocaching.com
+ */
+ public static void detectGcCustomDate() {
+
+ final String result = Network.getResponseData(Network.getRequest("http://www.geocaching.com/account/ManagePreferences.aspx"));
+
+ if (null == result) {
+ Log.w("Login.detectGcCustomDate: result is null");
+ return;
+ }
+
+ final String customDate = TextUtils.getMatch(result, GCConstants.PATTERN_CUSTOMDATE, true, null);
+ if (null != customDate) {
+ Settings.setGcCustomDate(customDate);
+ }
+ }
+
+ public static Date parseGcCustomDate(final String input, final String format) throws ParseException {
+ if (StringUtils.isBlank(input)) {
+ throw new ParseException("Input is null", 0);
+ }
+
+ final String trimmed = input.trim();
+
+ if (GC_CUSTOM_DATE_FORMATS.containsKey(format)) {
+ try {
+ return GC_CUSTOM_DATE_FORMATS.get(format).parse(trimmed);
+ } catch (final ParseException e) {
+ }
+ }
+
+ for (final SimpleDateFormat sdf : GC_CUSTOM_DATE_FORMATS.values()) {
+ try {
+ return sdf.parse(trimmed);
+ } catch (final ParseException e) {
+ }
+ }
+
+ throw new ParseException("No matching pattern", 0);
+ }
+
+ public static Date parseGcCustomDate(final String input) throws ParseException {
+ return parseGcCustomDate(input, Settings.getGcCustomDate());
+ }
+
+ public static SimpleDateFormat getCustomGcDateFormat() {
+ final String format = Settings.getGcCustomDate();
+ if (GC_CUSTOM_DATE_FORMATS.containsKey(format)) {
+ return GC_CUSTOM_DATE_FORMATS.get(format);
+ }
+
+ return GC_CUSTOM_DATE_FORMATS.get(DEFAULT_CUSTOM_DATE_FORMAT);
+ }
+
+ /**
+ * checks if an Array of Strings is empty or not. Empty means:
+ * - Array is null
+ * - or all elements are null or empty strings
+ */
+ public static boolean isEmpty(String[] a) {
+ if (a == null) {
+ return true;
+ }
+
+ for (final String s : a) {
+ if (StringUtils.isNotEmpty(s)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * read all viewstates from page
+ *
+ * @return String[] with all view states
+ */
+ public static String[] getViewstates(String page) {
+ // Get the number of viewstates.
+ // If there is only one viewstate, __VIEWSTATEFIELDCOUNT is not present
+
+ if (page == null) { // no network access
+ return null;
+ }
+
+ int count = 1;
+ final MatcherWrapper matcherViewstateCount = new MatcherWrapper(GCConstants.PATTERN_VIEWSTATEFIELDCOUNT, page);
+ if (matcherViewstateCount.find()) {
+ try {
+ count = Integer.parseInt(matcherViewstateCount.group(1));
+ } catch (final NumberFormatException e) {
+ Log.e("getViewStates", e);
+ }
+ }
+
+ final String[] viewstates = new String[count];
+
+ // Get the viewstates
+ final MatcherWrapper matcherViewstates = new MatcherWrapper(GCConstants.PATTERN_VIEWSTATES, page);
+ while (matcherViewstates.find()) {
+ final String sno = matcherViewstates.group(1); // number of viewstate
+ int no;
+ if (StringUtils.isEmpty(sno)) {
+ no = 0;
+ } else {
+ try {
+ no = Integer.parseInt(sno);
+ } catch (final NumberFormatException e) {
+ Log.e("getViewStates", e);
+ no = 0;
+ }
+ }
+ viewstates[no] = matcherViewstates.group(2);
+ }
+
+ if (viewstates.length != 1 || viewstates[0] != null) {
+ return viewstates;
+ }
+ // no viewstates were present
+ return null;
+ }
+
+ /**
+ * put viewstates into request parameters
+ */
+ public static void putViewstates(final Parameters params, final String[] viewstates) {
+ if (ArrayUtils.isEmpty(viewstates)) {
+ return;
+ }
+ params.put("__VIEWSTATE", viewstates[0]);
+ if (viewstates.length > 1) {
+ for (int i = 1; i < viewstates.length; i++) {
+ params.put("__VIEWSTATE" + i, viewstates[i]);
+ }
+ params.put("__VIEWSTATEFIELDCOUNT", String.valueOf(viewstates.length));
+ }
+ }
+
+ /**
+ * transfers the viewstates variables from a page (response) to parameters
+ * (next request)
+ */
+ public static void transferViewstates(final String page, final Parameters params) {
+ putViewstates(params, getViewstates(page));
+ }
+
+ /**
+ * POST HTTP request. Do the request a second time if the user is not logged in
+ *
+ * @param uri
+ * @return
+ */
+ public static String postRequestLogged(final String uri, final Parameters params) {
+ final String data = Network.getResponseData(Network.postRequest(uri, params));
+
+ if (getLoginStatus(data)) {
+ return data;
+ }
+
+ if (login() == StatusCode.NO_ERROR) {
+ return Network.getResponseData(Network.postRequest(uri, params));
+ }
+
+ Log.i("Working as guest.");
+ return data;
+ }
+
+ /**
+ * GET HTTP request. Do the request a second time if the user is not logged in
+ *
+ * @param uri
+ * @param params
+ * @return
+ */
+ @Nullable
+ public static String getRequestLogged(@NonNull final String uri, @Nullable final Parameters params) {
+ final HttpResponse response = Network.getRequest(uri, params);
+ final String data = Network.getResponseData(response, canRemoveWhitespace(uri));
+
+ // A page not found will not be found if the user logs in either
+ if (Network.isPageNotFound(response) || getLoginStatus(data)) {
+ return data;
+ }
+
+ if (login() == StatusCode.NO_ERROR) {
+ return Network.getResponseData(Network.getRequest(uri, params), canRemoveWhitespace(uri));
+ }
+
+ Log.w("Working as guest.");
+ return data;
+ }
+
+ /**
+ * Unfortunately the cache details page contains user generated whitespace in the personal note, therefore we cannot
+ * remove the white space from cache details pages.
+ *
+ * @param uri
+ * @return
+ */
+ private static boolean canRemoveWhitespace(final String uri) {
+ return !StringUtils.contains(uri, "cache_details");
+ }
+
+ /** Get user session & session token from the Live Map. Needed for following requests */
+ public static String[] getMapTokens() {
+ final HttpResponse response = Network.getRequest(GCConstants.URL_LIVE_MAP);
+ final String data = Network.getResponseData(response);
+ final String userSession = TextUtils.getMatch(data, GCConstants.PATTERN_USERSESSION, "");
+ final String sessionToken = TextUtils.getMatch(data, GCConstants.PATTERN_SESSIONTOKEN, "");
+ return new String[] { userSession, sessionToken };
+ }
+}