Package com.google.gerrit.httpd.plugins

Source Code of com.google.gerrit.httpd.plugins.HttpPluginServlet$WrappedRequest

// 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 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.gerrit.extensions.registration.RegistrationHandle;
import com.google.gerrit.server.MimeUtilFileTypeRegistry;
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.plugins.Plugin;
import com.google.gerrit.server.plugins.ReloadPluginListener;
import com.google.gerrit.server.plugins.StartPluginListener;
import com.google.gerrit.server.ssh.SshInfo;
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 org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.util.IO;
import org.eclipse.jgit.util.RawParseUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.FilterChain;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;

@Singleton
class HttpPluginServlet extends HttpServlet
    implements StartPluginListener, ReloadPluginListener {
  private static final int SMALL_RESOURCE = 128 * 1024;
  private static final long serialVersionUID = 1L;
  private static final Logger log
      = LoggerFactory.getLogger(HttpPluginServlet.class);

  private final MimeUtilFileTypeRegistry mimeUtil;
  private final Provider<String> webUrl;
  private final Cache<ResourceKey, Resource> resourceCache;
  private final String sshHost;
  private final int sshPort;

  private List<Plugin> pending = Lists.newArrayList();
  private String base;
  private final ConcurrentMap<String, PluginHolder> plugins
      = Maps.newConcurrentMap();

  @Inject
  HttpPluginServlet(MimeUtilFileTypeRegistry mimeUtil,
      @CanonicalWebUrl Provider<String> webUrl,
      @Named(HttpPluginModule.PLUGIN_RESOURCES) Cache<ResourceKey, Resource> cache,
      @GerritServerConfig Config cfg,
      SshInfo sshInfo) {
    this.mimeUtil = mimeUtil;
    this.webUrl = webUrl;
    this.resourceCache = cache;

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

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

    String path = config.getServletContext().getContextPath();
    base = Strings.nullToEmpty(path) + "/plugins/";
    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(new RegistrationHandle() {
      @Override
      public void remove() {
        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) {
        log.warn(String.format("Plugin %s cannot load GuiceFilter", name), e);
        return null;
      }

      try {
        WrappedContext ctx = new WrappedContext(plugin, base + name);
        filter.init(new WrappedFilterConfig(ctx));
      } catch (ServletException e) {
        log.warn(String.format("Plugin %s failed to initialize HTTP", name), e);
        return null;
      }

      plugin.add(new RegistrationHandle() {
        @Override
        public void remove() {
          filter.destroy();
        }
      });
      return filter;
    }
    return null;
  }

  @Override
  public void service(HttpServletRequest req, HttpServletResponse res)
      throws IOException, ServletException {
    String name = extractName(req);
    final PluginHolder holder = plugins.get(name);
    if (holder == null) {
      noCache(res);
      res.sendError(HttpServletResponse.SC_NOT_FOUND);
      return;
    }

    WrappedRequest wr = new WrappedRequest(req, base + name);
    FilterChain chain = new FilterChain() {
      @Override
      public void doFilter(ServletRequest req, ServletResponse res)
          throws IOException {
        onDefault(holder, (HttpServletRequest) req, (HttpServletResponse) res);
      }
    };
    if (holder.filter != null) {
      holder.filter.doFilter(wr, res, chain);
    } else {
      chain.doFilter(wr, res);
    }
  }

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

    String uri = req.getRequestURI();
    String ctx = req.getContextPath();
    if (uri.length() <= ctx.length()) {
      Resource.NOT_FOUND.send(req, res);
      return;
    }

    String file = uri.substring(ctx.length() + 1);
    ResourceKey key = new ResourceKey(holder.plugin, file);
    Resource rsc = resourceCache.getIfPresent(key);
    if (rsc != null) {
      rsc.send(req, res);
      return;
    }

    if ("".equals(file)) {
      res.sendRedirect(uri + "Documentation/index.html");
    } else if (file.startsWith("static/")) {
      JarFile jar = holder.plugin.getJarFile();
      JarEntry entry = jar.getJarEntry(file);
      if (exists(entry)) {
        sendResource(jar, entry, key, res);
      } else {
        resourceCache.put(key, Resource.NOT_FOUND);
        Resource.NOT_FOUND.send(req, res);
      }
    } else if (file.equals("Documentation")) {
      res.sendRedirect(uri + "/index.html");
    } else if (file.startsWith("Documentation/") && file.endsWith("/")) {
      res.sendRedirect(uri + "index.html");
    } else if (file.startsWith("Documentation/")) {
      JarFile jar = holder.plugin.getJarFile();
      JarEntry entry = jar.getJarEntry(file);
      if (!exists(entry)) {
        entry = findSource(jar, file);
      }
      if (!exists(entry) && file.endsWith("/index.html")) {
        String pfx = file.substring(0, file.length() - "index.html".length());
        sendAutoIndex(jar, pfx, holder.plugin.getName(), key, res);
      } else if (exists(entry) && entry.getName().endsWith(".md")) {
        sendMarkdownAsHtml(jar, entry, holder.plugin.getName(), key, res);
      } else if (exists(entry)) {
        sendResource(jar, entry, 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 void sendAutoIndex(JarFile jar,
      String prefix, String pluginName,
      ResourceKey cacheKey, HttpServletResponse res) throws IOException {
    List<JarEntry> cmds = Lists.newArrayList();
    List<JarEntry> docs = Lists.newArrayList();
    Enumeration<JarEntry> entries = jar.entries();
    while (entries.hasMoreElements()) {
      JarEntry entry = entries.nextElement();
      String name = entry.getName();
      long size = entry.getSize();
      if (name.startsWith(prefix)
          && (name.endsWith(".md")
              || name.endsWith(".html"))
          && 0 < size && size <= SMALL_RESOURCE) {
        if (name.substring(prefix.length()).startsWith("cmd-")) {
          cmds.add(entry);
        } else {
          docs.add(entry);
        }
      }
    }
    Collections.sort(cmds, new Comparator<JarEntry>() {
      @Override
      public int compare(JarEntry a, JarEntry b) {
        return a.getName().compareTo(b.getName());
      }
    });
    Collections.sort(docs, new Comparator<JarEntry>() {
      @Override
      public int compare(JarEntry a, JarEntry b) {
        return a.getName().compareTo(b.getName());
      }
    });

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

    if (!docs.isEmpty()) {
      md.append("## Documentation ##\n");
      for(JarEntry entry : docs) {
        String rsrc = entry.getName().substring(prefix.length());
        String title;
        if (rsrc.endsWith(".html")) {
          title = rsrc.substring(0, rsrc.length() - 5).replace('-', ' ');
        } else if (rsrc.endsWith(".md")) {
          title = extractTitleFromMarkdown(jar, entry);
          if (Strings.isNullOrEmpty(title)) {
            title = rsrc.substring(0, rsrc.length() - 3).replace('-', ' ');
          }
          rsrc = rsrc.substring(0, rsrc.length() - 3) + ".html";
        } else {
          title = rsrc.replace('-', ' ');
        }
        md.append(String.format("* [%s](%s)\n", title, rsrc));
      }
      md.append("\n");
    }

    if (!cmds.isEmpty()) {
      md.append("## Commands ##\n");
      for(JarEntry entry : cmds) {
        String rsrc = entry.getName().substring(prefix.length());
        String title;
        if (rsrc.endsWith(".html")) {
          title = rsrc.substring(4, rsrc.length() - 5).replace('-', ' ');
        } else if (rsrc.endsWith(".md")) {
          title = extractTitleFromMarkdown(jar, entry);
          if (Strings.isNullOrEmpty(title)) {
            title = rsrc.substring(4, rsrc.length() - 3).replace('-', ' ');
          }
          rsrc = rsrc.substring(0, rsrc.length() - 3) + ".html";
        } else {
          title = rsrc.substring(4).replace('-', ' ');
        }
        md.append(String.format("* [%s](%s)\n", title, rsrc));
      }
      md.append("\n");
    }

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

  private void sendMarkdownAsHtml(String md, String pluginName,
      ResourceKey cacheKey, HttpServletResponse res)
      throws UnsupportedEncodingException, IOException {
    Map<String, String> macros = Maps.newHashMap();
    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");
    resourceCache.put(cacheKey, new SmallResource(html)
        .setContentType("text/html")
        .setCharacterEncoding("UTF-8"));
    res.setContentType("text/html");
    res.setCharacterEncoding("UTF-8");
    res.setContentLength(html.length);
    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 u = main.getValue(Attributes.Name.IMPLEMENTATION_URL);
      String a = main.getValue("Gerrit-ApiVersion");

      html.append("<table class=\"plugin_info\">");
      if (!Strings.isNullOrEmpty(t)) {
        html.append("<tr><th>Name</th><td>")
            .append(t)
            .append("</td></tr>\n");
      }
      if (!Strings.isNullOrEmpty(n)) {
        html.append("<tr><th>Vendor</th><td>")
            .append(n)
            .append("</td></tr>\n");
      }
      if (!Strings.isNullOrEmpty(v)) {
        html.append("<tr><th>Version</th><td>")
            .append(v)
            .append("</td></tr>\n");
      }
      if (!Strings.isNullOrEmpty(u)) {
        html.append("<tr><th>URL</th><td>")
            .append(String.format("<a href=\"%s\">%s</a>", u, u))
            .append("</td></tr>\n");
      }
      if (!Strings.isNullOrEmpty(a)) {
        html.append("<tr><th>API Version</th><td>")
            .append(a)
            .append("</td></tr>\n");
      }
      html.append("</table>\n");
    }
  }

  private static String extractTitleFromMarkdown(JarFile jar, JarEntry entry)
        throws IOException {
    String charEnc = null;
    Attributes atts = entry.getAttributes();
    if (atts != null) {
      charEnc = Strings.emptyToNull(atts.getValue("Character-Encoding"));
    }
    if (charEnc == null) {
      charEnc = "UTF-8";
    }
    return new MarkdownFormatter().extractTitleFromMarkdown(
          readWholeEntry(jar, entry),
          charEnc);
  }

  private static JarEntry findSource(JarFile jar, String file) {
    if (file.endsWith(".html")) {
      int d = file.lastIndexOf('.');
      return jar.getJarEntry(file.substring(0, d) + ".md");
    }
    return null;
  }

  private static boolean exists(JarEntry entry) {
    return entry != null && entry.getSize() > 0;
  }

  private void sendMarkdownAsHtml(JarFile jar, JarEntry entry,
      String pluginName, ResourceKey key, HttpServletResponse res)
      throws IOException {
    byte[] rawmd = readWholeEntry(jar, entry);
    String encoding = null;
    Attributes atts = entry.getAttributes();
    if (atts != null) {
      encoding = Strings.emptyToNull(atts.getValue("Character-Encoding"));
    }

    String txtmd = RawParseUtils.decode(
        Charset.forName(encoding != null ? encoding : "UTF-8"),
        rawmd);
    long time = entry.getTime();
    if (0 < time) {
      res.setDateHeader("Last-Modified", time);
    }
    sendMarkdownAsHtml(txtmd, pluginName, key, res);
  }

  private void sendResource(JarFile jar, JarEntry entry,
      ResourceKey key, HttpServletResponse res)
      throws IOException {
    byte[] data = null;
    if (entry.getSize() <= SMALL_RESOURCE) {
      data = readWholeEntry(jar, entry);
    }

    String contentType = null;
    String charEnc = null;
    Attributes atts = entry.getAttributes();
    if (atts != null) {
      contentType = Strings.emptyToNull(atts.getValue("Content-Type"));
      charEnc = Strings.emptyToNull(atts.getValue("Character-Encoding"));
    }
    if (contentType == null) {
      contentType = mimeUtil.getMimeType(entry.getName(), data).toString();
    }

    long time = entry.getTime();
    if (0 < time) {
      res.setDateHeader("Last-Modified", time);
    }
    res.setHeader("Content-Length", Long.toString(entry.getSize()));
    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 {
      InputStream in = jar.getInputStream(entry);
      try {
        OutputStream out = res.getOutputStream();
        try {
          byte[] tmp = new byte[1024];
          int n;
          while ((n = in.read(tmp)) > 0) {
            out.write(tmp, 0, n);
          }
        } finally {
          out.close();
        }
      } finally {
        in.close();
      }
    }
  }

  private static byte[] readWholeEntry(JarFile jar, JarEntry entry)
      throws IOException {
    byte[] data = new byte[(int) entry.getSize()];
    InputStream in = jar.getInputStream(entry);
    try {
      IO.readFully(in, data, 0, data.length);
    } finally {
      in.close();
    }
    return data;
  }

  private static String extractName(HttpServletRequest req) {
    String path = req.getPathInfo();
    if (Strings.isNullOrEmpty(path) || "/".equals(path)) {
      return "";
    }
    int s = path.indexOf('/', 1);
    return 0 <= s ? path.substring(1, s) : path.substring(1);
  }

  static void noCache(HttpServletResponse res) {
    res.setHeader("Expires", "Fri, 01 Jan 1980 00:00:00 GMT");
    res.setHeader("Pragma", "no-cache");
    res.setHeader("Cache-Control", "no-cache, must-revalidate");
    res.setHeader("Content-Disposition", "attachment");
  }

  private static class PluginHolder {
    final Plugin plugin;
    final GuiceFilter filter;

    PluginHolder(Plugin plugin, GuiceFilter filter) {
      this.plugin = plugin;
      this.filter = filter;
    }
  }

  private static class WrappedRequest extends HttpServletRequestWrapper {
    private final String contextPath;

    WrappedRequest(HttpServletRequest req, String contextPath) {
      super(req);
      this.contextPath = contextPath;
    }

    @Override
    public String getContextPath() {
      return contextPath;
    }

    @Override
    public String getServletPath() {
      return ((HttpServletRequest) getRequest()).getRequestURI();
    }
  }
}
TOP

Related Classes of com.google.gerrit.httpd.plugins.HttpPluginServlet$WrappedRequest

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.