Merge "Save cookies in the credentials file, not passwords."
diff --git a/pom.xml b/pom.xml
index 4d114a7..50a4e09 100644
--- a/pom.xml
+++ b/pom.xml
@@ -94,21 +94,7 @@
 				<groupId>org.codehaus.mojo</groupId>
 				<artifactId>exec-maven-plugin</artifactId>
 				<version>1.1.1</version>
-				<executions>
-					<execution>
-						<phase>prepare-package</phase>
-						<goals>
-							<goal>java</goal>
-						</goals>
-						<configuration>
-							<mainClass>com.google.gwt.site.uploader.Uploader</mainClass>
-							<arguments>
-								<argument>${project.build.directory}/gwt-site-unpack</argument>
-								<argument>${basedir}/credentials</argument>
-							</arguments>
-						</configuration>
-					</execution>
-				</executions>
+
 			</plugin>
 
 		</plugins>
diff --git a/save_credentials.sh b/save_credentials.sh
new file mode 100755
index 0000000..278851c
--- /dev/null
+++ b/save_credentials.sh
@@ -0,0 +1,6 @@
+#!/bin/bash -e
+#
+# Run this command to save your account's cookie to 'credentials'
+
+mvn -q compile
+mvn -q exec:java -Dexec.mainClass=com.google.gwt.site.uploader.SaveCredentials
diff --git a/src/main/java/com/google/gwt/site/uploader/CredentialsProvider.java b/src/main/java/com/google/gwt/site/uploader/CredentialsProvider.java
index 37ddcdf..1f9e154 100644
--- a/src/main/java/com/google/gwt/site/uploader/CredentialsProvider.java
+++ b/src/main/java/com/google/gwt/site/uploader/CredentialsProvider.java
@@ -14,75 +14,76 @@
 
 package com.google.gwt.site.uploader;
 
