| // Copyright (C) 2008 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.server.http; |
| |
| import com.google.gerrit.client.HostPageData; |
| import com.google.gerrit.client.data.GerritConfig; |
| import com.google.gerrit.server.CurrentUser; |
| import com.google.gerrit.server.IdentifiedUser; |
| import com.google.gerrit.server.config.CanonicalWebUrl; |
| import com.google.gerrit.server.config.Nullable; |
| import com.google.gerrit.server.config.SitePath; |
| import com.google.gwt.user.server.rpc.RPCServletUtils; |
| import com.google.gwtjsonrpc.server.JsonServlet; |
| import com.google.inject.Inject; |
| import com.google.inject.Provider; |
| import com.google.inject.Singleton; |
| |
| import org.spearce.jgit.lib.Constants; |
| import org.spearce.jgit.lib.ObjectId; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| |
| import java.io.File; |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.io.StringWriter; |
| import java.security.MessageDigest; |
| |
| import javax.servlet.ServletContext; |
| import javax.servlet.http.HttpServlet; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| |
| /** Sends the Gerrit host page to clients. */ |
| @SuppressWarnings("serial") |
| @Singleton |
| public class HostPageServlet extends HttpServlet { |
| private final Provider<CurrentUser> currentUser; |
| private final Provider<WebSession> webSession; |
| private final File sitePath; |
| private final GerritConfig config; |
| private final Provider<String> urlProvider; |
| private final boolean wantSSL; |
| private final Document hostDoc; |
| |
| @Inject |
| HostPageServlet(final Provider<CurrentUser> cu, |
| final Provider<WebSession> ws, @SitePath final File path, |
| final GerritConfig gc, |
| @CanonicalWebUrl @Nullable final Provider<String> up, |
| @CanonicalWebUrl @Nullable final String configuredUrl, |
| final ServletContext servletContext) throws IOException { |
| currentUser = cu; |
| webSession = ws; |
| urlProvider = up; |
| sitePath = path; |
| config = gc; |
| wantSSL = configuredUrl != null && configuredUrl.startsWith("https:"); |
| |
| final String hostPageName = "WEB-INF/HostPage.html"; |
| hostDoc = HtmlDomUtil.parseFile(servletContext, "/" + hostPageName); |
| if (hostDoc == null) { |
| throw new FileNotFoundException("No " + hostPageName + " in webapp"); |
| } |
| fixModuleReference(hostDoc, servletContext); |
| injectCssFile(hostDoc, "gerrit_sitecss", sitePath, "GerritSite.css"); |
| injectXmlFile(hostDoc, "gerrit_header", sitePath, "GerritSiteHeader.html"); |
| injectXmlFile(hostDoc, "gerrit_footer", sitePath, "GerritSiteFooter.html"); |
| } |
| |
| private void injectXmlFile(final Document hostDoc, final String id, |
| final File sitePath, final String fileName) throws IOException { |
| final Element banner = HtmlDomUtil.find(hostDoc, id); |
| if (banner == null) { |
| return; |
| } |
| |
| while (banner.getFirstChild() != null) { |
| banner.removeChild(banner.getFirstChild()); |
| } |
| |
| final Document html = HtmlDomUtil.parseFile(sitePath, fileName); |
| if (html == null) { |
| banner.getParentNode().removeChild(banner); |
| return; |
| } |
| |
| final Element content = html.getDocumentElement(); |
| banner.appendChild(hostDoc.importNode(content, true)); |
| } |
| |
| private void injectCssFile(final Document hostDoc, final String id, |
| final File sitePath, final String fileName) throws IOException { |
| final Element banner = HtmlDomUtil.find(hostDoc, id); |
| if (banner == null) { |
| return; |
| } |
| |
| while (banner.getFirstChild() != null) { |
| banner.removeChild(banner.getFirstChild()); |
| } |
| |
| final String css = HtmlDomUtil.readFile(sitePath, fileName); |
| if (css == null) { |
| banner.getParentNode().removeChild(banner); |
| return; |
| } |
| |
| banner.removeAttribute("id"); |
| banner.appendChild(hostDoc.createCDATASection("\n" + css + "\n")); |
| } |
| |
| private void injectJson(final Document hostDoc, final String id, |
| final Object obj) { |
| final Element scriptNode = HtmlDomUtil.find(hostDoc, id); |
| if (scriptNode == null) { |
| return; |
| } |
| |
| while (scriptNode.getFirstChild() != null) { |
| scriptNode.removeChild(scriptNode.getFirstChild()); |
| } |
| |
| if (obj == null) { |
| scriptNode.getParentNode().removeChild(scriptNode); |
| return; |
| } |
| |
| final StringWriter w = new StringWriter(); |
| w.write("<!--\n"); |
| w.write("var "); |
| w.write(id); |
| w.write("_obj="); |
| JsonServlet.defaultGsonBuilder().create().toJson(obj, w); |
| w.write(";\n// -->\n"); |
| scriptNode.removeAttribute("id"); |
| scriptNode.setAttribute("type", "text/javascript"); |
| scriptNode.setAttribute("language", "javascript"); |
| scriptNode.appendChild(hostDoc.createCDATASection(w.toString())); |
| } |
| |
| private void fixModuleReference(final Document hostDoc, |
| final ServletContext servletContext) throws IOException { |
| final Element scriptNode = HtmlDomUtil.find(hostDoc, "gerrit_module"); |
| if (scriptNode == null) { |
| throw new IOException("No gerrit_module to rewrite in host document"); |
| } |
| scriptNode.removeAttribute("id"); |
| |
| final String src = scriptNode.getAttribute("src"); |
| InputStream in = servletContext.getResourceAsStream("/" + src); |
| if (in == null) { |
| throw new IOException("No " + src + " in webapp root"); |
| } |
| |
| final MessageDigest md = Constants.newMessageDigest(); |
| try { |
| try { |
| final byte[] buf = new byte[1024]; |
| int n; |
| while ((n = in.read(buf)) > 0) { |
| md.update(buf, 0, n); |
| } |
| } finally { |
| in.close(); |
| } |
| } catch (IOException e) { |
| throw new IOException("Failed reading " + src, e); |
| } |
| |
| final String vstr = ObjectId.fromRaw(md.digest()).name(); |
| scriptNode.setAttribute("src", src + "?content=" + vstr); |
| } |
| |
| @Override |
| protected void doGet(final HttpServletRequest req, |
| final HttpServletResponse rsp) throws IOException { |
| // If we wanted SSL, but the user didn't come to us over an SSL channel, |
| // force it to be SSL by issuing a protocol redirect. Try to keep the |
| // name "localhost" in case this is an SSH port tunnel. |
| // |
| if (wantSSL && !isSecure(req)) { |
| final StringBuffer reqUrl = req.getRequestURL(); |
| if (isLocalHost(req)) { |
| reqUrl.replace(0, reqUrl.indexOf(":"), "https"); |
| } else { |
| reqUrl.setLength(0); |
| reqUrl.append(urlProvider.get()); |
| } |
| rsp.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); |
| rsp.setHeader("Location", reqUrl.toString()); |
| return; |
| } |
| |
| final HostPageData pageData = new HostPageData(); |
| pageData.config = config; |
| |
| final CurrentUser user = currentUser.get(); |
| if (user instanceof IdentifiedUser) { |
| pageData.xsrfToken = webSession.get().getToken(); |
| pageData.userAccount = ((IdentifiedUser) user).getAccount(); |
| } |
| |
| final Document peruser = HtmlDomUtil.clone(hostDoc); |
| injectJson(peruser, "gerrit_hostpagedata", pageData); |
| |
| final byte[] raw = HtmlDomUtil.toUTF8(peruser); |
| final byte[] tosend; |
| if (RPCServletUtils.acceptsGzipEncoding(req)) { |
| rsp.setHeader("Content-Encoding", "gzip"); |
| tosend = HtmlDomUtil.compress(raw); |
| } else { |
| tosend = raw; |
| } |
| |
| rsp.setHeader("Expires", "Fri, 01 Jan 1980 00:00:00 GMT"); |
| rsp.setHeader("Pragma", "no-cache"); |
| rsp.setHeader("Cache-Control", "no-cache, must-revalidate"); |
| rsp.setContentType("text/html"); |
| rsp.setCharacterEncoding(HtmlDomUtil.ENC); |
| rsp.setContentLength(tosend.length); |
| final OutputStream out = rsp.getOutputStream(); |
| try { |
| out.write(tosend); |
| } finally { |
| out.close(); |
| } |
| } |
| |
| private static boolean isSecure(final HttpServletRequest req) { |
| return "https".equals(req.getScheme()) || req.isSecure(); |
| } |
| |
| private static boolean isLocalHost(final HttpServletRequest req) { |
| return "localhost".equals(req.getServerName()) |
| || "127.0.0.1".equals(req.getServerName()); |
| } |
| } |