LCOV - code coverage report
Current view: top level - server/config - GitwebConfig.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 21 161 13.0 %
Date: 2022-11-19 15:00:39 Functions: 8 23 34.8 %

          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             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750