All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gerrit.httpd.plugins.HttpPluginServlet Maven / Gradle / Ivy

There is a newer version: 3.11.0-rc3
Show newest version
// Copyright (C) 2012 The Android Open Source Project
//
// 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.gerrit.httpd.plugins;

import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS;
import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS;
import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN;
import static com.google.common.net.HttpHeaders.ORIGIN;
import static com.google.common.net.HttpHeaders.VARY;
import static com.google.gerrit.common.FileUtil.lastModified;
import static com.google.gerrit.server.plugins.PluginEntry.ATTR_CHARACTER_ENCODING;
import static com.google.gerrit.server.plugins.PluginEntry.ATTR_CONTENT_TYPE;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.stream.Collectors.toList;

import com.google.common.base.CharMatcher;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.cache.Cache;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.flogger.FluentLogger;
import com.google.common.io.ByteStreams;
import com.google.common.net.HttpHeaders;
import com.google.gerrit.httpd.resources.Resource;
import com.google.gerrit.httpd.resources.ResourceKey;
import com.google.gerrit.httpd.resources.SmallResource;
import com.google.gerrit.httpd.restapi.RestApiServlet;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.documentation.MarkdownFormatter;
import com.google.gerrit.server.mime.MimeUtilFileTypeRegistry;
import com.google.gerrit.server.plugins.Plugin;
import com.google.gerrit.server.plugins.Plugin.ApiType;
import com.google.gerrit.server.plugins.PluginContentScanner;
import com.google.gerrit.server.plugins.PluginEntry;
import com.google.gerrit.server.plugins.PluginsCollection;
import com.google.gerrit.server.plugins.ReloadPluginListener;
import com.google.gerrit.server.plugins.StartPluginListener;
import com.google.gerrit.server.ssh.SshInfo;
import com.google.gerrit.util.http.CacheHeaders;
import com.google.gerrit.util.http.RequestUtil;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
import com.google.inject.servlet.GuiceFilter;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Predicate;
import java.util.jar.Attributes;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.FilterChain;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.util.IO;
import org.eclipse.jgit.util.RawParseUtils;

@Singleton
class HttpPluginServlet extends HttpServlet implements StartPluginListener, ReloadPluginListener {
  private static final FluentLogger logger = FluentLogger.forEnclosingClass();

  private static final int SMALL_RESOURCE = 128 * 1024;
  private static final long serialVersionUID = 1L;

  private final MimeUtilFileTypeRegistry mimeUtil;
  private final Provider webUrl;
  private final Cache resourceCache;
  private final String sshHost;
  private final int sshPort;
  private final RestApiServlet managerApi;

  private List pending = new ArrayList<>();
  private ContextMapper wrapper;
  private final ConcurrentMap plugins = Maps.newConcurrentMap();
  private final Pattern allowOrigin;

  @Inject
  HttpPluginServlet(
      MimeUtilFileTypeRegistry mimeUtil,
      @CanonicalWebUrl Provider webUrl,
      @Named(HttpPluginModule.PLUGIN_RESOURCES) Cache cache,
      SshInfo sshInfo,
      RestApiServlet.Globals globals,
      PluginsCollection plugins,
      @GerritServerConfig Config cfg) {
    this.mimeUtil = mimeUtil;
    this.webUrl = webUrl;
    this.resourceCache = cache;
    this.managerApi = new RestApiServlet(globals, plugins);

    String sshHost = "review.example.com";
    int sshPort = 29418;
    if (!sshInfo.getHostKeys().isEmpty()) {
      String host = sshInfo.getHostKeys().get(0).getHost();
      int c = host.lastIndexOf(':');
      if (0 <= c) {
        sshHost = host.substring(0, c);
        sshPort = Integer.parseInt(host.substring(c + 1));
      } else {
        sshHost = host;
        sshPort = 22;
      }
    }
    this.sshHost = sshHost;
    this.sshPort = sshPort;
    this.allowOrigin = makeAllowOrigin(cfg);
  }

