xref: /JGit/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ObjectFileServlet.java (revision 25a6bd4d614589c968090fb506fc9b26d5c82fe2)
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