|
10 | 10 | import com.sun.net.httpserver.Headers; |
11 | 11 | import com.sun.net.httpserver.HttpExchange; |
12 | 12 | import com.sun.net.httpserver.HttpHandler; |
| 13 | +import org.apache.lucene.util.BytesRef; |
| 14 | +import org.apache.lucene.util.BytesRefIterator; |
13 | 15 | import org.elasticsearch.common.Nullable; |
14 | 16 | import org.elasticsearch.common.SuppressForbidden; |
15 | 17 | import org.elasticsearch.common.UUIDs; |
16 | 18 | import org.elasticsearch.common.bytes.BytesArray; |
17 | 19 | import org.elasticsearch.common.bytes.BytesReference; |
| 20 | +import org.elasticsearch.common.bytes.CompositeBytesReference; |
18 | 21 | import org.elasticsearch.common.collect.Tuple; |
19 | 22 | import org.elasticsearch.common.hash.MessageDigests; |
20 | 23 | import org.elasticsearch.common.io.Streams; |
21 | 24 | import org.elasticsearch.common.regex.Regex; |
22 | 25 | import org.elasticsearch.rest.RestStatus; |
23 | 26 | import org.elasticsearch.rest.RestUtils; |
24 | 27 |
|
25 | | -import java.io.BufferedInputStream; |
26 | 28 | import java.io.ByteArrayOutputStream; |
27 | 29 | import java.io.IOException; |
28 | 30 | import java.io.InputStream; |
29 | 31 | import java.io.InputStreamReader; |
| 32 | +import java.io.PrintStream; |
30 | 33 | import java.nio.charset.StandardCharsets; |
31 | 34 | import java.security.MessageDigest; |
| 35 | +import java.util.ArrayList; |
32 | 36 | import java.util.HashMap; |
33 | 37 | import java.util.HashSet; |
34 | 38 | import java.util.Iterator; |
| 39 | +import java.util.List; |
35 | 40 | import java.util.Locale; |
36 | 41 | import java.util.Map; |
37 | 42 | import java.util.Objects; |
@@ -272,98 +277,86 @@ private static String multipartKey(final String uploadId, int partNumber) { |
272 | 277 | return uploadId + "\n" + partNumber; |
273 | 278 | } |
274 | 279 |
|
275 | | - private static CheckedInputStream createCheckedInputStream(final InputStream inputStream, final MessageDigest digest) { |
276 | | - return new CheckedInputStream(inputStream, new Checksum() { |
277 | | - @Override |
278 | | - public void update(int b) { |
279 | | - digest.update((byte) b); |
280 | | - } |
| 280 | + private static final Pattern chunkSignaturePattern = Pattern.compile("^([0-9a-z]+);chunk-signature=([^\\r\\n]*)$"); |
281 | 281 |
|
282 | | - @Override |
283 | | - public void update(byte[] b, int off, int len) { |
284 | | - digest.update(b, off, len); |
285 | | - } |
| 282 | + private static Tuple<String, BytesReference> parseRequestBody(final HttpExchange exchange) throws IOException { |
| 283 | + try { |
| 284 | + final BytesReference bytesReference; |
286 | 285 |
|
287 | | - @Override |
288 | | - public long getValue() { |
289 | | - throw new UnsupportedOperationException(); |
290 | | - } |
| 286 | + final String headerDecodedContentLength = exchange.getRequestHeaders().getFirst("x-amz-decoded-content-length"); |
| 287 | + if (headerDecodedContentLength == null) { |
| 288 | + bytesReference = Streams.readFully(exchange.getRequestBody()); |
| 289 | + } else { |
| 290 | + BytesReference requestBody = Streams.readFully(exchange.getRequestBody()); |
| 291 | + int chunkIndex = 0; |
| 292 | + final List<BytesReference> chunks = new ArrayList<>(); |
291 | 293 |
|
292 | | - @Override |
293 | | - public void reset() { |
294 | | - digest.reset(); |
295 | | - } |
296 | | - }); |
297 | | - } |
| 294 | + while (true) { |
| 295 | + chunkIndex += 1; |
298 | 296 |
|
299 | | - private static final Pattern chunkSignaturePattern = Pattern.compile("^([0-9a-z]+);chunk-signature=([^\\r\\n]*)$"); |
| 297 | + final int headerLength = requestBody.indexOf((byte) '\n', 0) + 1; // includes terminating \r\n |
| 298 | + if (headerLength == 0) { |
| 299 | + throw new IllegalStateException("header of chunk [" + chunkIndex + "] was not terminated"); |
| 300 | + } |
| 301 | + if (headerLength > 150) { |
| 302 | + throw new IllegalStateException( |
| 303 | + "header of chunk [" + chunkIndex + "] was too long at [" + headerLength + "] bytes"); |
| 304 | + } |
| 305 | + if (headerLength < 3) { |
| 306 | + throw new IllegalStateException( |
| 307 | + "header of chunk [" + chunkIndex + "] was too short at [" + headerLength + "] bytes"); |
| 308 | + } |
| 309 | + if (requestBody.get(headerLength - 1) != '\n' || requestBody.get(headerLength - 2) != '\r') { |
| 310 | + throw new IllegalStateException("header of chunk [" + chunkIndex + "] not terminated with [\\r\\n]"); |
| 311 | + } |
300 | 312 |
|
301 | | - private static Tuple<String, BytesReference> parseRequestBody(final HttpExchange exchange) throws IOException { |
302 | | - final BytesReference bytesReference; |
| 313 | + final String header = requestBody.slice(0, headerLength - 2).utf8ToString(); |
| 314 | + final Matcher matcher = chunkSignaturePattern.matcher(header); |
| 315 | + if (matcher.find() == false) { |
| 316 | + throw new IllegalStateException( |
| 317 | + "header of chunk [" + chunkIndex + "] did not match expected pattern: [" + header + "]"); |
| 318 | + } |
| 319 | + final int chunkSize = Integer.parseUnsignedInt(matcher.group(1), 16); |
303 | 320 |
|
304 | | - final String headerDecodedContentLength = exchange.getRequestHeaders().getFirst("x-amz-decoded-content-length"); |
305 | | - if (headerDecodedContentLength == null) { |
306 | | - bytesReference = Streams.readFully(exchange.getRequestBody()); |
307 | | - } else { |
308 | | - BytesReference cc = Streams.readFully(exchange.getRequestBody()); |
309 | | - |
310 | | - final ByteArrayOutputStream blob = new ByteArrayOutputStream(); |
311 | | - try (BufferedInputStream in = new BufferedInputStream(cc.streamInput())) { |
312 | | - int chunkSize = 0; |
313 | | - int read; |
314 | | - while ((read = in.read()) != -1) { |
315 | | - boolean markAndContinue = false; |
316 | | - try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { |
317 | | - do { // search next consecutive {carriage return, new line} chars and stop |
318 | | - if ((char) read == '\r') { |
319 | | - int next = in.read(); |
320 | | - if (next != -1) { |
321 | | - if (next == '\n') { |
322 | | - break; |
323 | | - } |
324 | | - out.write(read); |
325 | | - out.write(next); |
326 | | - continue; |
327 | | - } |
328 | | - } |
329 | | - out.write(read); |
330 | | - } while ((read = in.read()) != -1); |
331 | | - |
332 | | - final String line = new String(out.toByteArray(), UTF_8); |
333 | | - if (line.length() == 0 || line.equals("\r\n")) { |
334 | | - markAndContinue = true; |
335 | | - } else { |
336 | | - Matcher matcher = chunkSignaturePattern.matcher(line); |
337 | | - if (matcher.find()) { |
338 | | - markAndContinue = true; |
339 | | - chunkSize = Integer.parseUnsignedInt(matcher.group(1), 16); |
340 | | - } |
341 | | - } |
342 | | - if (markAndContinue) { |
343 | | - in.mark(Integer.MAX_VALUE); |
344 | | - continue; |
345 | | - } |
| 321 | + if (requestBody.get(headerLength + chunkSize) != '\r' || requestBody.get(headerLength + chunkSize + 1) != '\n') { |
| 322 | + throw new IllegalStateException("chunk [" + chunkIndex + "] not terminated with [\\r\\n]"); |
346 | 323 | } |
347 | | - if (chunkSize > 0) { |
348 | | - in.reset(); |
349 | | - final byte[] buffer = new byte[chunkSize]; |
350 | | - in.read(buffer, 0, buffer.length); |
351 | | - blob.write(buffer); |
352 | | - blob.flush(); |
353 | | - chunkSize = 0; |
| 324 | + |
| 325 | + if (chunkSize != 0) { |
| 326 | + chunks.add(requestBody.slice(headerLength, chunkSize)); |
354 | 327 | } |
| 328 | + |
| 329 | + final int toSkip = headerLength + chunkSize + 2; |
| 330 | + requestBody = requestBody.slice(toSkip, requestBody.length() - toSkip); |
| 331 | + |
| 332 | + if (chunkSize == 0) { |
| 333 | + break; |
| 334 | + } |
| 335 | + } |
| 336 | + |
| 337 | + bytesReference = CompositeBytesReference.of(chunks.toArray(new BytesReference[0])); |
| 338 | + |
| 339 | + if (bytesReference.length() != Integer.parseInt(headerDecodedContentLength)) { |
| 340 | + throw new IllegalStateException("Something went wrong when parsing the chunked request " + |
| 341 | + "[bytes read=" + bytesReference.length() + ", expected=" + headerDecodedContentLength + "]"); |
355 | 342 | } |
356 | 343 | } |
357 | | - if (blob.size() != Integer.parseInt(headerDecodedContentLength)) { |
358 | | - throw new IllegalStateException("Something went wrong when parsing the chunked request " + |
359 | | - "[bytes read=" + blob.size() + ", expected=" + headerDecodedContentLength + "]"); |
| 344 | + |
| 345 | + final MessageDigest digest = MessageDigests.md5(); |
| 346 | + BytesRef ref; |
| 347 | + final BytesRefIterator iterator = bytesReference.iterator(); |
| 348 | + while ((ref = iterator.next()) != null) { |
| 349 | + digest.update(ref.bytes, ref.offset, ref.length); |
| 350 | + } |
| 351 | + return Tuple.tuple(MessageDigests.toHexString(digest.digest()), bytesReference); |
| 352 | + } catch (Exception e) { |
| 353 | + exchange.sendResponseHeaders(500, 0); |
| 354 | + try (PrintStream printStream = new PrintStream(exchange.getResponseBody())) { |
| 355 | + printStream.println(e.toString()); |
| 356 | + e.printStackTrace(printStream); |
360 | 357 | } |
361 | | - bytesReference = new BytesArray(blob.toByteArray()); |
| 358 | + throw new AssertionError("parseRequestBody failed", e); |
362 | 359 | } |
363 | | - |
364 | | - final MessageDigest digest = MessageDigests.md5(); |
365 | | - Streams.readFully(createCheckedInputStream(bytesReference.streamInput(), digest)); |
366 | | - return Tuple.tuple(MessageDigests.toHexString(digest.digest()), bytesReference); |
367 | 360 | } |
368 | 361 |
|
369 | 362 | public static void sendError(final HttpExchange exchange, |
|
0 commit comments