  @Override
  public synchronized void init(ServletConfig config) throws ServletException {
    super.init(config);

    wrapper = new ContextMapper(config.getServletContext().getContextPath());
    for (Plugin plugin : pending) {
      install(plugin);
    }
    pending = null;
  }

  @Override
  public synchronized void onStartPlugin(Plugin plugin) {
    if (pending != null) {
      pending.add(plugin);
    } else {
      install(plugin);
    }
  }

  @Override
  public void onReloadPlugin(Plugin oldPlugin, Plugin newPlugin) {
    install(newPlugin);
  }

  private void install(Plugin plugin) {
    GuiceFilter filter = load(plugin);
    final String name = plugin.getName();
    final PluginHolder holder = new PluginHolder(plugin, filter);
    plugin.add(() -> plugins.remove(name, holder));
    plugins.put(name, holder);
  }

  private GuiceFilter load(Plugin plugin) {
    if (plugin.getHttpInjector() != null) {
      final String name = plugin.getName();
      final GuiceFilter filter;
      try {
        filter = plugin.getHttpInjector().getInstance(GuiceFilter.class);
      } catch (RuntimeException e) {
        logger.atWarning().withCause(e).log("Plugin %s cannot load GuiceFilter", name);
        return null;
      }

      try {
        ServletContext ctx = PluginServletContext.create(plugin, wrapper.getFullPath(name));
        filter.init(new WrappedFilterConfig(ctx));
      } catch (ServletException e) {
        logger.atWarning().withCause(e).log("Plugin %s failed to initialize HTTP", name);
        return null;
      }

      plugin.add(filter::destroy);
      return filter;
    }
    return null;
  }

  @Override
  public void service(HttpServletRequest req, HttpServletResponse res)
      throws IOException, ServletException {
    List parts =
        Lists.newArrayList(
            Splitter.on('/')
                .limit(3)
                .omitEmptyStrings()
                .split(Strings.nullToEmpty(RequestUtil.getEncodedPathInfo(req))));

    if (isApiCall(req, parts)) {
      managerApi.service(req, res);
      return;
    }

    String name = parts.get(0);
    final PluginHolder holder = plugins.get(name);
    if (holder == null) {
      CacheHeaders.setNotCacheable(res);
      res.sendError(HttpServletResponse.SC_NOT_FOUND);
      return;
    }

    HttpServletRequest wr = wrapper.create(req, name);
    FilterChain chain =
        (sreq, sres) -> onDefault(holder, (HttpServletRequest) sreq, (HttpServletResponse) sres);
    if (holder.filter != null) {
      holder.filter.doFilter(wr, res, chain);
    } else {
      chain.doFilter(wr, res);
    }
  }

  private static boolean isApiCall(HttpServletRequest req, List parts) {
    String method = req.getMethod();
    int cnt = parts.size();
    return cnt == 0
        || (cnt == 1 && ("PUT".equals(method) || "DELETE".equals(method)))
        || (cnt == 2 && parts.get(1).startsWith("gerrit~"));
  }

