15e33a1deSShawn O. Pearce /* 2*5c5f7c6bSMatthias Sohn * Copyright (C) 2009-2010, Google Inc. and others 35e33a1deSShawn O. Pearce * 4*5c5f7c6bSMatthias Sohn * This program and the accompanying materials are made available under the 5*5c5f7c6bSMatthias Sohn * terms of the Eclipse Distribution License v. 1.0 which is available at 6*5c5f7c6bSMatthias Sohn * https://www.eclipse.org/org/documents/edl-v10.php. 75e33a1deSShawn O. Pearce * 8*5c5f7c6bSMatthias Sohn * SPDX-License-Identifier: BSD-3-Clause 95e33a1deSShawn O. Pearce */ 105e33a1deSShawn O. Pearce 115e33a1deSShawn O. Pearce package org.eclipse.jgit.http.server; 125e33a1deSShawn O. Pearce 135e33a1deSShawn O. Pearce import static javax.servlet.http.HttpServletResponse.SC_PARTIAL_CONTENT; 145e33a1deSShawn O. Pearce import static javax.servlet.http.HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE; 155e33a1deSShawn O. Pearce import static org.eclipse.jgit.util.HttpSupport.HDR_ACCEPT_RANGES; 165e33a1deSShawn O. Pearce import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_LENGTH; 175e33a1deSShawn O. Pearce import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_RANGE; 185e33a1deSShawn O. Pearce import static org.eclipse.jgit.util.HttpSupport.HDR_IF_RANGE; 195e33a1deSShawn O. Pearce import static org.eclipse.jgit.util.HttpSupport.HDR_RANGE; 205e33a1deSShawn O. Pearce 215e33a1deSShawn O. Pearce import java.io.EOFException; 225e33a1deSShawn O. Pearce import java.io.File; 235e33a1deSShawn O. Pearce import java.io.FileNotFoundException; 245e33a1deSShawn O. Pearce import java.io.IOException; 255e33a1deSShawn O. Pearce import java.io.OutputStream; 265e33a1deSShawn O. Pearce import java.io.RandomAccessFile; 27f3d8a8ecSSasa Zivkov import java.text.MessageFormat; 2895e8264cSMatthias Sohn import java.time.Instant; 295e33a1deSShawn O. Pearce import java.util.Enumeration; 305e33a1deSShawn O. Pearce 315e33a1deSShawn O. Pearce import javax.servlet.http.HttpServletRequest; 325e33a1deSShawn O. Pearce import javax.servlet.http.HttpServletResponse; 335e33a1deSShawn O. Pearce 345e33a1deSShawn O. Pearce import org.eclipse.jgit.lib.ObjectId; 3595e8264cSMatthias Sohn import org.eclipse.jgit.util.FS; 365e33a1deSShawn O. Pearce 375e33a1deSShawn O. Pearce /** 385e33a1deSShawn O. Pearce * Dumps a file over HTTP GET (or its information via HEAD). 395e33a1deSShawn O. Pearce * <p> 405e33a1deSShawn O. Pearce * Supports a single byte range requested via {@code Range} HTTP header. This 415e33a1deSShawn O. Pearce * feature supports a dumb client to resume download of a larger object file. 425e33a1deSShawn O. Pearce */ 435e33a1deSShawn O. Pearce final class FileSender { 445e33a1deSShawn O. Pearce private final File path; 455e33a1deSShawn O. Pearce 465e33a1deSShawn O. Pearce private final RandomAccessFile source; 475e33a1deSShawn O. Pearce 4895e8264cSMatthias Sohn private final Instant lastModified; 495e33a1deSShawn O. Pearce 505e33a1deSShawn O. Pearce private final long fileLen; 515e33a1deSShawn O. Pearce 525e33a1deSShawn O. Pearce private long pos; 535e33a1deSShawn O. Pearce 545e33a1deSShawn O. Pearce private long end; 555e33a1deSShawn O. Pearce FileSender(File path)56f3ec7cf3SHan-Wen Nienhuys FileSender(File path) throws FileNotFoundException { 575e33a1deSShawn O. Pearce this.path = path; 585e33a1deSShawn O. Pearce this.source = new RandomAccessFile(path, "r"); 595e33a1deSShawn O. Pearce 605e33a1deSShawn O. Pearce try { 6195e8264cSMatthias Sohn this.lastModified = FS.DETECTED.lastModifiedInstant(path); 625e33a1deSShawn O. Pearce this.fileLen = source.getChannel().size(); 635e33a1deSShawn O. Pearce this.end = fileLen; 645e33a1deSShawn O. Pearce } catch (IOException e) { 655e33a1deSShawn O. Pearce try { 665e33a1deSShawn O. Pearce source.close(); 675e33a1deSShawn O. Pearce } catch (IOException closeError) { 685e33a1deSShawn O. Pearce // Ignore any error closing the stream. 695e33a1deSShawn O. Pearce } 705e33a1deSShawn O. Pearce 715e33a1deSShawn O. Pearce final FileNotFoundException r; 72f3d8a8ecSSasa Zivkov r = new FileNotFoundException(MessageFormat.format(HttpServerText.get().cannotGetLengthOf, path)); 735e33a1deSShawn O. Pearce r.initCause(e); 745e33a1deSShawn O. Pearce throw r; 755e33a1deSShawn O. Pearce } 765e33a1deSShawn O. Pearce } 775e33a1deSShawn O. Pearce close()785e33a1deSShawn O. Pearce void close() { 795e33a1deSShawn O. Pearce try { 805e33a1deSShawn O. Pearce source.close(); 815e33a1deSShawn O. Pearce } catch (IOException e) { 825e33a1deSShawn O. Pearce // Ignore close errors on a read-only stream. 835e33a1deSShawn O. Pearce } 845e33a1deSShawn O. Pearce } 855e33a1deSShawn O. Pearce getLastModified()8695e8264cSMatthias Sohn Instant getLastModified() { 875e33a1deSShawn O. Pearce return lastModified; 885e33a1deSShawn O. Pearce } 895e33a1deSShawn O. Pearce getTailChecksum()905e33a1deSShawn O. Pearce String getTailChecksum() throws IOException { 915e33a1deSShawn O. Pearce final int n = 20; 925e33a1deSShawn O. Pearce final byte[] buf = new byte[n]; 9316419dadSShawn O. Pearce source.seek(fileLen - n); 9416419dadSShawn O. Pearce source.readFully(buf, 0, n); 955e33a1deSShawn O. Pearce return ObjectId.fromRaw(buf).getName(); 965e33a1deSShawn O. Pearce } 975e33a1deSShawn O. Pearce serve(final HttpServletRequest req, final HttpServletResponse rsp, final boolean sendBody)985e33a1deSShawn O. Pearce void serve(final HttpServletRequest req, final HttpServletResponse rsp, 995e33a1deSShawn O. Pearce final boolean sendBody) throws IOException { 1005e33a1deSShawn O. Pearce if (!initRangeRequest(req, rsp)) { 1015e33a1deSShawn O. Pearce rsp.sendError(SC_REQUESTED_RANGE_NOT_SATISFIABLE); 1025e33a1deSShawn O. Pearce return; 1035e33a1deSShawn O. Pearce } 1045e33a1deSShawn O. Pearce 1055e33a1deSShawn O. Pearce rsp.setHeader(HDR_ACCEPT_RANGES, "bytes"); 1065e33a1deSShawn O. Pearce rsp.setHeader(HDR_CONTENT_LENGTH, Long.toString(end - pos)); 1075e33a1deSShawn O. Pearce 1085e33a1deSShawn O. Pearce if (sendBody) { 10907341b29SDavid Pursehouse try (OutputStream out = rsp.getOutputStream()) { 1105e33a1deSShawn O. Pearce final byte[] buf = new byte[4096]; 11116419dadSShawn O. Pearce source.seek(pos); 1125e33a1deSShawn O. Pearce while (pos < end) { 1135e33a1deSShawn O. Pearce final int r = (int) Math.min(buf.length, end - pos); 1145e33a1deSShawn O. Pearce final int n = source.read(buf, 0, r); 1155e33a1deSShawn O. Pearce if (n < 0) { 116f3d8a8ecSSasa Zivkov throw new EOFException(MessageFormat.format(HttpServerText.get().unexpectedeOFOn, path)); 1175e33a1deSShawn O. Pearce } 1185e33a1deSShawn O. Pearce out.write(buf, 0, n); 1195e33a1deSShawn O. Pearce pos += n; 1205e33a1deSShawn O. Pearce } 1215e33a1deSShawn O. Pearce out.flush(); 1225e33a1deSShawn O. Pearce } 1235e33a1deSShawn O. Pearce } 1245e33a1deSShawn O. Pearce } 1255e33a1deSShawn O. Pearce initRangeRequest(final HttpServletRequest req, final HttpServletResponse rsp)1265e33a1deSShawn O. Pearce private boolean initRangeRequest(final HttpServletRequest req, 1275e33a1deSShawn O. Pearce final HttpServletResponse rsp) throws IOException { 1285e33a1deSShawn O. Pearce final Enumeration<String> rangeHeaders = getRange(req); 1295e33a1deSShawn O. Pearce if (!rangeHeaders.hasMoreElements()) { 1305e33a1deSShawn O. Pearce // No range headers, the request is fine. 1315e33a1deSShawn O. Pearce return true; 1325e33a1deSShawn O. Pearce } 1335e33a1deSShawn O. Pearce 1345e33a1deSShawn O. Pearce final String range = rangeHeaders.nextElement(); 1355e33a1deSShawn O. Pearce if (rangeHeaders.hasMoreElements()) { 1365e33a1deSShawn O. Pearce // To simplify the code we support only one range. 1375e33a1deSShawn O. Pearce return false; 1385e33a1deSShawn O. Pearce } 1395e33a1deSShawn O. Pearce 1405e33a1deSShawn O. Pearce final int eq = range.indexOf('='); 1415e33a1deSShawn O. Pearce final int dash = range.indexOf('-'); 1425e33a1deSShawn O. Pearce if (eq < 0 || dash < 0 || !range.startsWith("bytes=")) { 1435e33a1deSShawn O. Pearce return false; 1445e33a1deSShawn O. Pearce } 1455e33a1deSShawn O. Pearce 1465e33a1deSShawn O. Pearce final String ifRange = req.getHeader(HDR_IF_RANGE); 1475e33a1deSShawn O. Pearce if (ifRange != null && !getTailChecksum().equals(ifRange)) { 1485e33a1deSShawn O. Pearce // If the client asked us to verify the ETag and its not 1495e33a1deSShawn O. Pearce // what they expected we need to send the entire content. 1505e33a1deSShawn O. Pearce return true; 1515e33a1deSShawn O. Pearce } 1525e33a1deSShawn O. Pearce 1535e33a1deSShawn O. Pearce try { 1545e33a1deSShawn O. Pearce if (eq + 1 == dash) { 1555e33a1deSShawn O. Pearce // "bytes=-500" means last 500 bytes 1565e33a1deSShawn O. Pearce pos = Long.parseLong(range.substring(dash + 1)); 1575e33a1deSShawn O. Pearce pos = fileLen - pos; 1585e33a1deSShawn O. Pearce } else { 1595e33a1deSShawn O. Pearce // "bytes=500-" (position 500 to end) 1605e33a1deSShawn O. Pearce // "bytes=500-1000" (position 500 to 1000) 1615e33a1deSShawn O. Pearce pos = Long.parseLong(range.substring(eq + 1, dash)); 1625e33a1deSShawn O. Pearce if (dash < range.length() - 1) { 1635e33a1deSShawn O. Pearce end = Long.parseLong(range.substring(dash + 1)); 1645e33a1deSShawn O. Pearce end++; // range was inclusive, want exclusive 1655e33a1deSShawn O. Pearce } 1665e33a1deSShawn O. Pearce } 1675e33a1deSShawn O. Pearce } catch (NumberFormatException e) { 1685e33a1deSShawn O. Pearce // We probably hit here because of a non-digit such as 1695e33a1deSShawn O. Pearce // "," appearing at the end of the first range telling 1705e33a1deSShawn O. Pearce // us there is a second range following. To simplify 1715e33a1deSShawn O. Pearce // the code we support only one range. 1725e33a1deSShawn O. Pearce return false; 1735e33a1deSShawn O. Pearce } 1745e33a1deSShawn O. Pearce 1755e33a1deSShawn O. Pearce if (end > fileLen) { 1765e33a1deSShawn O. Pearce end = fileLen; 1775e33a1deSShawn O. Pearce } 1785e33a1deSShawn O. Pearce if (pos >= end) { 1795e33a1deSShawn O. Pearce return false; 1805e33a1deSShawn O. Pearce } 1815e33a1deSShawn O. Pearce 1825e33a1deSShawn O. Pearce rsp.setStatus(SC_PARTIAL_CONTENT); 1835e33a1deSShawn O. Pearce rsp.setHeader(HDR_CONTENT_RANGE, "bytes " + pos + "-" + (end - 1) + "/" 1845e33a1deSShawn O. Pearce + fileLen); 1855e33a1deSShawn O. Pearce source.seek(pos); 1865e33a1deSShawn O. Pearce return true; 1875e33a1deSShawn O. Pearce } 1885e33a1deSShawn O. Pearce getRange(HttpServletRequest req)1896d370d83SHan-Wen Nienhuys private static Enumeration<String> getRange(HttpServletRequest req) { 1905e33a1deSShawn O. Pearce return req.getHeaders(HDR_RANGE); 1915e33a1deSShawn O. Pearce } 1925e33a1deSShawn O. Pearce } 193