+import com.google.appengine.tools.remoteapi.RemoteApiOptions;
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
+
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.IOException;
-import java.util.Properties;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import org.apache.commons.io.IOUtils;
-
-import com.google.gwt.site.uploader.model.Credentials;
-
 public class CredentialsProvider {
 
   private static final Logger logger = Logger.getLogger(CredentialsProvider.class.getName());
 
-  public Credentials readCredentialsFromFile(String credentialsFile) {
-    FileInputStream inputStream = null;
+  public RemoteApiOptions readCredentialsFromFile(String credentialsFile) throws IOException {
 
+    String serialized = Files.toString(new File(credentialsFile), Charsets.UTF_8);
+    Map<String, List<String>> props = parseProperties(serialized);
+
+    checkOneProperty(props, "host");
+    checkOneProperty(props, "email");
+
+    String host = props.get("host").get(0);
+    String email = props.get("email").get(0);
+
+    int port = 443;
     try {
-      inputStream = new FileInputStream(new File(credentialsFile));
-      Properties properties = new Properties();
-      properties.load(inputStream);
-      String username = properties.getProperty("username");
-      if (username == null) {
-
-        logger.log(Level.SEVERE, "No username found in credentials file, are you missing username=something");
-
-        throw new RuntimeException(
-            "No username found in credentials file, are you missing username=something");
+      if (props.containsKey("port")) {
+        checkOneProperty(props, "port");
+        port = Integer.parseInt(props.get("port").get(0));
       }
-
-      String password = properties.getProperty("password");
-      if (password == null) {
-
-        logger.log(Level.SEVERE, "No password found in credentials file, are you missing password=something");
-
-        throw new RuntimeException(
-            "No password found in credentials file, are you missing password=something");
-      }
-
-      String host = properties.getProperty("host");
-      if (host == null) {
-        logger.log(Level.SEVERE, "No host found in credentials file, are you missing host=something");
-
-        throw new RuntimeException(
-            "No host found in credentials file, are you missing host=something");
-      }
-
-      String portString = properties.getProperty("port");
-      if (portString == null) {
-        logger.log(Level.SEVERE, "No port found in credentials file, are you missing port=something");
-
-        throw new RuntimeException(
-            "No port found in credentials file, are you missing port=something");
-      }
-      try {
-        int port = Integer.parseInt(portString);
-        return new Credentials(host, port, username, password);
-      } catch (NumberFormatException e) {
-        logger.log(Level.SEVERE, "error while parsing port", e);
-        throw new RuntimeException("error while parsing port");
-      }
-
-    } catch (IOException e) {
-      logger.log(Level.SEVERE, "can not load credential files", e);
-
-      throw new RuntimeException("can not load credential files", e);
-    } finally {
-      IOUtils.closeQuietly(inputStream);
+    } catch (NumberFormatException e) {
+      logger.log(Level.SEVERE, "error while parsing port", e);
+      throw new RuntimeException("error while parsing port");
     }
+
+    return new RemoteApiOptions()
+        .server(host, port)
+        .reuseCredentials(email, serialized);
+  }
+
+  // taken from RemoteApiInstaller
+  private static void checkOneProperty(Map<String, List<String>> props, String key)
+      throws IOException {
+    if (props.get(key).size() != 1) {
+      String message = "invalid credential file (should have one property named '" + key + "')";
+      throw new IOException(message);
+    }
+  }
+
+  // taken from RemoteApiInstaller
+  private static Map<String, List<String>> parseProperties(String serializedCredentials) {
+    Map<String, List<String>> props = new HashMap<String, List<String>>();
+    for (String line : serializedCredentials.split("\n")) {
+      line = line.trim();
+      if (!line.startsWith("#") && line.contains("=")) {
+        int firstEqual = line.indexOf('=');
+        String key = line.substring(0, firstEqual);
+        String value = line.substring(firstEqual + 1);
+        List<String> values = props.get(key);
+        if (values == null) {
+          values = new ArrayList<String>();
+          props.put(key, values);
+        }
+        values.add(value);
+      }
+    }
+    return props;
   }
 }
diff --git a/src/main/java/com/google/gwt/site/uploader/ResourceUploaderAppEngineImpl.java b/src/main/java/com/google/gwt/site/uploader/ResourceUploaderAppEngineImpl.java
index 93b017c..a8fc602 100644
--- a/src/main/java/com/google/gwt/site/uploader/ResourceUploaderAppEngineImpl.java
+++ b/src/main/java/com/google/gwt/site/uploader/ResourceUploaderAppEngineImpl.java
@@ -14,17 +14,11 @@
 
 package com.google.gwt.site.uploader;
 
-import com.google.appengine.api.datastore.DatastoreService;
-import com.google.appengine.api.datastore.Entity;
-import com.google.appengine.api.datastore.Key;
-import com.google.appengine.api.datastore.KeyFactory;
-import com.google.appengine.api.datastore.Text;
+import com.google.appengine.api.datastore.*;
 import com.google.appengine.repackaged.com.google.api.client.util.Base64;
 import com.google.appengine.tools.remoteapi.RemoteApiInstaller;
 import com.google.appengine.tools.remoteapi.RemoteApiOptions;
-import com.google.gwt.site.uploader.model.Credentials;
 import com.google.gwt.site.uploader.model.Resource;
-
 import org.apache.commons.io.IOUtils;
 import org.json.JSONArray;
 import org.json.JSONException;
@@ -61,13 +55,13 @@
   private static final String DOC_MODEL = "DocModel";
   private static final Logger logger = Logger.getLogger(ResourceUploaderAppEngineImpl.class
       .getName());
-  private final Credentials credentials;
+  private final RemoteApiOptions credentials;
   private final DatastoreService ds;
   private final RemoteApiInstaller installer;
   private boolean isInitialized;
   private final KeyProvider keyProvider;
 
-  public ResourceUploaderAppEngineImpl(DatastoreService ds, Credentials credentials,
+  public ResourceUploaderAppEngineImpl(DatastoreService ds, RemoteApiOptions credentials,
       RemoteApiInstaller installer, KeyProvider keyProvider) {
     this.ds = ds;
     this.credentials = credentials;
@@ -198,7 +192,7 @@
       protocol = "https://";
     }
 
-    return protocol + credentials.getHost() + ":" + credentials.getPort() + "/hash?count=" + count;
+    return protocol + credentials.getHostname() + ":" + credentials.getPort() + "/hash?count=" + count;
   }
 
   @Override
@@ -206,16 +200,8 @@
     if (isInitialized) {
       throw new IllegalStateException("app engine remote api was already initialized");
     }
-    String username = credentials.getUsername();
-    String password = credentials.getPassword();
-    String host = credentials.getHost();
-    int port = credentials.getPort();
-
-    RemoteApiOptions options =
-        new RemoteApiOptions().server(host, port).credentials(username, password);
-
     try {
-      installer.install(options);
+      installer.install(credentials);
       isInitialized = true;
     } catch (IOException e) {
       logger.log(Level.SEVERE, "can not initialize app engine", e);
diff --git a/src/main/java/com/google/gwt/site/uploader/SaveCredentials.java b/src/main/java/com/google/gwt/site/uploader/SaveCredentials.java
new file mode 100644
index 0000000..713905a
--- /dev/null
+++ b/src/main/java/com/google/gwt/site/uploader/SaveCredentials.java
@@ -0,0 +1,63 @@
+package com.google.gwt.site.uploader;
+
+import com.google.appengine.api.datastore.DatastoreServiceFactory;
+import com.google.appengine.repackaged.com.google.common.io.Files;
+import com.google.appengine.tools.remoteapi.RemoteApiInstaller;
+import com.google.appengine.tools.remoteapi.RemoteApiOptions;
+import com.google.common.base.Charsets;
+
+import java.io.File;
+import java.io.IOException;
+
+public class SaveCredentials {
+
+  public static void main(String[] args) {
+    try {
+      run();
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+
+  public static void run() throws IOException {
+    if (System.console() == null) {
+      System.out.println("System.console not available. Please re-run from a shell.");
+      System.exit(1);
+    }
+
+    File f = new File("credentials");
+    if (!f.exists()) {
+      p("Expected a file named 'credentials' to exist.");
+      p("It should be in gwt-site-uploader.");
+      p("Please make sure you're in the right directory and touch the file if it's not there.");
+      System.exit(1);
+    }
+    if (!f.canWrite()) {
+      p("Can't write to credentials.");
+      System.exit(1);
+    }
+
+    p("Please enter your gwtproject.org credentials.");
+
+    String email = System.console().readLine("Email: ");
+    char[] password = System.console().readPassword("Password: ");
+
+    RemoteApiOptions options =
+        new RemoteApiOptions().server("gwtproject-demo.appspot.com", 443).credentials(email, new String(password));
+    RemoteApiInstaller installer = new RemoteApiInstaller();
+    installer.install(options);
+    // test the connection
+    DatastoreServiceFactory.getDatastoreService().allocateIds("foo", 1);
+
+    String creds = installer.serializeCredentials();
+    Files.write(creds, f, Charsets.UTF_8);
+    System.out.println("Credentials saved.");
+
+    installer.uninstall();
+  }
+
+
+  private static void p(String s) {
+    System.out.println(s);
+  }
+}
diff --git a/src/main/java/com/google/gwt/site/uploader/Uploader.java b/src/main/java/com/google/gwt/site/uploader/Uploader.java
index dab1d47..9bfedf1 100644
--- a/src/main/java/com/google/gwt/site/uploader/Uploader.java
+++ b/src/main/java/com/google/gwt/site/uploader/Uploader.java
@@ -17,33 +17,29 @@
 import com.google.appengine.api.datastore.DatastoreService;
 import com.google.appengine.api.datastore.DatastoreServiceFactory;
 import com.google.appengine.tools.remoteapi.RemoteApiInstaller;
-import com.google.gwt.site.uploader.model.Credentials;
+import com.google.appengine.tools.remoteapi.RemoteApiOptions;
 
 import java.io.File;
+import java.io.IOException;
 import java.util.logging.Logger;
 
 public class Uploader {
 
   private static final Logger logger = Logger.getLogger(Uploader.class.getName());
 
-  public static void main(String[] args) {
+  public static void main(String[] args) throws IOException {
 
     if (args.length != 2) {
-      System.out.println("Usage Uploader <filesDir> <credentialsFile>");
-      throw new IllegalArgumentException("Usage Uploader <filesDir> <credentialsFile>");
+      System.out.println("Usage Uploader <filesDir> <credentialsFile>|localhost");
+      throw new IllegalArgumentException("Usage Uploader <filesDir> <credentialsFile>|localhost");
     }
 
     String filesDir = args[0];
     logger.info("files directory: '" + filesDir + "'");
 
-    String credentialsFile = args[1];
-    logger.info("credentials file: '" + credentialsFile + "'");
-
-    CredentialsProvider credentialsProvider = new CredentialsProvider();
-    Credentials credentials = credentialsProvider.readCredentialsFromFile(credentialsFile);
+    RemoteApiOptions credentials = loadCredentials(args[1]);
 
     DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
-
     RemoteApiInstaller installer = new RemoteApiInstaller();
 
     HashCalculator hashCalculator = new HashCalculatorSha1Impl();
@@ -56,4 +52,14 @@
     UploadController uploadController = new UploadController(fileTraverser, fileUploader);
     uploadController.uploadOutdatedFiles();
   }
+
+  private static RemoteApiOptions loadCredentials(String fileOrLocalhost) throws IOException {
+    if (fileOrLocalhost.equals("localhost")) {
+      // special case for dev server
+      return new RemoteApiOptions().server("localhost", 8080).credentials("nobody@google.com", "ignored");
+    } else {
+      logger.info("credentials file: '" + fileOrLocalhost + "'");
+      return new CredentialsProvider().readCredentialsFromFile(fileOrLocalhost);
+    }
+  }
 }
diff --git a/src/main/java/com/google/gwt/site/uploader/model/Credentials.java b/src/main/java/com/google/gwt/site/uploader/model/Credentials.java
deleted file mode 100644
index 2438983..0000000
--- a/src/main/java/com/google/gwt/site/uploader/model/Credentials.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2013 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.site.uploader.model;
-
-public class Credentials {
-
-  private final String host;
-  private final String username;
-  private final String password;
-  private final int port;
-
-  public Credentials(String host, int port, String username, String password) {
-    this.host = host;
-    this.port = port;
-    this.username = username;
-    this.password = password;
-  }
-
-  public String getHost() {
-    return host;
-  }
-
-  public String getUsername() {
-    return username;
-  }
-
-  public String getPassword() {
-    return password;
-  }
-
-  public int getPort() {
-    return port;
-  }
-}
diff --git a/src/test/java/com/google/gwt/site/uploader/ResourceUploaderTest.java b/src/test/java/com/google/gwt/site/uploader/ResourceUploaderTest.java
index 7541b16..ec7fd10 100644
--- a/src/test/java/com/google/gwt/site/uploader/ResourceUploaderTest.java
+++ b/src/test/java/com/google/gwt/site/uploader/ResourceUploaderTest.java
@@ -21,7 +21,6 @@
 import com.google.appengine.tools.remoteapi.RemoteApiInstaller;
 import com.google.appengine.tools.remoteapi.RemoteApiOptions;
 import com.google.gwt.site.uploader.ResourceUploaderAppEngineImpl.KeyProvider;
-import com.google.gwt.site.uploader.model.Credentials;
 
 import org.apache.commons.io.IOUtils;
 import org.junit.Assert;
@@ -43,14 +42,13 @@
 public class ResourceUploaderTest {
 
   private DatastoreService ds;
-  private Credentials credentials;
   private RemoteApiInstaller remoteApiInstaller;
   private ResourceUploaderAppEngineImpl resourceUploader;
   private KeyProvider keyProvider;
 
   @Before
   public void setup() {
-    credentials = new Credentials("host", 80, "user", "pw");
+    RemoteApiOptions credentials = new RemoteApiOptions();
     ds = Mockito.mock(DatastoreService.class);
 
     remoteApiInstaller = Mockito.mock(RemoteApiInstaller.class);
diff --git a/upload.sh b/upload.sh
new file mode 100755
index 0000000..74015d8
--- /dev/null
+++ b/upload.sh
@@ -0,0 +1,7 @@
+#!/bin/bash -e
+#
+# Run with 'localhost' to deploy locally.
+# Run with 'credentials' (after running save_credentials.sh) to deploy to prod.
+
+mvn -q compile generate-resources
+mvn -q exec:java -Dexec.mainClass=com.google.gwt.site.uploader.Uploader -Dexec.args="target/gwt-site-unpack $1"