View Javadoc
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  
41  		Loose() {
42  			super("application/x-git-loose-object");
43  		}
44  
45  		@Override
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  
56  		PackData(String contentType) {
57  			super(contentType);
58  		}
59  
60  		@Override
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  
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  
77  		PackIdx() {
78  			super("application/x-git-packed-objects-toc");
79  		}
80  	}
81  
82  	private final String contentType;
83  
84  	ObjectFileServlet(String contentType) {
85  		this.contentType = contentType;
86  	}
87  
88  	abstract String etag(FileSender sender) throws IOException;
89  
90  	/** {@inheritDoc} */
91  	@Override
92  	public void doGet(final HttpServletRequest req,
93  			final HttpServletResponse rsp) throws IOException {
94  		serve(req, rsp, true);
95  	}
96  
97  	/** {@inheritDoc} */
98  	@Override
99  	protected void doHead(final HttpServletRequest req,
100 			final HttpServletResponse rsp) throws ServletException, IOException {
101 		serve(req, rsp, false);
102 	}
103 
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 
145 	private static File objects(HttpServletRequest req) {
146 		final Repository db = getRepository(req);
147 		return ((ObjectDirectory) db.getObjectDatabase()).getDirectory();
148 	}
149 }