Line data Source code
1 : // Copyright (C) 2019 The Android Open Source Project
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // http://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 :
15 : package com.google.gerrit.httpd.raw;
16 :
17 : import static com.google.gerrit.httpd.raw.IndexPreloadingUtil.RequestedPage;
18 : import static com.google.template.soy.data.ordainers.GsonOrdainer.serializeObject;
19 : import static java.util.stream.Collectors.toSet;
20 :
21 : import com.google.common.base.Strings;
22 : import com.google.common.collect.ImmutableMap;
23 : import com.google.common.flogger.FluentLogger;
24 : import com.google.gerrit.common.Nullable;
25 : import com.google.gerrit.common.UsedAt;
26 : import com.google.gerrit.common.UsedAt.Project;
27 : import com.google.gerrit.extensions.api.GerritApi;
28 : import com.google.gerrit.extensions.api.accounts.AccountApi;
29 : import com.google.gerrit.extensions.api.config.Server;
30 : import com.google.gerrit.extensions.client.ListOption;
31 : import com.google.gerrit.extensions.restapi.AuthException;
32 : import com.google.gerrit.extensions.restapi.RestApiException;
33 : import com.google.gerrit.json.OutputFormat;
34 : import com.google.gerrit.server.experiments.ExperimentFeatures;
35 : import com.google.gson.Gson;
36 : import com.google.template.soy.data.SanitizedContent;
37 : import java.net.URI;
38 : import java.net.URISyntaxException;
39 : import java.util.Arrays;
40 : import java.util.Collections;
41 : import java.util.HashMap;
42 : import java.util.HashSet;
43 : import java.util.Map;
44 : import java.util.Set;
45 : import java.util.function.Function;
46 :
47 : /** Helper for generating parts of {@code index.html}. */
48 : @UsedAt(Project.GOOGLE)
49 : public class IndexHtmlUtil {
50 1 : private static final FluentLogger logger = FluentLogger.forEnclosingClass();
51 :
52 1 : private static final Gson GSON = OutputFormat.JSON_COMPACT.newGson();
53 : /**
54 : * Returns both static and dynamic parameters of {@code index.html}. The result is to be used when
55 : * rendering the soy template.
56 : */
57 : public static ImmutableMap<String, Object> templateData(
58 : GerritApi gerritApi,
59 : ExperimentFeatures experimentFeatures,
60 : String canonicalURL,
61 : String cdnPath,
62 : String faviconPath,
63 : Map<String, String[]> urlParameterMap,
64 : Function<String, SanitizedContent> urlInScriptTagOrdainer,
65 : String requestedURL)
66 : throws URISyntaxException, RestApiException {
67 1 : ImmutableMap.Builder<String, Object> data = ImmutableMap.builder();
68 1 : data.putAll(
69 1 : staticTemplateData(
70 : canonicalURL, cdnPath, faviconPath, urlParameterMap, urlInScriptTagOrdainer))
71 1 : .putAll(dynamicTemplateData(gerritApi, requestedURL));
72 1 : Set<String> enabledExperiments = new HashSet<>();
73 1 : enabledExperiments.addAll(experimentFeatures.getEnabledExperimentFeatures());
74 : // Add all experiments enabled through url
75 1 : enabledExperiments.addAll(IndexHtmlUtil.experimentData(urlParameterMap));
76 1 : if (!enabledExperiments.isEmpty()) {
77 1 : data.put("enabledExperiments", serializeObject(GSON, enabledExperiments).toString());
78 : }
79 1 : return data.build();
80 : }
81 :
82 : /** Returns dynamic parameters of {@code index.html}. */
83 : public static ImmutableMap<String, Object> dynamicTemplateData(
84 : GerritApi gerritApi, String requestedURL) throws RestApiException, URISyntaxException {
85 1 : ImmutableMap.Builder<String, Object> data = ImmutableMap.builder();
86 1 : Map<String, SanitizedContent> initialData = new HashMap<>();
87 1 : Server serverApi = gerritApi.config().server();
88 1 : initialData.put("\"/config/server/info\"", serializeObject(GSON, serverApi.getInfo()));
89 1 : initialData.put("\"/config/server/version\"", serializeObject(GSON, serverApi.getVersion()));
90 1 : initialData.put("\"/config/server/top-menus\"", serializeObject(GSON, serverApi.topMenus()));
91 :
92 1 : String requestedPath = IndexPreloadingUtil.getPath(requestedURL);
93 1 : IndexPreloadingUtil.RequestedPage page = IndexPreloadingUtil.parseRequestedPage(requestedPath);
94 1 : switch (page) {
95 : case CHANGE:
96 : case DIFF:
97 1 : data.put(
98 1 : "defaultChangeDetailHex", ListOption.toHex(IndexPreloadingUtil.CHANGE_DETAIL_OPTIONS));
99 1 : data.put(
100 : "changeRequestsPath",
101 1 : IndexPreloadingUtil.computeChangeRequestsPath(requestedPath, page).get());
102 1 : data.put("changeNum", IndexPreloadingUtil.computeChangeNum(requestedPath, page).get());
103 1 : break;
104 : case DASHBOARD:
105 : // Dashboard is preloaded queries are added later when we check user is authenticated.
106 : case PAGE_WITHOUT_PRELOADING:
107 : break;
108 : }
109 :
110 : try {
111 0 : AccountApi accountApi = gerritApi.accounts().self();
112 0 : initialData.put("\"/accounts/self/detail\"", serializeObject(GSON, accountApi.get()));
113 0 : initialData.put(
114 0 : "\"/accounts/self/preferences\"", serializeObject(GSON, accountApi.getPreferences()));
115 0 : initialData.put(
116 : "\"/accounts/self/preferences.diff\"",
117 0 : serializeObject(GSON, accountApi.getDiffPreferences()));
118 0 : initialData.put(
119 : "\"/accounts/self/preferences.edit\"",
120 0 : serializeObject(GSON, accountApi.getEditPreferences()));
121 0 : data.put("userIsAuthenticated", true);
122 0 : if (page == RequestedPage.DASHBOARD) {
123 0 : data.put("defaultDashboardHex", ListOption.toHex(IndexPreloadingUtil.DASHBOARD_OPTIONS));
124 0 : data.put("dashboardQuery", IndexPreloadingUtil.computeDashboardQueryList(serverApi));
125 : }
126 1 : } catch (AuthException e) {
127 1 : logger.atFine().log("Can't inline account-related data because user is unauthenticated");
128 : // Don't render data
129 0 : }
130 :
131 1 : data.put("gerritInitialData", initialData);
132 1 : return data.build();
133 : }
134 :
135 : /** Returns experimentData to be used in {@code index.html}. */
136 : public static Set<String> experimentData(Map<String, String[]> urlParameterMap) {
137 : // Allow enable experiments with url
138 : // ?experiment=a&experiment=b should result in:
139 : // "experiment" => [a,b]
140 1 : if (urlParameterMap.containsKey("experiment")) {
141 1 : return Arrays.asList(urlParameterMap.get("experiment")).stream().collect(toSet());
142 : }
143 :
144 1 : return Collections.emptySet();
145 : }
146 :
147 : /** Returns all static parameters of {@code index.html}. */
148 : static Map<String, Object> staticTemplateData(
149 : String canonicalURL,
150 : String cdnPath,
151 : String faviconPath,
152 : Map<String, String[]> urlParameterMap,
153 : Function<String, SanitizedContent> urlInScriptTagOrdainer)
154 : throws URISyntaxException {
155 1 : String canonicalPath = computeCanonicalPath(canonicalURL);
156 :
157 1 : String staticPath = "";
158 1 : if (cdnPath != null) {
159 1 : staticPath = cdnPath;
160 1 : } else if (canonicalPath != null) {
161 1 : staticPath = canonicalPath;
162 : }
163 :
164 1 : SanitizedContent sanitizedStaticPath = urlInScriptTagOrdainer.apply(staticPath);
165 1 : ImmutableMap.Builder<String, Object> data = ImmutableMap.builder();
166 :
167 1 : if (canonicalPath != null) {
168 1 : data.put("canonicalPath", canonicalPath);
169 : }
170 1 : if (sanitizedStaticPath != null) {
171 1 : data.put("staticResourcePath", sanitizedStaticPath);
172 : }
173 1 : if (faviconPath != null) {
174 1 : data.put("faviconPath", faviconPath);
175 : }
176 :
177 1 : if (urlParameterMap.containsKey("ce")) {
178 0 : data.put("polyfillCE", "true");
179 : }
180 1 : if (urlParameterMap.containsKey("gf")) {
181 1 : data.put("useGoogleFonts", "true");
182 : }
183 :
184 1 : return data.build();
185 : }
186 :
187 : private static String computeCanonicalPath(@Nullable String canonicalURL)
188 : throws URISyntaxException {
189 1 : if (Strings.isNullOrEmpty(canonicalURL)) {
190 0 : return "";
191 : }
192 :
193 : // If we serving from a sub-directory rather than root, determine the path
194 : // from the cannonical web URL.
195 1 : URI uri = new URI(canonicalURL);
196 1 : return uri.getPath().replaceAll("/$", "");
197 : }
198 :
199 : private IndexHtmlUtil() {}
200 : }
|