protected String getRelativePath(HttpServletRequest request, boolean allowEmptyPath) { // IMPORTANT: DefaultServlet can be mapped to '/' or '/path/*' but always // serves resources from the web app root with context rooted paths. // i.e. it cannot be used to mount the web app root under a sub-path // This method must construct a complete context rooted path, although // subclasses can change this behaviour.
String servletPath;
String pathInfo;
if (request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI) != null) { // For includes, get the info from the attributes
pathInfo = (String) request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);
servletPath = (String) request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
} else {
pathInfo = request.getPathInfo();
servletPath = request.getServletPath();
}
StringBuilder result = new StringBuilder(); if (servletPath.length() > 0) {
result.append(servletPath);
} if (pathInfo != null) {
result.append(pathInfo);
} if (result.length() == 0 && !allowEmptyPath) {
result.append('/');
}
// Serve the requested resource, including the data content
serveResource(request, response, true, fileEncoding);
}
/** *ProcessaHEADrequestforthespecifiedresource. * *@paramrequestTheservletrequestweareprocessing *@paramresponseTheservletresponsewearecreating * *@exceptionIOExceptionifaninput/outputerroroccurs *@exceptionServletExceptionifaservlet-specifiederroroccurs
*/
@Override protectedvoid doHead(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // Serve the requested resource, without the data content unless we are // being included since in that case the content needs to be provided so // the correct content length is reported for the including resource boolean serveContent = DispatcherType.INCLUDE.equals(request.getDispatcherType());
serveResource(request, response, serveContent, fileEncoding);
}
ContentRange range = parseContentRange(req, resp);
if (range == null) { // Processing error. parseContentRange() set the error code return;
}
InputStream resourceInputStream = null;
try { // Append data specified in ranges to existing content for this // resource - create a temp. file on the local filesystem to // perform this operation // Assume just one range is specified for now if (range == IGNORE) {
resourceInputStream = req.getInputStream();
} else {
File contentFile = executePartialPut(req, range, path);
resourceInputStream = new FileInputStream(contentFile);
}
// Append data specified in ranges to existing content for this // resource - create a temp. file on the local filesystem to // perform this operation
File tempDir = (File) getServletContext().getAttribute(ServletContext.TEMPDIR); // Convert all '/' characters to '.' in resourcePath
String convertedResourcePath = path.replace('/', '.');
File contentFile = new File(tempDir, convertedResourcePath); if (contentFile.createNewFile()) { // Clean up contentFile when Tomcat is terminated
contentFile.deleteOnExit();
}
try (RandomAccessFile randAccessContentFile = new RandomAccessFile(contentFile, "rw")) {
// Copy data in oldRevisionContent to contentFile if (oldResource.isFile()) { try (BufferedInputStream bufOldRevStream = new BufferedInputStream(oldResource.getInputStream(), BUFFER_SIZE)) {
int numBytesRead; byte[] copyBuffer = newbyte[BUFFER_SIZE]; while ((numBytesRead = bufOldRevStream.read(copyBuffer)) != -1) {
randAccessContentFile.write(copyBuffer, 0, numBytesRead);
}
if (!resource.exists()) { // Check if we're included so we can return the appropriate // missing resource name in the error
String requestUri = (String) request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI); if (requestUri == null) {
requestUri = request.getRequestURI();
} else { // We're included // SRV.9.3 says we must throw a FNFE thrownew FileNotFoundException(sm.getString("defaultServlet.missingResource", requestUri));
}
if (!resource.canRead()) { // Check if we're included so we can return the appropriate // missing resource name in the error
String requestUri = (String) request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI); if (requestUri == null) {
requestUri = request.getRequestURI();
} else { // We're included // Spec doesn't say what to do in this case but a FNFE seems // reasonable thrownew FileNotFoundException(sm.getString("defaultServlet.missingResource", requestUri));
}
boolean included = false; // Check if the conditions specified in the optional If headers are // satisfied. if (resource.isFile()) { // Checking If headers
included = (request.getAttribute(RequestDispatcher.INCLUDE_CONTEXT_PATH) != null); if (!included && !isError && !checkIfHeaders(request, response, resource)) { return;
}
}
// These need to reflect the original resource, not the potentially // precompressed version of the resource so get them now if they are going to // be needed later
String eTag = null;
String lastModifiedHttp = null; if (resource.isFile() && !isError) {
eTag = generateETag(resource);
lastModifiedHttp = resource.getLastModifiedHttp();
}
// Serve a precompressed version of the file if present boolean usingPrecompressedVersion = false; if (compressionFormats.length > 0 && !included && resource.isFile() && !pathEndsWithCompressedExtension(path)) {
List<PrecompressedResource> precompressedResources = getAvailablePrecompressedResources(path); if (!precompressedResources.isEmpty()) {
ResponseUtil.addVaryFieldName(response, "accept-encoding");
PrecompressedResource bestResource = getBestPrecompressedResource(request, precompressedResources); if (bestResource != null) {
response.addHeader("Content-Encoding", bestResource.format.encoding);
resource = bestResource.resource;
usingPrecompressedVersion = true;
}
}
}
Ranges ranges = FULL; long contentLength = -1L;
if (resource.isDirectory()) { if (!path.endsWith("/")) {
doDirectoryRedirect(request, response); return;
}
// Skip directory listings if we have been configured to // suppress them if (!listings) {
response.sendError(HttpServletResponse.SC_NOT_FOUND,
sm.getString("defaultServlet.missingResource", request.getRequestURI())); return;
}
contentType = "text/html;charset=UTF-8";
} else { if (!isError) { if (useAcceptRanges) { // Accept ranges header
response.setHeader("Accept-Ranges", "bytes");
}
// Parse range specifier
ranges = parseRange(request, response, resource); if (ranges == null) { return;
}
// Get content length
contentLength = resource.getContentLength(); // Special case for zero length files, which would cause a // (silent) ISE when setting the output buffer size if (contentLength == 0L) {
serveContent = false;
}
}
if (serveContent) { // Trying to retrieve the servlet output stream try {
ostream = response.getOutputStream();
} catch (IllegalStateException e) { // If it fails, we try to get a Writer instead if we're // trying to serve a text file if (!usingPrecompressedVersion && isText(contentType)) {
writer = response.getWriter(); // Cannot reliably serve partial content with a Writer
ranges = FULL;
} else { throw e;
}
}
}
// Check to see if a Filter, Valve or wrapper has written some content. // If it has, disable range requests and setting of a content length // since neither can be done reliably.
ServletResponse r = response; long contentWritten = 0; while (r instanceof ServletResponseWrapper) {
r = ((ServletResponseWrapper) r).getResponse();
} if (r instanceof ResponseFacade) {
contentWritten = ((ResponseFacade) r).getContentWritten();
} if (contentWritten > 0) {
ranges = FULL;
}
String outputEncoding = response.getCharacterEncoding();
Charset charset = B2CConverter.getCharset(outputEncoding); boolean conversionRequired; /* *Thetestbelowdeliberatelyuses!=tocomparetwoStrings.Thisisbecausethecodeislookingtoseeifthe *defaultcharacterencodinghasbeenreturnedbecausenoexplicitcharacterencodinghasbeendefined.There *isnocleanwayofdoingthisviatheServletAPI.ItwouldbepossibletoaddaTomcatspecificAPIbutthat *wouldrequirequiteabitofcodetogettotheTomcatspecificrequestobjectthatmayhavebeenwrapped. *The!=testisa(slightlyhacky)quickwayofdoingthis.
*/ boolean outputEncodingSpecified = outputEncoding != org.apache.coyote.Constants.DEFAULT_BODY_CHARSET.name() &&
outputEncoding != resources.getContext().getResponseCharacterEncoding(); if (!usingPrecompressedVersion && isText(contentType) && outputEncodingSpecified &&
!charset.equals(fileEncodingCharset)) {
conversionRequired = true; // Conversion often results fewer/more/different bytes. // That does not play nicely with range requests.
ranges = FULL;
} else {
conversionRequired = false;
}
if (resource.isDirectory() || isError || ranges == FULL) { // Set the appropriate output headers if (contentType != null) { if (debug > 0) {
log("DefaultServlet.serveFile: contentType='" + contentType + "'");
} // Don't override a previously set content type if (response.getContentType() == null) {
response.setContentType(contentType);
}
} if (resource.isFile() && contentLength >= 0 && (!serveContent || ostream != null)) { if (debug > 0) {
log("DefaultServlet.serveFile: contentLength=" + contentLength);
} // Don't set a content length if something else has already // written to the response or if conversion will be taking place if (contentWritten == 0 && !conversionRequired) {
response.setContentLengthLong(contentLength);
}
}
if (serveContent) { try {
response.setBufferSize(output);
} catch (IllegalStateException e) { // Silent catch
}
InputStream renderResult = null; if (ostream == null) { // Output via a writer so can't use sendfile or write // content directly. if (resource.isDirectory()) {
renderResult = render(request, getPathPrefix(request), resource, inputEncoding);
} else {
renderResult = resource.getInputStream(); if (included) { // Need to make sure any BOM is removed if (!renderResult.markSupported()) {
renderResult = new BufferedInputStream(renderResult);
}
Charset bomCharset = processBom(renderResult, useBomIfPresent.stripBom); if (bomCharset != null && useBomIfPresent.useBomEncoding) {
inputEncoding = bomCharset.name();
}
}
}
copy(renderResult, writer, inputEncoding);
} else { // Output is via an OutputStream if (resource.isDirectory()) {
renderResult = render(request, getPathPrefix(request), resource, inputEncoding);
} else { // Output is content of resource // Check to see if conversion is required if (conversionRequired || included) { // When including a file, we need to check for a BOM // to determine if a conversion is required, so we // might as well always convert
InputStream source = resource.getInputStream(); if (!source.markSupported()) {
source = new BufferedInputStream(source);
}
Charset bomCharset = processBom(source, useBomIfPresent.stripBom); if (bomCharset != null && useBomIfPresent.useBomEncoding) {
inputEncoding = bomCharset.name();
} // Following test also ensures included resources // are converted if an explicit output encoding was // specified if (outputEncodingSpecified) {
OutputStreamWriter osw = new OutputStreamWriter(ostream, charset);
PrintWriter pw = new PrintWriter(osw);
copy(source, pw, inputEncoding);
pw.flush();
} else { // Just included but no conversion
renderResult = source;
}
} else { if (!checkSendfile(request, response, resource, contentLength, null)) { // sendfile not possible so check if resource // content is available directly via // CachedResource. Do not want to call // getContent() on other resource // implementations as that could trigger loading // the contents of a very large file into memory byte[] resourceBody = null; if (resource instanceof CachedResource) {
resourceBody = resource.getContent();
} if (resourceBody == null) { // Resource content not directly available, // use InputStream
renderResult = resource.getInputStream();
} else { // Use the resource content directly
ostream.write(resourceBody);
}
}
}
} // If a stream was configured, it needs to be copied to // the output (this method closes the stream) if (renderResult != null) {
copy(renderResult, ostream);
}
}
}
} else {
if ((ranges == null) || (ranges.getEntries().isEmpty())) { return;
}
Ranges.Entry range = ranges.getEntries().get(0); long start = getStart(range, contentLength); long end = getEnd(range, contentLength);
response.addHeader("Content-Range", "bytes " + start + "-" + end + "/" + contentLength); long length = end - start + 1;
response.setContentLengthLong(length);
if (contentType != null) { if (debug > 0) {
log("DefaultServlet.serveFile: contentType='" + contentType + "'");
}
response.setContentType(contentType);
}
if (serveContent) { try {
response.setBufferSize(output);
} catch (IllegalStateException e) { // Silent catch
} if (ostream != null) { if (!checkSendfile(request, response, resource, contentLength, range)) {
copy(resource, contentLength, ostream, range);
}
} else { // we should not get here thrownew IllegalStateException();
}
}
} else {
response.setContentType("multipart/byteranges; boundary=" + mimeSeparation); if (serveContent) { try {
response.setBufferSize(output);
} catch (IllegalStateException e) { // Silent catch
} if (ostream != null) {
copy(resource, contentLength, ostream, ranges, contentType);
} else { // we should not get here thrownew IllegalStateException();
}
}
}
}
}
/* *CodeborrowedheavilyfromJasper'sEncodingDetector
*/ privatestatic Charset processBom(InputStream is, boolean stripBom) throws IOException { // Java supported character sets do not use BOMs longer than 4 bytes byte[] bom = newbyte[4];
is.mark(bom.length);
int count = is.read(bom);
// BOMs are at least 2 bytes if (count < 2) {
skip(is, 0, stripBom); returnnull;
}
// Look for two byte BOMs int b0 = bom[0] & 0xFF; int b1 = bom[1] & 0xFF; if (b0 == 0xFE && b1 == 0xFF) {
skip(is, 2, stripBom); return StandardCharsets.UTF_16BE;
} // Delay the UTF_16LE check if there are more that 2 bytes since it // overlaps with UTF-32LE. if (count == 2 && b0 == 0xFF && b1 == 0xFE) {
skip(is, 2, stripBom); return StandardCharsets.UTF_16LE;
}
// Remaining BOMs are at least 3 bytes if (count < 3) {
skip(is, 0, stripBom); returnnull;
}
// UTF-8 is only 3-byte BOM int b2 = bom[2] & 0xFF; if (b0 == 0xEF && b1 == 0xBB && b2 == 0xBF) {
skip(is, 3, stripBom); return StandardCharsets.UTF_8;
}
if (count < 4) {
skip(is, 0, stripBom); returnnull;
}
// Now we can check for UTF16-LE. There is an assumption here that we // won't see a UTF16-LE file with a BOM where the first real data is // 0x00 0x00 if (b0 == 0xFF && b1 == 0xFE) {
skip(is, 2, stripBom); return StandardCharsets.UTF_16LE;
}
skip(is, 0, stripBom); returnnull;
}
privatestaticvoid skip(InputStream is, int skip, boolean stripBom) throws IOException {
is.reset(); if (stripBom) { while (skip-- > 0) { if (is.read() < 0) { // Ignore since included break;
}
}
}
}
privatestaticboolean validate(ContentRange range) { // bytes is the only range unit supported return (range != null) && ("bytes".equals(range.getUnits())) && (range.getStart() >= 0) &&
(range.getEnd() >= 0) && (range.getStart() <= range.getEnd()) && (range.getLength() > 0);
}
privatestaticboolean validate(Ranges.Entry range, long length) { long start = getStart(range, length); long end = getEnd(range, length); return (start >= 0) && (end >= 0) && (start <= end);
}
privatestaticlong getStart(Ranges.Entry range, long length) { long start = range.getStart(); if (start == -1) { long end = range.getEnd(); // If there is no start, then the start is based on the end if (end >= length) { return0;
} else { return length - end;
}
} else { return start;
}
}
privatestaticlong getEnd(Ranges.Entry range, long length) { long end = range.getEnd(); if (range.getStart() == -1 || end == -1 || end >= length) { return length - 1;
} else { return end;
}
}
privateboolean pathEndsWithCompressedExtension(String path) { for (CompressionFormat format : compressionFormats) { if (path.endsWith(format.extension)) { returntrue;
}
} returnfalse;
}
private List<PrecompressedResource> getAvailablePrecompressedResources(String path) {
List<PrecompressedResource> ret = new ArrayList<>(compressionFormats.length); for (CompressionFormat format : compressionFormats) {
WebResource precompressedResource = resources.getResource(path + format.extension); if (precompressedResource.exists() && precompressedResource.isFile()) {
ret.add(new PrecompressedResource(precompressedResource, format));
}
} return ret;
}
// Range headers are only valid on GET requests. That implies they are // also valid on HEAD requests. This method is only called by doGet() // and doHead() so no need to check the request method.
String eTag = generateETag(resource); long lastModified = resource.getLastModified();
if (headerValueTime == (-1L)) { // If the ETag the client gave does not match the entity // etag, then the entire entity is returned. if (!eTag.equals(headerValue.trim())) { return FULL;
}
} else { // If the timestamp of the entity the client got differs from // the last modification date of the entity, the entire entity // is returned. if (Math.abs(lastModified - headerValueTime) > 1000) { return FULL;
}
}
}
long fileLength = resource.getContentLength();
if (fileLength == 0) { // Range header makes no sense for a zero length resource. Tomcat // therefore opts to ignore it. return FULL;
}
// Retrieving the range header (if any is specified
String rangeHeader = request.getHeader("Range");
if (rangeHeader == null) { // No Range header is the same as ignoring any Range header return FULL;
}
if (ranges == null) { // The Range header is present but not formatted correctly. // Could argue for a 400 response but 416 is more specific. // There is also the option to ignore the (invalid) Range header. // RFC7233#4.4 notes that many servers do ignore the Range header in // these circumstances but Tomcat has always returned a 416.
response.addHeader("Content-Range", "bytes */" + fileLength);
response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE); returnnull;
}
// bytes is the only range unit supported (and I don't see the point // of adding new ones). if (!ranges.getUnits().equals("bytes")) { // RFC7233#3.1 Servers must ignore range units they don't understand return FULL;
}
for (Ranges.Entry range : ranges.getEntries()) { if (!validate(range, fileLength)) {
response.addHeader("Content-Range", "bytes */" + fileLength);
response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE); returnnull;
}
}
// rewriteUrl(contextPath) is expensive. cache result for later reuse
String rewrittenContextPath = rewriteUrl(contextPath);
String directoryWebappPath = resource.getWebappPath();
for (String entry : entries) {
if (entry.equalsIgnoreCase("WEB-INF") || entry.equalsIgnoreCase("META-INF") ||
entry.equalsIgnoreCase(localXsltFile)) { continue;
}
if ((directoryWebappPath + entry).equals(contextXsltFile)) { continue;
}
sb.append('>');
sb.append(Escape.htmlElementContent(entry)); if (childResource.isDirectory()) {
sb.append('/');
}
sb.append("</entry>");
}
sb.append("</entries>");
String readme = getReadme(resource, encoding);
if (readme != null) {
sb.append("<readme><![CDATA[");
sb.append(readme);
sb.append("]]></readme>");
}
sb.append("</listing>");
// Prevent possible memory leak. Ensure Transformer and // TransformerFactory are not loaded from the web application.
ClassLoader original; Thread currentThread = Thread.currentThread(); if (Globals.IS_SECURITY_ENABLED) {
PrivilegedGetTccl pa = new PrivilegedGetTccl(currentThread);
original = AccessController.doPrivileged(pa);
} else {
original = currentThread.getContextClassLoader();
} try { if (Globals.IS_SECURITY_ENABLED) {
PrivilegedSetTccl pa = new PrivilegedSetTccl(currentThread, DefaultServlet.class.getClassLoader());
AccessController.doPrivileged(pa);
} else {
currentThread.setContextClassLoader(DefaultServlet.class.getClassLoader());
}
// Prepare a writer to a buffered area
ByteArrayOutputStream stream = new ByteArrayOutputStream();
OutputStreamWriter osWriter = new OutputStreamWriter(stream, StandardCharsets.UTF_8);
PrintWriter writer = new PrintWriter(osWriter);
File baseConf = new File(context.getCatalinaBase(), "conf");
File result = validateGlobalXsltFile(baseConf); if (result == null) {
File homeConf = new File(context.getCatalinaHome(), "conf"); if (!baseConf.equals(homeConf)) {
result = validateGlobalXsltFile(homeConf);
}
}
return result;
}
private File validateGlobalXsltFile(File base) {
File candidate = new File(globalXsltFile); if (!candidate.isAbsolute()) {
candidate = new File(base, globalXsltFile);
}
if (!candidate.isFile()) { returnnull;
}
// First check that the resulting path is under the provided base try { if (!candidate.getCanonicalFile().toPath().startsWith(base.getCanonicalFile().toPath())) { returnnull;
}
} catch (IOException ioe) { returnnull;
}
// Next check that an .xsl or .xslt file has been specified
String nameLower = candidate.getName().toLowerCase(Locale.ENGLISH); if (!nameLower.endsWith(".xslt") && !nameLower.endsWith(".xsl")) { returnnull;
}
return candidate;
}
private Source secureXslt(InputStream is) { // Need to filter out any external entities
Source result = null; try {
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(secureEntityResolver);
Document document = builder.parse(is);
result = new DOMSource(document);
} catch (ParserConfigurationException | SAXException | IOException e) { if (debug > 0) {
log(e.getMessage(), e);
}
} finally { if (is != null) { try {
is.close();
} catch (IOException e) { // Ignore
}
}
} return result;
}
// If an If-None-Match header has been specified, if modified since // is ignored. if ((request.getHeader("If-None-Match") == null) && (lastModified < headerValue + 1000)) { // The entity has not been modified since the date // specified by the client. This is not an error case.
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
response.setHeader("ETag", generateETag(resource));
if (conditionSatisfied) { // For GET and HEAD, we should respond with // 304 Not Modified. // For every other method, 412 Precondition Failed is sent // back. if ("GET".equals(request.getMethod()) || "HEAD".equals(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
response.setHeader("ETag", resourceETag);
} else {
response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
} returnfalse;
}
} returntrue;
}
/** *Checkiftheif-unmodified-sinceconditionissatisfied. * *@paramrequestTheservletrequestweareprocessing *@paramresponseTheservletresponsewearecreating *@paramresourceTheresource * *@return<code>true</code>iftheresourcemeetsthespecifiedcondition,and<code>false</code>ifthecondition *isnotsatisfied,inwhichcaserequestprocessingisstopped * *@throwsIOExceptionanIOerroroccurred
*/ protectedboolean checkIfUnmodifiedSince(HttpServletRequest request, HttpServletResponse response,
WebResource resource) throws IOException { try { long lastModified = resource.getLastModified(); long headerValue = request.getDateHeader("If-Unmodified-Since"); if (headerValue != -1) { if (lastModified >= (headerValue + 1000)) { // The entity has not been modified since the date // specified by the client. This is not an error case.
response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED); returnfalse;
}
}
} catch (IllegalArgumentException illegalArgument) { returntrue;
} returntrue;
}
publicstaticfinal Order NAME = new Order('N', false); publicstaticfinal Order NAME_ASC = new Order('N', true); publicstaticfinal Order SIZE = new Order('S', false); publicstaticfinal Order SIZE_ASC = new Order('S', true); publicstaticfinal Order LAST_MODIFIED = new Order('M', false); publicstaticfinal Order LAST_MODIFIED_ASC = new Order('M', true);
publicstaticfinal Order DEFAULT = NAME;
}
}
privatestatic Comparator<WebResource> comparingTrueFirst(Function<WebResource,Boolean> keyExtractor) { return (s1, s2) -> { Boolean r1 = keyExtractor.apply(s1); Boolean r2 = keyExtractor.apply(s2); if (r1.booleanValue()) { if (r2.booleanValue()) { return0;
} else { return -1; // r1 (property is true) first
}
} elseif (r2.booleanValue()) { return1; // r2 (property is true) first
} else { return0;
}
};
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.