/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.apache.catalina.servlets;
/** * <p> * The default resource-serving servlet for most web applications, used to serve static resources such as HTML pages and * images. * </p> * <p> * This servlet is intended to be mapped to <em>/</em> e.g.: * </p> * * <pre> * <servlet-mapping> * <servlet-name>default</servlet-name> * <url-pattern>/</url-pattern> * </servlet-mapping> * </pre> * <p> * It can be mapped to sub-paths, however in all cases resources are served from the web application resource root using * the full path from the root of the web application context. <br> * e.g. given a web application structure: * </p> * * <pre> * /context * /images * tomcat2.jpg * /static * /images * tomcat.jpg * </pre> * <p> * ... and a servlet mapping that maps only <code>/static/*</code> to the default servlet: * </p> * * <pre> * <servlet-mapping> * <servlet-name>default</servlet-name> * <url-pattern>/static/*</url-pattern> * </servlet-mapping> * </pre> * <p> * Then a request to <code>/context/static/images/tomcat.jpg</code> will succeed while a request to * <code>/context/images/tomcat2.jpg</code> will fail. * </p> * * @author Craig R. McClanahan * @author Remy Maucherat
*/ publicclass DefaultServlet extends HttpServlet {
privatestaticfinallong serialVersionUID = 1L;
/** * The string manager for this package.
*/ protectedstaticfinal StringManager sm = StringManager.getManager(DefaultServlet.class);
/** * Allow a readme file to be included.
*/ protected String readmeFile = null;
/** * The complete set of web application resources
*/ protectedtransient WebResourceRoot resources = null;
/** * File encoding to be used when reading static files. If none is specified the platform default is used.
*/ protected String fileEncoding = null; privatetransient Charset fileEncodingCharset = null;
/** * If a file has a BOM, should that be used in preference to fileEncoding? Will default to {@link BomConfig#TRUE} in * {@link #init()}.
*/ private BomConfig useBomIfPresent = null;
/** * Minimum size for sendfile usage in bytes.
*/ protectedint sendfileSize = 48 * 1024;
/** * Should the Accept-Ranges: bytes header be send with static resources?
*/ protectedboolean useAcceptRanges = true;
/** * Flag to determine if server information is presented.
*/ protectedboolean showServerInfo = true;
/** * Flag to determine if resources should be sorted.
*/ protectedboolean sortListings = false;
/** * The sorting manager for sorting files and directories.
*/ protectedtransient SortManager sortManager;
/** * Flag that indicates whether partial PUTs are permitted.
*/ privateboolean allowPartialPut = true;
// --------------------------------------------------------- Public Methods
/** * Return the relative path associated with this servlet. * * @param request The servlet request we are processing * * @return the relative path
*/ protected String getRelativePath(HttpServletRequest request) { return getRelativePath(request, false);
}
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('/');
}
return result.toString();
}
/** * Determines the appropriate path to prepend resources with when generating directory listings. Depending on the * behaviour of {@link #getRelativePath(HttpServletRequest)} this will change. * * @param request the request to determine the path for * * @return the prefix to apply to all resources in the listing.
*/ protected String getPathPrefix(final HttpServletRequest request) { return request.getContextPath();
}
/** * Process a GET request for the specified resource. * * @param request The servlet request we are processing * @param response The servlet response we are creating * * @exception IOException if an input/output error occurs * @exception ServletException if a servlet-specified error occurs
*/
@Override protectedvoid doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
// Serve the requested resource, including the data content
serveResource(request, response, true, fileEncoding);
}
/** * Process a HEAD request for the specified resource. * * @param request The servlet request we are processing * @param response The servlet response we are creating * * @exception IOException if an input/output error occurs * @exception ServletException if a servlet-specified error occurs
*/
@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);
}
/** * Override default implementation to ensure that TRACE is correctly handled. * * @param req the {@link HttpServletRequest} object that contains the request the client made of the servlet * @param resp the {@link HttpServletResponse} object that contains the response the servlet returns to the client * * @exception IOException if an input or output error occurs while the servlet is handling the OPTIONS request * @exception ServletException if the request for the OPTIONS cannot be handled
*/
@Override protectedvoid doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
/** * Process a POST request for the specified resource. * * @param request The servlet request we are processing * @param response The servlet response we are creating * * @exception IOException if an input/output error occurs * @exception ServletException if a servlet-specified error occurs
*/
@Override protectedvoid doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
doGet(request, response);
}
/** * Process a PUT request for the specified resource. * * @param req The servlet request we are processing * @param resp The servlet response we are creating * * @exception IOException if an input/output error occurs * @exception ServletException if a servlet-specified error occurs
*/
@Override protectedvoid doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
if (readOnly) {
sendNotAllowed(req, resp); return;
}
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);
}
/** * Handle a partial PUT. New content specified in request is appended to existing content in oldRevisionContent (if * present). This code does not support simultaneous partial updates to the same resource. * * @param req The Servlet request * @param range The range that will be written * @param path The path * * @return the associated file object * * @throws IOException an IO error occurred
*/ protected File executePartialPut(HttpServletRequest req, ContentRange range, String path) throws IOException {
// 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);
}
// Append data in request input stream to contentFile
randAccessContentFile.seek(range.getStart()); int numBytesRead; byte[] transferBuffer = newbyte[BUFFER_SIZE]; try (BufferedInputStream requestBufInStream = new BufferedInputStream(req.getInputStream(), BUFFER_SIZE)) { while ((numBytesRead = requestBufInStream.read(transferBuffer)) != -1) {
randAccessContentFile.write(transferBuffer, 0, numBytesRead);
}
}
}
return contentFile;
}
/** * Process a DELETE request for the specified resource. * * @param req The servlet request we are processing * @param resp The servlet response we are creating * * @exception IOException if an input/output error occurs * @exception ServletException if a servlet-specified error occurs
*/
@Override protectedvoid doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
if (readOnly) {
sendNotAllowed(req, resp); return;
}
if (resource.exists()) { if (resource.delete()) {
resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
} else {
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
}
} else {
resp.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
/** * Check if the conditions specified in the optional If headers are satisfied. * * @param request The servlet request we are processing * @param response The servlet response we are creating * @param resource The resource * * @return <code>true</code> if the resource meets all the specified conditions, and <code>false</code> if any of * the conditions is not satisfied, in which case request processing is stopped * * @throws IOException an IO error occurred
*/ protectedboolean checkIfHeaders(HttpServletRequest request, HttpServletResponse response, WebResource resource) throws IOException {
/** * URL rewriter. * * @param path Path which has to be rewritten * * @return the rewritten path
*/ protected String rewriteUrl(String path) { return URLEncoder.DEFAULT.encode(path, StandardCharsets.UTF_8);
}
/** * Serve the specified resource, optionally including the data content. * * @param request The servlet request we are processing * @param response The servlet response we are creating * @param content Should the content be included? * @param inputEncoding The encoding to use if it is necessary to access the source as characters rather than as * bytes * * @exception IOException if an input/output error occurs * @exception ServletException if a servlet-specified error occurs
*/ protectedvoid serveResource(HttpServletRequest request, HttpServletResponse response, boolean content,
String inputEncoding) throws IOException, ServletException {
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; /* * The test below deliberately uses != to compare two Strings. This is because the code is looking to see if the * default character encoding has been returned because no explicit character encoding has been defined. There * is no clean way of doing this via the Servlet API. It would be possible to add a Tomcat specific API but that * would require quite a bit of code to get to the Tomcat specific request object that may have been wrapped. * The != test is a (slightly hacky) quick way of doing this.
*/ 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();
}
}
}
}
}
/* * Code borrowed heavily from Jasper's EncodingDetector
*/ 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) { return 0;
} 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;
}
/** * Match the client preferred encoding formats to the available precompressed resources. * * @param request The servlet request we are processing * @param precompressedResources List of available precompressed resources. * * @return The best matching precompressed resource or null if no match was found.
*/ private PrecompressedResource getBestPrecompressedResource(HttpServletRequest request,
List<PrecompressedResource> precompressedResources) {
Enumeration<String> headers = request.getHeaders("Accept-Encoding");
PrecompressedResource bestResource = null; double bestResourceQuality = 0; int bestResourcePreference = Integer.MAX_VALUE; while (headers.hasMoreElements()) {
String header = headers.nextElement(); for (String preference : header.split(",")) { double quality = 1; int qualityIdx = preference.indexOf(';'); if (qualityIdx > 0) { int equalsIdx = preference.indexOf('=', qualityIdx + 1); if (equalsIdx == -1) { continue;
}
quality = Double.parseDouble(preference.substring(equalsIdx + 1).trim());
} if (quality >= bestResourceQuality) {
String encoding = preference; if (qualityIdx > 0) {
encoding = encoding.substring(0, qualityIdx);
}
encoding = encoding.trim(); if ("identity".equals(encoding)) {
bestResource = null;
bestResourceQuality = quality;
bestResourcePreference = Integer.MAX_VALUE; continue;
} if ("*".equals(encoding)) {
bestResource = precompressedResources.get(0);
bestResourceQuality = quality;
bestResourcePreference = 0; continue;
} for (int i = 0; i < precompressedResources.size(); ++i) {
PrecompressedResource resource = precompressedResources.get(i); if (encoding.equals(resource.format.encoding)) { if (quality > bestResourceQuality || i < bestResourcePreference) {
bestResource = resource;
bestResourceQuality = quality;
bestResourcePreference = i;
} break;
}
}
}
}
} return bestResource;
}
/** * Parse the content-range header. * * @param request The servlet request we are processing * @param response The servlet response we are creating * * @return the partial content-range, {@code null} if the content-range header was invalid or {@code #IGNORE} if * there is no header to process * * @throws IOException an IO error occurred
*/ protected ContentRange parseContentRange(HttpServletRequest request, HttpServletResponse response) throws IOException {
// Retrieving the content-range header (if any is specified
String contentRangeHeader = request.getHeader("Content-Range");
if (contentRangeHeader == null) { return IGNORE;
}
if (!allowPartialPut) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST); returnnull;
}
if (contentRange == null) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST); returnnull;
}
if (!validate(contentRange)) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST); returnnull;
}
return contentRange;
}
/** * Parse the range header. * * @param request The servlet request we are processing * @param response The servlet response we are creating * @param resource The resource * * @return a list of ranges, {@code null} if the range header was invalid or {@code #FULL} if the Range header * should be ignored. * * @throws IOException an IO error occurred
*/ protected Ranges parseRange(HttpServletRequest request, HttpServletResponse response, WebResource resource) throws IOException {
// 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;
}
}
return ranges;
}
/** * Decide which way to render. HTML or XML. * * @param request The HttpServletRequest being served * @param contextPath The path * @param resource The resource * @param encoding The encoding to use to process the readme (if any) * * @return the input stream with the rendered output * * @throws IOException an IO error occurred * @throws ServletException rendering error
*/ protected InputStream render(HttpServletRequest request, String contextPath, WebResource resource, String encoding) throws IOException, ServletException {
/** * Return an InputStream to an XML representation of the contents this directory. * * @param request The HttpServletRequest being served * @param contextPath Context path to which our internal paths are relative * @param resource The associated resource * @param xsltSource The XSL stylesheet * @param encoding The encoding to use to process the readme (if any) * * @return the XML data * * @throws IOException an IO error occurred * @throws ServletException rendering error
*/ protected InputStream renderXml(HttpServletRequest request, String contextPath, WebResource resource,
Source xsltSource, String encoding) throws IOException, ServletException {
// 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("");
}
sb.append("");
String readme = getReadme(resource, encoding);
if (readme != null) {
sb.append(");
sb.append(readme);
sb.append("]]>");
}
sb.append("");
// 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());
}
ByteArrayOutputStream stream = new ByteArrayOutputStream();
OutputStreamWriter osWriter = new OutputStreamWriter(stream, StandardCharsets.UTF_8);
StreamResult out = new StreamResult(osWriter);
transformer.transform(xmlSource, out);
osWriter.flush(); returnnew ByteArrayInputStream(stream.toByteArray());
} catch (TransformerException e) { thrownew ServletException(sm.getString("defaultServlet.xslError"), e);
} finally { if (Globals.IS_SECURITY_ENABLED) {
PrivilegedSetTccl pa = new PrivilegedSetTccl(currentThread, original);
AccessController.doPrivileged(pa);
} else {
currentThread.setContextClassLoader(original);
}
}
}
/** * Return an InputStream to an HTML representation of the contents of this directory. * * @param request The HttpServletRequest being served * @param contextPath Context path to which our internal paths are relative * @param resource The associated resource * @param encoding The encoding to use to process the readme (if any) * * @return the HTML data * * @throws IOException an IO error occurred
*/ protected InputStream renderHtml(HttpServletRequest request, String contextPath, WebResource resource,
--> --------------------
--> maximum size reached
--> --------------------
¤ Dauer der Verarbeitung: 0.51 Sekunden
(vorverarbeitet)
¤
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 ist noch experimentell.