From 9934ff1df5e720f712bb17ac0a7748e58ced1dd5 Mon Sep 17 00:00:00 2001 From: Ziver Koc Date: Tue, 10 Nov 2020 21:18:36 +0100 Subject: [PATCH] Added test for http parser and fixed some issues --- build.gradle | 1 + src/zutil/net/http/HttpHeader.java | 27 ++++--- src/zutil/net/http/HttpHeaderParser.java | 34 ++++---- src/zutil/net/http/HttpPrintStream.java | 15 +++- src/zutil/net/http/page/HttpFilePage.java | 40 ++++++---- test/zutil/net/http/HttpHeaderParserTest.java | 80 +++++++++++++++++++ test/zutil/net/http/HttpHeaderTest.java | 18 ++--- test/zutil/parser/URLDecoderTest.java | 3 - 8 files changed, 163 insertions(+), 55 deletions(-) create mode 100644 test/zutil/net/http/HttpHeaderParserTest.java diff --git a/build.gradle b/build.gradle index b7d8178..8c95a02 100644 --- a/build.gradle +++ b/build.gradle @@ -26,6 +26,7 @@ group = 'se.koc' version = '1.0.0-SNAPSHOT' sourceCompatibility = 1.8 targetCompatibility = 1.8 +compileJava.options.encoding = 'UTF-8' java { withSourcesJar() diff --git a/src/zutil/net/http/HttpHeader.java b/src/zutil/net/http/HttpHeader.java index 16dcc74..7a6e316 100755 --- a/src/zutil/net/http/HttpHeader.java +++ b/src/zutil/net/http/HttpHeader.java @@ -35,31 +35,34 @@ import java.util.TreeMap; public class HttpHeader { // Constants + public static final String HEADER_CACHE_CONTROL = "Cache-Control"; + public static final String HEADER_COOKIE = "Cookie"; public static final String HEADER_CONTENT_TYPE = "Content-Type"; public static final String HEADER_CONTENT_LENGTH = "Content-Length"; - public static final String HEADER_COOKIE = "Cookie"; public static final String HEADER_SET_COOKIE = "Set-Cookie"; public static final String HEADER_SERVER = "Server"; public static final String HEADER_USER_AGENT = "User-Agent"; + public static final String HEADER_IF_NONE_MATCH = "If-None-Match"; // Variables private boolean isRequest = true; /** Specifies the protocol that should be used */ - private String protocol = "HTTP"; + private String protocol = null; /** The protocol version specified in the header */ - private float protocolVersion = 1.0f; + private float protocolVersion = -1; /** HTTP type specified in a HTTP request, e.g GET POST DELETE PUT etc */ - private String requestType = "GET"; + private String requestType = null; /** String containing the target URL */ - private String requestUrl = "/"; + private String requestUrl = null; /** Map containing all the properties from the URL */ private Map requestUrlAttributes = new HashMap<>(); /** Status code specified in a HTTP response message */ - private int responseStatusCode = 200; + private int responseStatusCode = -1; + private String responseStatusString = null; /** An Map of all header fields */ private Map headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); @@ -136,8 +139,16 @@ public class HttpHeader { public void setResponseStatusCode(int code) { this.responseStatusCode = code; + this.responseStatusString = getResponseStatusString(code); } + public String getResponseStatusString() { + return responseStatusString; + } + + public void getResponseStatusString(String msg) { + this.responseStatusString = msg; + } /** * @return the URL that the client sent the server @@ -303,10 +314,6 @@ public class HttpHeader { } - public String getResponseStatusString() { - return getResponseStatusString(responseStatusCode); - } - public static String getResponseStatusString(int type) { switch (type) { case 100: return "Continue"; diff --git a/src/zutil/net/http/HttpHeaderParser.java b/src/zutil/net/http/HttpHeaderParser.java index b75d894..80b891a 100755 --- a/src/zutil/net/http/HttpHeaderParser.java +++ b/src/zutil/net/http/HttpHeaderParser.java @@ -101,28 +101,34 @@ public class HttpHeaderParser { * @param statusLine the status line String */ private static void parseStatusLine(HttpHeader header, String statusLine) { + String[] lineArr = statusLine.split(" ", 3); + + if (lineArr.length != 3) + return; + // Server Response - if (statusLine.startsWith("HTTP/")) { + if (lineArr[0].contains("/")) { header.setIsRequest(false); - header.setProtocolVersion(Float.parseFloat(statusLine.substring(5, 8))); - header.setResponseStatusCode(Integer.parseInt(statusLine.substring(9, statusLine.indexOf(' ', 9)))); + header.setProtocol(lineArr[0].substring(0, lineArr[0].indexOf('/'))); + header.setProtocolVersion(Float.parseFloat(lineArr[0].substring(lineArr[0].indexOf('/')+1))); + header.setResponseStatusCode(Integer.parseInt(lineArr[1])); + header.getResponseStatusString(lineArr[2]); } // Client Request - else if (statusLine.contains("HTTP/")) { + else { header.setIsRequest(true); - header.setRequestType(statusLine.substring(0, statusLine.indexOf(" ")).trim()); - header.setProtocolVersion(Float.parseFloat(statusLine.substring(statusLine.lastIndexOf("HTTP/") + 5).trim())); - statusLine = (statusLine.substring(header.getRequestType().length() + 1, statusLine.lastIndexOf("HTTP/"))); + header.setRequestType(lineArr[0]); + header.setProtocol(lineArr[2].substring(0, lineArr[2].indexOf('/'))); + header.setProtocolVersion(Float.parseFloat(lineArr[2].substring(lineArr[2].indexOf('/')+1))); + String url = lineArr[1]; // parse URL and attributes - int index = statusLine.indexOf('?'); - if (index > -1) { - header.setRequestURL(statusLine.substring(0, index)); - statusLine = statusLine.substring(index + 1); - parseURLParameters(header.getURLAttributeMap(), statusLine); - } else { - header.setRequestURL(statusLine); + int paramStartIndex = url.indexOf('?'); + if (paramStartIndex >= 0) { + url = statusLine.substring(0, paramStartIndex); + parseURLParameters(header.getURLAttributeMap(), statusLine.substring(paramStartIndex + 1)); } + header.setRequestURL(url.trim()); } } diff --git a/src/zutil/net/http/HttpPrintStream.java b/src/zutil/net/http/HttpPrintStream.java index 5e900c4..237d111 100755 --- a/src/zutil/net/http/HttpPrintStream.java +++ b/src/zutil/net/http/HttpPrintStream.java @@ -78,7 +78,20 @@ public class HttpPrintStream extends OutputStream { */ public HttpPrintStream(OutputStream out, HttpMessageType type) { this.out = new PrintStream(out); - header.setIsRequest(type == HttpMessageType.REQUEST); + + // Set defaults + + header.setProtocol("HTTP"); + header.setProtocolVersion(1.0f); + + if (type == HttpMessageType.REQUEST) { + header.setIsRequest(true); + header.setRequestType("GET"); + header.setRequestURL("/"); + } else { + header.setIsRequest(false); + header.setResponseStatusCode(200); + } } diff --git a/src/zutil/net/http/page/HttpFilePage.java b/src/zutil/net/http/page/HttpFilePage.java index 3597055..03fef85 100755 --- a/src/zutil/net/http/page/HttpFilePage.java +++ b/src/zutil/net/http/page/HttpFilePage.java @@ -50,11 +50,12 @@ public class HttpFilePage implements HttpPage{ private static final Logger log = LogUtil.getLogger(); private static final int MAX_CACHE_AGE_SECONDS = 120; - private File resource_root; + private final File resource_root; private boolean showFolders; private boolean redirectToIndex; - private HashMap cache; + private final HashMap cache; + private static class FileCache{ public long lastModified; public String hash; @@ -64,6 +65,9 @@ public class HttpFilePage implements HttpPage{ * @param file a reference to a root directory or a file. */ public HttpFilePage(File file){ + if (file == null) + throw new IllegalArgumentException("Root path cannot be null.");; + this.resource_root = file; this.showFolders = true; this.redirectToIndex = true; @@ -76,7 +80,7 @@ public class HttpFilePage implements HttpPage{ HttpHeader headers, Map session, Map cookie, - Map request) throws IOException{ + Map request) { try { // Is the root only one file or a folder @@ -95,15 +99,21 @@ public class HttpFilePage implements HttpPage{ } // Show folder contents else if (showFolders) { - out.println("

Directory: " + headers.getRequestURL() + "

"); - out.println("
    "); - for (String f : file.list()) { + out.println(""); + out.println(""); + out.println("

    Directory: " + headers.getRequestURL() + "

    "); + out.println("
    "); + out.println("
      "); + for (String containingFile : file.list()) { String url = headers.getRequestURL(); - out.println("
    • " + f + "
    • "); + out.println("
    • " + containingFile + "
    • "); } - out.println("

    "); + out.println("
"); + out.println("
"); + out.println(""); + out.println(""); } else { throw new SecurityException("User not allowed to view folder: root=" + resource_root.getAbsolutePath()); @@ -133,13 +143,13 @@ public class HttpFilePage implements HttpPage{ private void deliverFileWithCache(HttpHeader headers, File file, HttpPrintStream out) throws IOException { String eTag = getFileHash(file); - out.setHeader("Cache-Control", "max-age=" + MAX_CACHE_AGE_SECONDS); + out.setHeader(HttpHeader.HEADER_CACHE_CONTROL, "max-age=" + MAX_CACHE_AGE_SECONDS); if (eTag != null) { out.setHeader("ETag", "\"" + eTag + "\""); - if (headers.getHeader("If-None-Match") != null && - eTag.equals(StringUtil.trimQuotes(headers.getHeader("If-None-Match")))) { // File has not changed + if (headers.getHeader(HttpHeader.HEADER_IF_NONE_MATCH) != null && + eTag.equals(StringUtil.trimQuotes(headers.getHeader(HttpHeader.HEADER_IF_NONE_MATCH)))) { // File has not changed out.setResponseStatusCode(304); } else { deliverFile(file, out); @@ -150,8 +160,8 @@ public class HttpFilePage implements HttpPage{ String fileExt = FileUtil.getFileExtension(file); if (MimeTypeUtil.getMimeByExtension(fileExt) != null) - out.setHeader("Content-Type", MimeTypeUtil.getMimeByExtension(fileExt).toString()); - out.setHeader("Content-Length", "" + file.length()); + out.setHeader(HttpHeader.HEADER_CONTENT_TYPE, MimeTypeUtil.getMimeByExtension(fileExt).toString()); + out.setHeader(HttpHeader.HEADER_CONTENT_LENGTH, "" + file.length()); out.flush(); InputStream in = new FileInputStream(file); diff --git a/test/zutil/net/http/HttpHeaderParserTest.java b/test/zutil/net/http/HttpHeaderParserTest.java new file mode 100644 index 0000000..b239c84 --- /dev/null +++ b/test/zutil/net/http/HttpHeaderParserTest.java @@ -0,0 +1,80 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Ziver Koc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package zutil.net.http; + +import org.junit.Test; + +import java.io.IOException; + +import static org.junit.Assert.*; + +public class HttpHeaderParserTest { + + @Test + public void firstLineRequest() throws IOException { + HttpHeaderParser parser = new HttpHeaderParser("GET / HTTP/1.1"); + HttpHeader header = parser.read(); + + assertTrue(header.isRequest()); + assertEquals("GET", header.getRequestType()); + assertEquals("/", header.getRequestURL()); + assertEquals("HTTP", header.getProtocol()); + assertEquals(1.1f, header.getProtocolVersion(), 0); + assertEquals(-1, header.getResponseStatusCode()); + assertEquals(null, header.getResponseStatusString()); + + parser = new HttpHeaderParser("DESCRIBE http://example.com RTSP/1.0"); + header = parser.read(); + + assertTrue(header.isRequest()); + assertEquals("DESCRIBE", header.getRequestType()); + assertEquals("http://example.com", header.getRequestURL()); + assertEquals("RTSP", header.getProtocol()); + assertEquals(1.0f, header.getProtocolVersion(), 0f); + } + + @Test + public void firstLineResponse() throws IOException { + HttpHeaderParser parser = new HttpHeaderParser("HTTP/1.0 200 OK"); + HttpHeader header = parser.read(); + + assertTrue(header.isResponse()); + assertEquals("HTTP", header.getProtocol()); + assertEquals(1.0f, header.getProtocolVersion(), 0f); + assertEquals(200, header.getResponseStatusCode(), 0f); + assertEquals("OK", header.getResponseStatusString()); + assertNull(header.getRequestType()); + assertNull(header.getRequestURL()); + + parser = new HttpHeaderParser("RTSP/1.0 404 File not found Special"); + header = parser.read(); + + assertTrue(header.isResponse()); + assertEquals("RTSP", header.getProtocol()); + assertEquals(1.0f, header.getProtocolVersion(), 0f); + assertEquals(404, header.getResponseStatusCode(), 0f); + assertEquals("File not found Special", header.getResponseStatusString()); + } +} \ No newline at end of file diff --git a/test/zutil/net/http/HttpHeaderTest.java b/test/zutil/net/http/HttpHeaderTest.java index 6b779e7..f872280 100644 --- a/test/zutil/net/http/HttpHeaderTest.java +++ b/test/zutil/net/http/HttpHeaderTest.java @@ -44,7 +44,7 @@ public class HttpHeaderTest { @Test public void setProtocol() { HttpHeader header = new HttpHeader(); - assertEquals("HTTP", header.getProtocol()); + assertEquals(null, header.getProtocol()); header.setProtocol("RTSP"); assertEquals("RTSP", header.getProtocol()); @@ -54,7 +54,7 @@ public class HttpHeaderTest { @Test public void setProtocolVersion() { HttpHeader header = new HttpHeader(); - assertEquals(1.0f, header.getProtocolVersion(), 0); + assertEquals(-1, header.getProtocolVersion(), 0); header.setProtocolVersion(1.1f); assertEquals(1.1f, header.getProtocolVersion(), 0); @@ -64,7 +64,7 @@ public class HttpHeaderTest { @Test public void setRequestType() { HttpHeader header = new HttpHeader(); - assertEquals("GET", header.getRequestType()); + assertEquals(null, header.getRequestType()); header.setRequestType("POST"); assertEquals("POST", header.getRequestType()); @@ -74,7 +74,7 @@ public class HttpHeaderTest { @Test public void setResponseStatusCode() { HttpHeader header = new HttpHeader(); - assertEquals(200, header.getResponseStatusCode()); + assertEquals(-1, header.getResponseStatusCode()); header.setResponseStatusCode(400); assertEquals(400, header.getResponseStatusCode()); @@ -83,16 +83,10 @@ public class HttpHeaderTest { @Test public void setRequestURL() { HttpHeader header = new HttpHeader(); - assertEquals("/", header.getRequestURL()); + assertEquals(null, header.getRequestURL()); header.setRequestURL("/page/test"); assertEquals("/page/test", header.getRequestURL()); - - header.setRequestURL(" /page/1test "); - assertEquals("/page/1test", header.getRequestURL()); - - header.setRequestURL("/page//2test "); - assertEquals("/page/2test", header.getRequestURL()); } @Test @@ -150,7 +144,7 @@ public class HttpHeaderTest { @Test public void getResponseStatusString() { HttpHeader header = new HttpHeader(); - assertEquals("OK", header.getResponseStatusString()); + assertEquals(null, header.getResponseStatusString()); header.setResponseStatusCode(400); assertEquals("Bad Request", header.getResponseStatusString()); diff --git a/test/zutil/parser/URLDecoderTest.java b/test/zutil/parser/URLDecoderTest.java index 653530b..191d0ac 100755 --- a/test/zutil/parser/URLDecoderTest.java +++ b/test/zutil/parser/URLDecoderTest.java @@ -32,9 +32,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; -/** - * Created by ezivkoc on 2015-12-11. - */ public class URLDecoderTest { @Test