package com.dimtion.shaarlier.helpers; import android.support.annotation.NonNull; import android.util.Base64; import android.util.Log; import com.dimtion.shaarlier.utils.Link; import com.dimtion.shaarlier.utils.ShaarliAccount; import org.json.JSONArray; import org.json.JSONException; import org.jsoup.Connection; import org.jsoup.Jsoup; import org.jsoup.nodes.Element; import java.io.IOException; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; /** * Created by dimtion on 05/04/2015. * This class handle all the communications with Shaarli and other web services */ public class PasswordNetworkManager implements NetworkManager { private final String mShaarliUrl; private final String mUsername; private final String mPassword; private final boolean mValidateCert; private final String mBasicAuth; private Map<String, String> mCookies; private String mToken; private String mDatePostLink; private String mSharedUrl; private Exception mLastError; private String mPrefetchedTitle; private String mPrefetchedDescription; private String mPrefetchedTags; PasswordNetworkManager(@NonNull ShaarliAccount account) { this.mShaarliUrl = account.getUrlShaarli(); this.mUsername = account.getUsername(); this.mPassword = account.getPassword(); this.mValidateCert = account.isValidateCert(); if (!"".equals(account.getBasicAuthUsername()) && !"".equals(account.getBasicAuthPassword())) { String login = account.getBasicAuthUsername() + ":" + account.getBasicAuthPassword(); this.mBasicAuth = new String(Base64.encode(login.getBytes(), Base64.NO_WRAP)); } else { this.mBasicAuth = ""; } } /** * Helper method which create a new connection to Shaarli * * @param url the url of the shaarli * @param method set the HTTP method to use * @return pre-made jsoupConnection */ private Connection newConnection(String url, Connection.Method method) { Connection jsoupConnection = Jsoup.connect(url); if (!"".equals(this.mBasicAuth)) { jsoupConnection = jsoupConnection.header("Authorization", "Basic " + this.mBasicAuth); } if (this.mCookies != null) { jsoupConnection = jsoupConnection.cookies(this.mCookies); } return jsoupConnection .validateTLSCertificates(this.mValidateCert) .timeout(NetworkUtils.TIME_OUT) .followRedirects(true) .method(method); } @Override public boolean isCompatibleShaarli() throws IOException { final String loginFormUrl = this.mShaarliUrl + "?do=login"; try { Connection.Response loginFormPage = this.newConnection(loginFormUrl, Connection.Method.GET).execute(); this.mCookies = loginFormPage.cookies(); this.mToken = loginFormPage.parse().body().select("input[name=token]").first().attr("value"); } catch (NullPointerException | IllegalArgumentException e) { return false; } return true; } /** * Method which retrieve the cookie saying that we are logged in */ @Override public boolean login() throws IOException { try { Connection.Response loginPage = this.newConnection(this.mShaarliUrl, Connection.Method.POST) .data("login", this.mUsername) .data("password", this.mPassword) .data("token", this.mToken) .data("returnurl", this.mShaarliUrl) .execute(); this.mCookies = loginPage.cookies(); loginPage.parse().body().select("a[href=?do=logout]").first() .attr("href"); // If this fails, you're not connected } catch (NullPointerException e) { return false; } return true; } /** * Method which retrieve a token for posting links * Update the cookie, the token and the date * Assume being logged in */ private void retrievePostLinkToken(String encodedSharedLink) throws IOException { final String postFormUrl = this.mShaarliUrl + "?post=" + encodedSharedLink; Connection.Response postFormPage = this.newConnection(postFormUrl, Connection.Method.GET) .execute(); final Element postFormBody = postFormPage.parse().body(); // Update our situation: // TODO: Soft fail: if one field does not load, try the others anyway mToken = postFormBody.select("input[name=token]").first().attr("value"); mDatePostLink = postFormBody.select("input[name=lf_linkdate]").first().attr("value"); // Date chosen by the server mSharedUrl = postFormBody.select("input[name=lf_url]").first().attr("value"); mPrefetchedTitle = postFormBody.select("input[name=lf_title]").first().attr("value"); mPrefetchedDescription = postFormBody.select("textarea[name=lf_description]").first().html(); mPrefetchedTags = postFormBody.select("input[name=lf_tags]").first().attr("value"); } @Override public Link prefetchLinkData(Link link) throws IOException { String encodedShareUrl = URLEncoder.encode(link.getUrl(), "UTF-8"); retrievePostLinkToken(encodedShareUrl); Link newLink = new Link(link); newLink.setUrl(mSharedUrl); newLink.setTitle(mPrefetchedTitle); newLink.setDescription(mPrefetchedDescription); newLink.setTags(mPrefetchedTags); newLink.setDatePostLink(mDatePostLink); newLink.setToken(mToken); return newLink; } /** * Method which publishes a link to shaarli * Assume being logged in * TODO: use the prefetch function */ @Override public void pushLink(Link link) throws IOException { String encodedShareUrl = URLEncoder.encode(link.getUrl(), "UTF-8"); retrievePostLinkToken(encodedShareUrl); if (NetworkUtils.isUrl(link.getUrl())) { // In case the url isn't really one, just post the one chosen by the server. this.mSharedUrl = link.getUrl(); } final String postUrl = this.mShaarliUrl + "?post=" + encodedShareUrl; Connection postPageConn = this.newConnection(postUrl, Connection.Method.POST) .data("save_edit", "Save") .data("token", this.mToken) .data("lf_tags", link.getTags()) .data("lf_linkdate", this.mDatePostLink) .data("lf_url", this.mSharedUrl) .data("lf_title", link.getTitle()) .data("lf_description", link.getDescription()); if (link.isPrivate()) postPageConn.data("lf_private", "on"); if (link.isTweet()) postPageConn.data("tweet", "on"); if (link.isToot()) postPageConn.data("toot", "on"); postPageConn.execute(); // Then we post } @Override public List<String> retrieveTags() { List<String> tags = new ArrayList<>(); try { String[] awesompleteTags = this.retrieveTagsFromAwesomplete(); Collections.addAll(tags, awesompleteTags); } catch (Exception e) { Log.w("TAG", e.toString()); } try { String[] wsTags = this.retrieveTagsFromWs(); // kept for compatibility with old Shaarli instances Collections.addAll(tags, wsTags); } catch (Exception e) { Log.w("TAG", e.toString()); } return tags; } /** * Method which retrieve tags from the WS (old shaarli) * Assume being logged in */ private String[] retrieveTagsFromWs() throws IOException, JSONException { final String requestUrl = this.mShaarliUrl + "?ws=tags&term=+"; String[] predictionsArr = {}; String json = this.newConnection(requestUrl, Connection.Method.POST) .ignoreContentType(true) .execute() .body(); JSONArray ja = new JSONArray(json); predictionsArr = new String[ja.length()]; for (int i = 0; i < ja.length(); i++) { // add each entry to our array predictionsArr[i] = ja.getString(i); } return predictionsArr; } /** * Method which retrieve tags from awesomplete (new shaarli) * Assume being logged in */ private String[] retrieveTagsFromAwesomplete() throws IOException { final String requestUrl = this.mShaarliUrl + "?post="; String tagsString = this.newConnection(requestUrl, Connection.Method.GET) .execute() .parse() .body() .select("input[name=lf_tags]") .first() .attr("data-list"); return tagsString.split(", "); } }