1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44 package org.eclipse.jgit.http.server;
45
46 import static javax.servlet.http.HttpServletResponse.SC_PARTIAL_CONTENT;
47 import static javax.servlet.http.HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE;
48 import static org.eclipse.jgit.util.HttpSupport.HDR_ACCEPT_RANGES;
49 import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_LENGTH;
50 import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_RANGE;
51 import static org.eclipse.jgit.util.HttpSupport.HDR_IF_RANGE;
52 import static org.eclipse.jgit.util.HttpSupport.HDR_RANGE;
53
54 import java.io.EOFException;
55 import java.io.File;
56 import java.io.FileNotFoundException;
57 import java.io.IOException;
58 import java.io.OutputStream;
59 import java.io.RandomAccessFile;
60 import java.text.MessageFormat;
61 import java.time.Instant;
62 import java.util.Enumeration;
63
64 import javax.servlet.http.HttpServletRequest;
65 import javax.servlet.http.HttpServletResponse;
66
67 import org.eclipse.jgit.lib.ObjectId;
68 import org.eclipse.jgit.util.FS;
69
70
71
72
73
74
75
76 final class FileSender {
77 private final File path;
78
79 private final RandomAccessFile source;
80
81 private final Instant lastModified;
82
83 private final long fileLen;
84
85 private long pos;
86
87 private long end;
88
89 FileSender(File path) throws FileNotFoundException {
90 this.path = path;
91 this.source = new RandomAccessFile(path, "r");
92
93 try {
94 this.lastModified = FS.DETECTED.lastModifiedInstant(path);
95 this.fileLen = source.getChannel().size();
96 this.end = fileLen;
97 } catch (IOException e) {
98 try {
99 source.close();
100 } catch (IOException closeError) {
101
102 }
103
104 final FileNotFoundException r;
105 r = new FileNotFoundException(MessageFormat.format(HttpServerText.get().cannotGetLengthOf, path));
106 r.initCause(e);
107 throw r;
108 }
109 }
110
111 void close() {
112 try {
113 source.close();
114 } catch (IOException e) {
115
116 }
117 }
118
119 Instant getLastModified() {
120 return lastModified;
121 }
122
123 String getTailChecksum() throws IOException {
124 final int n = 20;
125 final byte[] buf = new byte[n];
126 source.seek(fileLen - n);
127 source.readFully(buf, 0, n);
128 return ObjectId.fromRaw(buf).getName();
129 }
130
131 void serve(final HttpServletRequest req, final HttpServletResponse rsp,
132 final boolean sendBody) throws IOException {
133 if (!initRangeRequest(req, rsp)) {
134 rsp.sendError(SC_REQUESTED_RANGE_NOT_SATISFIABLE);
135 return;
136 }
137
138 rsp.setHeader(HDR_ACCEPT_RANGES, "bytes");
139 rsp.setHeader(HDR_CONTENT_LENGTH, Long.toString(end - pos));
140
141 if (sendBody) {
142 try (OutputStream out = rsp.getOutputStream()) {
143 final byte[] buf = new byte[4096];
144 source.seek(pos);
145 while (pos < end) {
146 final int r = (int) Math.min(buf.length, end - pos);
147 final int n = source.read(buf, 0, r);
148 if (n < 0) {
149 throw new EOFException(MessageFormat.format(HttpServerText.get().unexpectedeOFOn, path));
150 }
151 out.write(buf, 0, n);
152 pos += n;
153 }
154 out.flush();
155 }
156 }
157 }
158
159 private boolean initRangeRequest(final HttpServletRequest req,
160 final HttpServletResponse rsp) throws IOException {
161 final Enumeration<String> rangeHeaders = getRange(req);
162 if (!rangeHeaders.hasMoreElements()) {
163
164 return true;
165 }
166
167 final String range = rangeHeaders.nextElement();
168 if (rangeHeaders.hasMoreElements()) {
169
170 return false;
171 }
172
173 final int eq = range.indexOf('=');
174 final int dash = range.indexOf('-');
175 if (eq < 0 || dash < 0 || !range.startsWith("bytes=")) {
176 return false;
177 }
178
179 final String ifRange = req.getHeader(HDR_IF_RANGE);
180 if (ifRange != null && !getTailChecksum().equals(ifRange)) {
181
182
183 return true;
184 }
185
186 try {
187 if (eq + 1 == dash) {
188
189 pos = Long.parseLong(range.substring(dash + 1));
190 pos = fileLen - pos;
191 } else {
192
193
194 pos = Long.parseLong(range.substring(eq + 1, dash));
195 if (dash < range.length() - 1) {
196 end = Long.parseLong(range.substring(dash + 1));
197 end++;
198 }
199 }
200 } catch (NumberFormatException e) {
201
202
203
204
205 return false;
206 }
207
208 if (end > fileLen) {
209 end = fileLen;
210 }
211 if (pos >= end) {
212 return false;
213 }
214
215 rsp.setStatus(SC_PARTIAL_CONTENT);
216 rsp.setHeader(HDR_CONTENT_RANGE, "bytes " + pos + "-" + (end - 1) + "/"
217 + fileLen);
218 source.seek(pos);
219 return true;
220 }
221
222 private static Enumeration<String> getRange(HttpServletRequest req) {
223 return req.getHeaders(HDR_RANGE);
224 }
225 }