Added test for http parser and fixed some issues

This commit is contained in:
Ziver Koc 2020-11-10 21:18:36 +01:00
parent c196a7cca0
commit 9934ff1df5
8 changed files with 163 additions and 55 deletions

View file

@ -26,6 +26,7 @@ group = 'se.koc'
version = '1.0.0-SNAPSHOT' version = '1.0.0-SNAPSHOT'
sourceCompatibility = 1.8 sourceCompatibility = 1.8
targetCompatibility = 1.8 targetCompatibility = 1.8
compileJava.options.encoding = 'UTF-8'
java { java {
withSourcesJar() withSourcesJar()

View file

@ -35,31 +35,34 @@ import java.util.TreeMap;
public class HttpHeader { public class HttpHeader {
// Constants // 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_TYPE = "Content-Type";
public static final String HEADER_CONTENT_LENGTH = "Content-Length"; 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_SET_COOKIE = "Set-Cookie";
public static final String HEADER_SERVER = "Server"; public static final String HEADER_SERVER = "Server";
public static final String HEADER_USER_AGENT = "User-Agent"; public static final String HEADER_USER_AGENT = "User-Agent";
public static final String HEADER_IF_NONE_MATCH = "If-None-Match";
// Variables // Variables
private boolean isRequest = true; private boolean isRequest = true;
/** Specifies the protocol that should be used */ /** Specifies the protocol that should be used */
private String protocol = "HTTP"; private String protocol = null;
/** The protocol version specified in the header */ /** 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 */ /** 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 */ /** String containing the target URL */
private String requestUrl = "/"; private String requestUrl = null;
/** Map containing all the properties from the URL */ /** Map containing all the properties from the URL */
private Map<String, String> requestUrlAttributes = new HashMap<>(); private Map<String, String> requestUrlAttributes = new HashMap<>();
/** Status code specified in a HTTP response message */ /** 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 */ /** An Map of all header fields */
private Map<String, String> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); private Map<String, String> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
@ -136,8 +139,16 @@ public class HttpHeader {
public void setResponseStatusCode(int code) { public void setResponseStatusCode(int code) {
this.responseStatusCode = 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 * @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) { public static String getResponseStatusString(int type) {
switch (type) { switch (type) {
case 100: return "Continue"; case 100: return "Continue";

View file

@ -101,28 +101,34 @@ public class HttpHeaderParser {
* @param statusLine the status line String * @param statusLine the status line String
*/ */
private static void parseStatusLine(HttpHeader header, String statusLine) { private static void parseStatusLine(HttpHeader header, String statusLine) {
String[] lineArr = statusLine.split(" ", 3);
if (lineArr.length != 3)
return;
// Server Response // Server Response
if (statusLine.startsWith("HTTP/")) { if (lineArr[0].contains("/")) {
header.setIsRequest(false); header.setIsRequest(false);
header.setProtocolVersion(Float.parseFloat(statusLine.substring(5, 8))); header.setProtocol(lineArr[0].substring(0, lineArr[0].indexOf('/')));
header.setResponseStatusCode(Integer.parseInt(statusLine.substring(9, statusLine.indexOf(' ', 9)))); header.setProtocolVersion(Float.parseFloat(lineArr[0].substring(lineArr[0].indexOf('/')+1)));
header.setResponseStatusCode(Integer.parseInt(lineArr[1]));
header.getResponseStatusString(lineArr[2]);
} }
// Client Request // Client Request
else if (statusLine.contains("HTTP/")) { else {
header.setIsRequest(true); header.setIsRequest(true);
header.setRequestType(statusLine.substring(0, statusLine.indexOf(" ")).trim()); header.setRequestType(lineArr[0]);
header.setProtocolVersion(Float.parseFloat(statusLine.substring(statusLine.lastIndexOf("HTTP/") + 5).trim())); header.setProtocol(lineArr[2].substring(0, lineArr[2].indexOf('/')));
statusLine = (statusLine.substring(header.getRequestType().length() + 1, statusLine.lastIndexOf("HTTP/"))); header.setProtocolVersion(Float.parseFloat(lineArr[2].substring(lineArr[2].indexOf('/')+1)));
String url = lineArr[1];
// parse URL and attributes // parse URL and attributes
int index = statusLine.indexOf('?'); int paramStartIndex = url.indexOf('?');
if (index > -1) { if (paramStartIndex >= 0) {
header.setRequestURL(statusLine.substring(0, index)); url = statusLine.substring(0, paramStartIndex);
statusLine = statusLine.substring(index + 1); parseURLParameters(header.getURLAttributeMap(), statusLine.substring(paramStartIndex + 1));
parseURLParameters(header.getURLAttributeMap(), statusLine);
} else {
header.setRequestURL(statusLine);
} }
header.setRequestURL(url.trim());
} }
} }

View file

@ -78,7 +78,20 @@ public class HttpPrintStream extends OutputStream {
*/ */
public HttpPrintStream(OutputStream out, HttpMessageType type) { public HttpPrintStream(OutputStream out, HttpMessageType type) {
this.out = new PrintStream(out); 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);
}
} }

View file

@ -50,11 +50,12 @@ public class HttpFilePage implements HttpPage{
private static final Logger log = LogUtil.getLogger(); private static final Logger log = LogUtil.getLogger();
private static final int MAX_CACHE_AGE_SECONDS = 120; private static final int MAX_CACHE_AGE_SECONDS = 120;
private File resource_root; private final File resource_root;
private boolean showFolders; private boolean showFolders;
private boolean redirectToIndex; private boolean redirectToIndex;
private HashMap<File,FileCache> cache; private final HashMap<File,FileCache> cache;
private static class FileCache{ private static class FileCache{
public long lastModified; public long lastModified;
public String hash; public String hash;
@ -64,6 +65,9 @@ public class HttpFilePage implements HttpPage{
* @param file a reference to a root directory or a file. * @param file a reference to a root directory or a file.
*/ */
public HttpFilePage(File file){ public HttpFilePage(File file){
if (file == null)
throw new IllegalArgumentException("Root path cannot be null.");;
this.resource_root = file; this.resource_root = file;
this.showFolders = true; this.showFolders = true;
this.redirectToIndex = true; this.redirectToIndex = true;
@ -76,7 +80,7 @@ public class HttpFilePage implements HttpPage{
HttpHeader headers, HttpHeader headers,
Map<String, Object> session, Map<String, Object> session,
Map<String, String> cookie, Map<String, String> cookie,
Map<String, String> request) throws IOException{ Map<String, String> request) {
try { try {
// Is the root only one file or a folder // Is the root only one file or a folder
@ -95,15 +99,21 @@ public class HttpFilePage implements HttpPage{
} }
// Show folder contents // Show folder contents
else if (showFolders) { else if (showFolders) {
out.println("<HTML><BODY><H1>Directory: " + headers.getRequestURL() + "</H1>"); out.println("<html>");
out.println("<HR><UL>"); out.println("<body>");
for (String f : file.list()) { out.println(" <h1>Directory: " + headers.getRequestURL() + "</h1>");
out.println(" <hr>");
out.println(" <ul>");
for (String containingFile : file.list()) {
String url = headers.getRequestURL(); String url = headers.getRequestURL();
out.println("<LI><A href='" + out.println(" <li><a href='" +
url + (url.charAt(url.length()-1)=='/'?"":"/")+ f url + (url.endsWith("/") ? "" : "/") + containingFile
+"'>" + f + "</A></LI>"); +"'>" + containingFile + "</a></li>");
} }
out.println("</UL><HR></BODY></HTML>"); out.println(" </ul>");
out.println(" <hr>");
out.println("</body>");
out.println("</html>");
} }
else { else {
throw new SecurityException("User not allowed to view folder: root=" + resource_root.getAbsolutePath()); 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 { private void deliverFileWithCache(HttpHeader headers, File file, HttpPrintStream out) throws IOException {
String eTag = getFileHash(file); 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) { if (eTag != null) {
out.setHeader("ETag", "\"" + eTag + "\""); out.setHeader("ETag", "\"" + eTag + "\"");
if (headers.getHeader("If-None-Match") != null && if (headers.getHeader(HttpHeader.HEADER_IF_NONE_MATCH) != null &&
eTag.equals(StringUtil.trimQuotes(headers.getHeader("If-None-Match")))) { // File has not changed eTag.equals(StringUtil.trimQuotes(headers.getHeader(HttpHeader.HEADER_IF_NONE_MATCH)))) { // File has not changed
out.setResponseStatusCode(304); out.setResponseStatusCode(304);
} else { } else {
deliverFile(file, out); deliverFile(file, out);
@ -150,8 +160,8 @@ public class HttpFilePage implements HttpPage{
String fileExt = FileUtil.getFileExtension(file); String fileExt = FileUtil.getFileExtension(file);
if (MimeTypeUtil.getMimeByExtension(fileExt) != null) if (MimeTypeUtil.getMimeByExtension(fileExt) != null)
out.setHeader("Content-Type", MimeTypeUtil.getMimeByExtension(fileExt).toString()); out.setHeader(HttpHeader.HEADER_CONTENT_TYPE, MimeTypeUtil.getMimeByExtension(fileExt).toString());
out.setHeader("Content-Length", "" + file.length()); out.setHeader(HttpHeader.HEADER_CONTENT_LENGTH, "" + file.length());
out.flush(); out.flush();
InputStream in = new FileInputStream(file); InputStream in = new FileInputStream(file);

View file

@ -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());
}
}

View file

@ -44,7 +44,7 @@ public class HttpHeaderTest {
@Test @Test
public void setProtocol() { public void setProtocol() {
HttpHeader header = new HttpHeader(); HttpHeader header = new HttpHeader();
assertEquals("HTTP", header.getProtocol()); assertEquals(null, header.getProtocol());
header.setProtocol("RTSP"); header.setProtocol("RTSP");
assertEquals("RTSP", header.getProtocol()); assertEquals("RTSP", header.getProtocol());
@ -54,7 +54,7 @@ public class HttpHeaderTest {
@Test @Test
public void setProtocolVersion() { public void setProtocolVersion() {
HttpHeader header = new HttpHeader(); HttpHeader header = new HttpHeader();
assertEquals(1.0f, header.getProtocolVersion(), 0); assertEquals(-1, header.getProtocolVersion(), 0);
header.setProtocolVersion(1.1f); header.setProtocolVersion(1.1f);
assertEquals(1.1f, header.getProtocolVersion(), 0); assertEquals(1.1f, header.getProtocolVersion(), 0);
@ -64,7 +64,7 @@ public class HttpHeaderTest {
@Test @Test
public void setRequestType() { public void setRequestType() {
HttpHeader header = new HttpHeader(); HttpHeader header = new HttpHeader();
assertEquals("GET", header.getRequestType()); assertEquals(null, header.getRequestType());
header.setRequestType("POST"); header.setRequestType("POST");
assertEquals("POST", header.getRequestType()); assertEquals("POST", header.getRequestType());
@ -74,7 +74,7 @@ public class HttpHeaderTest {
@Test @Test
public void setResponseStatusCode() { public void setResponseStatusCode() {
HttpHeader header = new HttpHeader(); HttpHeader header = new HttpHeader();
assertEquals(200, header.getResponseStatusCode()); assertEquals(-1, header.getResponseStatusCode());
header.setResponseStatusCode(400); header.setResponseStatusCode(400);
assertEquals(400, header.getResponseStatusCode()); assertEquals(400, header.getResponseStatusCode());
@ -83,16 +83,10 @@ public class HttpHeaderTest {
@Test @Test
public void setRequestURL() { public void setRequestURL() {
HttpHeader header = new HttpHeader(); HttpHeader header = new HttpHeader();
assertEquals("/", header.getRequestURL()); assertEquals(null, header.getRequestURL());
header.setRequestURL("/page/test"); header.setRequestURL("/page/test");
assertEquals("/page/test", header.getRequestURL()); assertEquals("/page/test", header.getRequestURL());
header.setRequestURL(" /page/1test ");
assertEquals("/page/1test", header.getRequestURL());
header.setRequestURL("/page//2test ");
assertEquals("/page/2test", header.getRequestURL());
} }
@Test @Test
@ -150,7 +144,7 @@ public class HttpHeaderTest {
@Test @Test
public void getResponseStatusString() { public void getResponseStatusString() {
HttpHeader header = new HttpHeader(); HttpHeader header = new HttpHeader();
assertEquals("OK", header.getResponseStatusString()); assertEquals(null, header.getResponseStatusString());
header.setResponseStatusCode(400); header.setResponseStatusCode(400);
assertEquals("Bad Request", header.getResponseStatusString()); assertEquals("Bad Request", header.getResponseStatusString());

View file

@ -32,9 +32,6 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
/**
* Created by ezivkoc on 2015-12-11.
*/
public class URLDecoderTest { public class URLDecoderTest {
@Test @Test