From 585077e751107729b7a2dd495a3b17c677a3f528 Mon Sep 17 00:00:00 2001
From: Luke Granger-Brown <git@lukegb.com>
Date: Thu, 2 Jul 2020 23:03:02 +0100
Subject: [PATCH 3/3] Add titles to CLs over HTTP

---
 .../gerrit/httpd/raw/IndexHtmlUtil.java       | 13 +++-
 .../google/gerrit/httpd/raw/IndexServlet.java |  8 ++-
 .../google/gerrit/httpd/raw/StaticModule.java |  5 +-
 .../gerrit/httpd/raw/TitleComputer.java       | 67 +++++++++++++++++++
 .../gerrit/httpd/raw/PolyGerritIndexHtml.soy  |  4 +-
 5 files changed, 89 insertions(+), 8 deletions(-)
 create mode 100644 java/com/google/gerrit/httpd/raw/TitleComputer.java

diff --git a/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java b/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java
index ce22ae8e59..952ba1fef4 100644
--- a/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java
+++ b/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java
@@ -40,6 +40,7 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.function.Function;
 
@@ -61,13 +62,14 @@ public class IndexHtmlUtil {
       String faviconPath,
       Map<String, String[]> urlParameterMap,
       Function<String, SanitizedContent> urlInScriptTagOrdainer,
-      String requestedURL)
+      String requestedURL,
+      TitleComputer titleComputer)
       throws URISyntaxException, RestApiException {
     ImmutableMap.Builder<String, Object> data = ImmutableMap.builder();
     data.putAll(
             staticTemplateData(
                 canonicalURL, cdnPath, faviconPath, urlParameterMap, urlInScriptTagOrdainer))
-        .putAll(dynamicTemplateData(gerritApi, requestedURL));
+        .putAll(dynamicTemplateData(gerritApi, requestedURL, titleComputer));
     Set<String> enabledExperiments = experimentFeatures.getEnabledExperimentFeatures();
 
     if (!enabledExperiments.isEmpty()) {
@@ -78,7 +80,8 @@ public class IndexHtmlUtil {
 
   /** Returns dynamic parameters of {@code index.html}. */
   public static ImmutableMap<String, Object> dynamicTemplateData(
-      GerritApi gerritApi, String requestedURL) throws RestApiException, URISyntaxException {
+      GerritApi gerritApi, String requestedURL, TitleComputer titleComputer)
+		  throws RestApiException, URISyntaxException {
     ImmutableMap.Builder<String, Object> data = ImmutableMap.builder();
     Map<String, SanitizedContent> initialData = new HashMap<>();
     Server serverApi = gerritApi.config().server();
@@ -131,6 +134,10 @@ public class IndexHtmlUtil {
     }
 
     data.put("gerritInitialData", initialData);
+
+    Optional<String> title = titleComputer.computeTitle(requestedURL);
+    title.ifPresent(s -> data.put("title", s));
+
     return data.build();
   }
 
diff --git a/java/com/google/gerrit/httpd/raw/IndexServlet.java b/java/com/google/gerrit/httpd/raw/IndexServlet.java
index fcb821e5ae..36eb0c990c 100644
--- a/java/com/google/gerrit/httpd/raw/IndexServlet.java
+++ b/java/com/google/gerrit/httpd/raw/IndexServlet.java
@@ -48,13 +48,15 @@ public class IndexServlet extends HttpServlet {
   private final ExperimentFeatures experimentFeatures;
   private final SoySauce soySauce;
   private final Function<String, SanitizedContent> urlOrdainer;
+  private TitleComputer titleComputer;
 
   IndexServlet(
       @Nullable String canonicalUrl,
       @Nullable String cdnPath,
       @Nullable String faviconPath,
       GerritApi gerritApi,
-      ExperimentFeatures experimentFeatures) {
+      ExperimentFeatures experimentFeatures,
+      TitleComputer titleComputer) {
     this.canonicalUrl = canonicalUrl;
     this.cdnPath = cdnPath;
     this.faviconPath = faviconPath;
@@ -69,6 +71,7 @@ public class IndexServlet extends HttpServlet {
         (s) ->
             UnsafeSanitizedContentOrdainer.ordainAsSafe(
                 s, SanitizedContent.ContentKind.TRUSTED_RESOURCE_URI);
+    this.titleComputer = titleComputer;
   }
 
   @Override
@@ -86,7 +89,8 @@ public class IndexServlet extends HttpServlet {
               faviconPath,
               parameterMap,
               urlOrdainer,
-              getRequestUrl(req));
+              getRequestUrl(req),
+	      titleComputer);
       renderer = soySauce.renderTemplate("com.google.gerrit.httpd.raw.Index").setData(templateData);
     } catch (URISyntaxException | RestApiException e) {
       throw new IOException(e);
diff --git a/java/com/google/gerrit/httpd/raw/StaticModule.java b/java/com/google/gerrit/httpd/raw/StaticModule.java
index 8e8a9d27f0..a0fb168554 100644
--- a/java/com/google/gerrit/httpd/raw/StaticModule.java
+++ b/java/com/google/gerrit/httpd/raw/StaticModule.java
@@ -226,10 +226,11 @@ public class StaticModule extends ServletModule {
         @CanonicalWebUrl @Nullable String canonicalUrl,
         @GerritServerConfig Config cfg,
         GerritApi gerritApi,
-        ExperimentFeatures experimentFeatures) {
+        ExperimentFeatures experimentFeatures,
+	TitleComputer titleComputer) {
       String cdnPath = options.devCdn().orElse(cfg.getString("gerrit", null, "cdnPath"));
       String faviconPath = cfg.getString("gerrit", null, "faviconPath");
-      return new IndexServlet(canonicalUrl, cdnPath, faviconPath, gerritApi, experimentFeatures);
+      return new IndexServlet(canonicalUrl, cdnPath, faviconPath, gerritApi, experimentFeatures, titleComputer);
     }
 
     @Provides
diff --git a/java/com/google/gerrit/httpd/raw/TitleComputer.java b/java/com/google/gerrit/httpd/raw/TitleComputer.java
new file mode 100644
index 0000000000..8fd2053ad0
--- /dev/null
+++ b/java/com/google/gerrit/httpd/raw/TitleComputer.java
@@ -0,0 +1,67 @@
+package com.google.gerrit.httpd.raw;
+
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.server.change.ChangeResource;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.restapi.change.ChangesCollection;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@Singleton
+public class TitleComputer {
+  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+  @Inject
+  public TitleComputer(Provider<ChangesCollection> changes) {
+    this.changes = changes;
+  }
+
+  public Optional<String> computeTitle(String requestedURI) {
+    URL url = null;
+    try {
+      url = new URL(requestedURI);
+    } catch (MalformedURLException e) {
+      logger.atWarning().log("Failed to turn %s into a URL.", requestedURI);
+      return Optional.empty();
+    }
+
+    // Try to turn this into a change.
+    Optional<Change.Id> changeId = tryExtractChange(url.getPath());
+    if (changeId.isPresent()) {
+      return titleFromChangeId(changeId.get());
+    }
+
+    return Optional.empty();
+  }
+
+  private static final Pattern extractChangeIdRegex = Pattern.compile("^/(?:c/.*/\\+/)?(?<changeId>[0-9]+)(?:/[0-9]+)?(?:/.*)?$");
+  private final Provider<ChangesCollection> changes;
+
+  private Optional<Change.Id> tryExtractChange(String path) {
+    Matcher m = extractChangeIdRegex.matcher(path);
+    if (!m.matches()) {
+      return Optional.empty();
+    }
+    return Change.Id.tryParse(m.group("changeId"));
+  }
+
+  private Optional<String> titleFromChangeId(Change.Id changeId) {
+    ChangesCollection changesCollection = changes.get();
+    try {
+      ChangeResource changeResource = changesCollection.parse(changeId);
+      return Optional.of(changeResource.getChange().getSubject());
+    } catch (ResourceConflictException | ResourceNotFoundException | PermissionBackendException e) {
+      return Optional.empty();
+    }
+  }
+}
diff --git a/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy b/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy
index 8c97a49e81..129092dc7e 100644
--- a/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy
+++ b/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy
@@ -32,10 +32,12 @@
   {@param? defaultDashboardHex: ?}
   {@param? dashboardQuery: ?}
   {@param? userIsAuthenticated: ?}
+  {@param? title: ?}
   <!DOCTYPE html>{\n}
   <html lang="en">{\n}
   <meta charset="utf-8">{\n}
-  <meta name="description" content="Gerrit Code Review">{\n}
+  {if $title}<title>{$title} · Gerrit Code Review</title>{\n}{/if}
+  <meta name="description" content="{if $title}{$title} · {/if}Gerrit Code Review">{\n}
   <meta name="referrer" content="never">{\n}
   <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">{\n}
 
-- 
2.36.0

