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