package kr.kdev.dg1s.biowiki.ui.accounts; import android.content.Context; import android.webkit.URLUtil; import org.xmlpull.v1.XmlPullParserException; import org.xmlrpc.android.ApiHelper; import org.xmlrpc.android.XMLRPCClientInterface; import org.xmlrpc.android.XMLRPCException; import org.xmlrpc.android.XMLRPCFactory; import org.xmlrpc.android.XMLRPCFault; import java.io.IOException; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.net.ssl.SSLHandshakeException; import kr.kdev.dg1s.biowiki.BioWiki; import kr.kdev.dg1s.biowiki.Constants; import kr.kdev.dg1s.biowiki.R; import kr.kdev.dg1s.biowiki.models.Blog; import kr.kdev.dg1s.biowiki.util.AppLog; import kr.kdev.dg1s.biowiki.util.MapUtils; import kr.kdev.dg1s.biowiki.util.StringUtils; import kr.kdev.dg1s.biowiki.util.UrlUtils; import kr.kdev.dg1s.biowiki.util.Utils; public class SetupBlog { private static final String DEFAULT_IMAGE_SIZE = "2000"; private String mUsername; private String mPassword; private String mHttpUsername = ""; private String mHttpPassword = ""; private String mXmlrpcUrl; private int mErrorMsgId; private boolean mIsCustomUrl; private String mSelfHostedURL; private boolean mHttpAuthRequired; private boolean mErroneousSslCertificate; private boolean mCurrentSslCertificatesForcedTrusted; public SetupBlog() { } public int getErrorMsgId() { return mErrorMsgId; } public String getXmlrpcUrl() { return mXmlrpcUrl; } public String getPassword() { return mPassword; } public void setPassword(String mPassword) { this.mPassword = mPassword; } public String getUsername() { return mUsername; } public void setUsername(String mUsername) { this.mUsername = mUsername; } public void setHttpUsername(String mHttpUsername) { this.mHttpUsername = mHttpUsername; } public void setHttpPassword(String mHttpPassword) { this.mHttpPassword = mHttpPassword; } public void setSelfHostedURL(String mSelfHostedURL) { this.mSelfHostedURL = mSelfHostedURL; } public boolean isHttpAuthRequired() { return mHttpAuthRequired; } public void setHttpAuthRequired(boolean mHttpAuthRequired) { this.mHttpAuthRequired = mHttpAuthRequired; } public boolean isErroneousSslCertificates() { return mErroneousSslCertificate; } public List<Map<String, Object>> getBlogList() { if (mSelfHostedURL != null && mSelfHostedURL.length() != 0) { mXmlrpcUrl = getSelfHostedXmlrpcUrl(mSelfHostedURL); } else { mXmlrpcUrl = Constants.wpcomXMLRPCURL; } if (mXmlrpcUrl == null) { if (!mHttpAuthRequired) mErrorMsgId = R.string.no_site_error; return null; } // Validate the URL found before calling the client. Prevent a crash that can occur // during the setup of self-hosted sites. URI uri; try { uri = URI.create(mXmlrpcUrl); } catch (Exception e1) { mErrorMsgId = R.string.no_site_error; return null; } XMLRPCClientInterface client = XMLRPCFactory.instantiate(uri, mHttpUsername, mHttpPassword); Object[] params = {mUsername, mPassword}; try { Object[] userBlogs = (Object[]) client.call("wp.getUsersBlogs", params); if (userBlogs == null) { // Could happen if the returned server response is truncated mErrorMsgId = R.string.xmlrpc_error; ; return null; } Arrays.sort(userBlogs, Utils.BlogNameComparator); List<Map<String, Object>> userBlogList = new ArrayList<Map<String, Object>>(); for (Object blog : userBlogs) { try { userBlogList.add((Map<String, Object>) blog); } catch (ClassCastException e) { AppLog.e(AppLog.T.NUX, "invalid data received from XMLRPC call wp.getUsersBlogs"); } } return userBlogList; } catch (XmlPullParserException parserException) { mErrorMsgId = R.string.xmlrpc_error; AppLog.e(AppLog.T.NUX, "invalid data received from XMLRPC call wp.getUsersBlogs", parserException); } catch (XMLRPCFault xmlRpcFault) { AppLog.e(AppLog.T.NUX, "XMLRPCFault received from XMLRPC call wp.getUsersBlogs", xmlRpcFault); switch (xmlRpcFault.getFaultCode()) { case 403: mErrorMsgId = R.string.username_or_password_incorrect; break; case 404: mErrorMsgId = R.string.xmlrpc_error; break; case 425: mErrorMsgId = R.string.account_two_step_auth_enabled; break; default: mErrorMsgId = R.string.no_site_error; break; } } catch (XMLRPCException xmlRpcException) { AppLog.e(AppLog.T.NUX, "XMLRPCException received from XMLRPC call wp.getUsersBlogs", xmlRpcException); mErrorMsgId = R.string.no_site_error; } catch (SSLHandshakeException e) { if (!UrlUtils.getDomainFromUrl(mXmlrpcUrl).endsWith("wordpress.com")) { mErroneousSslCertificate = true; } AppLog.w(AppLog.T.NUX, "SSLHandshakeException failed. Erroneous SSL certificate detected."); } catch (IOException e) { AppLog.e(AppLog.T.NUX, "Exception received from XMLRPC call wp.getUsersBlogs", e); mErrorMsgId = R.string.no_site_error; } return null; } private String getRsdUrl(String baseUrl) throws SSLHandshakeException { String rsdUrl; rsdUrl = ApiHelper.getRSDMetaTagHrefRegEx(baseUrl); if (rsdUrl == null) { rsdUrl = ApiHelper.getRSDMetaTagHref(baseUrl); } return rsdUrl; } private boolean isHTTPAuthErrorMessage(Exception e) { if (e != null && e.getMessage().contains("401")) { mHttpAuthRequired = true; return mHttpAuthRequired; } return false; } private String getmXmlrpcByUserEnteredPath(String baseUrl) { String xmlRpcUrl = null; // Try the user entered path URI uri = URI.create(baseUrl); XMLRPCClientInterface client = XMLRPCFactory.instantiate(uri, mHttpUsername, mHttpPassword); try { client.call("system.listMethods"); xmlRpcUrl = baseUrl; mIsCustomUrl = true; return xmlRpcUrl; } catch (XMLRPCException e) { AppLog.i(AppLog.T.NUX, "system.listMethods failed on: " + baseUrl); if (isHTTPAuthErrorMessage(e)) { return null; } } catch (IOException e) { AppLog.i(AppLog.T.NUX, "system.listMethods failed on: " + baseUrl); if (isHTTPAuthErrorMessage(e)) { return null; } } catch (XmlPullParserException e) { AppLog.i(AppLog.T.NUX, "system.listMethods failed on: " + baseUrl); if (isHTTPAuthErrorMessage(e)) { return null; } } // Guess the xmlrpc path String guessURL = baseUrl; if (guessURL.substring(guessURL.length() - 1, guessURL.length()).equals("/")) { guessURL = guessURL.substring(0, guessURL.length() - 1); } guessURL += "/xmlrpc.php"; uri = URI.create(guessURL); client = XMLRPCFactory.instantiate(uri, mHttpUsername, mHttpPassword); try { client.call("system.listMethods"); xmlRpcUrl = guessURL; return xmlRpcUrl; } catch (XMLRPCException e) { AppLog.e(AppLog.T.NUX, "system.listMethods failed on: " + guessURL, e); } catch (IOException e) { AppLog.e(AppLog.T.NUX, "system.listMethods failed on: " + guessURL, e); } catch (XmlPullParserException e) { AppLog.e(AppLog.T.NUX, "system.listMethods failed on: " + guessURL, e); } return null; } // Attempts to retrieve the xmlrpc url for a self-hosted site, in this order: // 1: Try to retrieve it by finding the ?rsd url in the site's header // 2: Take whatever URL the user entered to see if that returns a correct response // 3: Finally, just guess as to what the xmlrpc url should be private String getSelfHostedXmlrpcUrl(String url) { String xmlrpcUrl = null; // Convert IDN names to punycode if necessary url = UrlUtils.convertUrlToPunycodeIfNeeded(url); // Add http to the beginning of the URL if needed url = UrlUtils.addHttpProcolIfNeeded(url, mCurrentSslCertificatesForcedTrusted); if (!URLUtil.isValidUrl(url)) { mErrorMsgId = R.string.invalid_url_message; return null; } // Attempt to get the XMLRPC URL via RSD String rsdUrl; try { rsdUrl = getRsdUrl(url); } catch (SSLHandshakeException e) { if (!UrlUtils.getDomainFromUrl(url).endsWith("wordpress.com")) { mErroneousSslCertificate = true; } AppLog.w(AppLog.T.NUX, "SSLHandshakeException failed. Erroneous SSL certificate detected."); return null; } try { if (rsdUrl != null) { xmlrpcUrl = ApiHelper.getXMLRPCUrl(rsdUrl); if (xmlrpcUrl == null) { xmlrpcUrl = rsdUrl.replace("?rsd", ""); } } else { xmlrpcUrl = getmXmlrpcByUserEnteredPath(url); } } catch (SSLHandshakeException e) { if (!UrlUtils.getDomainFromUrl(url).endsWith("wordpress.com")) { mErroneousSslCertificate = true; } AppLog.w(AppLog.T.NUX, "SSLHandshakeException failed. Erroneous SSL certificate detected."); return null; } return xmlrpcUrl; } public Blog addBlog(String blogName, String xmlRpcUrl, String homeUrl, String blogId, String username, String password, boolean isAdmin) { Blog blog = null; if (!BioWiki.wpDB.isBlogInDatabase(Integer.parseInt(blogId), xmlRpcUrl)) { // The blog isn't in the app, so let's create it blog = new Blog(xmlRpcUrl, username, password); blog.setHomeURL(homeUrl); blog.setHttpuser(mHttpUsername); blog.setHttppassword(mHttpPassword); blog.setBlogName(blogName); blog.setImagePlacement(""); //deprecated blog.setFullSizeImage(false); blog.setMaxImageWidth(DEFAULT_IMAGE_SIZE); blog.setMaxImageWidthId(0); //deprecated blog.setRemoteBlogId(Integer.parseInt(blogId)); blog.setDotcomFlag(xmlRpcUrl.contains("wordpress.com")); blog.setWpVersion(""); // assigned later in getOptions call blog.setAdmin(isAdmin); BioWiki.wpDB.saveBlog(blog); } else { // Update blog name int localTableBlogId = BioWiki.wpDB.getLocalTableBlogIdForRemoteBlogIdAndXmlRpcUrl( Integer.parseInt(blogId), xmlRpcUrl); try { blog = BioWiki.wpDB.instantiateBlogByLocalId(localTableBlogId); if (!blogName.equals(blog.getBlogName())) { blog.setBlogName(blogName); BioWiki.wpDB.saveBlog(blog); } } catch (Exception e) { AppLog.e(AppLog.T.NUX, "localTableBlogId: " + localTableBlogId + " not found"); } } return blog; } /** * Remove blogs that are not in the list and add others * TODO: it's horribly slow due to datastructures used (List of Map), We should replace * that by a HashSet of a specialized Blog class (that supports comparison) */ public void syncBlogs(Context context, List<Map<String, Object>> newBlogList) { // Add all blogs from blogList addBlogs(newBlogList); // Delete blogs if not in blogList List<Map<String, Object>> allBlogs = BioWiki.wpDB.getAccountsBy("dotcomFlag=1", null); Set<String> newBlogURLs = new HashSet<String>(); for (Map<String, Object> blog : newBlogList) { newBlogURLs.add(blog.get("xmlrpc").toString() + blog.get("blogid").toString()); } for (Map<String, Object> blog : allBlogs) { if (!newBlogURLs.contains(blog.get("url").toString() + blog.get("blogId"))) { BioWiki.wpDB.deleteAccount(context, Integer.parseInt(blog.get("id").toString())); } } } /** * Add selected blog(s) to the database */ public void addBlogs(List<Map<String, Object>> blogList) { for (int i = 0; i < blogList.size(); i++) { Map<String, Object> blogMap = blogList.get(i); String blogName = StringUtils.unescapeHTML(blogMap.get("blogName").toString()); String xmlrpcUrl = (mIsCustomUrl) ? mXmlrpcUrl : blogMap.get("xmlrpc").toString(); String homeUrl = blogMap.get("url").toString(); String blogId = blogMap.get("blogid").toString(); boolean isAdmin = MapUtils.getMapBool(blogMap, "isAdmin"); addBlog(blogName, xmlrpcUrl, homeUrl, blogId, mUsername, mPassword, isAdmin); } } }