Skip to content

Commit

Permalink
Parser issue, public files and dirs
Browse files Browse the repository at this point in the history
  • Loading branch information
Voxxin committed Jun 6, 2024
1 parent 07bde5e commit 8457fde
Show file tree
Hide file tree
Showing 12 changed files with 304 additions and 46 deletions.
3 changes: 2 additions & 1 deletion src/main/java/com/github/voxxin/web/AbstractRoute.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ public class AbstractRoute {
public final String route;

public AbstractRoute(String route) {
this.route = route;
route = route.replaceAll("^/+", "");
this.route = "/"+route;
}

/**
Expand Down
39 changes: 39 additions & 0 deletions src/main/java/com/github/voxxin/web/FilePathRoute.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.github.voxxin.web;

import com.github.voxxin.web.request.FormattedRequest;
import com.github.voxxin.web.request.FormattedResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;

import java.io.*;

class FilePathRoute extends AbstractRoute {
private final Logger LOGGER = LoggerFactory.getLogger(WebServer.class);
private final File file;

public FilePathRoute(File file, String route) {
super(route + file.getName());
this.file = file;
}

@Override
public OutputStream handleRequests(FormattedRequest request, OutputStream outputStream) throws IOException {
try (FileInputStream fileInputStream = new FileInputStream(file)) {
outputStream.write(new FormattedResponse()
.contentType(Files.probeContentType(file.toPath()))
.content(fileInputStream.readAllBytes())
.statusCode(200)
.statusMessage("OK").build());
} catch (IOException e) {
LOGGER.error("Error occurred while handling file request: {}", e.getMessage());
}

return outputStream;
}
}

110 changes: 110 additions & 0 deletions src/main/java/com/github/voxxin/web/WebServer.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
package com.github.voxxin.web;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
Expand Down Expand Up @@ -123,4 +130,107 @@ public void changePort(int newPort) {
public void errorPage(AbstractRoute errorRoute) {
this.errorRoute = errorRoute;
}

/**
* Adds a directory path to be served publicly.
* @param dirPath The path of the directory to be served.
* @param publicPath The public path where the directory should be accessible.
* @param pathType The type of path, either INTERNAL or EXTERNAL.
* @param directoryPosition The position of the directory relative to the publicPath.
*/
public void addPublicDirPath(String dirPath, String publicPath, PathType pathType, DirectoryPosition directoryPosition) {
switch (pathType) {
case INTERNAL:
case EXTERNAL:
processDirPath(dirPath, publicPath, pathType.toString(), directoryPosition);
break;
default:
LOGGER.error("Invalid path type: " + pathType);
}
}

/**
* Processes a directory path recursively.
* @param dirPath The path of the directory to process.
* @param publicPath The public path corresponding to the directory.
* @param type The type of path, either "INTERNAL" or "EXTERNAL".
* @param directoryPosition The position of the directory relative to the publicPath.
*/
private void processDirPath(String dirPath, String publicPath, String type, DirectoryPosition directoryPosition) {
try {
Path directory = type.equals("INTERNAL") ? Paths.get(WebServer.class.getClassLoader().getResource(dirPath).toURI()) : Paths.get(dirPath);
if (!Files.exists(directory)) {
LOGGER.error(type + " directory not found: " + dirPath);
return;
}

try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory)) {
for (Path path : stream) {
if (Files.isDirectory(path)) {
String newPath = dirPath + "/" + path.getFileName() + "/";
String newPublicPath = "";

if (directoryPosition == DirectoryPosition.CURRENT) newPublicPath = publicPath;
else if (directoryPosition == DirectoryPosition.SUBDIRECTORY)
newPublicPath = publicPath + path.getFileName() + "/";
else continue;

processDirPath(newPath, newPublicPath, type, directoryPosition);
} else if (Files.isRegularFile(path)) {
processPublicFile(path.toFile(), publicPath);
}
}
} catch (IOException e) {
LOGGER.error("Error reading files in " + type.toLowerCase() + " directory: " + dirPath, e);
}
} catch (URISyntaxException e) {
LOGGER.error("Invalid directory path: " + dirPath, e);
}
}


