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