blob: aed8061e4e99a18b448a3e41683aa4ff7c43a179 [file] [log] [blame] [raw]
// Copyright 2012 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gitiles;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import javax.servlet.http.HttpServletRequest;
import org.eclipse.jgit.lib.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** Linkifier for blocks of text such as commit message descriptions. */
public class Linkifier {
private static final Logger log = LoggerFactory.getLogger(Linkifier.class);
private static final String COMMENTLINK = "commentlink";
private static final Pattern HTTP_URL_PATTERN;
private static final Pattern CHANGE_ID_PATTERN;
static {
// HTTP URL regex adapted from com.google.gwtexpui.safehtml.client.SafeHtml.
String part = "(?:" + "[a-zA-Z0-9$_.+!*',%;:@=?#/~<>-]" + "|&(?!lt;|gt;)" + ")";
String httpUrl = "https?://" + part + "{2,}" + "(?:[(]" + part + "*" + "[)])*" + part + "*";
HTTP_URL_PATTERN = Pattern.compile(httpUrl);
CHANGE_ID_PATTERN = Pattern.compile("(\\bI[0-9a-f]{8,40}\\b)");
}
private final GitilesUrls urls;
private final List<CommentLinkInfo> commentLinks;
private final Pattern allPattern;
public Linkifier(GitilesUrls urls, Config config) {
this.urls = checkNotNull(urls, "urls");
List<CommentLinkInfo> list = new ArrayList<>();
list.add(new CommentLinkInfo(HTTP_URL_PATTERN, "$0"));
List<String> patterns = new ArrayList<>();
patterns.add(HTTP_URL_PATTERN.pattern());
patterns.add(CHANGE_ID_PATTERN.pattern());
for (String subsection : config.getSubsections(COMMENTLINK)) {
String match = config.getString(COMMENTLINK, subsection, "match");
String link = config.getString(COMMENTLINK, subsection, "link");
String html = config.getString(COMMENTLINK, subsection, "html");
if (html != null) {
log.warn(
"Beware: html in commentlinks is unsupported in gitiles; "
+ "Did you copy it from a gerrit config?");
}
if (Strings.isNullOrEmpty(match)) {
log.warn("invalid commentlink.{}.match", subsection);
continue;
}
Pattern pattern;
try {
pattern = Pattern.compile(match);
} catch (PatternSyntaxException ex) {
String msg = "invalid commentlink." + subsection + ".match";
if (log.isDebugEnabled()) {
log.debug(msg, ex);
} else {
log.warn("{}: {}", msg, ex.getMessage());
}
continue;
}
if (Strings.isNullOrEmpty(link)) {
log.warn("invalid commentlink.{}.link", subsection);
continue;
}
list.add(new CommentLinkInfo(pattern, link));
patterns.add(match);
}
this.commentLinks = Collections.unmodifiableList(list);
allPattern = Pattern.compile(Joiner.on('|').join(patterns));
}
public List<Map<String, String>> linkify(HttpServletRequest req, String message) {
List<CommentLinkInfo> operationalCommentLinks = new ArrayList<>(commentLinks);
// Because we're relying on 'req' as a dynamic parameter, we need to construct
// the CommentLinkInfo for ChangeIds on the fly.
String baseGerritUrl = urls.getBaseGerritUrl(req);
if (baseGerritUrl != null) {
CommentLinkInfo changeIds = new CommentLinkInfo(CHANGE_ID_PATTERN, baseGerritUrl + "#/q/$0");
operationalCommentLinks.add(changeIds);
}
List<Map<String, String>> parsed = Lists.newArrayList();
parsed.add(ImmutableMap.of("text", message));
for (int index = 0; index < parsed.size(); index++) {
if (parsed.get(index).get("url") != null) {
continue;
}
Matcher m = allPattern.matcher(parsed.get(index).get("text"));
if (!m.find()) {
continue;
}
for (CommentLinkInfo cli : operationalCommentLinks) {
// No need to apply more rules if this is already a link.
if (parsed.get(index).get("url") != null) {
break;
}
String text = parsed.get(index).get("text");
parsed.remove(index);
parsed.addAll(index, cli.linkify(text));
}
}
return parsed;
}
}