  private void onDefault(PluginHolder holder, HttpServletRequest req, HttpServletResponse res)
      throws IOException {
    if (!"GET".equals(req.getMethod()) && !"HEAD".equals(req.getMethod())) {
      CacheHeaders.setNotCacheable(res);
      res.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
      return;
    }

    String pathInfo = RequestUtil.getEncodedPathInfo(req);
    if (pathInfo.length() < 1) {
      Resource.NOT_FOUND.send(req, res);
      return;
    }

    checkCors(req, res);

    String file = pathInfo.substring(1);
    PluginResourceKey key = PluginResourceKey.create(holder.plugin, file);
    Resource rsc = resourceCache.getIfPresent(key);
    if (rsc != null && req.getHeader(HttpHeaders.IF_MODIFIED_SINCE) == null) {
      rsc.send(req, res);
      return;
    }

    String uri = req.getRequestURI();
    if ("".equals(file)) {
      res.sendRedirect(uri + holder.docPrefix + "index.html");
      return;
    }

    if (file.startsWith(holder.staticPrefix)) {
      if (holder.plugin.getApiType() == ApiType.JS) {
        sendJsPlugin(holder.plugin, key, req, res);
      } else {
        PluginContentScanner scanner = holder.plugin.getContentScanner();
        Optional entry = scanner.getEntry(file);
        if (entry.isPresent()) {
          if (hasUpToDateCachedResource(rsc, entry.get().getTime())) {
            rsc.send(req, res);
          } else {
            sendResource(scanner, entry.get(), key, res);
          }
        } else {
          resourceCache.put(key, Resource.NOT_FOUND);
          Resource.NOT_FOUND.send(req, res);
        }
      }
    } else if (file.equals(holder.docPrefix.substring(0, holder.docPrefix.length() - 1))) {
      res.sendRedirect(uri + "/index.html");
    } else if (file.startsWith(holder.docPrefix) && file.endsWith("/")) {
      res.sendRedirect(uri + "index.html");
    } else if (file.startsWith(holder.docPrefix)) {
      PluginContentScanner scanner = holder.plugin.getContentScanner();
      Optional entry = scanner.getEntry(file);
      if (!entry.isPresent()) {
        entry = findSource(scanner, file);
      }
      if (!entry.isPresent() && file.endsWith("/index.html")) {
        String pfx = file.substring(0, file.length() - "index.html".length());
        long pluginLastModified = lastModified(holder.plugin.getSrcFile());
        if (hasUpToDateCachedResource(rsc, pluginLastModified)) {
          rsc.send(req, res);
        } else {
          sendAutoIndex(scanner, pfx, holder.plugin.getName(), key, res, pluginLastModified);
        }
      } else if (entry.isPresent() && entry.get().getName().endsWith(".md")) {
        if (hasUpToDateCachedResource(rsc, entry.get().getTime())) {
          rsc.send(req, res);
        } else {
          sendMarkdownAsHtml(scanner, entry.get(), holder.plugin.getName(), key, res);
        }
      } else if (entry.isPresent()) {
        if (hasUpToDateCachedResource(rsc, entry.get().getTime())) {
          rsc.send(req, res);
        } else {
          sendResource(scanner, entry.get(), key, res);
        }
      } else {
        resourceCache.put(key, Resource.NOT_FOUND);
        Resource.NOT_FOUND.send(req, res);
      }
    } else {
      resourceCache.put(key, Resource.NOT_FOUND);
      Resource.NOT_FOUND.send(req, res);
    }
  }

  private static Pattern makeAllowOrigin(Config cfg) {
    String[] allow = cfg.getStringList("site", null, "allowOriginRegex");
    if (allow.length > 0) {
      return Pattern.compile(Joiner.on('|').join(allow));
    }
    return null;
  }

  private void checkCors(HttpServletRequest req, HttpServletResponse res) {
    String origin = req.getHeader(ORIGIN);
    if (!Strings.isNullOrEmpty(origin) && isOriginAllowed(origin)) {
      res.addHeader(VARY, ORIGIN);
      setCorsHeaders(res, origin);
    }
  }

