Search in sources :

Example 1 with Ranges

use of org.apache.tomcat.util.http.parser.Ranges in project tomcat by apache.

the class DefaultServlet method copy.

/**
 * Copy the contents of the specified input stream to the specified
 * output stream, and ensure that both streams are closed before returning
 * (even in the face of an exception).
 *
 * @param resource      The source resource
 * @param length the resource length
 * @param ostream       The output stream to write to
 * @param ranges        Enumeration of the ranges the client wanted to
 *                          retrieve
 * @param contentType   Content type of the resource
 * @exception IOException if an input/output error occurs
 */
protected void copy(WebResource resource, long length, ServletOutputStream ostream, Ranges ranges, String contentType) throws IOException {
    IOException exception = null;
    for (Ranges.Entry range : ranges.getEntries()) {
        if (exception != null) {
            break;
        }
        InputStream resourceInputStream = resource.getInputStream();
        try (InputStream istream = new BufferedInputStream(resourceInputStream, input)) {
            // Writing MIME header.
            ostream.println();
            ostream.println("--" + mimeSeparation);
            if (contentType != null) {
                ostream.println("Content-Type: " + contentType);
            }
            long start = getStart(range, length);
            long end = getEnd(range, length);
            ostream.println("Content-Range: bytes " + start + "-" + end + "/" + (end - start));
            ostream.println();
            // Printing content
            exception = copyRange(istream, ostream, start, end);
        }
    }
    ostream.println();
    ostream.print("--" + mimeSeparation + "--");
    // Rethrow any exception that has occurred
    if (exception != null) {
        throw exception;
    }
}
Also used : Ranges(org.apache.tomcat.util.http.parser.Ranges) BufferedInputStream(java.io.BufferedInputStream) BufferedInputStream(java.io.BufferedInputStream) ByteArrayInputStream(java.io.ByteArrayInputStream) FileInputStream(java.io.FileInputStream) InputStream(java.io.InputStream) IOException(java.io.IOException)

Example 2 with Ranges

use of org.apache.tomcat.util.http.parser.Ranges in project tomcat by apache.

the class DefaultServlet method parseRange.

/**
 * 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.
    // Checking If-Range
    String headerValue = request.getHeader("If-Range");
    if (headerValue != null) {
        long headerValueTime = (-1L);
        try {
            headerValueTime = request.getDateHeader("If-Range");
        } catch (IllegalArgumentException e) {
        // Ignore
        }
        String eTag = generateETag(resource);
        long lastModified = resource.getLastModified();
        if (headerValueTime == (-1L)) {
            // etag, then the entire entity is returned.
            if (!eTag.equals(headerValue.trim())) {
                return FULL;
            }
        } else {
            // is returned.
            if (Math.abs(lastModified - headerValueTime) > 1000) {
                return FULL;
            }
        }
    }
    long fileLength = resource.getContentLength();
    if (fileLength == 0) {
        // 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;
    }
    Ranges ranges = Ranges.parse(new StringReader(rangeHeader));
    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);
        return null;
    }
    // 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);
            return null;
        }
    }
    return ranges;
}
Also used : Ranges(org.apache.tomcat.util.http.parser.Ranges) StringReader(java.io.StringReader)

Example 3 with Ranges

use of org.apache.tomcat.util.http.parser.Ranges in project tomcat by apache.

the class DefaultServlet method serveResource.

/**
 * 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
 */
