Use gerrit-keyapplet to help users load their SSH public keys

This utility applet helps users navigate ~/.ssh and load an
SSH 2 style public key file into the text box, so they can
submit the key to Gerrit.

Signed-off-by: Shawn O. Pearce <sop@google.com>
diff --git a/pom.xml b/pom.xml
index dd15e6b..52cb1b0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -338,6 +338,13 @@
                   <outputDirectory>${project.build.directory}/executablewar</outputDirectory>
                   <includes>**/*.class</includes>
                 </artifactItem>
+                <artifactItem>
+                  <groupId>gerrit</groupId>
+                  <artifactId>gerrit-keyapplet</artifactId>
+                  <overWrite>true</overWrite>
+                  <outputDirectory>${project.build.directory}/gerrit-keyapplet</outputDirectory>
+                  <includes>**/*</includes>
+                </artifactItem>
               </artifactItems>
             </configuration>
           </execution>
@@ -371,6 +378,7 @@
             <configuration>
               <tasks>
                 <property name="d" location="${basedir}/target/${project.name}-${project.version}"/>
+                <property name="keyapplet" location="${basedir}/target/gerrit-keyapplet"/>
 
                 <copy todir="${d}">
                   <fileset dir="${basedir}/target/executablewar" includes="**/*"/>
@@ -399,6 +407,12 @@
                     <outputmapper type="glob" from="*" to="${d}/*.gz"/>
                   </redirector>
                 </apply>
+
+                <zip
+                    destfile="${d}/gerrit-keyapplet.cache.jar"
+                    compress="true">
+                  <fileset dir="${keyapplet}" includes="**/*"/>
+                </zip>
               </tasks>
             </configuration>
             <goals>
@@ -433,6 +447,13 @@
     </dependency>
 
     <dependency>
+      <groupId>gerrit</groupId>
+      <artifactId>gerrit-keyapplet</artifactId>
+      <version>1.0</version>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
       <groupId>com.dyuproject</groupId>
       <artifactId>dyuproject-openid</artifactId>
       <version>1.1.1</version>
diff --git a/src/main/java/com/google/gerrit/client/account/AccountConstants.java b/src/main/java/com/google/gerrit/client/account/AccountConstants.java
index 70300b8..49a5318 100644
--- a/src/main/java/com/google/gerrit/client/account/AccountConstants.java
+++ b/src/main/java/com/google/gerrit/client/account/AccountConstants.java
@@ -35,6 +35,7 @@
 
   String buttonDeleteSshKey();
   String buttonClearSshKeyInput();
+  String buttonOpenSshKey();
   String buttonAddSshKey();
 
   String sshKeyInvalid();
@@ -46,6 +47,7 @@
 
   String addSshKeyPanelHeader();
   String addSshKeyHelp();
+  String sshJavaAppletNotAvailable();
   String invalidSshKeyError();
 
   String webIdLastUsed();
diff --git a/src/main/java/com/google/gerrit/client/account/AccountConstants.properties b/src/main/java/com/google/gerrit/client/account/AccountConstants.properties
index b3d8bde..663e326 100644
--- a/src/main/java/com/google/gerrit/client/account/AccountConstants.properties
+++ b/src/main/java/com/google/gerrit/client/account/AccountConstants.properties
@@ -16,6 +16,7 @@
 
 buttonDeleteSshKey = Delete
 buttonClearSshKeyInput = Clear
+buttonOpenSshKey = Open Key ...
 buttonAddSshKey = Add
 
 sshKeyInvalid = Invalid Key
