blob: 8225278b4a7e3524768cf97471ecc1fb07b9eee5 [file] [log] [blame]
Dave Borowitz9de65952012-08-13 16:09:45 -07001// Copyright 2012 Google Inc. All Rights Reserved.
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
15package com.google.gitiles;
16
17import static com.google.common.base.Preconditions.checkNotNull;
18
19import com.google.common.base.Joiner;
Stefan Beller6307b3d2014-11-13 17:53:03 -080020import com.google.common.base.Strings;
Dave Borowitz9de65952012-08-13 16:09:45 -070021import com.google.common.collect.ImmutableMap;
22import com.google.common.collect.Lists;
23
Stefan Beller6307b3d2014-11-13 17:53:03 -080024import org.eclipse.jgit.lib.Config;
25import org.slf4j.Logger;
26import org.slf4j.LoggerFactory;
27
28import java.util.ArrayList;
29import java.util.Collections;
Dave Borowitz9de65952012-08-13 16:09:45 -070030import java.util.List;
31import java.util.Map;
32import java.util.regex.Matcher;
33import java.util.regex.Pattern;
Nodir Turakulov56a226e2015-08-28 09:49:31 -070034import java.util.regex.PatternSyntaxException;
Dave Borowitz9de65952012-08-13 16:09:45 -070035
36import javax.servlet.http.HttpServletRequest;
37
38/** Linkifier for blocks of text such as commit message descriptions. */
39public class Linkifier {
Stefan Beller6307b3d2014-11-13 17:53:03 -080040 private static final Logger log = LoggerFactory.getLogger(Linkifier.class);
41
42 private static final String COMMENTLINK = "commentlink";
43 private static final Pattern HTTP_URL_PATTERN;
44 private static final Pattern CHANGE_ID_PATTERN;
Dave Borowitz9de65952012-08-13 16:09:45 -070045
46 static {
47 // HTTP URL regex adapted from com.google.gwtexpui.safehtml.client.SafeHtml.
Dave Borowitz5871ac82013-03-21 12:14:28 -070048 String part = "(?:" +
49 "[a-zA-Z0-9$_.+!*',%;:@=?#/~<>-]" +
50 "|&(?!lt;|gt;)" +
51 ")";
Dave Borowitz9de65952012-08-13 16:09:45 -070052 String httpUrl = "https?://" +
53 part + "{2,}" +
54 "(?:[(]" + part + "*" + "[)])*" +
55 part + "*";
Stefan Beller6307b3d2014-11-13 17:53:03 -080056 HTTP_URL_PATTERN = Pattern.compile(httpUrl);
57 CHANGE_ID_PATTERN = Pattern.compile("(\\bI[0-9a-f]{8,40}\\b)");
Dave Borowitz9de65952012-08-13 16:09:45 -070058 }
59
60 private final GitilesUrls urls;
Stefan Beller6307b3d2014-11-13 17:53:03 -080061 private final List<CommentLinkInfo> commentLinks;
62 private final Pattern allPattern;
Dave Borowitz9de65952012-08-13 16:09:45 -070063
Stefan Beller6307b3d2014-11-13 17:53:03 -080064 public Linkifier(GitilesUrls urls, Config config) {
Dave Borowitz9de65952012-08-13 16:09:45 -070065 this.urls = checkNotNull(urls, "urls");
Stefan Beller6307b3d2014-11-13 17:53:03 -080066
67 List<CommentLinkInfo> list = new ArrayList<>();
68 list.add(new CommentLinkInfo(HTTP_URL_PATTERN, "$0"));
Stefan Beller6307b3d2014-11-13 17:53:03 -080069
70 List<String> patterns = new ArrayList<>();
Stefan Beller6307b3d2014-11-13 17:53:03 -080071 patterns.add(HTTP_URL_PATTERN.pattern());
72 patterns.add(CHANGE_ID_PATTERN.pattern());
73
Nodir Turakulov56a226e2015-08-28 09:49:31 -070074 for (String subsection : config.getSubsections(COMMENTLINK)) {
Stefan Beller6307b3d2014-11-13 17:53:03 -080075 String match = config.getString("commentlink", subsection, "match");
76 String link = config.getString("commentlink", subsection, "link");
77 String html = config.getString("commentlink", subsection, "html");
78 if (html != null) {
79 log.warn("Beware: html in commentlinks is unsupported in gitiles; "
80 + "Did you copy it from a gerrit config?");
81 }
82 if (Strings.isNullOrEmpty(match)) {
83 log.warn("invalid commentlink.%s.match", subsection);
84 continue;
85 }
Nodir Turakulov56a226e2015-08-28 09:49:31 -070086 Pattern pattern;
87 try {
88 pattern = Pattern.compile(match);
89 } catch(PatternSyntaxException ex) {
90 log.warn("invalid commentlink." + subsection + ".match", ex);
91 continue;
92 }
Stefan Beller6307b3d2014-11-13 17:53:03 -080093 if (Strings.isNullOrEmpty(link)) {
94 log.warn("invalid commentlink.%s.link", subsection);
95 continue;
96 }
Stefan Beller6307b3d2014-11-13 17:53:03 -080097 list.add(new CommentLinkInfo(pattern, link));
98 patterns.add(match);
99 }
100 this.commentLinks = Collections.unmodifiableList(list);
101 allPattern = Pattern.compile(Joiner.on('|').join(patterns));
Dave Borowitz9de65952012-08-13 16:09:45 -0700102 }
103
104 public List<Map<String, String>> linkify(HttpServletRequest req, String message) {
Stefan Beller6307b3d2014-11-13 17:53:03 -0800105
106 List<CommentLinkInfo> operationalCommentLinks = new ArrayList<>(commentLinks);
107 // Because we're relying on 'req' as a dynamic parameter, we need to construct
108 // the CommentLinkInfo for ChangeIds on the fly.
Dave Borowitz9de65952012-08-13 16:09:45 -0700109 String baseGerritUrl = urls.getBaseGerritUrl(req);
Stefan Beller6307b3d2014-11-13 17:53:03 -0800110
111 if (baseGerritUrl != null) {
112 CommentLinkInfo changeIds =
113 new CommentLinkInfo(CHANGE_ID_PATTERN, baseGerritUrl + "#/q/$0,n,z");
114 operationalCommentLinks.add(changeIds);
115 }
116
Dave Borowitz9de65952012-08-13 16:09:45 -0700117 List<Map<String, String>> parsed = Lists.newArrayList();
Stefan Beller6307b3d2014-11-13 17:53:03 -0800118 parsed.add(ImmutableMap.of("text", message));
119
120 for (int index = 0; index < parsed.size(); index++) {
121 if (parsed.get(index).get("url") != null) {
122 continue;
123 }
124 Matcher m = allPattern.matcher(parsed.get(index).get("text"));
125 if (!m.find()) {
126 continue;
127 }
128
129 for (CommentLinkInfo cli : operationalCommentLinks) {
130 // No need to apply more rules if this is already a link.
131 if (parsed.get(index).get("url") != null) {
132 break;
Dave Borowitz9de65952012-08-13 16:09:45 -0700133 }
Stefan Beller6307b3d2014-11-13 17:53:03 -0800134 String text = parsed.get(index).get("text");
135 parsed.remove(index);
136 parsed.addAll(index, cli.linkify(text));
Dave Borowitz9de65952012-08-13 16:09:45 -0700137 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700138 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700139 return parsed;
140 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700141}