package org.ovirt.engine.core.utils.servlet; import java.io.File; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.Locale; import javax.servlet.RequestDispatcher; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.ovirt.engine.core.utils.LocaleUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class serves locale dependent documents. It takes the current selected locale * and finds the appropriate file based on that locale and the file requested and * returns it the browser. */ public class DocsServlet extends FileServlet { // The log: private static final Logger log = LoggerFactory.getLogger(FileServlet.class); private static final long serialVersionUID = 3804716423059474163L; public static final String REFERER = "Referer"; public static final String LANG_PAGE_SHOWN = "langPageShown"; private static final String ENGLISH_HREF = "englishHref"; private static final String LOCALE_DOCS_MISSING = "localeDocsMissingURI"; private String localeDocsMissing; @Override public void init(final ServletConfig config) throws ServletException { // Let the parent do its work: super.init(config); localeDocsMissing = config.getInitParameter(LOCALE_DOCS_MISSING); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // Locate the requested file: File originalFile = ServletUtils.makeFileFromSanePath(request.getPathInfo(), base); Locale locale = getLocaleFromRequest(request); File file = determineActualFile(request, locale); file = checkForIndex(request, response, file, request.getPathInfo()); if (file == null) { response.sendError(HttpServletResponse.SC_NOT_FOUND); } else if (!response.isCommitted()) { //If the response is committed, we have already redirected. boolean languagePageShown = isLangPageShown(request); if (!file.equals(originalFile) && !file.getAbsolutePath().equals(replaceLocaleWithOtherLocale(originalFile.getAbsolutePath(), locale, locale))) { //We determined that we are going to redirect the user to the English version URI. String redirect = getServletContext().getContextPath() + request.getServletPath() + replaceLocaleWithUSLocale(request.getPathInfo(), locale); if (!languagePageShown) { setLangPageShown(response, true); request.setAttribute(LocaleFilter.LOCALE, locale); request.setAttribute(ENGLISH_HREF, redirect); final ServletContext forwardContext = getServletContext(); if (forwardContext != null) { final RequestDispatcher dispatcher = forwardContext.getRequestDispatcher(localeDocsMissing); if (dispatcher != null) { dispatcher.forward(request, response); } else { log.error("Unable to determine dispatcher"); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "unable to determine dispatcher"); } } else { log.error("Unable to determine forwarding context"); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "unable to determine forward context"); } } else { //Redirect to English version of the document response.sendRedirect(redirect); } } else { // Send the content of the file: // type is the default MIME type of the Servlet(passed in through WebInit parameter). ServletUtils.sendFile(request, response, file, type); } } } private boolean isLangPageShown(HttpServletRequest request) { boolean result = false; Cookie[] cookies = request.getCookies(); if (cookies != null) { for (Cookie cookie : cookies) { if (LANG_PAGE_SHOWN.equalsIgnoreCase(cookie.getName())) { result = Boolean.parseBoolean(cookie.getValue()); break; } } } return result; } private void setLangPageShown(HttpServletResponse response, boolean value) { Cookie cookie = new Cookie(LANG_PAGE_SHOWN, Boolean.toString(value)); // Scope this cookie to the (root) application context URL cookie.setPath(getServletContext().getContextPath()); cookie.setHttpOnly(true); // Don't set max age, i.e. let this be a session cookie response.addCookie(cookie); } /** * Determine the actual file based on the passed in {@code HttpServletRequest} path and * the passed in {@code Locale}. If the Locale is invalid or cannot be found, use the * default (EN_US) locale. * Returns null if the file cannot be found. * * @param request The {@code HttpServletRequest} containing the path. * @param locale The {@code Locale} to search for. * @return A {@code File} object pointing to the requested file. */ protected File determineActualFile(final HttpServletRequest request, Locale locale) { // webapp asks for files in xx_XX locale, but the directory has a - not a _ . Convert. File file = ServletUtils.makeFileFromSanePath(replaceLocaleWithOtherLocale(request.getPathInfo(), locale, locale), base); // Check if file is found. If not found go ahead and try and look up the English US locale version. if (file != null && !ServletUtils.canReadFile(file)) { file = ServletUtils.makeFileFromSanePath(replaceLocaleWithUSLocale(request.getPathInfo(), locale), base); } return file; } private String replaceLocaleWithUSLocale(String originalString, Locale locale) { return replaceLocaleWithOtherLocale(originalString != null ? originalString : "", //$NON-NLS-1$ locale, Locale.US); } private String replaceLocaleWithOtherLocale(String originalString, Locale searchLocale, Locale targetLocale) { //Create regex to match either the toString() or toLanguageTag() version of the locale //For US Locale this means: /en\-US|/en_US //For Brazil this means: /pt\-BR|/pt_BR //For Japan this means: /ja|/ja (yes I know its the same). String regex = "/"+ searchLocale.toLanguageTag().replaceAll("-", "\\\\-") + "|/" + searchLocale.toString(); //This will match for instance '/pt-BR/something' and turn it into '/en-US/something', but //it will also match '/pt_BR/something' and turn it into '/en-US/something' if targetLocale is en_US. return originalString.replaceAll(regex, "/" + targetLocale.toLanguageTag()); } /** * Determines the locale based on the request passed in. It will first try to determine the locale * from the referer URI passed. If it can't determine the Locale, it will attempt to use the pathinfo * of the request. If that fails it defaults back to the US locale. * @param request The request to use to determine the locale. * @return A {@code Locale} */ protected Locale getLocaleFromRequest(final HttpServletRequest request) { String localeString = getLocaleStringFromReferer(request); //Unable to determine locale string from referer (preferred way) if (localeString == null) { //Note this fails if the is something like /menu.css localeString = getLocaleStringFromPath(request.getPathInfo()); } //Validate that the locale string is valid. Locale locale = LocaleUtils.getLocaleFromString(localeString, true); return locale; } /** * Get the locale string from the path. * Assumption the path format is the following: * /<locale>/stuff * @param path The path to get the locale from. * @return The locale string stripped from the path. If the base + * the found locale is a file and not a directory, return null as the locale is a filename. */ protected String getLocaleStringFromPath(final String path) { String result = null; if (path != null) { if (!path.startsWith("/")) { log.warn("Path should start with a '/'"); return null; } //Attempt to determine locale from path info. String[] pathElements = path.substring(1).split("/"); File localeFile = new File(base, pathElements[0]); //Check to make sure the file doesn't exist, and if it does, that it is a directory. //This excludes anything like /docs/menu.css if (!localeFile.exists() || localeFile.isDirectory()) { result = pathElements[0]; } } return result; } /** * Attempt to determine the locale from the referer URL. The referer must have a parameter that looks like this:<br> * ?locale=<locale string><br> * for instance the following will return 'fr'<br> * http://127.0.0.1:8700/webadmin/webadmin/WebAdmin.html?locale=fr<br> * if there is no referer or the referer does not have a 'locale' parameter described, the result will be null.<br> * @param request The {@code HttpServletRequest} that has the referer header. * @return The referer parameter locale, null otherwise */ protected String getLocaleStringFromReferer(final HttpServletRequest request) { // Determine the local passed in. To do this check the referer final URI refererURL; String result = null; try { String referer = request.getHeader(REFERER); if (referer != null) { refererURL = new URI(referer); String query = refererURL.getQuery(); if (query != null) { String[] parameters = query.split("&"); for (int i = 0; i < parameters.length; i++) { String[] keyValues = parameters[i].split("="); if (LocaleFilter.LOCALE.equalsIgnoreCase(keyValues[0])) { result = keyValues[1]; break; } } } } } catch (URISyntaxException e) { log.error("Unable to determine referer URI", e); } return result; } }