Add alternative URL for Gerrit's managed Gitweb.

This patch allows configuration of alternative base URL for:
- "(gitweb)" links in Gerrit,
- all links in Gerrit's managed Gitweb.

Please note that this changes only URL of the links and not real URL
under which Gerrit's managed Gitweb is served ("<gerrit>/gitweb").
This means that this feature is useful only in setups with front-end
web servers.

To enable this feature, set both: gitweb.cgi and gitweb.url.

Change-Id: I869e2c0e151deb0e0248ed86b9611e1e5ac7e431
Signed-off-by: Piotr Sikora <piotr.sikora@frickle.com>
diff --git a/Documentation/config-gitweb.txt b/Documentation/config-gitweb.txt
index eb43733..a08eb87 100644
--- a/Documentation/config-gitweb.txt
+++ b/Documentation/config-gitweb.txt
@@ -21,6 +21,18 @@
 
 ====
   git config --file $site_path/etc/gerrit.config gitweb.cgi /usr/lib/cgi-bin/gitweb.cgi
+  git config --file $site_path/etc/gerrit.config --unset gitweb.url
+====
+
+Alternatively, if Gerrit is served behind reverse proxy, it can
+generate different URLs for gitweb's links (they need to be
+rewritten to `<gerrit>/gitweb?args` on the web server). This allows
+for serving gitweb under different URL than the Gerrit instance.
+To enable this feature, set both: `gitweb.cgi` and `gitweb.url`.
+
+====
+  git config --file $site_path/etc/gerrit.config gitweb.cgi /usr/lib/cgi-bin/gitweb.cgi
+  git config --file $site_path/etc/gerrit.config gitweb.url /pretty/path/to/gitweb
 ====
 
 After updating `'$site_path'/etc/gerrit.config`, the Gerrit server must
@@ -69,6 +81,7 @@
 
 ====
   git config --file $site_path/etc/gerrit.config gitweb.url http://example.com/gitweb.cgi
+  git config --file $site_path/etc/gerrit.config --unset gitweb.cgi
 ====
 
 After updating `'$site_path'/etc/gerrit.config`, the Gerrit server must
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitWebConfig.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitWebConfig.java
index 57a4b90..b35fe6e 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitWebConfig.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitWebConfig.java
@@ -69,7 +69,7 @@
       return;
     }
 
-    if (cfgUrl != null) {
+    if ((cfgUrl != null) && (cfgCgi == null || cfgCgi.isEmpty())) {
       // Use an externally managed gitweb instance, and not an internal one.
       //
       url = cfgUrl;
@@ -126,7 +126,11 @@
       }
     }
 
-    url = cgi != null ? "gitweb" : null;
+    if (cfgUrl == null || cfgUrl.isEmpty()) {
+      url = cgi != null ? "gitweb" : null;
+    } else {
+      url = cgi != null ? cfgUrl : null;
+    }
     gitweb_cgi = cgi;
     gitweb_css = css;
     gitweb_js = js;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java
index 2d7ed09..d2adaf7 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java
@@ -57,6 +57,8 @@
 import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.net.URLDecoder;
 import java.util.Enumeration;
 import java.util.HashMap;
@@ -78,6 +80,7 @@
   private final Set<String> deniedActions;
   private final int bufferSize = 8192;
   private final File gitwebCgi;
+  private final URI gitwebUrl;
   private final LocalDiskRepositoryManager repoManager;
   private final ProjectControl.Factory projectControl;
   private final EnvList _env;
@@ -92,6 +95,19 @@
     this.gitwebCgi = gitWebConfig.getGitwebCGI();
     this.deniedActions = new HashSet<String>();
 
+    final String url = gitWebConfig.getUrl();
+    if ((url != null) && (!url.equals("gitweb"))) {
+      URI uri = null;
+      try {
+        uri = new URI(url);
+      } catch (URISyntaxException e) {
+        log.error("Invalid gitweb.url: " + url);
+      }
+      gitwebUrl = uri;
+    } else {
+      gitwebUrl = null;
+    }
+
     deniedActions.add("forks");
     deniedActions.add("opml");
     deniedActions.add("project_list");
@@ -487,6 +503,42 @@
     }
     env.set("REMOTE_USER", remoteUser);
 
+    // Override CGI settings using alternative URI provided by gitweb.url.
+    // This is required to trick Gitweb into thinking that it's served under
+    // different URL. Setting just $my_uri on the perl's side isn't enough,
+    // because few actions (atom, blobdiff_plain, commitdiff_plain) rely on
+    // URL returned by $cgi->self_url().
+    //
+    if (gitwebUrl != null) {
+      int schemePort = -1;
+
+      if (gitwebUrl.getScheme() != null) {
+        if (gitwebUrl.getScheme().equals("http")) {
+          env.set("HTTPS", "OFF");
+          schemePort = 80;
+        } else {
+          env.set("HTTPS", "ON");
+          schemePort = 443;
+        }
+      }
+
+      if (gitwebUrl.getHost() != null) {
+        env.set("SERVER_NAME", gitwebUrl.getHost());
+        env.set("HTTP_HOST", gitwebUrl.getHost());
+      }
+
+      if (gitwebUrl.getPort() != -1) {
+        env.set("SERVER_PORT", Integer.toString(gitwebUrl.getPort()));
+      } else if (schemePort != -1) {
+        env.set("SERVER_PORT", Integer.toString(schemePort));
+      }
+
+      if (gitwebUrl.getPath() != null) {
+        env.set("SCRIPT_NAME", gitwebUrl.getPath().isEmpty()
+                               ? "/" : gitwebUrl.getPath());
+      }
+    }
+
     return env.getEnvArray();
   }