/* * Copyright (c) 1998-2018 John Caron and University Corporation for Atmospheric Research/Unidata * See LICENSE for license information. */ package thredds.servlet; import thredds.core.ConfigCatalogHtmlWriter; import thredds.util.ContentType; import thredds.util.RequestForwardUtils; import ucar.nc2.constants.CDM; import ucar.nc2.util.EscapeStrings; import ucar.nc2.util.IO; import ucar.unidata.io.RandomAccessFile; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.*; import java.net.URI; import java.net.URISyntaxException; import java.util.*; public class ServletUtil { public static final org.slf4j.Logger logServerStartup = org.slf4j.LoggerFactory.getLogger("serverStartup"); private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ServletUtil.class); /** * Return the file path dealing with leading and trailing path * seperators (which must be a slash ("/")) for the given directory * and file paths. *
* Note: Dealing with path strings is fragile. * ToDo: Switch from using path strings to java.io.Files. * * @param dirPath the directory path. * @param filePath the file path. * @return a full file path with the given directory and file paths. */ public static String formFilename(String dirPath, String filePath) { if ((dirPath == null) || (filePath == null)) return null; if (filePath.startsWith("/")) filePath = filePath.substring(1); return dirPath.endsWith("/") ? dirPath + filePath : dirPath + "/" + filePath; } /** * Write a file to the response stream. * * @param servlet called from this servlet, may be null * @param contentPath file root path * @param path file path reletive to the root * @param req the request * @param res the response * @param contentType content type, or null * @throws IOException on write error */ public static void returnFile(HttpServlet servlet, String contentPath, String path, HttpServletRequest req, HttpServletResponse res, String contentType) throws IOException { String filename = ServletUtil.formFilename(contentPath, path); log.debug("returnFile(): returning file <" + filename + ">."); // No file, nothing to view if (filename == null) { res.sendError(HttpServletResponse.SC_NOT_FOUND); return; } // dontallow .. if (filename.contains("..")) { res.sendError(HttpServletResponse.SC_FORBIDDEN); return; } // dont allow access to WEB-INF or META-INF String upper = filename.toUpperCase(); if (upper.contains("WEB-INF") || upper.contains("META-INF")) { res.sendError(HttpServletResponse.SC_FORBIDDEN); return; } returnFile(servlet, req, res, new File(filename), contentType); } /** * Write a file to the response stream. Handles Range requests. * * @param servlet called from this servlet, may be null * @param req the request * @param res the response * @param file to serve * @param contentType content type, if null, will try to guess * @throws IOException on write error */ public static void returnFile(HttpServlet servlet, HttpServletRequest req, HttpServletResponse res, File file, String contentType) throws IOException { // No file, nothing to view if (file == null) { res.sendError(HttpServletResponse.SC_NOT_FOUND); return; } // check that it exists if (!file.exists()) { res.sendError(HttpServletResponse.SC_NOT_FOUND); return; } // not a directory if (!file.isFile()) { res.sendError(HttpServletResponse.SC_BAD_REQUEST); return; } // Set the type of the file String filename = file.getPath(); if (null == contentType) { if (filename.endsWith(".html")) contentType = ContentType.html.getContentHeader(); else if (filename.endsWith(".xml")) contentType = ContentType.xml.getContentHeader(); else if (filename.endsWith(".txt") || (filename.endsWith(".log"))) contentType = ContentType.text.getContentHeader(); else if (filename.indexOf(".log.") > 0) contentType = ContentType.text.getContentHeader(); else if (filename.endsWith(".nc")) contentType = ContentType.netcdf.getContentHeader(); else if (filename.endsWith(".nc4")) contentType = ContentType.netcdf.getContentHeader(); else if (servlet != null) contentType = servlet.getServletContext().getMimeType(filename); if (contentType == null) contentType = ContentType.binary.getContentHeader(); } returnFile(req, res, file, contentType); } /** * Write a file to the response stream. Handles Range requests. * * @param req request * @param res response * @param file must exist and not be a directory * @param contentType must not be null * @throws IOException or error */ public static void returnFile(HttpServletRequest req, HttpServletResponse res, File file, String contentType) throws IOException { res.setContentType(contentType); res.addDateHeader("Last-Modified", file.lastModified()); // res.setHeader("Content-Disposition", "attachment; filename=\"" + file.getName() + "\""); // see if its a Range Request boolean isRangeRequest = false; long startPos = 0, endPos = Long.MAX_VALUE; String rangeRequest = req.getHeader("Range"); if (rangeRequest != null) { // bytes=12-34 or bytes=12- int pos = rangeRequest.indexOf("="); if (pos > 0) { int pos2 = rangeRequest.indexOf("-"); if (pos2 > 0) { String startString = rangeRequest.substring(pos + 1, pos2); String endString = rangeRequest.substring(pos2 + 1); startPos = Long.parseLong(startString); if (endString.length() > 0) endPos = Long.parseLong(endString) + 1; isRangeRequest = true; } } } // set content length long fileSize = file.length(); long contentLength = fileSize; if (isRangeRequest) { endPos = Math.min(endPos, fileSize); contentLength = endPos - startPos; } // when compression is turned on, ContentLength has to be overridden // this is also true for HEAD, since this must be the same as GET without the body if (contentLength > Integer.MAX_VALUE) res.addHeader("Content-Length", Long.toString(contentLength)); // allow content length > MAX_INT else res.setContentLength((int) contentLength); String filename = file.getPath(); // indicate we allow Range Requests res.addHeader("Accept-Ranges", "bytes"); if (req.getMethod().equals("HEAD")) { return; } try { if (isRangeRequest) { // set before content is sent res.addHeader("Content-Range", "bytes " + startPos + "-" + (endPos - 1) + "/" + fileSize); res.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); try (RandomAccessFile craf = RandomAccessFile.acquire(filename)) { IO.copyRafB(craf, startPos, contentLength, res.getOutputStream(), new byte[60000]); return; } } // Return the file ServletOutputStream out = res.getOutputStream(); IO.copyFileB(file, out, 60 * 1000); /* try (WritableByteChannel cOut = Channels.newChannel(out)) { IO.copyFileWithChannels(file, cOut); res.flushBuffer(); } */ } // @todo Split up this exception handling: those from file access vs those from dealing with response // File access: catch and res.sendError() // response: don't catch (let bubble up out of doGet() etc) catch (FileNotFoundException e) { log.error("returnFile(): FileNotFoundException= " + filename); if (!res.isCommitted()) res.sendError(HttpServletResponse.SC_NOT_FOUND); } catch (java.net.SocketException e) { log.info("returnFile(): SocketException sending file: " + filename + " " + e.getMessage()); } catch (IOException e) { String eName = e.getClass().getName(); // dont want compile time dependency on ClientAbortException if (eName.equals("org.apache.catalina.connector.ClientAbortException")) { log.debug("returnFile(): ClientAbortException while sending file: " + filename + " " + e.getMessage()); return; } if (e.getMessage().startsWith("File transfer not complete")) { // coming from FileTransfer.transferTo() log.debug("returnFile() "+e.getMessage()); return; } log.error("returnFile(): IOException (" + e.getClass().getName() + ") sending file ", e); if (!res.isCommitted()) res.sendError(HttpServletResponse.SC_NOT_FOUND, "Problem sending file: " + e.getMessage()); } } /** * Send given content string as the HTTP response. * * @param contents the string to return as the HTTP response. * @param res the HttpServletResponse * @throws IOException if an I/O error occurs while writing the response. */ public static void returnString(String contents, HttpServletResponse res) throws IOException { try { ServletOutputStream out = res.getOutputStream(); IO.copy(new ByteArrayInputStream(contents.getBytes(CDM.utf8Charset)), out); } catch (IOException e) { log.error(" IOException sending string: ", e); res.sendError(HttpServletResponse.SC_NOT_FOUND, "Problem sending string: " + e.getMessage()); } } /** * Set the proper content length for the string * * @param response the HttpServletResponse to act upon * @param s the string that will be returned * @return the number of bytes * @throws UnsupportedEncodingException on bad character encoding */ public static int setResponseContentLength(HttpServletResponse response, String s) throws UnsupportedEncodingException { int length = s.getBytes(response.getCharacterEncoding()).length; response.setContentLength(length); return length; } /** * Return the request URL relative to the server (i.e., starting with the context path). * * @param req request * @return URL relative to the server */ public static String getReletiveURL(HttpServletRequest req) { return req.getContextPath() + req.getServletPath() + req.getPathInfo(); } /** * Forward this request to the CatalogServices servlet ("/catalog.html"). * * @param req request * @param res response * @throws IOException on IO error * @throws ServletException other error */ public static void forwardToCatalogServices(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { String reqs = "catalog=" + getReletiveURL(req); String query = req.getQueryString(); if (query != null) reqs = reqs + "&" + query; log.info("forwardToCatalogServices(): request string = \"/catalog.html?" + reqs + "\""); // dispatch to CatalogHtml servlet RequestForwardUtils.forwardRequestRelativeToCurrentContext("/catalog.html?" + reqs, req, res); } static public void showSystemProperties(PrintStream out) { Properties sysp = System.getProperties(); Enumeration e = sysp.propertyNames(); List"); out.println("