import java.net.*; import java.io.*; import java.security.*; import java.math.*; import java.util.*; import java.util.regex.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; // // TODO // // - Journal looks by saving latest entries. // - Index page for browsing // - Memories // - Check if entry file name exist and append _2, etc if so. // - Support > 1 journal backup per login // - Proper exception handling; don't have methods throw Exception. // - Replace prev/next links in entries with local links. // - Save all referenced images and link them properly. // public class LjBackup { private static final boolean DEBUG = true; public static void main(String[] args) { new LjBackupApp(); } private static class BackupParams { String username; String loginCookie; String backupDir; boolean backupEntries; boolean backupUserInfo; boolean backupSettings; String baseUrl; } private static class ProgressDialog extends JDialog { private JLabel textLabel; private JLabel fileLabel; private boolean cancelled = false; private boolean handled = false; public ProgressDialog(JFrame owner) { super(owner, "Backup Progress", true); createGui(); } private void createGui() { GridBagLayout GBL = new GridBagLayout(); GridBagConstraints gc = new GridBagConstraints( 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0); JPanel p = new JPanel(GBL); textLabel = new JLabel(); GBL.setConstraints(textLabel, gc); p.add(textLabel); gc.gridy++; gc.insets = new Insets(0, 5, 5, 5); fileLabel = new JLabel(); GBL.setConstraints(fileLabel, gc); p.add(fileLabel); gc.gridy++; gc.weightx = 0.0; gc.fill = GridBagConstraints.NONE; gc.anchor = GridBagConstraints.CENTER; gc.insets = new Insets(10, 5, 5, 5); JButton button = new JButton(new AbstractAction("Cancel") { public void actionPerformed(ActionEvent e) { // Give control back to owner. cancelled = true; hide(); } }); GBL.setConstraints(button, gc); p.add(button); setContentPane(p); setSize(550, 120); } public void update(String text) { update(text, " "); } public void update(String text, File file) { update(text, file.toString()); } public void update(String text, String file) { textLabel.setText(text); fileLabel.setText(file); } public void cancel() { cancelled = true; // Assume caller will handle everything. handled = true; } public boolean cancelled() { return cancelled; } public boolean handled() { return handled; } } private static class BackupThread extends Thread { private LjBackupApp owner; private ProgressDialog progressDlg; public BackupThread(LjBackupApp owner, ProgressDialog progressDlg) { super("BackupThread"); this.owner = owner; this.progressDlg = progressDlg; } public void run() { try { progressDlg.update("Constructing login cookie..."); BackupParams params = owner.getBackupParams(); backupJournal(this, params, progressDlg); } catch (Exception e) { JOptionPane.showMessageDialog( owner, "Error during backup: "+e.getMessage()); e.printStackTrace(); } finally { progressDlg.hide(); } } private void backupJournal( Thread backupThread, BackupParams params, ProgressDialog progressDlg) throws Exception { File backupDir = createBackupDir(params, progressDlg, owner); if (null == backupDir) { return; } if (params.backupEntries) { backupEntries(params, backupDir, backupThread, progressDlg); } if (params.backupUserInfo) { backupUserInfo(params, backupDir, progressDlg); } if (params.backupSettings) { backupSettings(params, backupDir, progressDlg); } /* // TODO: Log off. String sessionId = getSessionId( params.username, params.loginCookie); String logoutStr = getHtml( params.baseUrl + "/logout.bml", "user="+params.username+"; sessid="+sessionId); System.out.println("logout: sessionId = "+sessionId+"; html = \n"+logoutStr); */ } private File createBackupDir( BackupParams params, ProgressDialog progressDlg, LjBackupApp owner) { File backupDir = new File(params.backupDir + "/" + params.username); progressDlg.update("Creating backup directory...", backupDir); if (backupDir.exists()) { int answer = JOptionPane.showOptionDialog( owner, "Directory \""+backupDir+"\" exists.\nWhat do you "+ "want to do with the contents of this directory?", "Directory Exists!", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, new String[] { "Overwrite", "Wipe", "Cancel" }, "Overwrite"); if (JOptionPane.CANCEL_OPTION == answer) { progressDlg.cancel(); return null; } else if (JOptionPane.OK_OPTION == answer) { // Wipe contents of directory before continuing. // TODO } // Else just overwrite, so nothing to do. } backupDir.mkdirs(); return backupDir; } private void backupEntries( BackupParams params, File backupDir, Thread backupThread, ProgressDialog progressDlg) throws Exception { File backupEntriesDir = new File(backupDir, "Entries"); progressDlg.update("Preparing to back up journal entries...", backupEntriesDir); backupEntriesDir.mkdir(); // Get ID of most recent entry. String currentUrl = getMostRecentUrl( params.username, params.baseUrl, params.loginCookie); while (false == backupThread.isInterrupted() && null != currentUrl) { progressDlg.update("Downloading journal entry...", currentUrl); String html = getHtml(currentUrl, params.loginCookie); String filename = getFileName(params.username, html, false); if (null == filename) { System.err.println("COULD NOT SAVE "+currentUrl+" (OK IF FIRST POST IN JOURNAL)"); // TODO: JOptionPane? } else { // Save page to disk. saveFile(backupEntriesDir + "/" + filename, html, progressDlg, "Saving journal entry..."); } // Navigate to next page, using url currentUrl = getPreviousUrl( params.username, params.baseUrl, html); } } private String getMostRecentUrl( String username, String baseUrl, String loginCookieStr) throws Exception { // The info returned from getevents cannot be used, as the date // is the date the user said they posted, and could be backdated. // So, the calendar will be used to get the most recent page ID. // Note: using "/users/" for communities is okay. String calendarHtml = getHtml(baseUrl + "/users/" + username + "/calendar", loginCookieStr); // Links to posts look like baseUrl/*/[digits]/[digits]/[digits]; // find the latest one. String latestDateUrl = null; int latestNum = -1; Pattern urlPattern = Pattern.compile( baseUrl + "/.*?/" + username + "/\\d+/\\d+/\\d+"); Pattern datePattern = Pattern.compile("\\d+/\\d+/\\d+"); Matcher urlMatcher = urlPattern.matcher(calendarHtml); while (urlMatcher.find()) { String url = urlMatcher.group(); Matcher dateMatcher = datePattern.matcher(url); dateMatcher.find(); String date = dateMatcher.group(); int dateNum = Integer.parseInt(date.replaceAll("/", "")); if (dateNum > latestNum) { latestNum = dateNum; latestDateUrl = url; } } // Get the HTML for the latest date. String dayHtml = getHtml(latestDateUrl, loginCookieStr); // Reconstruct the URL. String filename = getFileName(username, dayHtml, true); if (null == filename) { return null; } else { return baseUrl + "/users/" + username + "/" + filename; } } private String getFileName( String username, String html, boolean findMaxItemId) { // Extract the page ID of the latest entry. // Comment URLs look like baseUrl/*/[digits].html?mode=reply. String latestDateUrl = null; int latestItemId = -1; Pattern urlPattern = Pattern.compile( "/" + username + "/\\d+\\.html\\?mode=reply"); Pattern filenamePattern = Pattern.compile("\\d+\\.html"); Matcher urlMatcher = urlPattern.matcher(html); boolean keepGoing = true; while (keepGoing && urlMatcher.find()) { if (false == findMaxItemId) { keepGoing = false; } String url = urlMatcher.group(); Matcher filenameMatcher = filenamePattern.matcher(url); filenameMatcher.find(); String date = filenameMatcher.group(); int itemId = Integer.parseInt( date.substring(0, date.indexOf('.'))); if (itemId > latestItemId) { latestItemId = itemId; latestDateUrl = url; } } if (null != latestDateUrl) { // Strip off "?mode=reply". latestDateUrl = latestDateUrl.substring( 0, latestDateUrl.indexOf('?')); // Strip off "//". latestDateUrl = latestDateUrl.substring( latestDateUrl.lastIndexOf('/') + 1); } return latestDateUrl; } private String getPreviousUrl( String username, String baseUrl, String html) throws Exception { // Links to previous posts look like // baseUrl/go.bml?journal=& // itemid=&dir=prev String previousUrl = null; Pattern urlPattern = Pattern.compile( "/go\\.bml\\?journal="+username +"\\&itemid=\\d+\\&dir=prev"); Matcher urlMatcher = urlPattern.matcher(html); if (urlMatcher.find()) { previousUrl = baseUrl + urlMatcher.group(); previousUrl = previousUrl.replaceAll("&", "&"); } return previousUrl; } private void backupUserInfo( BackupParams params, File backupDir, ProgressDialog progressDlg) throws Exception { File backupUserInfoDir = new File(backupDir, "UserInfo"); progressDlg.update("Preparing to back up user info...", backupUserInfoDir); backupUserInfoDir.mkdir(); // The full user info url looks like // baseUrl/userinfo.bml?user=justmartin&mode=full String url = params.baseUrl + "/userinfo.bml?user=" + params.username + "&mode=full"; progressDlg.update("Downloading user info...", url); String html = getHtml(url, params.loginCookie); // Save page to disk. saveFile(backupUserInfoDir + "/userinfo.html", html, progressDlg, "Saving user info..."); // The edit user info url looks like baseUrl/editinfo.bml url = params.baseUrl + "/editinfo.bml"; progressDlg.update("Downloading modify user info...", url); html = getHtml(url, params.loginCookie); // Save page to disk. saveFile(backupUserInfoDir + "/editinfo.html", html, progressDlg, "Saving modify user info..."); } private void backupSettings( BackupParams params, File backupDir, ProgressDialog progressDlg) throws Exception { File backupSettingsDir = new File(backupDir, "Settings"); progressDlg.update("Preparing to back up journal settings...", backupSettingsDir); backupSettingsDir.mkdir(); // The modify journal url looks like // baseUrl/modify.bml String url = params.baseUrl + "/modify.bml"; progressDlg.update("Downloading journal settings...", url); String html = getHtml(url, params.loginCookie); // Save page to disk. saveFile(backupSettingsDir + "/modify.html", html, progressDlg, "Saving journal settings..."); } private String getSessionId(String username, String loginCookieStr) { // The cookie string contains // ljsession=ws::: String match = "ws:" + username + ":"; int start = loginCookieStr.indexOf(match) + match.length(); int end = loginCookieStr.indexOf(":", start); String sessionId = loginCookieStr.substring(start, end); return sessionId; } private String getHtml( String urlStr, String loginCookieStr) throws Exception { StringBuffer html = new StringBuffer(16384); URL url = new URL(urlStr); URLConnection connection = url.openConnection(); if (null != loginCookieStr) { connection.setRequestProperty("Cookie", loginCookieStr); } BufferedReader in = new BufferedReader( new InputStreamReader( connection.getInputStream())); String inputLine = null; while ((inputLine = in.readLine()) != null) html.append(inputLine).append('\n'); // add newline back in.close(); return html.toString(); } private void saveFile( String filePath, String content, ProgressDialog progressDlg, String prompt) throws Exception { File file = new File(filePath); progressDlg.update(prompt, file); file.createNewFile(); FileOutputStream os = new FileOutputStream(file); os.write(content.getBytes()); os.close(); } } public static class LjBackupApp extends JFrame { public LjBackupApp() { super("LJ Backup"); createAndShowGui(); } private void createAndShowGui() { GridBagLayout GBL = new GridBagLayout(); GridBagConstraints gc = new GridBagConstraints( 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0); JPanel cp = new JPanel(GBL); ActionListener okListener = new ActionListener() { public void actionPerformed(ActionEvent e) { doBackup(); } }; JPanel p = createLoginPanel(okListener); GBL.setConstraints(p, gc); cp.add(p); gc.gridy++; p = createOptionsPanel(); GBL.setConstraints(p, gc); cp.add(p); gc.gridy++; p = createLjSettingsPanel(); GBL.setConstraints(p, gc); cp.add(p); gc.gridy++; gc.weightx = 0.0; gc.fill = GridBagConstraints.NONE; gc.anchor = GridBagConstraints.EAST; p = createButtonsPanel(okListener); GBL.setConstraints(p, gc); cp.add(p); // When user presses "X" in top-right corner of window, just exit. addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); setContentPane(cp); setLocationRelativeTo(null); pack(); show(); } private JPanel createLoginPanel(ActionListener okListener) { GridBagLayout GBL = new GridBagLayout(); GridBagConstraints gc = new GridBagConstraints( 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0); JPanel p = new JPanel(GBL); p.setBorder(BorderFactory.createTitledBorder( "Login")); JLabel l = new JLabel("Username:"); GBL.setConstraints(l, gc); p.add(l); gc.gridy = 1; l = new JLabel("Password:"); GBL.setConstraints(l, gc); p.add(l); gc.gridx = 1; gc.gridy = 0; gc.weightx = 1.0; gc.fill = GridBagConstraints.HORIZONTAL; usernameField = new JTextField(20); usernameField.addActionListener(okListener); GBL.setConstraints(usernameField, gc); p.add(usernameField); gc.gridy = 1; passwordField = new JPasswordField(20); passwordField.addActionListener(okListener); GBL.setConstraints(passwordField, gc); p.add(passwordField); return p; } private JPanel createOptionsPanel() { GridBagLayout GBL = new GridBagLayout(); GridBagConstraints gc = new GridBagConstraints( 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0); JPanel p = new JPanel(GBL); p.setBorder(BorderFactory.createTitledBorder( "Backup Options")); JLabel l = new JLabel("Directory:"); GBL.setConstraints(l, gc); p.add(l); gc.gridx = 1; gc.weightx = 1.0; gc.fill = GridBagConstraints.HORIZONTAL; backupDirField = new JTextField( System.getProperty("user.home"), 25); GBL.setConstraints(backupDirField, gc); p.add(backupDirField); // TODO: add file chooser gc.gridx = 0; gc.gridy++; gc.gridwidth = 2; gc.insets = new Insets(5, 0, 5, 5); backupEntriesBox = new JCheckBox("Journal Entries"); backupEntriesBox.setSelected(true); GBL.setConstraints(backupEntriesBox, gc); p.add(backupEntriesBox); gc.gridy++; backupUserInfoBox = new JCheckBox("User Info"); backupUserInfoBox.setSelected(true); GBL.setConstraints(backupUserInfoBox, gc); p.add(backupUserInfoBox); gc.gridy++; backupSettingsBox = new JCheckBox("Journal Settings"); backupSettingsBox.setSelected(true); GBL.setConstraints(backupSettingsBox, gc); p.add(backupSettingsBox); return p; } private JPanel createLjSettingsPanel() { GridBagLayout GBL = new GridBagLayout(); GridBagConstraints gc = new GridBagConstraints( 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0); JPanel p = new JPanel(GBL); p.setBorder(BorderFactory.createTitledBorder( "LiveJournal Settings")); JLabel l = new JLabel("Base URL:"); GBL.setConstraints(l, gc); p.add(l); gc.gridx = 1; gc.weightx = 1.0; gc.fill = GridBagConstraints.HORIZONTAL; baseUrlField = new JTextField("http://www.livejournal.com"); GBL.setConstraints(baseUrlField, gc); p.add(baseUrlField); return p; } private JPanel createButtonsPanel(ActionListener okListener) { GridBagLayout GBL = new GridBagLayout(); GridBagConstraints gc = new GridBagConstraints( 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(10, 5, 5, 0), 0, 0); JPanel p = new JPanel(GBL); okButton = new JButton("Backup"); okButton.addActionListener(okListener); GBL.setConstraints(okButton, gc); p.add(okButton); gc.gridx++; cancelButton = new JButton(new AbstractAction("Exit") { public void actionPerformed(ActionEvent e) { doExit(); } }); GBL.setConstraints(cancelButton, gc); p.add(cancelButton); return p; } private void doBackup() { enableGui(false); setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); // Make sure login is valid. String loginResult = validateLogin(); if (null == loginResult) { // Login ok, start backup. // Create progress dialog, but don't show it yet. ProgressDialog progressDlg = new ProgressDialog(this); // Create and start backup thread. BackupThread backupThread = new BackupThread(this, progressDlg); backupThread.start(); // Backup is running, now show progress dialog. progressDlg.show(); // UI is blocked until backup thread finishes and hides // progress dialog, or when user presses Cancel button. if (progressDlg.cancelled() && false == progressDlg.handled()) { backupThread.interrupt(); } // All done, now user can backup more, or exit. enableGui(true); setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); if (progressDlg.cancelled()) { JOptionPane.showMessageDialog(this, "Backup cancelled."); } else { JOptionPane.showMessageDialog(this, "Backup completed."); } } else { // Report error and focus the correct field. enableGui(true); setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); if (loginResult.toUpperCase().indexOf("PASSWORD") >= 0) { passwordField.requestFocus(); passwordField.selectAll(); } else { usernameField.requestFocus(); usernameField.selectAll(); } JOptionPane.showMessageDialog(this, loginResult); } } private void doExit() { System.exit(0); } private String validateLogin() { String result = null; try { String username = usernameField.getText().trim(); String md5_password = encodeMd5( new String(passwordField.getPassword())); String baseUrl = baseUrlField.getText().trim(); Map loginResult = callServerMethod( username, md5_password, baseUrl, "login", "", false); // Specifying "getmoods=0" will return a moods list of the form // mood_33_parent=61, mood_33_name=lazy, mood_33_id=33, ... // Not necessary for a backup program. //callServerMethod(username, md5_password, baseUrl, "login", // "&getmenus=1&getpickws=1&getpickwurls=1", false); if (false == "OK".equals((String)loginResult.get("success"))) { result = (String)loginResult.get("errmsg"); } } catch (Exception e) { result = e.getMessage(); } return result; } private BackupParams getBackupParams() throws Exception { BackupParams params = new BackupParams(); params.backupDir = backupDirField.getText().trim(); params.backupEntries = backupEntriesBox.isSelected(); params.backupUserInfo = backupUserInfoBox.isSelected(); params.backupSettings = backupSettingsBox.isSelected(); params.baseUrl = baseUrlField.getText().trim(); params.username = usernameField.getText().trim(); String md5_password = encodeMd5( new String(passwordField.getPassword())); String challenge = getChallenge(params.username, params.baseUrl); String response = generateResponse(challenge, md5_password); params.loginCookie = getLoginCookieString( params.username, params.baseUrl, challenge, response); return params; } private String getLoginCookieString( String username, String baseUrl, String challenge, String response) throws Exception { boolean loggedIn = false; URL url = new URL(baseUrl + "/login.bml"); URLConnection connection = url.openConnection(); connection.setDoOutput(true); PrintWriter out = new PrintWriter(connection.getOutputStream()); out.println("user="+username+"&chal="+challenge +"&response="+response+"&bindip=no&expire=close"); out.close(); BufferedReader in = new BufferedReader( new InputStreamReader(connection.getInputStream())); String inputLine; while ((inputLine = in.readLine()) != null) { if (inputLine.indexOf(", you are now logged in to LiveJournal.com") >= 0) { loggedIn = true; break; } } in.close(); String loginCookieStr = null; if (loggedIn) { loginCookieStr = getCookies(connection); } else { throw new RuntimeException("Could not log in..."); } return loginCookieStr; } private String getCookies(URLConnection conn) { Map cookies = new HashMap(); // Get all cookies from the server. for (int i=0; ; i++) { String headerName = conn.getHeaderFieldKey(i); String headerValue = conn.getHeaderField(i); if (headerName == null && headerValue == null) { // No more headers break; } if ("Set-Cookie".equalsIgnoreCase(headerName)) { // Parse cookie String[] fields = headerValue.split(";\\s*"); String cookieValue = fields[0]; if (cookieValue.indexOf('=') > 0) { String[] v = cookieValue.split("="); if (v.length >= 2) cookies.put(v[0], v[1]); else cookies.put(v[0], ""); } else { cookies.put(cookieValue, null); } // Parse each field for (int j = 1; j < fields.length; j++) { if ("secure".equalsIgnoreCase(fields[j])) { cookies.put(fields[j], null); } else if (fields[j].indexOf('=') > 0) { String[] f = fields[j].split("="); cookies.put(f[0], f[1]); } } } } StringBuffer cookieStrBuf = new StringBuffer(); for (Iterator i = cookies.keySet().iterator(); i.hasNext(); ) { String key = (String)i.next(); String value = (String)cookies.get(key); cookieStrBuf.append(key); if (null != value) { cookieStrBuf.append("=").append(value); } cookieStrBuf.append("; "); } return cookieStrBuf.toString(); } private void enableGui(boolean enable) { usernameField.setEnabled(enable); passwordField.setEnabled(enable); backupDirField.setEnabled(enable); backupEntriesBox.setEnabled(enable); backupUserInfoBox.setEnabled(enable); backupSettingsBox.setEnabled(enable); baseUrlField.setEnabled(enable); okButton.setEnabled(enable); cancelButton.setEnabled(enable); } // Login fields. public JTextField usernameField; public JPasswordField passwordField; // Backup option fields. public JTextField backupDirField; public JCheckBox backupEntriesBox; public JCheckBox backupUserInfoBox; public JCheckBox backupSettingsBox; // LJ settings. public JTextField baseUrlField; // OK/Cancel buttons. private JButton okButton; private JButton cancelButton; } //////////////////////////////////////////////////////////////// // // Start alternative (not used) flat interface code. // public static void main2(String[] args) throws Exception { String username = ""; // TODO String md5_password = ""; // TODO String baseUrl = "http://www.livejournal.com"; do { // TODO: ask for username/password. // TODO: exit if user cancelled. // Make sure username/password combination is correct. } while (false == login(username, md5_password, baseUrl)); // These are the (possibly) interesting information a backup tool needs. friendOf(username, md5_password, baseUrl); getDayCounts(username, md5_password, baseUrl); getFriendGroups(username, md5_password, baseUrl); getFriends(username, md5_password, baseUrl); syncItems(username, md5_password, baseUrl); getEvents(username, md5_password, baseUrl); } private static boolean login( String username, String md5_password, String baseUrl) throws Exception { boolean result = true; try { callServerMethod(username, md5_password, baseUrl, "login", "", true); // Specifying "getmoods=0" will return a moods list of the form: // mood_33_parent=61, mood_33_name=lazy, mood_33_id=33, ... // Not necessary for a backup program. //callServerMethod(username, md5_password, baseUrl, "login", // "&getmenus=1&getpickws=1&getpickwurls=1", true); } catch (Exception e) { result = false; } return result; } private static void friendOf( String username, String md5_password, String baseUrl) throws Exception { Map result = callServerMethod(username, md5_password, baseUrl, "friendof", "", true); } private static void getDayCounts( String username, String md5_password, String baseUrl) throws Exception { Map result = callServerMethod(username, md5_password, baseUrl, "getdaycounts", "", true); } private static void getFriendGroups( String username, String md5_password, String baseUrl) throws Exception { Map result = callServerMethod(username, md5_password, baseUrl, "getfriendgroups", "", true); } private static void getFriends( String username, String md5_password, String baseUrl) throws Exception { Map result = callServerMethod(username, md5_password, baseUrl, "getfriends", "", true); } private static void syncItems( String username, String md5_password, String baseUrl) throws Exception { Map result = callServerMethod(username, md5_password, baseUrl, "syncitems", "", true); } private static void getEvents( String username, String md5_password, String baseUrl) throws Exception { Map result = callServerMethod(username, md5_password, baseUrl, "getevents", "&prefersubject=0&noprops=0" + "&selecttype=one&itemid=-1&lineendings=pc", true); } // // End alternative (not used) flat interface code. // //////////////////////////////////////////////////////////////// private static Map callServerMethod( String username, String md5_password, String baseUrl, String mode, String arguments, boolean throwIfError) throws Exception { // Get a challenge from the server. String challenge = getChallenge(username, baseUrl); // Generate the response to the challenge. String response = generateResponse(challenge, md5_password); // Call the server method. System.out.println("\n"+mode+"..."); // Note: need to have *something* after auth_response, otherwise // may complain about Invalid Password... Map result = processRequest(baseUrl, "mode=" + mode + "&auth_method=challenge&auth_challenge=" + challenge + "&auth_response=" + response + "&user=" + username + arguments, throwIfError); System.out.println(mode+" result: "+result); return result; } private static Map processRequest( String baseUrl, String request, boolean throwIfError) throws Exception { URL url = new URL(baseUrl + "/interface/flat"); URLConnection connection = url.openConnection(); connection.setDoOutput(true); PrintWriter out = new PrintWriter(connection.getOutputStream()); out.println(request); out.close(); Map result = new HashMap(); BufferedReader in = new BufferedReader( new InputStreamReader( connection.getInputStream())); String inputLine; while ((inputLine = in.readLine()) != null) { String key = inputLine; String value = in.readLine(); result.put(key, value); } in.close(); if (throwIfError && false == "OK".equals((String)result.get("success"))) { throw new RuntimeException("Request '" + request + "' failed: " + result.get("errmsg")); } return result; } private static String getChallenge( String username, String baseUrl) throws Exception { Map result = processRequest(baseUrl, "mode=getchallenge&user=" + username, true); String challenge = (String)result.get("challenge"); return challenge; } private static String generateResponse( String challenge, String md5_password) throws Exception { String response = encodeMd5(challenge + md5_password); return response; } private static String encodeMd5(String input) throws Exception { MessageDigest md = MessageDigest.getInstance( "MD5" ); md.update( input.getBytes() ); BigInteger hash = new BigInteger( 1, md.digest() ); return hash.toString( 16 ); } private static void debug(String s) { if (DEBUG) System.out.println(s); } //////////////////////////////////////////////////////////////// // // Start obsolete code. // private static String extractChallengeFromLoginPage( String baseUrl) throws Exception { String challenge = null; // Generate a challenge. The login page contains one. URL url = new URL(baseUrl + "/login.bml"); // Extract the challenge from the login page. BufferedReader in = new BufferedReader( new InputStreamReader( url.openStream())); String inputLine; while ((inputLine = in.readLine()) != null) if (inputLine.indexOf("id='login_chal'") >= 0) { int start = inputLine.indexOf("value='") + "value='".length(); int end = inputLine.indexOf("'", start); challenge = inputLine.substring(start, end); break; } in.close(); return challenge; } // // End obsolete code. // //////////////////////////////////////////////////////////////// }