1 /* 2 * Copyright (C) 2009-2010, Google Inc. and others 3 * 4 * This program and the accompanying materials are made available under the 5 * terms of the Eclipse Distribution License v. 1.0 which is available at 6 * https://www.eclipse.org/org/documents/edl-v10.php. 7 * 8 * SPDX-License-Identifier: BSD-3-Clause 9 */ 10 11 package org.eclipse.jgit.http.server; 12 13 import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; 14 import static javax.servlet.http.HttpServletResponse.SC_NOT_MODIFIED; 15 import static org.eclipse.jgit.http.server.ServletUtils.getRepository; 16 import static org.eclipse.jgit.util.HttpSupport.HDR_ETAG; 17 import static org.eclipse.jgit.util.HttpSupport.HDR_IF_MODIFIED_SINCE; 18 import static org.eclipse.jgit.util.HttpSupport.HDR_IF_NONE_MATCH; 19 import static org.eclipse.jgit.util.HttpSupport.HDR_LAST_MODIFIED; 20 21 import java.io.File; 22 import java.io.FileNotFoundException; 23 import java.io.IOException; 24 import java.time.Instant; 25 26 import javax.servlet.ServletException; 27 import javax.servlet.http.HttpServlet; 28 import javax.servlet.http.HttpServletRequest; 29 import javax.servlet.http.HttpServletResponse; 30 31 import org.eclipse.jgit.internal.storage.file.ObjectDirectory; 32 import org.eclipse.jgit.lib.Repository; 33 34 /** Sends any object from {@code GIT_DIR/objects/??/0 38}, or any pack file. */ 35 abstract class ObjectFileServlet extends HttpServlet { 36 private static final long serialVersionUID = 1L; 37 38 static class Loose extends ObjectFileServlet { 39 private static final long serialVersionUID = 1L; 40 Loose()41 Loose() { 42 super("application/x-git-loose-object"); 43 } 44 45 @Override etag(FileSender sender)46 String etag(FileSender sender) throws IOException { 47 Instant lastModified = sender.getLastModified(); 48 return Long.toHexString(lastModified.getEpochSecond()) 49 + Long.toHexString(lastModified.getNano()); 50 } 51 } 52 53 private abstract static class PackData extends ObjectFileServlet { 54 private static final long serialVersionUID = 1L; 55 PackData(String contentType)56 PackData(String contentType) { 57 super(contentType); 58 } 59 60 @Override etag(FileSender sender)61 String etag(FileSender sender) throws IOException { 62 return sender.getTailChecksum(); 63 } 64 } 65 66 static class Pack extends PackData { 67 private static final long serialVersionUID = 1L; 68 Pack()69 Pack() { 70 super("application/x-git-packed-objects"); 71 } 72 } 73 74 static class PackIdx extends PackData { 75 private static final long serialVersionUID = 1L; 76 PackIdx()77 PackIdx() { 78 super("application/x-git-packed-objects-toc"); 79 } 80 } 81 82 private final String contentType; 83 ObjectFileServlet(String contentType)84 ObjectFileServlet(String contentType) { 85 this.contentType = contentType; 86 } 87 etag(FileSender sender)88 abstract String etag(FileSender sender) throws IOException; 89 90 /** {@inheritDoc} */ 91 @Override doGet(final HttpServletRequest req, final HttpServletResponse rsp)92 public void doGet(final HttpServletRequest req, 93 final HttpServletResponse rsp) throws IOException { 94 serve(req, rsp, true); 95 } 96 97 /** {@inheritDoc} */ 98 @Override doHead(final HttpServletRequest req, final HttpServletResponse rsp)99 protected void doHead(final HttpServletRequest req, 100 final HttpServletResponse rsp) throws ServletException, IOException { 101 serve(req, rsp, false); 102 } 103 serve(final HttpServletRequest req, final HttpServletResponse rsp, final boolean sendBody)104 private void serve(final HttpServletRequest req, 105 final HttpServletResponse rsp, final boolean sendBody) 106 throws IOException { 107 final File obj = new File(objects(req), req.getPathInfo()); 108 final FileSender sender; 109 try { 110 sender = new FileSender(obj); 111 } catch (FileNotFoundException e) { 112 rsp.sendError(SC_NOT_FOUND); 113 return; 114 } 115 116 try { 117 final String etag = etag(sender); 118 // HTTP header Last-Modified header has a resolution of 1 sec, see 119 // https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.29 120 final long lastModified = sender.getLastModified().getEpochSecond(); 121 122 String ifNoneMatch = req.getHeader(HDR_IF_NONE_MATCH); 123 if (etag != null && etag.equals(ifNoneMatch)) { 124 rsp.sendError(SC_NOT_MODIFIED); 125 return; 126 } 127 128 long ifModifiedSince = req.getDateHeader(HDR_IF_MODIFIED_SINCE); 129 if (0 < lastModified && lastModified < ifModifiedSince) { 130 rsp.sendError(SC_NOT_MODIFIED); 131 return; 132 } 133 134 if (etag != null) 135 rsp.setHeader(HDR_ETAG, etag); 136 if (0 < lastModified) 137 rsp.setDateHeader(HDR_LAST_MODIFIED, lastModified); 138 rsp.setContentType(contentType); 139 sender.serve(req, rsp, sendBody); 140 } finally { 141 sender.close(); 142 } 143 } 144 objects(HttpServletRequest req)145 private static File objects(HttpServletRequest req) { 146 final Repository db = getRepository(req); 147 return ((ObjectDirectory) db.getObjectDatabase()).getDirectory(); 148 } 149 } 150