From 822e07c243978a458bcf8cb785cb8549764e6339 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 28 May 2019 16:16:29 +0100 Subject: [PATCH] parsing of headers --- .../com/muwire/core/files/FileManager.groovy | 1 - .../com/muwire/core/upload/Request.groovy | 76 +++++++++++- .../muwire/core/upload/UploadManager.groovy | 7 +- .../core/upload/RequestParsingTest.groovy | 108 ++++++++++++++++++ 4 files changed, 188 insertions(+), 4 deletions(-) create mode 100644 core/src/test/groovy/com/muwire/core/upload/RequestParsingTest.groovy diff --git a/core/src/main/groovy/com/muwire/core/files/FileManager.groovy b/core/src/main/groovy/com/muwire/core/files/FileManager.groovy index e208239e..26dd41fa 100644 --- a/core/src/main/groovy/com/muwire/core/files/FileManager.groovy +++ b/core/src/main/groovy/com/muwire/core/files/FileManager.groovy @@ -56,7 +56,6 @@ class FileManager { void onFileUnsharedEvent(FileUnsharedEvent e) { SharedFile sf = e.unsharedFile byte [] root = sf.getInfoHash().getRoot() - Set existing Set existing = rootToFiles.get(root) if (existing != null) { existing.remove(sf) diff --git a/core/src/main/groovy/com/muwire/core/upload/Request.groovy b/core/src/main/groovy/com/muwire/core/upload/Request.groovy index b29f14b6..8238ba2e 100644 --- a/core/src/main/groovy/com/muwire/core/upload/Request.groovy +++ b/core/src/main/groovy/com/muwire/core/upload/Request.groovy @@ -1,16 +1,90 @@ package com.muwire.core.upload +import java.nio.charset.StandardCharsets + import com.muwire.core.InfoHash +import groovy.util.logging.Log import net.i2p.data.Base64 +@Log class Request { + + private static final byte R = "\r".getBytes(StandardCharsets.US_ASCII)[0] + private static final byte N = "\n".getBytes(StandardCharsets.US_ASCII)[0] + InfoHash infoHash Range range Map headers - static Range parse(InfoHash infoHash, InputStream is) throws IOException { + static Request parse(InfoHash infoHash, InputStream is) throws IOException { + Map headers = new HashMap<>() + byte [] tmp = new byte[0x1 << 14] + while(true) { + boolean r = false + boolean n = false + int idx = 0 + while (true) { + byte read = is.read() + if (read == -1) + throw new IOException("Stream closed") + + if (!r && read == N) + throw new IOException("Received N before R") + if (read == R) { + if (r) + throw new IOException("double R") + r = true + continue + } + + if (r && !n) { + if (read != N) + throw new IOException("R not followed by N") + n = true + break + } + if (idx == 0x1 << 14) + throw new IOException("Header too long") + tmp[idx++] = read + } + + if (idx == 0) + break + + String header = new String(tmp, 0, idx, StandardCharsets.US_ASCII) + log.fine("Read header $header") + + int keyIdx = header.indexOf(":") + if (keyIdx < 1) + throw new IOException("Header key not found") + if (keyIdx == header.length()) + throw new IOException("Header value not found") + String key = header.substring(0, keyIdx) + String value = header.substring(keyIdx + 1) + headers.put(key, value) + } + if (!headers.containsKey("Range")) + throw new IOException("Range header not found") + + String range = headers.get("Range").trim() + String[] split = range.split("-") + if (split.length != 2) + throw new IOException("Invalid range header $range") + long start + long end + try { + start = Long.parseLong(split[0]) + end = Long.parseLong(split[1]) + } catch (NumberFormatException nfe) { + throw new IOException(nfe) + } + + if (start < 0 || end < start) + throw new IOException("Invalid range $start - $end") + + new Request( infoHash : infoHash, range : new Range(start, end), headers : headers) } } diff --git a/core/src/main/groovy/com/muwire/core/upload/UploadManager.groovy b/core/src/main/groovy/com/muwire/core/upload/UploadManager.groovy index fbb68d35..0cd47556 100644 --- a/core/src/main/groovy/com/muwire/core/upload/UploadManager.groovy +++ b/core/src/main/groovy/com/muwire/core/upload/UploadManager.groovy @@ -48,8 +48,11 @@ public class UploadManager { Request request = Request.parse(new InfoHash(infoHashRoot), e.getInputStream()) Uploader uploader = new Uploader(request, e) eventBus.publish(new UploadEvent(uploader)) - uploader.respond() - eventBus.publish(new UploadFinishedEvent(uploader)) + try { + uploader.respond() + } finally { + eventBus.publish(new UploadFinishedEvent(uploader)) + } } } diff --git a/core/src/test/groovy/com/muwire/core/upload/RequestParsingTest.groovy b/core/src/test/groovy/com/muwire/core/upload/RequestParsingTest.groovy new file mode 100644 index 00000000..e5205561 --- /dev/null +++ b/core/src/test/groovy/com/muwire/core/upload/RequestParsingTest.groovy @@ -0,0 +1,108 @@ +package com.muwire.core.upload + +import java.nio.charset.StandardCharsets + +import org.junit.Before +import org.junit.Test + +import com.muwire.core.InfoHash + +class RequestParsingTest { + + Request request + + private void fromString(String requestString) { + def is = new ByteArrayInputStream(requestString.getBytes(StandardCharsets.US_ASCII)) + request = Request.parse(new InfoHash(new byte[InfoHash.SIZE]), is) + } + + + private static void failed(String requestString) { + try { + def is = new ByteArrayInputStream(requestString.getBytes(StandardCharsets.US_ASCII)) + Request.parse(new InfoHash(new byte[InfoHash.SIZE]), is) + assert false + } catch (IOException expected) {} + } + + @Before + public void setup() { + request = null + } + + @Test + public void testSuccessful() { + fromString("Range: 1-2\r\n\r\n") + assert request != null + assert request.getRange().start == 1 + assert request.getRange().end == 2 + } + + @Test + public void testRNMissing() { + failed("Range: 1-2") + } + + @Test + public void testRNMissing2() { + failed("Range: 1-2\r\n") + } + + @Test + public void testRR() { + failed("Range: 1-2\r\r") + } + + @Test + public void testNR() { + failed("Range: 1-2\n\r") + } + + @Test + public void testR() { + failed("Range: 1-2\r") + } + + @Test + public void testRX() { + failed("Range: 1-2\rx") + } + + @Test + public void testTwoHeaders() { + fromString("Range: 1-2\r\nA:B\r\n\r\n") + assert request != null + assert request.getRange().start == 1 + assert request.getRange().end == 2 + assert request.getHeaders().size() == 2 + assert request.getHeaders().get("Range").trim() == "1-2" + assert request.getHeaders().get("A").trim() == "B" + } + + @Test + public void testRangeMissing() { + failed("A:B\r\n") + } + + @Test + public void testNoHeaders() { + failed("\r\n") + } + + @Test + public void testInvalidRange() { + failed("Range 1-2\r\n\r\n") + failed("Range:\r\n\r\n") + failed("Range: -1-2\r\n\r\n") + failed("Range: 1-x\r\n\r\n") + failed("Range: x") + } + + @Test + public void testHeaderTooLong() { + StringBuilder sb = new StringBuilder() + for (int i = 0; i < (0x1 << 14) + 1; i++) + sb.append("x") + failed(sb.toString()) + } +}