  private void setCorsHeaders(HttpServletResponse res, String origin) {
    res.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN, origin);
    res.setHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
    res.setHeader(ACCESS_CONTROL_ALLOW_METHODS, "GET, HEAD");
  }

  private boolean isOriginAllowed(String origin) {
    return allowOrigin == null || allowOrigin.matcher(origin).matches();
  }

  private boolean hasUpToDateCachedResource(Resource cachedResource, long lastUpdateTime) {
    return cachedResource != null && cachedResource.isUnchanged(lastUpdateTime);
  }

  private void appendPageAsSection(
      PluginContentScanner scanner, PluginEntry pluginEntry, String sectionTitle, StringBuilder md)
      throws IOException {
    InputStreamReader isr = new InputStreamReader(scanner.getInputStream(pluginEntry), UTF_8);
    StringBuilder content = new StringBuilder();
    try (BufferedReader reader = new BufferedReader(isr)) {
      String line;
      while ((line = reader.readLine()) != null) {
        line = StringUtils.stripEnd(line, null);
        if (line.isEmpty()) {
          content.append("\n");
        } else {
          content.append(line).append("\n");
        }
      }
    }

    // Only append the section if there was anything in it
    if (content.toString().trim().length() > 0) {
      md.append("## ");
      md.append(sectionTitle);
      md.append(" ##\n");
      md.append("\n").append(content);
      md.append("\n");
    }
  }

  private void appendEntriesSection(
      PluginContentScanner scanner,
      List entries,
      String sectionTitle,
      StringBuilder md,
      String prefix,
      int nameOffset)
      throws IOException {
    if (!entries.isEmpty()) {
      md.append("## ").append(sectionTitle).append(" ##\n");
      for (PluginEntry entry : entries) {
        String rsrc = entry.getName().substring(prefix.length());
        String entryTitle;
        if (rsrc.endsWith(".html")) {
          entryTitle = rsrc.substring(nameOffset, rsrc.length() - 5).replace('-', ' ');
        } else if (rsrc.endsWith(".md")) {
          entryTitle = extractTitleFromMarkdown(scanner, entry);
          if (Strings.isNullOrEmpty(entryTitle)) {
            entryTitle = rsrc.substring(nameOffset, rsrc.length() - 3).replace('-', ' ');
          }
        } else {
          entryTitle = rsrc.substring(nameOffset).replace('-', ' ');
        }
        md.append(String.format("* [%s](%s)\n", entryTitle, rsrc));
      }
      md.append("\n");
    }
  }

  private void sendAutoIndex(
      PluginContentScanner scanner,
      final String prefix,
      final String pluginName,
      PluginResourceKey cacheKey,
      HttpServletResponse res,
      long lastModifiedTime)
      throws IOException {
    List cmds = new ArrayList<>();
    List servlets = new ArrayList<>();
    List restApis = new ArrayList<>();
    List docs = new ArrayList<>();
    PluginEntry about = null;
    PluginEntry toc = null;

    Predicate filter =
        entry -> {
          String name = entry.getName();
          Optional size = entry.getSize();
          if (name.startsWith(prefix)
              && (name.endsWith(".md") || name.endsWith(".html"))
              && size.isPresent()) {
            if (size.get() <= 0 || size.get() > SMALL_RESOURCE) {
              logger.atWarning().log(
                  "Plugin %s: %s omitted from document index. " + "Size %d out of range (0,%d).",
                  pluginName, name.substring(prefix.length()), size.get(), SMALL_RESOURCE);
              return false;
            }
            return true;
          }
          return false;
        };

    List entries =
        Collections.list(scanner.entries()).stream().filter(filter).collect(toList());
    for (PluginEntry entry : entries) {
      String name = entry.getName().substring(prefix.length());
      if (name.startsWith("cmd-")) {
        cmds.add(entry);
      } else if (name.startsWith("servlet-")) {
        servlets.add(entry);
      } else if (name.startsWith("rest-api-")) {
        restApis.add(entry);
      } else if (name.startsWith("about.")) {
        if (about == null) {
          about = entry;
        } else {
          logger.atWarning().log(
              "Plugin %s: Multiple 'about' documents found; using %s",
              pluginName, about.getName().substring(prefix.length()));
        }
      } else if (name.startsWith("toc.")) {
        if (toc == null) {
          toc = entry;
        } else {
          logger.atWarning().log(
              "Plugin %s: Multiple 'toc' documents found; using %s",
              pluginName, toc.getName().substring(prefix.length()));
        }
      } else {
        docs.add(entry);
      }
    }

    cmds.sort(PluginEntry.COMPARATOR_BY_NAME);
    docs.sort(PluginEntry.COMPARATOR_BY_NAME);

    StringBuilder md = new StringBuilder();
    md.append(String.format("# Plugin %s #\n", pluginName));
    md.append("\n");
    appendPluginInfoTable(md, scanner.getManifest().getMainAttributes());

    if (about != null) {
      appendPageAsSection(scanner, about, "About", md);
    }

    if (toc != null) {
      appendPageAsSection(scanner, toc, "Documentation", md);
    } else {
      appendEntriesSection(scanner, docs, "Documentation", md, prefix, 0);
      appendEntriesSection(scanner, servlets, "Servlets", md, prefix, "servlet-".length());
      appendEntriesSection(scanner, restApis, "REST APIs", md, prefix, "rest-api-".length());
      appendEntriesSection(scanner, cmds, "Commands", md, prefix, "cmd-".length());
    }

    sendMarkdownAsHtml(md.toString(), pluginName, cacheKey, res, lastModifiedTime);
  }

  private void sendMarkdownAsHtml(
      String md,
      String pluginName,
      PluginResourceKey cacheKey,
      HttpServletResponse res,
      long lastModifiedTime)
      throws UnsupportedEncodingException, IOException {
    Map macros = new HashMap<>();
    macros.put("PLUGIN", pluginName);
    macros.put("SSH_HOST", sshHost);
    macros.put("SSH_PORT", "" + sshPort);
    String url = webUrl.get();
    if (Strings.isNullOrEmpty(url)) {
      url = "http://review.example.com/";
    }
    macros.put("URL", url);

    Matcher m = Pattern.compile("(\\\\)?@([A-Z_]+)@").matcher(md);
    StringBuffer sb = new StringBuffer();
    while (m.find()) {
      String key = m.group(2);
      String val = macros.get(key);
      if (m.group(1) != null) {
        m.appendReplacement(sb, "@" + key + "@");
      } else if (val != null) {
        m.appendReplacement(sb, val);
      } else {
        m.appendReplacement(sb, "@" + key + "@");
      }
    }
    m.appendTail(sb);

    byte[] html = new MarkdownFormatter().markdownToDocHtml(sb.toString(), UTF_8.name());
    resourceCache.put(
        cacheKey,
        new SmallResource(html)
            .setContentType("text/html")
            .setCharacterEncoding(UTF_8.name())
            .setLastModified(lastModifiedTime));
    res.setContentType("text/html");
    res.setCharacterEncoding(UTF_8.name());
    res.setContentLength(html.length);
    res.setDateHeader("Last-Modified", lastModifiedTime);
    res.getOutputStream().write(html);
  }

  private static void appendPluginInfoTable(StringBuilder html, Attributes main) {
    if (main != null) {
      String t = main.getValue(Attributes.Name.IMPLEMENTATION_TITLE);
      String n = main.getValue(Attributes.Name.IMPLEMENTATION_VENDOR);
      String v = main.getValue(Attributes.Name.IMPLEMENTATION_VERSION);
      String a = main.getValue("Gerrit-ApiVersion");

      html.append("");
      if (!Strings.isNullOrEmpty(t)) {
        html.append("\n");
      }
      if (!Strings.isNullOrEmpty(n)) {
        html.append("\n");
      }
      if (!Strings.isNullOrEmpty(v)) {
        html.append("\n");
      }
      if (!Strings.isNullOrEmpty(a)) {
        html.append("\n");
      }
      html.append("
Name").append(t).append("
Vendor").append(n).append("
Version").append(v).append("
API Version").append(a).append("
\n"); } } private static String extractTitleFromMarkdown(PluginContentScanner scanner, PluginEntry entry) throws IOException { String charEnc = null; Map atts = entry.getAttrs(); if (atts != null) { charEnc = Strings.emptyToNull(atts.get(ATTR_CHARACTER_ENCODING)); } if (charEnc == null) { charEnc = UTF_8.name(); } return new MarkdownFormatter() .extractTitleFromMarkdown(readWholeEntry(scanner, entry), charEnc); } private static Optional findSource(PluginContentScanner scanner, String file) throws IOException { if (file.endsWith(".html")) { int d = file.lastIndexOf('.'); return scanner.getEntry(file.substring(0, d) + ".md"); } return Optional.empty(); } private void sendMarkdownAsHtml( PluginContentScanner scanner, PluginEntry entry, String pluginName, PluginResourceKey key, HttpServletResponse res) throws IOException { byte[] rawmd = readWholeEntry(scanner, entry); String encoding = null; Map atts = entry.getAttrs(); if (atts != null) { encoding = Strings.emptyToNull(atts.get(ATTR_CHARACTER_ENCODING)); } String txtmd = RawParseUtils.decode(Charset.forName(encoding != null ? encoding : UTF_8.name()), rawmd); long time = entry.getTime(); if (0 < time) { res.setDateHeader("Last-Modified", time); } sendMarkdownAsHtml(txtmd, pluginName, key, res, time); } private void sendResource( PluginContentScanner scanner, PluginEntry entry, PluginResourceKey key, HttpServletResponse res) throws IOException { byte[] data = null; Optional size = entry.getSize(); if (size.isPresent() && size.get() <= SMALL_RESOURCE) { data = readWholeEntry(scanner, entry); } String contentType = null; String charEnc = null; Map atts = entry.getAttrs(); if (atts != null) { contentType = Strings.emptyToNull(atts.get(ATTR_CONTENT_TYPE)); charEnc = Strings.emptyToNull(atts.get(ATTR_CHARACTER_ENCODING)); } if (contentType == null) { contentType = mimeUtil.getMimeType(entry.getName(), data).toString(); if ("application/octet-stream".equals(contentType) && entry.getName().endsWith(".js")) { contentType = "application/javascript"; } else if ("application/x-pointplus".equals(contentType) && entry.getName().endsWith(".css")) { contentType = "text/css"; } } long time = entry.getTime(); if (0 < time) { res.setDateHeader("Last-Modified", time); } if (size.isPresent()) { res.setHeader("Content-Length", size.get().toString()); } res.setContentType(contentType); if (charEnc != null) { res.setCharacterEncoding(charEnc); } if (data != null) { resourceCache.put( key, new SmallResource(data) .setContentType(contentType) .setCharacterEncoding(charEnc) .setLastModified(time)); res.getOutputStream().write(data); } else { writeToResponse(res, scanner.getInputStream(entry)); } } private void sendJsPlugin( Plugin plugin, PluginResourceKey key, HttpServletRequest req, HttpServletResponse res) throws IOException { Path path = plugin.getSrcFile(); if (req.getRequestURI().endsWith(getJsPluginPath(plugin)) && Files.exists(path)) { res.setHeader("Content-Length", Long.toString(Files.size(path))); if (path.toString().toLowerCase(Locale.US).endsWith(".html")) { res.setContentType("text/html"); } else { res.setContentType("application/javascript"); } writeToResponse(res, Files.newInputStream(path)); } else { resourceCache.put(key, Resource.NOT_FOUND); Resource.NOT_FOUND.send(req, res); } } private static String getJsPluginPath(Plugin plugin) { return String.format( "/plugins/%s/static/%s", plugin.getName(), plugin.getSrcFile().getFileName()); } private void writeToResponse(HttpServletResponse res, InputStream inputStream) throws IOException { try (InputStream in = inputStream; OutputStream out = res.getOutputStream()) { ByteStreams.copy(in, out); } } private static byte[] readWholeEntry(PluginContentScanner scanner, PluginEntry entry) throws IOException { try (InputStream in = scanner.getInputStream(entry)) { return IO.readWholeStream(in, entry.getSize().get().intValue()).array(); } } private static class PluginHolder { final Plugin plugin; final GuiceFilter filter; final String staticPrefix; final String docPrefix; PluginHolder(Plugin plugin, GuiceFilter filter) { this.plugin = plugin; this.filter = filter; this.staticPrefix = getPrefix(plugin, "Gerrit-HttpStaticPrefix", "static/"); this.docPrefix = getPrefix(plugin, "Gerrit-HttpDocumentationPrefix", "Documentation/"); } private static String getPrefix(Plugin plugin, String attr, String def) { Path path = plugin.getSrcFile(); PluginContentScanner scanner = plugin.getContentScanner(); if (path == null || scanner == PluginContentScanner.EMPTY) { return def; } try { String prefix = scanner.getManifest().getMainAttributes().getValue(attr); if (prefix != null) { return CharMatcher.is('/').trimFrom(prefix) + "/"; } return def; } catch (IOException e) { logger.atWarning().withCause(e).log( "Error getting %s for plugin %s, using default", attr, plugin.getName()); return null; } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy