/* * Copyright (C) 2015 Red Hat, Inc. and/or its affiliates. * * 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 org.jboss.errai.common.server; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.jboss.errai.common.client.api.Assert; /** * This servlet provides a cache manifest file specific to the requesting user * agent. It responds to .appcache requests and dispatches to user agent * specific appcache.manifest files (i.e. safari.appcache.manifest). These files * are generated at compile time by a dedicated linker. See the Errai reference * guide for details on how to activate this linker. * * @author Christian Sadilek <csadilek@redhat.com> */ @WebServlet(urlPatterns = "*.appcache") public class CacheManifestServlet extends HttpServlet { private static final long serialVersionUID = 1L; private static final String MANIFEST_FILE_EXTENSION = ".appcache.manifest"; // Lazily populated cache for user agent names for which a manifest file // exists, on a per module basis private final Map<String, Set<String>> manifestsPerModule = new ConcurrentHashMap<String, Set<String>>(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { final Pattern pattern = Pattern.compile("/([a-zA-Z0-9_]+)/errai.appcache"); final Matcher matcher = pattern.matcher(req.getServletPath()); if (!matcher.find()) { resp.sendError(HttpServletResponse.SC_NOT_FOUND); return; } final String module = matcher.group(1); String userAgentManifestPath = null; final String referrer = req.getHeader("referer"); if (referrer != null && referrer.contains("gwt.codesvr")) { // Serve an empty manifest in development mode. This is not reliable as // some browser won't send the referer header when requesting the // manifest. In that case we simply return a 404 (the manifest is not // needed in dev mode anyway but we try to avoid the error, if possible). userAgentManifestPath = "/" + module + "/dev.appcache.manifest"; } else { userAgentManifestPath = "/" + module + "/" + getUserAgentManifestName(req, module); } resp.setHeader("Cache-Control", "no-cache"); resp.setHeader("Pragma", "no-cache"); resp.setContentType("text/cache-manifest"); req.getRequestDispatcher(userAgentManifestPath).forward(req, resp); } private String getUserAgentManifestName(HttpServletRequest req, String module) { final String userAgentHeader = req.getHeader("user-agent").toLowerCase(); String agentPrefix = ""; // Do not change the order of these checks. To verify this, compile a // nocache.js file in pretty mode and compare the corresponding client-side // logic provided by GWT. Tested with GWT 2.5 and 2.6. if (userAgentHeader.contains("opera") && manifestExists("opera", module)) { agentPrefix = "opera"; } else if (userAgentHeader.contains("webkit") && manifestExists("safari", module)) { agentPrefix = "safari"; } else if (userAgentHeader.contains("msie 10") && manifestExists("ie10", module)) { agentPrefix = "ie10"; } else if (userAgentHeader.contains("msie 10") && manifestExists("ie9", module)) { agentPrefix = "ie9"; } else if (userAgentHeader.contains("msie 9") && manifestExists("ie9", module)) { agentPrefix = "ie9"; } else if (userAgentHeader.contains("msie 8") && manifestExists("ie8", module)) { agentPrefix = "ie8"; } else if (userAgentHeader.contains("msie") && manifestExists("ie6", module)) { agentPrefix = "ie6"; } else if (userAgentHeader.contains("msie") && manifestExists("ie8", module)) { agentPrefix = "ie8"; } else if (userAgentHeader.contains("gecko") && manifestExists("gecko1_8", module)) { agentPrefix = "gecko1_8"; } return agentPrefix + MANIFEST_FILE_EXTENSION; } /** * Returns the set of user agent names for which a manifest file exists in the * provided module. * * @param module * the name of the GWT module. Not null. * @return the user agent names for which a manifest file exists. */ private Set<String> getManifestsForModule(String module) { Assert.notNull(module); Set<String> manifests = manifestsPerModule.get(module); if (manifests == null) { final String path = getServletContext().getRealPath(module); final File[] manifestFiles = new File(path).listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.endsWith(MANIFEST_FILE_EXTENSION); } }); manifests = new HashSet<String>(); if (manifestFiles != null) { for (final File manifestFile : manifestFiles) { final String name = manifestFile.getName(); manifests.add(name.replace(MANIFEST_FILE_EXTENSION, "")); } } manifestsPerModule.put(module, manifests); } return manifests; } /** * Checks if a user agent specific manifest file exists for the provided * module. * * @param userAgent * the user agent * @param module * the GWT module name * @return true if the manifest exists, otherwise false. */ private boolean manifestExists(String userAgent, String module) { return getManifestsForModule(module).contains(userAgent); } }