package games.strategy.engine.pbem; import java.net.HttpURLConnection; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.http.HttpEntity; import org.apache.http.NameValuePair; import org.apache.http.client.CookieStore; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.entity.ContentType; import org.apache.http.entity.mime.MultipartEntityBuilder; import org.apache.http.impl.client.BasicCookieStore; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.LaxRedirectStrategy; import org.apache.http.message.BasicNameValuePair; import org.apache.http.protocol.BasicHttpContext; import org.apache.http.protocol.HttpContext; import games.strategy.debug.ClientLogger; import games.strategy.engine.framework.system.HttpProxy; import games.strategy.net.OpenFileUtility; import games.strategy.triplea.help.HelpSupport; import games.strategy.util.Util; /** * A poster for www.tripleawarclub.org forum * We log in and out every time we post, so we don't need to keep state. */ public class TripleAWarClubForumPoster extends AbstractForumPoster { private static final long serialVersionUID = -4017550807078258152L; private static final String WAR_CLUB_FORUM_URL = "http://www.tripleawarclub.org/modules/newbb"; private static Pattern s_XOOPS_TOKEN_REQUEST = Pattern.compile(".*XOOPS_TOKEN_REQUEST[^>]*value=\"([^\"]*)\".*", Pattern.DOTALL | Pattern.CASE_INSENSITIVE); /** * Logs into the website. * * @throws Exception * if login fails */ private HttpContext login(CloseableHttpClient client) throws Exception { final HttpPost httpPost = new HttpPost("http://www.tripleawarclub.org/user.php"); CookieStore cookieStore = new BasicCookieStore(); HttpContext httpContext = new BasicHttpContext(); httpContext.setAttribute(HttpClientContext.COOKIE_STORE, cookieStore); List<NameValuePair> pairs = new ArrayList<>(); pairs.add(new BasicNameValuePair("uname", getUsername())); pairs.add(new BasicNameValuePair("pass", getPassword())); pairs.add(new BasicNameValuePair("submit", "Login")); pairs.add(new BasicNameValuePair("rememberme", "On")); pairs.add(new BasicNameValuePair("xoops_redirect", "/")); pairs.add(new BasicNameValuePair("op", "login")); httpPost.setEntity(new UrlEncodedFormEntity(pairs, StandardCharsets.UTF_8)); HttpProxy.addProxy(httpPost); try (CloseableHttpResponse response = client.execute(httpPost, httpContext)) { final int status = response.getStatusLine().getStatusCode(); if (status != HttpURLConnection.HTTP_OK) { throw new Exception("Login failed, server returned status: " + status); } final String body = Util.getStringFromInputStream(response.getEntity().getContent()); final String lowerBody = body.toLowerCase(); if (lowerBody.contains("incorrect login!")) { throw new Exception("Incorrect login credentials"); } if (!lowerBody.contains("thank you for logging in")) { System.out.println("Unknown login error, site response " + body); throw new Exception("Unknown login error"); } } return httpContext; } /** * Post the turn summary and save game to the forum * After login we must load the post page to get the XOOPS_TOKEN_REQUEST (which I think is CSRF nounce) * then we can post the reply. * * @param summary * the forum summary * @param subject * the forum subject * @return true if the post was successful */ @Override public boolean postTurnSummary(final String summary, final String subject) { try (CloseableHttpClient client = HttpClientBuilder.create().setRedirectStrategy(new LaxRedirectStrategy()).build()) { HttpContext httpContext = login(client); // load the reply page final String s_forumId = "20"; final String url = WAR_CLUB_FORUM_URL + "/reply.php?forum=" + s_forumId + "&topic_id=" + URLEncoder.encode(m_topicId, StandardCharsets.UTF_8.name()); String XOOPS_TOKEN_REQUEST; HttpGet httpGet = new HttpGet(url); HttpProxy.addProxy(httpGet); try (CloseableHttpResponse response = client.execute(httpGet, httpContext)) { final int status = response.getStatusLine().getStatusCode(); if (status != HttpURLConnection.HTTP_OK) { throw new Exception("Could not load reply page: " + url + ". Site returned " + status); } final String body = Util.getStringFromInputStream(response.getEntity().getContent()); final Matcher m = s_XOOPS_TOKEN_REQUEST.matcher(body); if (!m.matches()) { throw new Exception("Unable to find 'XOOPS_TOKEN_REQUEST' form field on reply page"); } XOOPS_TOKEN_REQUEST = m.group(1); } MultipartEntityBuilder builder = MultipartEntityBuilder.create() .addTextBody("subject", subject) .addTextBody("message", summary) .addTextBody("forum", s_forumId) .addTextBody("topic_id", m_topicId) .addTextBody("XOOPS_TOKEN_REQUEST", XOOPS_TOKEN_REQUEST) .addTextBody("xoops_upload_file[]", "userfile") .addTextBody("contents_submit", "Submit") .addTextBody("doxcode", "1") .addTextBody("dosmiley", "1") .addTextBody("dohtml", "1") .addTextBody("dobr", "1") .addTextBody("editor", "dhtmltextarea"); if (m_includeSaveGame && m_saveGameFile != null) { builder.addBinaryBody("userfile", m_saveGameFile, ContentType.APPLICATION_OCTET_STREAM, m_saveGameFileName); } HttpEntity entity = builder.build(); HttpPost httpPost = new HttpPost(WAR_CLUB_FORUM_URL + "/post.php"); HttpProxy.addProxy(httpPost); httpPost.setEntity(entity); try (CloseableHttpResponse response = client.execute(httpPost, httpContext)) { final int status = response.getStatusLine().getStatusCode(); if (status != HttpURLConnection.HTTP_OK) { throw new Exception("Posting summary failed, the server returned status: " + status); } final String body = Util.getStringFromInputStream(response.getEntity().getContent()); if (!body.toLowerCase().contains("thanks for your submission!")) { throw new Exception("Posting summary failed, the server didn't respond with thank you message"); } m_turnSummaryRef = "www.tripleawarclub.org/modules/newbb/viewtopic.php?topic_id=" + m_topicId + "&forum=" + s_forumId; } // now logout, this is just to be nice, so we don't care if this fails try { httpGet = new HttpGet("http://www.tripleawarclub.org/user.php?op=logout"); HttpProxy.addProxy(httpGet); client.execute(httpGet, httpContext); } catch (Exception e) { ClientLogger.logQuietly("Failed to log out", e); } } catch (final Exception e) { m_turnSummaryRef = e.getMessage(); ClientLogger.logQuietly(e); return false; } return true; } @Override public String getTestMessage() { return "Testing, this will take a couple of seconds..."; } @Override public String getHelpText() { return HelpSupport.loadHelp("tripleAWarClubForum.html"); } @Override public IForumPoster doClone() { final TripleAWarClubForumPoster clone = new TripleAWarClubForumPoster(); clone.setTopicId(getTopicId()); clone.setIncludeSaveGame(getIncludeSaveGame()); clone.setAlsoPostAfterCombatMove(getAlsoPostAfterCombatMove()); clone.setPassword(getPassword()); clone.setUsername(getUsername()); return clone; } @Override public boolean supportsSaveGame() { return true; } @Override public String getDisplayName() { return "TripleaWarClub.org"; } @Override public void viewPosted() { final String url = WAR_CLUB_FORUM_URL + "/viewtopic.php?topic_id=" + m_topicId; OpenFileUtility.openURL(url); } }