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(final 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 final OutputStream out = rsp.getOutputStream();
141 try {
142 final byte[] buf = new byte[4096];
143 source.seek(pos);
144 while (pos < end) {
145 final int r = (int) Math.min(buf.length, end - pos);
146 final int n = source.read(buf, 0, r);
147 if (n < 0) {
148 throw new EOFException(MessageFormat.format(HttpServerText.get().unexpectedeOFOn, path));
149 }
150 out.write(buf, 0, n);
151 pos += n;
152 }
153 out.flush();
154 } finally {
155 out.close();
156 }
157 }
158 }
159
160 private boolean initRangeRequest(final HttpServletRequest req,
161 final HttpServletResponse rsp) throws IOException {
162 final Enumeration<String> rangeHeaders = getRange(req);
163 if (!rangeHeaders.hasMoreElements()) {
164
165 return true;
166 }
167
168 final String range = rangeHeaders.nextElement();
169 if (rangeHeaders.hasMoreElements()) {
170
171 return false;
172 }
173
174 final int eq = range.indexOf('=');
175 final int dash = range.indexOf('-');
176 if (eq < 0 || dash < 0 || !range.startsWith("bytes=")) {
177 return false;
178 }
179
180 final String ifRange = req.getHeader(HDR_IF_RANGE);
181 if (ifRange != null && !getTailChecksum().equals(ifRange)) {
182
183
184 return true;
185 }
186
187 try {
188 if (eq + 1 == dash) {
189
190 pos = Long.parseLong(range.substring(dash + 1));
191 pos = fileLen - pos;
192 } else {
193
194
195 pos = Long.parseLong(range.substring(eq + 1, dash));
196 if (dash < range.length() - 1) {
197 end = Long.parseLong(range.substring(dash + 1));
198 end++;
199 }
200 }
201 } catch (NumberFormatException e) {
202
203
204
205
206 return false;
207 }
208
209 if (end > fileLen) {
210 end = fileLen;
211 }
212 if (pos >= end) {
213 return false;
214 }
215
216 rsp.setStatus(SC_PARTIAL_CONTENT);
217 rsp.setHeader(HDR_CONTENT_RANGE, "bytes " + pos + "-" + (end - 1) + "/"
218 + fileLen);
219 source.seek(pos);
220 return true;
221 }
222
223 private static Enumeration<String> getRange(final HttpServletRequest req) {
224 return req.getHeaders(HDR_RANGE);
225 }
226 }