Line data Source code
1 : // Copyright (C) 2009 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.server.config;
16 :
17 : import static com.google.common.base.MoreObjects.firstNonNull;
18 : import static com.google.common.base.Strings.emptyToNull;
19 : import static com.google.common.base.Strings.isNullOrEmpty;
20 : import static com.google.common.base.Strings.nullToEmpty;
21 :
22 : import com.google.common.flogger.FluentLogger;
23 : import com.google.gerrit.common.Nullable;
24 : import com.google.gerrit.common.data.GitwebType;
25 : import com.google.gerrit.common.data.ParameterizedString;
26 : import com.google.gerrit.extensions.common.WebLinkInfo;
27 : import com.google.gerrit.extensions.registration.DynamicSet;
28 : import com.google.gerrit.extensions.restapi.Url;
29 : import com.google.gerrit.extensions.webui.BranchWebLink;
30 : import com.google.gerrit.extensions.webui.FileHistoryWebLink;
31 : import com.google.gerrit.extensions.webui.FileWebLink;
32 : import com.google.gerrit.extensions.webui.ParentWebLink;
33 : import com.google.gerrit.extensions.webui.PatchSetWebLink;
34 : import com.google.gerrit.extensions.webui.ProjectWebLink;
35 : import com.google.gerrit.extensions.webui.ResolveConflictsWebLink;
36 : import com.google.gerrit.extensions.webui.TagWebLink;
37 : import com.google.inject.AbstractModule;
38 : import com.google.inject.Inject;
39 : import com.google.inject.Singleton;
40 : import java.net.MalformedURLException;
41 : import java.net.URL;
42 : import org.eclipse.jgit.lib.Config;
43 :
44 : public class GitwebConfig {
45 152 : private static final FluentLogger logger = FluentLogger.forEnclosingClass();
46 :
47 : public static boolean isDisabled(Config cfg) {
48 99 : return isEmptyString(cfg, "gitweb", null, "url")
49 99 : || isEmptyString(cfg, "gitweb", null, "cgi")
50 99 : || "disabled".equals(cfg.getString("gitweb", null, "type"));
51 : }
52 :
53 : public static class LegacyModule extends AbstractModule {
54 : private final Config cfg;
55 :
56 152 : public LegacyModule(Config cfg) {
57 152 : this.cfg = cfg;
58 152 : }
59 :
60 : @Override
61 : protected void configure() {
62 152 : GitwebType type = typeFromConfig(cfg);
63 152 : if (type != null) {
64 0 : bind(GitwebType.class).toInstance(type);
65 :
66 0 : if (!isNullOrEmpty(type.getBranch())) {
67 0 : DynamicSet.bind(binder(), BranchWebLink.class).to(GitwebLinks.class);
68 : }
69 :
70 0 : if (!isNullOrEmpty(type.getTag())) {
71 0 : DynamicSet.bind(binder(), TagWebLink.class).to(GitwebLinks.class);
72 : }
73 :
74 0 : if (!isNullOrEmpty(type.getFile()) || !isNullOrEmpty(type.getRootTree())) {
75 0 : DynamicSet.bind(binder(), FileWebLink.class).to(GitwebLinks.class);
76 : }
77 :
78 0 : if (!isNullOrEmpty(type.getFileHistory())) {
79 0 : DynamicSet.bind(binder(), FileHistoryWebLink.class).to(GitwebLinks.class);
80 : }
81 :
82 0 : if (!isNullOrEmpty(type.getRevision())) {
83 0 : DynamicSet.bind(binder(), PatchSetWebLink.class).to(GitwebLinks.class);
84 0 : DynamicSet.bind(binder(), ParentWebLink.class).to(GitwebLinks.class);
85 0 : DynamicSet.bind(binder(), ResolveConflictsWebLink.class).to(GitwebLinks.class);
86 : }
87 :
88 0 : if (!isNullOrEmpty(type.getProject())) {
89 0 : DynamicSet.bind(binder(), ProjectWebLink.class).to(GitwebLinks.class);
90 : }
91 : }
92 152 : }
93 : }
94 :
95 : private static boolean isEmptyString(Config cfg, String section, String subsection, String name) {
96 : // This is currently the only way to check for the empty string in a JGit
97 : // config. Fun!
98 99 : String[] values = cfg.getStringList(section, subsection, name);
99 99 : return values.length > 0 && isNullOrEmpty(values[0]);
100 : }
101 :
102 : @Nullable
103 : private static GitwebType typeFromConfig(Config cfg) {
104 152 : GitwebType defaultType = defaultType(cfg.getString("gitweb", null, "type"));
105 152 : if (defaultType == null) {
106 152 : return null;
107 : }
108 0 : GitwebType type = new GitwebType();
109 :
110 0 : type.setLinkName(
111 0 : firstNonNull(cfg.getString("gitweb", null, "linkname"), defaultType.getLinkName()));
112 0 : type.setBranch(firstNonNull(cfg.getString("gitweb", null, "branch"), defaultType.getBranch()));
113 0 : type.setTag(firstNonNull(cfg.getString("gitweb", null, "tag"), defaultType.getTag()));
114 0 : type.setProject(
115 0 : firstNonNull(cfg.getString("gitweb", null, "project"), defaultType.getProject()));
116 0 : type.setRevision(
117 0 : firstNonNull(cfg.getString("gitweb", null, "revision"), defaultType.getRevision()));
118 0 : type.setRootTree(
119 0 : firstNonNull(cfg.getString("gitweb", null, "roottree"), defaultType.getRootTree()));
120 0 : type.setFile(firstNonNull(cfg.getString("gitweb", null, "file"), defaultType.getFile()));
121 0 : type.setFileHistory(
122 0 : firstNonNull(cfg.getString("gitweb", null, "filehistory"), defaultType.getFileHistory()));
123 0 : type.setUrlEncode(cfg.getBoolean("gitweb", null, "urlencode", defaultType.getUrlEncode()));
124 0 : String pathSeparator = cfg.getString("gitweb", null, "pathSeparator");
125 0 : if (pathSeparator != null) {
126 0 : if (pathSeparator.length() == 1) {
127 0 : char c = pathSeparator.charAt(0);
128 0 : if (isValidPathSeparator(c)) {
129 0 : type.setPathSeparator(firstNonNull(c, defaultType.getPathSeparator()));
130 : } else {
131 0 : logger.atWarning().log("Invalid gitweb.pathSeparator: %s", c);
132 : }
133 0 : } else {
134 0 : logger.atWarning().log("gitweb.pathSeparator is not a single character: %s", pathSeparator);
135 : }
136 : }
137 0 : return type;
138 : }
139 :
140 : @Nullable
141 : private static GitwebType defaultType(String typeName) {
142 152 : GitwebType type = new GitwebType();
143 152 : switch (nullToEmpty(typeName)) {
144 : case "gitweb":
145 0 : type.setLinkName("gitweb");
146 0 : type.setProject("?p=${project}.git;a=summary");
147 0 : type.setRevision("?p=${project}.git;a=commit;h=${commit}");
148 0 : type.setBranch("?p=${project}.git;a=shortlog;h=${branch}");
149 0 : type.setTag("?p=${project}.git;a=shortlog;h=${tag}");
150 0 : type.setRootTree("?p=${project}.git;a=tree;hb=${commit}");
151 0 : type.setFile("?p=${project}.git;hb=${commit};f=${file}");
152 0 : type.setFileHistory("?p=${project}.git;a=history;hb=${branch};f=${file}");
153 0 : break;
154 : case "cgit":
155 0 : type.setLinkName("cgit");
156 0 : type.setProject("${project}.git/summary");
157 0 : type.setRevision("${project}.git/commit/?id=${commit}");
158 0 : type.setBranch("${project}.git/log/?h=${branch}");
159 0 : type.setTag("${project}.git/tag/?h=${tag}");
160 0 : type.setRootTree("${project}.git/tree/?h=${commit}");
161 0 : type.setFile("${project}.git/tree/${file}?h=${commit}");
162 0 : type.setFileHistory("${project}.git/log/${file}?h=${branch}");
163 0 : break;
164 : case "custom":
165 : // For a custom type with no explicit link name, just reuse "gitweb".
166 0 : type.setLinkName("gitweb");
167 0 : type.setProject("");
168 0 : type.setRevision("");
169 0 : type.setBranch("");
170 0 : type.setTag("");
171 0 : type.setRootTree("");
172 0 : type.setFile("");
173 0 : type.setFileHistory("");
174 0 : break;
175 : case "":
176 : case "disabled":
177 : default:
178 152 : return null;
179 : }
180 0 : return type;
181 : }
182 :
183 : private final String url;
184 : private final GitwebType type;
185 :
186 : @Inject
187 : GitwebConfig(
188 : GitwebCgiConfig cgiConfig,
189 : @GerritServerConfig Config cfg,
190 : @Nullable @CanonicalWebUrl String gerritUrl)
191 0 : throws MalformedURLException {
192 0 : if (isDisabled(cfg)) {
193 0 : type = null;
194 0 : url = null;
195 : } else {
196 0 : String cfgUrl = cfg.getString("gitweb", null, "url");
197 0 : type = typeFromConfig(cfg);
198 0 : if (type == null) {
199 0 : url = null;
200 0 : } else if (cgiConfig.getGitwebCgi() == null) {
201 : // Use an externally managed gitweb instance, and not an internal one.
202 0 : url = cfgUrl;
203 : } else {
204 : String baseGerritUrl;
205 0 : if (gerritUrl != null) {
206 0 : URL u = new URL(gerritUrl);
207 0 : baseGerritUrl = u.getPath();
208 0 : } else {
209 0 : baseGerritUrl = "/";
210 : }
211 0 : url = firstNonNull(cfgUrl, baseGerritUrl + "gitweb");
212 : }
213 : }
214 0 : }
215 :
216 : /** Returns GitwebType for gitweb viewer. */
217 : @Nullable
218 : public GitwebType getGitwebType() {
219 0 : return type;
220 : }
221 :
222 : /**
223 : * Returns URL of the entry point into gitweb. This URL may be relative to our context if gitweb
224 : * is hosted by ourselves; or absolute if its hosted elsewhere; or null if gitweb has not been
225 : * configured.
226 : */
227 : public String getUrl() {
228 0 : return url;
229 : }
230 :
231 : /**
232 : * Determines if a given character can be used unencoded in an URL as a replacement for the path
233 : * separator '/'.
234 : *
235 : * <p>Reasoning: http://www.ietf.org/rfc/rfc1738.txt ยง 2.2:
236 : *
237 : * <p>... only alphanumerics, the special characters "$-_.+!*'(),", and reserved characters used
238 : * for their reserved purposes may be used unencoded within a URL.
239 : *
240 : * <p>The following characters might occur in file names, however:
241 : *
242 : * <p>alphanumeric characters,
243 : *
244 : * <p>"$-_.+!',"
245 : */
246 : static boolean isValidPathSeparator(char c) {
247 1 : switch (c) {
248 : case '*':
249 : case '(':
250 : case ')':
251 1 : return true;
252 : default:
253 1 : return false;
254 : }
255 : }
256 :
257 : @Singleton
258 : static class GitwebLinks
259 : implements BranchWebLink,
260 : FileHistoryWebLink,
261 : FileWebLink,
262 : PatchSetWebLink,
263 : ParentWebLink,
264 : ProjectWebLink,
265 : ResolveConflictsWebLink,
266 : TagWebLink {
267 : private final String url;
268 : private final GitwebType type;
269 : private final ParameterizedString branch;
270 : private final ParameterizedString file;
271 : private final ParameterizedString fileHistory;
272 : private final ParameterizedString project;
273 : private final ParameterizedString revision;
274 : private final ParameterizedString tag;
275 :
276 : @Inject
277 0 : GitwebLinks(GitwebConfig config, GitwebType type) {
278 0 : this.url = config.getUrl();
279 0 : this.type = type;
280 0 : this.branch = parse(type.getBranch());
281 0 : this.file = parse(firstNonNull(emptyToNull(type.getFile()), nullToEmpty(type.getRootTree())));
282 0 : this.fileHistory = parse(type.getFileHistory());
283 0 : this.project = parse(type.getProject());
284 0 : this.revision = parse(type.getRevision());
285 0 : this.tag = parse(type.getTag());
286 0 : }
287 :
288 : @Nullable
289 : @Override
290 : public WebLinkInfo getBranchWebLink(String projectName, String branchName) {
291 0 : if (branch != null) {
292 0 : return link(
293 : branch
294 0 : .replace("project", encode(projectName))
295 0 : .replace("branch", encode(branchName))
296 0 : .toString());
297 : }
298 0 : return null;
299 : }
300 :
301 : @Nullable
302 : @Override
303 : public WebLinkInfo getTagWebLink(String projectName, String tagName) {
304 0 : if (tag != null) {
305 0 : return link(
306 0 : tag.replace("project", encode(projectName)).replace("tag", encode(tagName)).toString());
307 : }
308 0 : return null;
309 : }
310 :
311 : @Nullable
312 : @Override
313 : public WebLinkInfo getFileHistoryWebLink(String projectName, String revision, String fileName) {
314 0 : if (fileHistory != null) {
315 0 : return link(
316 : fileHistory
317 0 : .replace("project", encode(projectName))
318 0 : .replace("branch", encode(revision))
319 0 : .replace("file", encode(fileName))
320 0 : .toString());
321 : }
322 0 : return null;
323 : }
324 :
325 : @Nullable
326 : @Override
327 : public WebLinkInfo getFileWebLink(
328 : String projectName, String revision, String hash, String fileName) {
329 0 : if (file != null) {
330 0 : return link(
331 0 : file.replace("project", encode(projectName))
332 0 : .replace("commit", encode(revision))
333 0 : .replace("hash", encode(hash))
334 0 : .replace("file", encode(fileName))
335 0 : .toString());
336 : }
337 0 : return null;
338 : }
339 :
340 : @Nullable
341 : @Override
342 : public WebLinkInfo getPatchSetWebLink(
343 : String projectName, String commit, String commitMessage, String branchName) {
344 0 : if (revision != null) {
345 : // commitMessage and branchName are not needed, hence not used.
346 0 : return link(
347 : revision
348 0 : .replace("project", encode(projectName))
349 0 : .replace("commit", encode(commit))
350 0 : .toString());
351 : }
352 0 : return null;
353 : }
354 :
355 : @Override
356 : public WebLinkInfo getResolveConflictsWebLink(
357 : String projectName, String commit, String commitMessage, String branchName) {
358 : // For Gitweb treat resolve conflicts links the same as patch set links
359 0 : return getPatchSetWebLink(projectName, commit, commitMessage, branchName);
360 : }
361 :
362 : @Override
363 : public WebLinkInfo getParentWebLink(
364 : String projectName, String commit, String commitMessage, String branchName) {
365 : // For Gitweb treat parent revision links the same as patch set links
366 0 : return getPatchSetWebLink(projectName, commit, commitMessage, branchName);
367 : }
368 :
369 : @Nullable
370 : @Override
371 : public WebLinkInfo getProjectWeblink(String projectName) {
372 0 : if (project != null) {
373 0 : return link(project.replace("project", encode(projectName)).toString());
374 : }
375 0 : return null;
376 : }
377 :
378 : private String encode(String val) {
379 0 : if (type.getUrlEncode()) {
380 0 : return Url.encode(type.replacePathSeparator(val));
381 : }
382 0 : return val;
383 : }
384 :
385 : private WebLinkInfo link(String rest) {
386 0 : return new WebLinkInfo(type.getLinkName(), null, url + rest, null);
387 : }
388 :
389 : @Nullable
390 : private static ParameterizedString parse(String pattern) {
391 0 : if (!isNullOrEmpty(pattern)) {
392 0 : return new ParameterizedString(pattern);
393 : }
394 0 : return null;
395 : }
396 : }
397 : }
|