@@ -33,6 +34,7 @@
 addSshKeyPanelHeader = Add SSH Public Key
 addSshKeyHelp = (<a href="http://github.com/guides/providing-your-ssh-key" target="_blank">GitHub's Guide to SSH Keys</a>)
 invalidSshKeyError = Invalid SSH Key
+sshJavaAppletNotAvailable = Open Key Unavailable: Java not enabled
 
 watchedProjects = Watched Projects
 buttonWatchProject = Watch
diff --git a/src/main/java/com/google/gerrit/client/account/SshKeyPanel.java b/src/main/java/com/google/gerrit/client/account/SshKeyPanel.java
index ae520a2..1aacd24 100644
--- a/src/main/java/com/google/gerrit/client/account/SshKeyPanel.java
+++ b/src/main/java/com/google/gerrit/client/account/SshKeyPanel.java
@@ -16,17 +16,24 @@
 
 import com.google.gerrit.client.ErrorDialog;
 import com.google.gerrit.client.FormatUtil;
+import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.reviewdb.AccountSshKey;
 import com.google.gerrit.client.rpc.GerritCallback;
 import com.google.gerrit.client.rpc.InvalidSshKeyException;
 import com.google.gerrit.client.ui.FancyFlexTable;
 import com.google.gerrit.client.ui.SmallHeading;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.DeferredCommand;
 import com.google.gwt.user.client.ui.Button;
 import com.google.gwt.user.client.ui.CheckBox;
 import com.google.gwt.user.client.ui.ClickListener;
 import com.google.gwt.user.client.ui.Composite;
 import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.RootPanel;
 import com.google.gwt.user.client.ui.SourcesTableEvents;
 import com.google.gwt.user.client.ui.TableListener;
 import com.google.gwt.user.client.ui.TextArea;
@@ -40,10 +47,16 @@
 import java.util.List;
 
 class SshKeyPanel extends Composite {
+  private static boolean loadedApplet;
+  private static Element applet;
+  private static String appletErrorInvalidKey;
+  private static String appletErrorSecurity;
+
   private SshKeyTable keys;
 
   private Button clearNew;
   private Button addNew;
+  private Button browse;
   private TextArea addTxt;
   private Button delSel;
 
@@ -87,6 +100,15 @@
       });
       buttons.add(clearNew);
 
+      browse = new Button(Util.C.buttonOpenSshKey());
+      browse.addClickListener(new ClickListener() {
+        public void onClick(final Widget sender) {
+          doBrowse();
+        }
+      });
+      browse.setVisible(GWT.isScript() && (!loadedApplet || applet != null));
+      buttons.add(browse);
+
       addNew = new Button(Util.C.buttonAddSshKey());
       addNew.addClickListener(new ClickListener() {
         public void onClick(final Widget sender) {
@@ -100,6 +122,91 @@
     initWidget(body);
   }
 
+  void doBrowse() {
+    browse.setEnabled(false);
+    if (!loadedApplet) {
+      applet = DOM.createElement("applet");
+      applet.setAttribute("code",
+          "com.google.gerrit.keyapplet.ReadPublicKey.class");
+      applet.setAttribute("archive", GWT.getModuleBaseURL()
+          + "gerrit-keyapplet.cache.jar?v=" + Gerrit.getVersion());
+      applet.setAttribute("mayscript", "true");
+      applet.setAttribute("width", "0");
+      applet.setAttribute("height", "0");
+      RootPanel.getBodyElement().appendChild(applet);
+      loadedApplet = true;
+
+      // We have to defer to allow the event loop time to setup that
+      // new applet tag we just created above, and actually load the
+      // applet into the runtime.
+      //
+      DeferredCommand.addCommand(new Command() {
+        public void execute() {
+          doBrowse();
+        }
+      });
+      return;
+    }
+    if (applet == null) {
+      // If the applet element is null, the applet was determined
+      // to have failed to load, and we are dead. Hide the button.
+      //
+      noBrowse();
+      return;
+    }
+
+    String txt;
+    try {
+      txt = openPublicKey(applet);
+    } catch (RuntimeException re) {
+      // If this call fails, the applet is dead. It is most likely
+      // not loading due to Java support being disabled.
+      //
+      noBrowse();
+      return;
+    }
+    if (txt == null) {
+      txt = "";
+    }
+
+    browse.setEnabled(true);
+
+    if (appletErrorInvalidKey == null) {
+      appletErrorInvalidKey = getErrorInvalidKey(applet);
+      appletErrorSecurity = getErrorSecurity(applet);
+    }
+
+    if (appletErrorInvalidKey.equals(txt)) {
+      new ErrorDialog(Util.C.invalidSshKeyError()).center();
+      return;
+    }
+    if (appletErrorSecurity.equals(txt)) {
+      new ErrorDialog(Util.C.invalidSshKeyError()).center();
+      return;
+    }
+
+    addTxt.setText(txt);
+    addNew.setFocus(true);
+  }
+
+  private void noBrowse() {
+    if (applet != null) {
+      applet.getParentElement().removeChild(applet);
+      applet = null;
+    }
+    browse.setVisible(false);
+    new ErrorDialog(Util.C.sshJavaAppletNotAvailable()).center();
+  }
+
+  private static native String openPublicKey(Element keyapp)
+  /*-{ var r = keyapp.openPublicKey(); return r == null ? null : ''+r; }-*/;
+
+  private static native String getErrorInvalidKey(Element keyapp)
+  /*-{ return ''+keyapp.getErrorInvalidKey(); }-*/;
+
+  private static native String getErrorSecurity(Element keyapp)
+  /*-{ return ''+keyapp.getErrorSecurity(); }-*/;
+
   void doAddNew() {
     final String txt = addTxt.getText();
     if (txt != null && txt.length() > 0) {