protected void serveResource(HttpServletRequest request, HttpServletResponse response, boolean content, String inputEncoding) throws IOException, ServletException {
    boolean serveContent = content;
    // Identify the requested resource path
    String path = getRelativePath(request, true);
    if (debug > 0) {
        if (serveContent) {
            log("DefaultServlet.serveResource:  Serving resource '" + path + "' headers and data");
        } else {
            log("DefaultServlet.serveResource:  Serving resource '" + path + "' headers only");
        }
    }
    if (path.length() == 0) {
        // Context root redirect
        doDirectoryRedirect(request, response);
        return;
    }
    WebResource resource = resources.getResource(path);
    boolean isError = DispatcherType.ERROR == request.getDispatcherType();
    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 {
            // SRV.9.3 says we must throw a FNFE
            throw new FileNotFoundException(sm.getString("defaultServlet.missingResource", requestUri));
        }
        if (isError) {
            response.sendError(((Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE)).intValue());
        } else {
            response.sendError(HttpServletResponse.SC_NOT_FOUND, sm.getString("defaultServlet.missingResource", requestUri));
        }
        return;
    }
    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 {
            // reasonable
            throw new FileNotFoundException(sm.getString("defaultServlet.missingResource", requestUri));
        }
        if (isError) {
            response.sendError(((Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE)).intValue());
        } else {
            response.sendError(HttpServletResponse.SC_FORBIDDEN, requestUri);
        }
        return;
    }
    boolean included = false;
    // satisfied.
    if (resource.isFile()) {
        // Checking If headers
        included = (request.getAttribute(RequestDispatcher.INCLUDE_CONTEXT_PATH) != null);
        if (!included && !isError && !checkIfHeaders(request, response, resource)) {
            return;
        }
    }
    // Find content type.
    String contentType = resource.getMimeType();
    if (contentType == null) {
        contentType = getServletContext().getMimeType(resource.getName());
        resource.setMimeType(contentType);
    }
    // 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;
        }
        // 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;
            }
            // ETag header
            response.setHeader("ETag", eTag);
            // Last-Modified header
            response.setHeader("Last-Modified", lastModifiedHttp);
        }
        // Get content length
        contentLength = resource.getContentLength();
        // (silent) ISE when setting the output buffer size
        if (contentLength == 0L) {
            serveContent = false;
        }
    }
    ServletOutputStream ostream = null;
    PrintWriter writer = null;
    if (serveContent) {
        // Trying to retrieve the servlet output stream
        try {
            ostream = response.getOutputStream();
        } catch (IllegalStateException e) {
            // 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);
            }
            // 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) {
                // 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 {
                    // 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();
                        }
                        // 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);
                            }
                        }
                    }
                }
                // the output (this method closes the stream)
                if (renderResult != null) {
                    copy(renderResult, ostream);
                }
            }
        }
    } else {
        if ((ranges == null) || (ranges.getEntries().isEmpty())) {
            return;
        }
        // Partial content response.
        response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
        if (ranges.getEntries().size() == 1) {
            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
                    throw new 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
                    throw new IllegalStateException();
                }
            }
        }
    }
}
Also used : Ranges(org.apache.tomcat.util.http.parser.Ranges) ServletResponse(jakarta.servlet.ServletResponse) HttpServletResponse(jakarta.servlet.http.HttpServletResponse) ServletOutputStream(jakarta.servlet.ServletOutputStream) BufferedInputStream(java.io.BufferedInputStream) ByteArrayInputStream(java.io.ByteArrayInputStream) FileInputStream(java.io.FileInputStream) InputStream(java.io.InputStream) FileNotFoundException(java.io.FileNotFoundException) WebResource(org.apache.catalina.WebResource) Charset(java.nio.charset.Charset) BufferedInputStream(java.io.BufferedInputStream) ResponseFacade(org.apache.catalina.connector.ResponseFacade) CachedResource(org.apache.catalina.webresources.CachedResource) OutputStreamWriter(java.io.OutputStreamWriter) ServletResponseWrapper(jakarta.servlet.ServletResponseWrapper) PrintWriter(java.io.PrintWriter)

Aggregations

Ranges (org.apache.tomcat.util.http.parser.Ranges)3 BufferedInputStream (java.io.BufferedInputStream)2 ByteArrayInputStream (java.io.ByteArrayInputStream)2 FileInputStream (java.io.FileInputStream)2 InputStream (java.io.InputStream)2 ServletOutputStream (jakarta.servlet.ServletOutputStream)1 ServletResponse (jakarta.servlet.ServletResponse)1 ServletResponseWrapper (jakarta.servlet.ServletResponseWrapper)1 HttpServletResponse (jakarta.servlet.http.HttpServletResponse)1 FileNotFoundException (java.io.FileNotFoundException)1 IOException (java.io.IOException)1 OutputStreamWriter (java.io.OutputStreamWriter)1 PrintWriter (java.io.PrintWriter)1 StringReader (java.io.StringReader)1 Charset (java.nio.charset.Charset)1 WebResource (org.apache.catalina.WebResource)1 ResponseFacade (org.apache.catalina.connector.ResponseFacade)1 CachedResource (org.apache.catalina.webresources.CachedResource)1