/**
* Processes a public file and adds it to the routes.
* @param file The public file to be processed.
* @param publicPath The public path corresponding to the file.
*/
private void processPublicFile(File file, String publicPath) {
FilePathRoute filePathRoute = new FilePathRoute(file, publicPath);
if (!routes.contains(filePathRoute)) routes.add(filePathRoute);
}

/**
* Enum representing the type of path.
*/
public enum PathType {
/**
* Represents an internal path, typically referring to resources within the application.
*/
INTERNAL,

/**
* Represents an external path, typically referring to resources outside the application.
*/
EXTERNAL;
}

/**
* Enum representing the position of the directory relative to the publicPath.
*/
public enum DirectoryPosition {
/**
* Indicates that the directory should not be included as a subsidiary div.
*/
NONE,

/**
* Indicates that the directory should be included as a subsidiary div at the same level as the publicPath.
*/
CURRENT,

/**
* Indicates that the directory should be included as a subsidiary div within the publicPath.
*/
SUBDIRECTORY;
}
}
9 changes: 7 additions & 2 deletions src/main/java/com/github/voxxin/web/element/HtmlElement.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
import java.util.List;

public class HtmlElement {
private final String tagName;
private final List<String> attributes;
private String tagName;
private List<String> attributes;
private List<HtmlElement> subElements;
private String subElement;

Expand Down Expand Up @@ -133,6 +133,11 @@ public String getTagName() {
return this.tagName;
}

public void setTagName(String tagName) {
this.tagName = tagName;
}


/**
* Get the list of attributes of this HtmlElement.
*
Expand Down
115 changes: 89 additions & 26 deletions src/main/java/com/github/voxxin/web/element/HtmlParser.java
Original file line number Diff line number Diff line change
@@ -1,59 +1,104 @@
package com.github.voxxin.web.element;

import java.util.*;
import java.util.regex.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class HtmlParser {

/**
* Parse HTML string into a list of HtmlElements.
* Parses the given HTML string and returns a list of HtmlElement objects representing the parsed content.
*
* @param htmlString The HTML string to parse.
* @return The list of HtmlElements parsed from the HTML string.
* @param htmlString the HTML string to be parsed
* @return a list of HtmlElement objects representing the parsed content
*/
public static List<HtmlElement> parseHtmlString(String htmlString) {
return parseContent(htmlString);
}

/**
* Parses the content of an HTML string recursively.
* Parses the given HTML string and returns a list of HtmlElement objects representing the parsed content.
*
* @param content The HTML content to parse.
* @return The list of HtmlElements parsed from the HTML content.
* @param html the HTML string to be parsed
* @return a list of HtmlElement objects representing the parsed content
*/
private static List<HtmlElement> parseContent(String content) {
private static List<HtmlElement> parseContent(String html) {
List<Tag> tags = new ArrayList<>();
List<HtmlElement> elements = new ArrayList<>();

Pattern pattern = Pattern.compile("<(\\w+)(.*?)>(.*?)</\\1>|<(\\w+)(.*?)>", Pattern.DOTALL);
Matcher matcher = pattern.matcher(content);
int end = html.length() - 1;
int layer = 0;

while (end >= 0) {
int closingIndex = html.lastIndexOf('>', end);
int openingIndex = html.lastIndexOf('<', closingIndex);

if (openingIndex != -1 && closingIndex != -1) {
String tagString = html.substring(openingIndex + 1, closingIndex);
boolean isOpeningTag = !tagString.startsWith("/");
String tagName = isOpeningTag ? tagString.split("\\s+")[0] : tagString.substring(1);
String attributes = isOpeningTag ? tagString.substring(tagName.length()).trim() : "";
int rareLayer = html.contains("</" + tagName + ">") ? layer : layer+1;

tags.add(new Tag(tagName, attributes, isOpeningTag, openingIndex, closingIndex + 1, rareLayer));

if (isOpeningTag && html.contains("</" + tagName + ">")) {
layer--;
} else if (!isOpeningTag) {
layer++;
}
end = openingIndex - 1;
} else {
break;
}
}

while (matcher.find()) {
String tagName = matcher.group(1) != null ? matcher.group(1) : matcher.group(4);
String attributes = matcher.group(2) != null ? matcher.group(2) : matcher.group(5);
String innerContent = matcher.group(3);
Collections.reverse(tags);

if (tagName != null) {
HtmlElement element = new HtmlElement(tagName, parseAttributes(attributes), innerContent != null ? innerContent : "");
for (int i = 0; i < tags.size(); i++) {
Tag tag = tags.get(i);

if (innerContent != null) {
List<HtmlElement> innerElements = parseContent(innerContent);
if (tag.layer != 1 || !tag.isOpeningTag) continue;

if (!innerElements.isEmpty()) element.addSubElements(innerElements);
else element.setSubElement(innerContent);
boolean hasClosingTag = false;
int sameTagRepeats = 0;
for (int j = i + 1; j < tags.size(); j++) {
Tag closingTag = tags.get(j);
if (closingTag.isOpeningTag && closingTag.tagName.equals(tag.tagName)) {
sameTagRepeats++;
continue;
}

elements.add(element);
if (!closingTag.isOpeningTag && closingTag.tagName.equals(tag.tagName)) {
if (sameTagRepeats > 0) {
sameTagRepeats--;
continue;
}

String content = html.substring(tag.closingIndex, closingTag.openingIndex);

List<HtmlElement> subElements = parseContent(content);
if (subElements.isEmpty()) elements.add(new HtmlElement(tag.tagName, parseAttributes(tag.attributes), content));
else elements.add(new HtmlElement(tag.tagName, parseAttributes(tag.attributes), subElements));

hasClosingTag = true;
break;
}
}

if (!hasClosingTag) {
elements.add(new HtmlElement(tag.tagName, parseAttributes(tag.attributes), (String) null));
}
}


return elements;
}

/**
* Parse HTML attributes into a list of strings.
* Parses the given attributes string and returns a list of attributes.
*
* @param attributesString The string containing HTML attributes.
* @return The list of parsed attributes.
* @param attributesString the string containing the attributes
* @return a list of attributes
*/
private static List<String> parseAttributes(String attributesString) {
List<String> attributes = new ArrayList<>();
Expand All @@ -68,4 +113,22 @@ private static List<String> parseAttributes(String attributesString) {
}
return attributes;
}
}

static class Tag {
String tagName;
String attributes;
boolean isOpeningTag;
int openingIndex;
int closingIndex;
int layer;

public Tag(String tagName, String attributes, boolean isOpeningTag, int openingIndex, int closingIndex, int layer) {
this.tagName = tagName;
this.attributes = attributes;
this.isOpeningTag = isOpeningTag;
this.openingIndex = openingIndex;
this.closingIndex = closingIndex;
this.layer = layer;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,22 @@

public class FormattedRequest {

private final HashMap<String, String> headers;
private final String body;
private final String method;
private HashMap<String, String> headers;
private String body;
private String method;
private String path;
private final String pathParameters;
private final HashMap<String, String> query;
private final String httpVersion;
private String pathParameters;
private HashMap<String, String> query;
private String httpVersion;

/**
* Constructor for FormattedRequest.
*
* @param inputHeaders The list of input headers.
*/
public FormattedRequest(List<String> inputHeaders) {
if (!inputHeaders.get(0).contains("HTTP/")) return;

this.body = (inputHeaders.get(0).split(" ").length > 1 && inputHeaders.get(0).split(" ")[2].contains("HTTP/")) ? null : inputHeaders.remove(0);

String[] mainMethods = inputHeaders.remove(0).split(" ");
Expand Down
Loading

0 comments on commit 8457fde

Please sign in to comment.