/*
* Copyright 1998-2014 University Corporation for Atmospheric Research/Unidata
*
* Portions of this software were developed by the Unidata Program at the
* University Corporation for Atmospheric Research.
*
* Access and use of this software shall impose the following obligations
* and understandings on the user. The user is granted the right, without
* any fee or cost, to use, copy, modify, alter, enhance and distribute
* this software, and any derivative works thereof, and its supporting
* documentation for any purpose whatsoever, provided that this entire
* notice appears in all copies of the software, derivative works and
* supporting documentation. Further, UCAR requests that the user credit
* UCAR/Unidata in any publications that result from the use of this
* software or in any product that includes this software. The names UCAR
* and/or Unidata, however, may not be used in any advertising or publicity
* to endorse or promote any products or commercial entity unless specific
* written permission is obtained from UCAR/Unidata. The user also
* understands that UCAR/Unidata is not obligated to provide the user with
* any support, consulting, training or assistance of any kind with regard
* to the use, operation and performance of this software nor to provide
* the user with any updates, revisions, new versions or "bug fixes."
*
* THIS SOFTWARE IS PROVIDED BY UCAR/UNIDATA "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL UCAR/UNIDATA BE LIABLE FOR ANY SPECIAL,
* INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
* FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
* WITH THE ACCESS, USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package thredds.servlet;
import java.io.*;
import java.nio.channels.Channels;
import java.nio.channels.WritableByteChannel;
import java.util.*;
import java.net.URI;
import java.net.URISyntaxException;
import javax.servlet.*;
import javax.servlet.http.*;
import thredds.util.ContentType;
import ucar.nc2.constants.CDM;
import ucar.nc2.util.IO;
import thredds.catalog.XMLEntityResolver;
import thredds.util.RequestForwardUtils;
import ucar.nc2.util.EscapeStrings;
import ucar.unidata.io.RandomAccessFile;
import ucar.unidata.util.StringUtil2;
public class ServletUtil {
public static final org.slf4j.Logger logServerStartup = org.slf4j.LoggerFactory.getLogger( "serverStartup" );
public static final String CONTENT_TEXT = "text/plain; charset=utf-8";
// bogus status returns for our logging
public static final int STATUS_CLIENT_ABORT = 1000;
public static final int STATUS_FORWARDED = 1001;
public static final int STATUS_FORWARD_FAILURE = 1002;
private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger( ServletUtil.class );
static private boolean isDebugInit = false;
static private String contextPath = null;
static private String rootPath = null;
static private String contentPath = null;
/**
*
* @param context the Servlet context.
* @deprecated Now handled in TdsContext.init().
*/
static public void initContext(ServletContext context)
{
// setContextPath(context);
if ( contextPath == null )
{
// Servlet 2.5 allows the following.
//contextPath = servletContext.getContextPath();
String tmpContextPath = context.getInitParameter( "ContextPath" ); // cannot be overridden in the ThreddsConfig file
if ( tmpContextPath == null )
tmpContextPath = "thredds";
contextPath = "/" + tmpContextPath;
}
// setRootPath(context);
if ( rootPath == null )
{
rootPath = context.getRealPath("/");
rootPath = rootPath.replace('\\', '/');
}
// setContentPath();
if ( contentPath == null )
{
String tmpContentPath = "../../content" + getContextPath() + "/";
File cf = new File(getRootPath(), tmpContentPath);
try {
contentPath = cf.getCanonicalPath() + "/";
contentPath = contentPath.replace('\\', '/');
} catch (IOException e) {
throw new RuntimeException(e.getMessage());
}
}
// initDebugging(context);
initDebugging( context );
}
static public void setContextPath( String newContextPath )
{
contextPath = newContextPath;
}
static public void setRootPath( String newRootPath )
{
rootPath = newRootPath;
}
static public void setContentPath( String newContentPath)
{
contentPath = newContentPath;
if (!contentPath.endsWith("/"))
contentPath = contentPath + "/";
}
static public void initDebugging(ServletContext webapp) {
if (isDebugInit) return;
isDebugInit = true;
String debugOn = webapp.getInitParameter("DebugOn");
if (debugOn != null) {
StringTokenizer toker = new StringTokenizer(debugOn);
while (toker.hasMoreTokens())
Debug.set(toker.nextToken(), true);
}
}
/**
* Return the real path on the servers file system that corresponds to the root document ("/") on the given servlet.
* @return the real path on the servers file system that corresponds to the root document ("/") on the given servlet.
*/
public static String getRootPath() {
return rootPath;
}
/**
* Return the context path for the given servlet.
* Note - ToDo: Why not just use ServletContext.getServletContextName()?
*
* @return the context path for the given servlet.
*/
public static String getContextPath() {
return contextPath;
}
/**
* Return the content path for the given servlet.
*
* @return the content path for the given servlet.
*/
public static String getContentPath() {
return contentPath;
}
/**
* Return the default/initial content path for the given servlet. The
* content of which is copied to the content path when the web app
* is first installed.
*
* @return the default/initial content path for the given servlet.
*/
public static String getInitialContentPath() {
return getRootPath() + "/WEB-INF/altContent/startup/";
}
/**
* 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;
}
/**
* Handle a request for a raw/static file (i.e., not a catalog or dataset request).
* <p/>
* Look in the content (user) directory then the root (distribution) directory
* for a file that matches the given path and, if found, return it as the
* content of the HttpServletResponse. If the file is forbidden (i.e., the
* path contains a "..", "WEB-INF", or "META-INF" directory), send a
* HttpServletResponse.SC_FORBIDDEN response. If no file matches the request
* (including an "index.html" file if the path ends in "/"), send an
* HttpServletResponse.SC_NOT_FOUND..
* <p/>
* <ol>
* <li>Make sure the path does not contain ".." directories. </li>
* <li>Make sure the path does not contain "WEB-INF" or "META-INF". </li>
* <li>Check for requested file in the content directory
* (if the path is a directory, make sure the path ends with "/" and
* check for an "index.html" file). </li>
* <li>Check for requested file in the root directory
* (if the path is a directory, make sure the path ends with "/" and
* check for an "index.html" file).</li>
* </ol
*
* @param path the requested path
* @param servlet the servlet handling the request
* @param req the HttpServletRequest
* @param res the HttpServletResponse
* @throws IOException if can't complete request due to IO problems.
*/
public static void handleRequestForRawFile(String path, HttpServlet servlet, HttpServletRequest req, HttpServletResponse res)
throws IOException {
// Don't allow ".." directories in path.
if (path.contains("/../")
|| path.equals("..")
|| path.startsWith("../")
|| path.endsWith("/..")) {
res.sendError(HttpServletResponse.SC_FORBIDDEN, "Path cannot contain \"..\" directory.");
return;
}
// Don't allow access to WEB-INF or META-INF directories.
String upper = path.toUpperCase();
if (upper.contains("WEB-INF") || upper.contains("META-INF")) {
res.sendError(HttpServletResponse.SC_FORBIDDEN, "Path cannot contain \"WEB-INF\" or \"META-INF\".");
return;
}
// Find a regular file
File regFile = null;
// Look in content directory for regular file.
File cFile = new File(ServletUtil.formFilename( getContentPath(), path));
if (cFile.exists()) {
if (cFile.isDirectory()) {
if (!path.endsWith("/")) {
String newPath = req.getRequestURL().append("/").toString();
ServletUtil.sendPermanentRedirect(newPath, req, res);
}
// If request path is a directory, check for index.html file.
cFile = new File(cFile, "index.html");
if (cFile.exists() && !cFile.isDirectory())
regFile = cFile;
}
// If not a directory, use this file.
else
regFile = cFile;
}
if (regFile == null) {
// Look in root directory.
File rFile = new File( ServletUtil.formFilename(getRootPath(), path));
if (rFile.exists()) {
if (rFile.isDirectory()) {
if (!path.endsWith("/")) {
String newPath = req.getRequestURL().append("/").toString();
ServletUtil.sendPermanentRedirect(newPath, req, res);
}
rFile = new File(rFile, "index.html");
if (rFile.exists() && !rFile.isDirectory())
regFile = rFile;
} else
regFile = rFile;
}
}
if (regFile == null) {
res.sendError(HttpServletResponse.SC_NOT_FOUND); // 404
return;
}
ServletUtil.returnFile(servlet, req, res, regFile, null);
}
/**
* Handle an explicit request for a content directory file (path must start
* with "/content/".
* <p/>
* Note: As these requests will show the configuration files for the server,
* these requests should be covered by security constraints.
* <p/>
* <ol>
* <li>Make sure the path does not contain ".." directories. </li>
* <li>Check for the requested file in the content directory. </li>
* </ol
*
* @param path the requested path (must start with "/content/")
* @param servlet the servlet handling the request
* @param req the HttpServletRequest
* @param res the HttpServletResponse
* @throws IOException if can't complete request due to IO problems.
*/
public static void handleRequestForContentFile(String path, HttpServlet servlet, HttpServletRequest req, HttpServletResponse res)
throws IOException {
handleRequestForContentOrRootFile("/content/", path, servlet, req, res);
}
/**
* Handle an explicit request for a root directory file (path must start
* with "/root/".
* <p/>
* Note: As these requests will show the configuration files for the server,
* these requests should be covered by security constraints.
* <p/>
* <ol>
* <li>Make sure the path does not contain ".." directories. </li>
* <li>Check for the requested file in the root directory. </li>
* </ol
*
* @param path the requested path (must start with "/root/")
* @param servlet the servlet handling the request
* @param req the HttpServletRequest
* @param res the HttpServletResponse
* @throws IOException if can't complete request due to IO problems.
*/
public static void handleRequestForRootFile(String path, HttpServlet servlet, HttpServletRequest req, HttpServletResponse res)
throws IOException {
handleRequestForContentOrRootFile("/root/", path, servlet, req, res);
}
/**
* Convenience routine used by handleRequestForContentFile()
* and handleRequestForRootFile().
*/
private static void handleRequestForContentOrRootFile(String pathPrefix, String path, HttpServlet servlet, HttpServletRequest req, HttpServletResponse res)
throws IOException {
if (!pathPrefix.equals("/content/")
&& !pathPrefix.equals("/root/")) {
log.error("handleRequestForContentFile(): The path prefix <" + pathPrefix + "> must be \"/content/\" or \"/root/\".");
throw new IllegalArgumentException("Path prefix must be \"/content/\" or \"/root/\".");
}
if (!path.startsWith(pathPrefix)) {
log.error("handleRequestForContentFile(): path <" + path + "> must start with \"" + pathPrefix + "\".");
throw new IllegalArgumentException("Path must start with \"" + pathPrefix + "\".");
}
// Don't allow ".." directories in path.
if (path.contains("/../")
|| path.equals("..")
|| path.startsWith("../")
|| path.endsWith("/..")) {
res.sendError(HttpServletResponse.SC_FORBIDDEN, "Path cannot contain \"..\" directory.");
return;
}
// Find the requested file.
File file = new File(ServletUtil.formFilename(getContentPath(), path.substring(pathPrefix.length() - 1)));
if (file.exists()) {
// Do not allow request for a directory.
if (file.isDirectory()) {
if (!path.endsWith("/")) {
String redirectPath = req.getRequestURL().append("/").toString();
ServletUtil.sendPermanentRedirect(redirectPath, req, res);
return;
}
HtmlWriter.getInstance().writeDirectory(res, file, path);
return;
}
// Return the requested file.
ServletUtil.returnFile(servlet, req, res, file, null);
} else {
// Requested file not found.
res.sendError(HttpServletResponse.SC_NOT_FOUND); // 404
}
}
/**
* Send a permanent redirect (HTTP status 301 "Moved Permanently") response
* with the given target path.
* <p/>
* The given target path may be relative or absolute. If it is relative, it
* will be resolved against the request URL.
*
* @param targetPath the path to which the client is redirected.
* @param req the HttpServletRequest
* @param res the HttpServletResponse
* @throws IOException if can't write the response.
*/
public static void sendPermanentRedirect(String targetPath, HttpServletRequest req, HttpServletResponse res)
throws IOException {
// Absolute URL needed so resolve the target path against the request URL.
URI uri;
try {
uri = new URI(req.getRequestURL().toString());
}
catch (URISyntaxException e) {
log.error("sendPermanentRedirect(): Bad syntax on request URL <" + req.getRequestURL() + ">.", e);
if ( ! res.isCommitted() ) res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return;
}
String absolutePath = uri.resolve(targetPath).toString();
absolutePath = res.encodeRedirectURL(absolutePath);
res.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
res.addHeader("Location", absolutePath);
String title = "Permanently Moved - 301";
String body = new StringBuilder()
.append("<p>")
.append("The requested URL <").append(req.getRequestURL())
.append("> has been permanently moved (HTTP status code 301).")
.append(" Instead, please use the following URL: <a href=\"").append(absolutePath).append("\">").append(absolutePath).append("</a>.")
.append("</p>")
.toString();
String htmlResp = new StringBuilder()
.append(HtmlWriter.getInstance().getHtmlDoctypeAndOpenTag())
.append("<head><title>")
.append(title)
.append("</title></head><body>")
.append("<h1>").append(title).append("</h1>")
.append(body)
.append("</body></html>")
.toString();
log.info( "sendPermanentRedirect(): redirect to " + absolutePath);
// Write the catalog out.
res.setContentType(ContentType.html.getContentHeader());
PrintWriter out = res.getWriter();
out.print(htmlResp);
out.flush();
}
/**
* 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);
}
/* static private FileCacheIF fileCacheRaf;
static public void setFileCache( FileCacheIF fileCache) { fileCacheRaf = fileCache; }
static public FileCacheIF getFileCache( ) { return fileCacheRaf; }
private static final ucar.nc2.util.cache.FileFactory fileFactory = new FileFactory() {
public FileCacheable open(String location, int buffer_size, CancelTask cancelTask, Object iospMessage) throws IOException {
return RandomAccessFile.acquire(location);
}
};
*/
/**
* 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.netcdf4.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.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();
boolean debugRequest = Debug.isSet("returnFile");
if (debugRequest) log.debug("returnFile(): filename = " + filename + " contentType = " + contentType +
" contentLength = " + contentLength);
// 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();
} */
if (debugRequest) log.debug("returnFile(): returnFile ok = " + filename);
}
// @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 );
}
public static boolean saveFile(HttpServlet servlet, String contentPath, String path, HttpServletRequest req,
HttpServletResponse res) {
// @todo Need to use logServerAccess() below here.
boolean debugRequest = Debug.isSet("SaveFile");
if (debugRequest) log.debug(" saveFile(): path= " + path);
String filename = contentPath + path; // absolute path
File want = new File(filename);
// backup current version if it exists
int version = getBackupVersion(want.getParent(), want.getName());
String fileSave = filename + "~" + version;
File file = new File(filename);
if (file.exists()) {
try {
IO.copyFile(filename, fileSave);
} catch (IOException e) {
log.error("saveFile(): Unable to save copy of file " + filename + " to " + fileSave + "\n" + e.getMessage());
return false;
}
}
// save new file
try ( OutputStream out = new BufferedOutputStream(new FileOutputStream(filename))) {
IO.copy(req.getInputStream(), out);
if (debugRequest) log.debug("saveFile(): ok= " + filename);
res.setStatus(HttpServletResponse.SC_CREATED);
return true;
} catch (IOException e) {
log.error("saveFile(): Unable to PUT file " + filename + " to " + fileSave + "\n" + e.getMessage());
return false;
}
}
private static int getBackupVersion(String dirName, String fileName) {
int maxN = 0;
File dir = new File(dirName);
if (!dir.exists())
return -1;
String[] files = dir.list();
if (null == files)
return -1;
for (String name : files) {
if (!name.contains(fileName)) continue;
int pos = name.indexOf('~');
if (pos < 0) continue;
String ver = name.substring(pos + 1);
int n = 0;
try {
n = Integer.parseInt(ver);
} catch (NumberFormatException e) {
log.error("Format Integer error on backup filename= " + ver);
}
maxN = Math.max(n, maxN);
}
return maxN + 1;
}
static public boolean copyDir(String fromDir, String toDir) throws IOException {
File contentFile = new File(toDir + ".INIT");
if (!contentFile.exists()) {
IO.copyDirTree(fromDir, toDir);
return contentFile.createNewFile();
}
return false;
}
/**
* ************************************************************************
* Sends an error to the client.
*
* @param t The exception that caused the problem.
* @param res The <code>HttpServletResponse</code> for the client.
*/
static public void handleException(Throwable t, HttpServletResponse res) {
try {
String message = t.getMessage();
if (message == null) message = "NULL message " + t.getClass().getName();
if (Debug.isSet("trustedMode")) { // security issue: only show stack if trusted
StringWriter sw = new StringWriter(10000);
t.printStackTrace(new PrintWriter(sw));
message = sw.toString();
}
log.error("handleException", t);
t.printStackTrace(); // debugging - log.error not showing stack trace !!
if ( ! res.isCommitted() )
res.sendError(HttpServletResponse.SC_BAD_REQUEST, message);
} catch (Throwable e) {
log.error("handleException() had problem reporting Exception", e);
t.printStackTrace();
}
}
static public void showServerInfo(PrintStream out) {
out.println("Server Info");
out.println(" getDocumentBuilderFactoryVersion(): " + XMLEntityResolver.getDocumentBuilderFactoryVersion());
out.println();
Properties sysp = System.getProperties();
Enumeration e = sysp.propertyNames();
List<String> list = Collections.list(e);
Collections.sort(list);
out.println("System Properties:");
for (String name : list) {
String value = System.getProperty(name);
out.println(" " + name + " = " + value);
}
out.println();
}
static public void showServletInfo(HttpServlet servlet, PrintStream out) {
out.println("Servlet Info");
out.println(" getServletName(): " + servlet.getServletName());
out.println(" getRootPath(): " + getRootPath());
out.println(" Init Parameters:");
Enumeration params = servlet.getInitParameterNames();
while (params.hasMoreElements()) {
String name = (String) params.nextElement();
out.println(" " + name + ": " + servlet.getInitParameter(name));
}
out.println();
ServletContext context = servlet.getServletContext();
out.println("Context Info");
try {
out.println(" context.getResource('/'): " + context.getResource("/"));
} catch (java.net.MalformedURLException e) {
logServerStartup.error("ServletUtil.showServletInfo", e);
} // cant happen
out.println(" context.getServerInfo(): " + context.getServerInfo());
out.println(" name: " + getServerInfoName(context.getServerInfo()));
out.println(" version: " + getServerInfoVersion(context.getServerInfo()));
out.println(" context.getInitParameterNames():");
params = context.getInitParameterNames();
while (params.hasMoreElements()) {
String name = (String) params.nextElement();
out.println(" " + name + ": " + context.getInitParameter(name));
}
out.println(" context.getAttributeNames():");
params = context.getAttributeNames();
while (params.hasMoreElements()) {
String name = (String) params.nextElement();
out.println(" context.getAttribute(\"" + name + "\"): " +
context.getAttribute(name));
}
out.println();
}
/**
* Show the pieces of the request, for debugging
*
* @param req the HttpServletRequest
* @return parsed request
*/
public static String getRequestParsed(HttpServletRequest req) {
return req.getRequestURI() + " = " + req.getContextPath() + "(context), " +
req.getServletPath() + "(servletPath), " +
req.getPathInfo() + "(pathInfo), " + req.getQueryString() + "(query)";
}
/**
* This is the server part, eg http://motherlode:8080
*
* @param req the HttpServletRequest
* @return request server
*/
public static String getRequestServer(HttpServletRequest req) {
return req.getScheme() + "://"+req.getServerName()+":"+ req.getServerPort();
}
/**
* This is everything except the query string
*
* @param req the HttpServletRequest
* @return parsed request base
*/
public static String getRequestBase(HttpServletRequest req) {
// return "http://"+req.getServerName()+":"+ req.getServerPort()+req.getRequestURI();
return req.getRequestURL().toString();
}
/**
* The request base as a URI
* @param req the HttpServletRequest
* @return parsed request as a URI
*/
public static URI getRequestURI(HttpServletRequest req) {
try {
return new URI(getRequestBase(req));
} catch (URISyntaxException e) {
e.printStackTrace();
return null;
}
}
/**
* servletPath + pathInfo
* @param req the HttpServletRequest
* @return parsed request servletPath + pathInfo
*/
public static String getRequestPath(HttpServletRequest req) {
StringBuilder buff = new StringBuilder();
if (req.getServletPath() != null)
buff.append(req.getServletPath());
if (req.getPathInfo() != null)
buff.append(req.getPathInfo());
return buff.toString();
}
/**
* The entire request including query string
* @param req the HttpServletRequest
* @return entire parsed request
*/
public static String getRequest(HttpServletRequest req) {
String query = req.getQueryString();
return getRequestBase(req) + (query == null ? "" : "?" + query);
}
/**
* Return the value of the given parameter for the given request. Should
* only be used if the parameter is known to only have one value. If used
* on a multi-valued parameter, the first value is returned.
*
* @param req the HttpServletRequest
* @param paramName the name of the parameter to find.
* @return the value of the given parameter for the given request.
*/
public static String getParameterIgnoreCase(HttpServletRequest req, String paramName) {
Enumeration e = req.getParameterNames();
while (e.hasMoreElements()) {
String s = (String) e.nextElement();
if (s.equalsIgnoreCase(paramName))
return req.getParameter(s);
}
return null;
}
/**
* Return the values of the given parameter (ignoring case) for the given request.
*
* @param req the HttpServletRequest
* @param paramName the name of the parameter to find.
* @return the values of the given parameter for the given request.
*/
public static String[] getParameterValuesIgnoreCase(HttpServletRequest req, String paramName) {
Enumeration e = req.getParameterNames();
while (e.hasMoreElements()) {
String s = (String) e.nextElement();
if (s.equalsIgnoreCase(paramName))
return req.getParameterValues(s);
}
return null;
}
public static String getFileURL(String filename) {
filename = filename.replace('\\', '/');
filename = StringUtil2.replace(filename, ' ', "+");
return "file:" + filename;
}
/**
* Show details about the request
*
* @param servlet used to get teh servlet context, may be null
* @param req the request
* @return string showing the details of the request.
*/
static public String showRequestDetail(HttpServlet servlet, HttpServletRequest req) {
StringBuilder sbuff = new StringBuilder();
sbuff.append("Request Info\n");
sbuff.append(" req.getServerName(): ").append(req.getServerName()).append("\n");
sbuff.append(" req.getServerPort(): ").append(req.getServerPort()).append("\n");
sbuff.append(" req.getContextPath:").append(req.getContextPath()).append("\n");
sbuff.append(" req.getServletPath:").append(req.getServletPath()).append("\n");
sbuff.append(" req.getPathInfo:").append(req.getPathInfo()).append("\n");
sbuff.append(" req.getQueryString:").append(req.getQueryString()).append("\n");
sbuff.append(" getQueryStringDecoded:").append(EscapeStrings.urlDecode(req.getQueryString())).append("\n");
/*try {
sbuff.append(" getQueryStringDecoded:").append(URLDecoder.decode(req.getQueryString(), "UTF-8")).append("\n");
} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
}*/
sbuff.append(" req.getRequestURI:").append(req.getRequestURI()).append("\n");
sbuff.append(" getRequestBase:").append(getRequestBase(req)).append("\n");
sbuff.append(" getRequestServer:").append(getRequestServer(req)).append("\n");
sbuff.append(" getRequest:").append(getRequest(req)).append("\n");
sbuff.append("\n");
sbuff.append(" req.getPathTranslated:").append(req.getPathTranslated()).append("\n");
String path = req.getPathTranslated();
if ((path != null) && (servlet != null)) {
ServletContext context = servlet.getServletContext();
sbuff.append(" getMimeType:").append(context.getMimeType(path)).append("\n");
}
sbuff.append("\n");
sbuff.append(" req.getScheme:").append(req.getScheme()).append("\n");
sbuff.append(" req.getProtocol:").append(req.getProtocol()).append("\n");
sbuff.append(" req.getMethod:").append(req.getMethod()).append("\n");
sbuff.append("\n");
sbuff.append(" req.getContentType:").append(req.getContentType()).append("\n");
sbuff.append(" req.getContentLength:").append(req.getContentLength()).append("\n");
sbuff.append(" req.getRemoteAddr():").append(req.getRemoteAddr());
try {
sbuff.append(" getRemoteHost():").append(java.net.InetAddress.getByName(req.getRemoteHost()).getHostName()).append("\n");
} catch (java.net.UnknownHostException e) {
sbuff.append(" getRemoteHost():").append(e.getMessage()).append("\n");
}
sbuff.append(" getRemoteUser():").append(req.getRemoteUser()).append("\n");
sbuff.append("\n");
sbuff.append("Request Parameters:\n");
Enumeration params = req.getParameterNames();
while (params.hasMoreElements()) {
String name = (String) params.nextElement();
String values[] = req.getParameterValues(name);
if (values != null) {
for (int i = 0; i < values.length; i++) {
sbuff.append(" ").append(name).append(" (").append(i).append("): ").append(values[i]).append("\n");
}
}
}
sbuff.append("\n");
sbuff.append("Request Headers:\n");
Enumeration names = req.getHeaderNames();
while (names.hasMoreElements()) {
String name = (String) names.nextElement();
Enumeration values = req.getHeaders(name); // support multiple values
if (values != null) {
while (values.hasMoreElements()) {
String value = (String) values.nextElement();
sbuff.append(" ").append(name).append(": ").append(value).append("\n");
}
}
}
sbuff.append(" ------------------\n");
return sbuff.toString();
}
static public String showRequestHeaders(HttpServletRequest req) {
StringBuilder sbuff = new StringBuilder();
sbuff.append("Request Headers:\n");
Enumeration names = req.getHeaderNames();
while (names.hasMoreElements()) {
String name = (String) names.nextElement();
Enumeration values = req.getHeaders(name); // support multiple values
if (values != null) {
while (values.hasMoreElements()) {
String value = (String) values.nextElement();
sbuff.append(" ").append(name).append(": ").append(value).append("\n");
}
}
}
return sbuff.toString();
}
static public void showSession(HttpServletRequest req, HttpServletResponse res,
PrintStream out) {
// res.setContentType("text/html");
// Get the current session object, create one if necessary
HttpSession session = req.getSession();
// Increment the hit count for this page. The value is saved
// in this client's session under the name "snoop.count".
Integer count = (Integer) session.getAttribute("snoop.count");
if (count == null) {
count = 1;
} else
count = count + 1;
session.setAttribute("snoop.count", count);
out.println(HtmlWriter.getInstance().getHtmlDoctypeAndOpenTag());
out.println("<HEAD><TITLE>SessionSnoop</TITLE></HEAD>");
out.println("<BODY><H1>Session Snoop</H1>");
// Display the hit count for this page
out.println("You've visited this page " + count + ((count == 1) ? " time." : " times."));
out.println("<P>");
out.println("<H3>Here is your saved session data:</H3>");
Enumeration atts = session.getAttributeNames();
while (atts.hasMoreElements()) {
String name = (String) atts.nextElement();
out.println(name + ": " + session.getAttribute(name) + "<BR>");
}
out.println("<H3>Here are some vital stats on your session:</H3>");
out.println("Session id: " + session.getId() +
" <I>(keep it secret)</I><BR>");
out.println("New session: " + session.isNew() + "<BR>");
out.println("Timeout: " + session.getMaxInactiveInterval());
out.println("<I>(" + session.getMaxInactiveInterval() / 60 +
" minutes)</I><BR>");
out.println("Creation time: " + session.getCreationTime());
out.println("<I>(" + new Date(session.getCreationTime()) + ")</I><BR>");
out.println("Last access time: " + session.getLastAccessedTime());
out.println("<I>(" + new Date(session.getLastAccessedTime()) +
")</I><BR>");
out.println("Requested session ID from cookie: " +
req.isRequestedSessionIdFromCookie() + "<BR>");
out.println("Requested session ID from URL: " +
req.isRequestedSessionIdFromURL() + "<BR>");
out.println("Requested session ID valid: " +
req.isRequestedSessionIdValid() + "<BR>");
out.println("<H3>Test URL Rewriting</H3>");
out.println("Click <A HREF=\"" +
res.encodeURL(req.getRequestURI()) + "\">here</A>");
out.println("to test that session tracking works via URL");
out.println("rewriting even when cookies aren't supported.");
out.println("</BODY></HTML>");
}
static public void showSession(HttpServletRequest req, PrintStream out) {
// res.setContentType("text/html");
// Get the current session object, create one if necessary
HttpSession session = req.getSession();
out.println("Session id: " + session.getId());
out.println(" session.isNew(): " + session.isNew());
out.println(" session.getMaxInactiveInterval(): " + session.getMaxInactiveInterval() + " secs");
out.println(" session.getCreationTime(): " + session.getCreationTime() + " (" + new Date(session.getCreationTime()) + ")");
out.println(" session.getLastAccessedTime(): " + session.getLastAccessedTime() + " (" + new Date(session.getLastAccessedTime()) + ")");
out.println(" req.isRequestedSessionIdFromCookie: " + req.isRequestedSessionIdFromCookie());
out.println(" req.isRequestedSessionIdFromURL: " + req.isRequestedSessionIdFromURL());
out.println(" req.isRequestedSessionIdValid: " + req.isRequestedSessionIdValid());
out.println("Saved session Attributes:");
Enumeration atts = session.getAttributeNames();
while (atts.hasMoreElements()) {
String name = (String) atts.nextElement();
out.println(" " + name + ": " + session.getAttribute(name) + "<BR>");
}
}
static public String showSecurity(HttpServletRequest req, String role) {
StringBuilder sbuff = new StringBuilder();
sbuff.append("Security Info\n");
sbuff.append(" req.getRemoteUser(): ").append(req.getRemoteUser()).append("\n");
sbuff.append(" req.getUserPrincipal(): ").append(req.getUserPrincipal()).append("\n");
sbuff.append(" req.isUserInRole(").append(role).append("):").append(req.isUserInRole(role)).append("\n");
sbuff.append(" ------------------\n");
return sbuff.toString();
}
/* from luca / ageci code, portResolver, portMapper not known
static public void getSecureRedirect(HttpServletRequest req) {
String pathInfo = req.getPathInfo();
String queryString = req.getQueryString();
String contextPath = req.getContextPath();
String destination = req.getServletPath() + ((pathInfo == null) ? "" : pathInfo)
+ ((queryString == null) ? "" : ("?" + queryString));
String redirectUrl = contextPath;
Integer httpPort = new Integer(portResolver.getServerPort(req));
Integer httpsPort = portMapper.lookupHttpsPort(httpPort);
if (httpsPort != null) {
boolean includePort = true;
if (httpsPort.intValue() == 443) {
includePort = false;
}
redirectUrl = "https://" + req.getServerName() + ((includePort) ? (":" + httpsPort) : "") + contextPath
+ destination;
}
} */
static private String getServerInfoName(String serverInfo) {
int slash = serverInfo.indexOf('/');
if (slash == -1) return serverInfo;
else return serverInfo.substring(0, slash);
}
static private String getServerInfoVersion(String serverInfo) {
// Version info is everything between the slash and the space
int slash = serverInfo.indexOf('/');
if (slash == -1) return null;
int space = serverInfo.indexOf(' ', slash);
if (space == -1) space = serverInfo.length();
return serverInfo.substring(slash + 1, space);
}
static public void showThreads(PrintStream pw) {
Thread current = Thread.currentThread();
ThreadGroup group = current.getThreadGroup();
while (true) {
if (group.getParent() == null) break;
group = group.getParent();
}
showThreads(pw, group, current);
}
static private void showThreads(PrintStream pw, ThreadGroup g, Thread current) {
int nthreads = g.activeCount();
pw.println("\nThread Group = " + g.getName() + " activeCount= " + nthreads);
Thread[] tarray = new Thread[nthreads];
int n = g.enumerate(tarray, false);
for (int i = 0; i < n; i++) {
Thread thread = tarray[i];
ClassLoader loader = thread.getContextClassLoader();
String loaderName = (loader == null) ? "Default" : loader.getClass().getName();
Thread.State state = thread.getState();
long id = thread.getId();
pw.print(" " + id + " " + thread.getName() + " " + state + " " + loaderName);
if (thread == current)
pw.println(" **** CURRENT ***");
else
pw.println();
}
int ngroups = g.activeGroupCount();
ThreadGroup[] garray = new ThreadGroup[ngroups];
int ng = g.enumerate(garray, false);
for (int i = 0; i < ng; i++) {
ThreadGroup nested = garray[i];
showThreads(pw, nested, current);
}
}
}