Bug fixes
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>org.openautonomousconnection</groupId>
|
<groupId>org.openautonomousconnection</groupId>
|
||||||
<artifactId>WebServer</artifactId>
|
<artifactId>WebServer</artifactId>
|
||||||
<version>1.0.1-BETA.0.1</version>
|
<version>1.0.1-BETA.0.3</version>
|
||||||
<description>The default DNS-Server</description>
|
<description>The default DNS-Server</description>
|
||||||
<url>https://open-autonomous-connection.org/</url>
|
<url>https://open-autonomous-connection.org/</url>
|
||||||
<issueManagement>
|
<issueManagement>
|
||||||
|
|||||||
4
pom.xml
4
pom.xml
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<groupId>org.openautonomousconnection</groupId>
|
<groupId>org.openautonomousconnection</groupId>
|
||||||
<artifactId>WebServer</artifactId>
|
<artifactId>WebServer</artifactId>
|
||||||
<version>1.0.1-BETA.0.1</version>
|
<version>1.0.1-BETA.0.3</version>
|
||||||
<organization>
|
<organization>
|
||||||
<name>Open Autonomous Connection</name>
|
<name>Open Autonomous Connection</name>
|
||||||
<url>https://open-autonomous-connection.org/</url>
|
<url>https://open-autonomous-connection.org/</url>
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.openautonomousconnection</groupId>
|
<groupId>org.openautonomousconnection</groupId>
|
||||||
<artifactId>Protocol</artifactId>
|
<artifactId>Protocol</artifactId>
|
||||||
<version>1.0.1-BETA.0.3</version>
|
<version>1.0.1-BETA.0.5</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ public final class ContentTypeResolver {
|
|||||||
return isFile(name, "image");
|
return isFile(name, "image");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isAudio(String name) {
|
public static boolean isAudioFile(String name) {
|
||||||
return isFile(name, "audio");
|
return isFile(name, "audio");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ public class Listener extends EventListener {
|
|||||||
public void onCommandNotFound(CommandNotFoundEvent event) {
|
public void onCommandNotFound(CommandNotFoundEvent event) {
|
||||||
StringBuilder argsBuilder = new StringBuilder();
|
StringBuilder argsBuilder = new StringBuilder();
|
||||||
for (String arg : event.getArgs()) argsBuilder.append(arg).append(" ");
|
for (String arg : event.getArgs()) argsBuilder.append(arg).append(" ");
|
||||||
Main.getProtocolBridge().getLogger().error("Command '" + event.getName() + argsBuilder.toString() + "' not found!");
|
Main.getProtocolBridge().getProtocolValues().logger.error("Command '" + event.getName() + argsBuilder.toString() + "' not found!");
|
||||||
}
|
}
|
||||||
|
|
||||||
@dev.unlegitdqrk.unlegitlibrary.event.Listener
|
@dev.unlegitdqrk.unlegitlibrary.event.Listener
|
||||||
public void onMissingCommandPermission(CommandExecutorMissingPermissionEvent event) {
|
public void onMissingCommandPermission(CommandExecutorMissingPermissionEvent event) {
|
||||||
Main.getProtocolBridge().getLogger().error("You do not have enough permissions to execute this command!");
|
Main.getProtocolBridge().getProtocolValues().logger.error("You do not have enough permissions to execute this command!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ public class Main {
|
|||||||
values = new ProtocolValues();
|
values = new ProtocolValues();
|
||||||
values.packetHandler = new PacketHandler();
|
values.packetHandler = new PacketHandler();
|
||||||
values.eventManager = new EventManager();
|
values.eventManager = new EventManager();
|
||||||
|
values.logger = new Logger(new File("logs"), false, true);
|
||||||
|
values.protocolVersion = ProtocolVersion.PV_1_0_1_BETA;
|
||||||
|
values.addonLoader = new AddonLoader(values.eventManager, values.logger);
|
||||||
|
|
||||||
if (!Files.exists(new File("config.properties").toPath()))
|
if (!Files.exists(new File("config.properties").toPath()))
|
||||||
Files.createFile(new File("config.properties").toPath());
|
Files.createFile(new File("config.properties").toPath());
|
||||||
@@ -76,15 +79,12 @@ public class Main {
|
|||||||
int sessionExpire = config.getInt("sessionexpiremin");
|
int sessionExpire = config.getInt("sessionexpiremin");
|
||||||
int maxUpload = config.getInt("maxuploadmb");
|
int maxUpload = config.getInt("maxuploadmb");
|
||||||
|
|
||||||
ClientAuthMode authMode = ClientAuthMode.valueOf(config.getString("clientauth").toUpperCase());
|
values.authMode = ClientAuthMode.valueOf(config.getString("clientauth").toUpperCase());
|
||||||
values.authMode = authMode;
|
|
||||||
Logger logger = new Logger(new File("logs"), false, true);
|
|
||||||
values.eventManager.registerListener(Listener.class);
|
values.eventManager.registerListener(Listener.class);
|
||||||
|
|
||||||
protocolBridge = new ProtocolBridge(new WebServer(
|
protocolBridge = new ProtocolBridge(new WebServer(
|
||||||
new File("auth.ini"), new File("rules.ini"),
|
new File("auth.ini"), new File("rules.ini"),
|
||||||
sessionExpire, maxUpload),
|
sessionExpire, maxUpload), values);
|
||||||
values, ProtocolVersion.PV_1_0_1_BETA, logger, new AddonLoader(values.eventManager, logger));
|
|
||||||
|
|
||||||
protocolBridge.getProtocolServer().getNetwork().start(tcpPort, udpPort);
|
protocolBridge.getProtocolServer().getNetwork().start(tcpPort, udpPort);
|
||||||
|
|
||||||
|
|||||||
@@ -20,12 +20,13 @@ import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeade
|
|||||||
import org.openautonomousconnection.webserver.api.SessionContext;
|
import org.openautonomousconnection.webserver.api.SessionContext;
|
||||||
import org.openautonomousconnection.webserver.runtime.JavaPageDispatcher;
|
import org.openautonomousconnection.webserver.runtime.JavaPageDispatcher;
|
||||||
import org.openautonomousconnection.webserver.utils.WebHasher;
|
import org.openautonomousconnection.webserver.utils.WebHasher;
|
||||||
|
import org.openautonomousconnection.webserver.utils.WebUrlUtil;
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.URL;
|
import java.net.URI;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@@ -33,27 +34,16 @@ import java.util.LinkedHashMap;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OAC WebServer implementation for WEB v1.0.1-BETA.
|
* OAC WebServer implementation for WEB v1.0.1-BETA.
|
||||||
*
|
*
|
||||||
* <p>Implements the three v1.0.1 entry points:</p>
|
* <p>Important: Server-side parsing MUST NOT use {@link java.net.URL} for "web://" because
|
||||||
* <ul>
|
* the server JVM does not need (and typically does not have) a URLStreamHandler installed.</p>
|
||||||
* <li>Navigation: {@link #onNavigateRequest(CustomConnectedClient, WebNavigateRequestPacket)}</li>
|
|
||||||
* <li>Resource: {@link #onResourceRequest(CustomConnectedClient, WebResourceRequestPacket)}</li>
|
|
||||||
* <li>Document apply: {@link #onDocumentApplyRequest(CustomConnectedClient, WebDocumentApplyRequestPacket)}</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
*
|
||||||
* <p>Supports:</p>
|
* <p>Therefore, request URLs are parsed using {@link java.net.URI} which accepts unknown schemes.</p>
|
||||||
* <ul>
|
|
||||||
* <li>Static file serving</li>
|
|
||||||
* <li>Java-page dispatch (server-side pages)</li>
|
|
||||||
* <li>Range requests ("bytes=start-end")</li>
|
|
||||||
* <li>Streaming for large responses (best-effort over UDP for video chunks)</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
*/
|
||||||
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.WEB)
|
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.WEB)
|
||||||
public final class WebServer extends ProtocolWebServer {
|
public final class WebServer extends ProtocolWebServer {
|
||||||
@@ -95,9 +85,10 @@ public final class WebServer extends ProtocolWebServer {
|
|||||||
Objects.requireNonNull(request, "request");
|
Objects.requireNonNull(request, "request");
|
||||||
|
|
||||||
final WebPacketHeader in = request.getHeader();
|
final WebPacketHeader in = request.getHeader();
|
||||||
final URL url;
|
|
||||||
|
final ParsedRequestUrl parsed;
|
||||||
try {
|
try {
|
||||||
url = new URL(request.getUrl());
|
parsed = ParsedRequestUrl.parse(request.getUrl());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return new WebNavigateAckPacket(
|
return new WebNavigateAckPacket(
|
||||||
mirrorHeader(in, WebPacketFlags.NAVIGATION),
|
mirrorHeader(in, WebPacketFlags.NAVIGATION),
|
||||||
@@ -106,7 +97,7 @@ public final class WebServer extends ProtocolWebServer {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String path = normalizePath(url.getPath());
|
String path = normalizePath(parsed.path());
|
||||||
if (RuleManager.isDenied(path)) {
|
if (RuleManager.isDenied(path)) {
|
||||||
return new WebNavigateAckPacket(
|
return new WebNavigateAckPacket(
|
||||||
mirrorHeader(in, WebPacketFlags.NAVIGATION),
|
mirrorHeader(in, WebPacketFlags.NAVIGATION),
|
||||||
@@ -147,14 +138,15 @@ public final class WebServer extends ProtocolWebServer {
|
|||||||
Objects.requireNonNull(request, "request");
|
Objects.requireNonNull(request, "request");
|
||||||
|
|
||||||
final WebPacketHeader in = request.getHeader();
|
final WebPacketHeader in = request.getHeader();
|
||||||
final URL url;
|
|
||||||
|
final ParsedRequestUrl parsed;
|
||||||
try {
|
try {
|
||||||
url = new URL(request.getUrl());
|
parsed = ParsedRequestUrl.parse(request.getUrl());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return error(in, 400, "Invalid URL: " + e.getMessage());
|
return error(in, 400, "Invalid URL: " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
String path = normalizePath(url.getPath());
|
String path = normalizePath(parsed.path());
|
||||||
|
|
||||||
if (RuleManager.isDenied(path) || !RuleManager.isAllowed(path)) {
|
if (RuleManager.isDenied(path) || !RuleManager.isAllowed(path)) {
|
||||||
return error(in, 403, "Forbidden");
|
return error(in, 403, "Forbidden");
|
||||||
@@ -172,7 +164,7 @@ public final class WebServer extends ProtocolWebServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
WebResourceResponsePacket javaResp = dispatchJavaPageAsResource(client, request, url);
|
WebResourceResponsePacket javaResp = dispatchJavaPageAsResource(client, request);
|
||||||
if (javaResp != null) {
|
if (javaResp != null) {
|
||||||
return javaResp;
|
return javaResp;
|
||||||
}
|
}
|
||||||
@@ -181,7 +173,7 @@ public final class WebServer extends ProtocolWebServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return serveStaticFile(client, request, url);
|
return serveStaticFile(client, request, parsed);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return error(in, 500, "Internal error: " + e.getClass().getSimpleName() + ": " + e.getMessage());
|
return error(in, 500, "Internal error: " + e.getClass().getSimpleName() + ": " + e.getMessage());
|
||||||
}
|
}
|
||||||
@@ -200,14 +192,14 @@ public final class WebServer extends ProtocolWebServer {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private WebResourceResponsePacket dispatchJavaPageAsResource(CustomConnectedClient client, WebResourceRequestPacket req, URL url) throws Exception {
|
private WebResourceResponsePacket dispatchJavaPageAsResource(CustomConnectedClient client, WebResourceRequestPacket req) throws Exception {
|
||||||
return JavaPageDispatcher.dispatch(client, this, req);
|
return JavaPageDispatcher.dispatch(client, this, req);
|
||||||
}
|
}
|
||||||
|
|
||||||
private WebResourceResponsePacket serveStaticFile(CustomConnectedClient client, WebResourceRequestPacket request, URL url) throws Exception {
|
private WebResourceResponsePacket serveStaticFile(CustomConnectedClient client, WebResourceRequestPacket request, ParsedRequestUrl parsed) throws Exception {
|
||||||
final WebPacketHeader in = request.getHeader();
|
final WebPacketHeader in = request.getHeader();
|
||||||
|
|
||||||
String path = normalizePath(url.getPath());
|
String path = normalizePath(parsed.path());
|
||||||
if (path.startsWith("/")) path = path.substring(1);
|
if (path.startsWith("/")) path = path.substring(1);
|
||||||
if (path.isEmpty()) path = "index.html";
|
if (path.isEmpty()) path = "index.html";
|
||||||
|
|
||||||
@@ -412,9 +404,7 @@ public final class WebServer extends ProtocolWebServer {
|
|||||||
private static boolean shouldStream(String fileName, String contentType, long size, boolean wantsRange) {
|
private static boolean shouldStream(String fileName, String contentType, long size, boolean wantsRange) {
|
||||||
if (size >= STREAM_THRESHOLD) return true;
|
if (size >= STREAM_THRESHOLD) return true;
|
||||||
|
|
||||||
// Many video/audio players use Range for progressive playback.
|
if (wantsRange && (ContentTypeResolver.isVideoFile(fileName) || ContentTypeResolver.isAudioFile(fileName))) {
|
||||||
// If Range is present and it's a video/audio, prefer stream to avoid big memory spikes.
|
|
||||||
if (wantsRange && (ContentTypeResolver.isVideoFile(fileName) || ContentTypeResolver.isAudio(fileName))) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -422,14 +412,11 @@ public final class WebServer extends ProtocolWebServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static TransportProtocol chooseChunkTransport(String fileName) {
|
private static TransportProtocol chooseChunkTransport(String fileName) {
|
||||||
// best-effort: chunks via UDP for video, TCP otherwise
|
|
||||||
return ContentTypeResolver.isVideoFile(fileName) ? TransportProtocol.UDP : TransportProtocol.TCP;
|
return ContentTypeResolver.isVideoFile(fileName) ? TransportProtocol.UDP : TransportProtocol.TCP;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String normalizePath(String p) {
|
private static String normalizePath(String p) {
|
||||||
if (p == null || p.isBlank()) return "/";
|
return WebUrlUtil.normalizeRequestPath(p);
|
||||||
String t = p.trim();
|
|
||||||
return t.isEmpty() ? "/" : t;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String contentTypeOrDefault(String ct) {
|
private static String contentTypeOrDefault(String ct) {
|
||||||
@@ -443,7 +430,6 @@ public final class WebServer extends ProtocolWebServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static Map<String, String> safeHeaders(WebNavigateRequestPacket request) {
|
private static Map<String, String> safeHeaders(WebNavigateRequestPacket request) {
|
||||||
// Navigate packet currently has no headers in your definition; keep empty.
|
|
||||||
return Map.of();
|
return Map.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -475,6 +461,62 @@ public final class WebServer extends ProtocolWebServer {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Small helper that parses an incoming request URL string without requiring a URLStreamHandler.
|
||||||
|
*
|
||||||
|
* <p>Uses {@link URI} because it supports unknown schemes such as "web".</p>
|
||||||
|
*/
|
||||||
|
private record ParsedRequestUrl(String raw, String scheme, String host, String path, String query) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses an input URL string into components.
|
||||||
|
*
|
||||||
|
* @param raw raw URL string from packet
|
||||||
|
* @return parsed representation
|
||||||
|
*/
|
||||||
|
public static ParsedRequestUrl parse(String raw) {
|
||||||
|
if (raw == null || raw.isBlank()) {
|
||||||
|
throw new IllegalArgumentException("URL is empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
final URI uri;
|
||||||
|
try {
|
||||||
|
uri = URI.create(raw.trim());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalArgumentException(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
String scheme = uri.getScheme();
|
||||||
|
if (scheme == null || scheme.isBlank()) {
|
||||||
|
throw new IllegalArgumentException("Missing scheme");
|
||||||
|
}
|
||||||
|
|
||||||
|
// For hierarchical URIs like "web://host/path", getHost() is expected to work.
|
||||||
|
String host = uri.getHost();
|
||||||
|
|
||||||
|
// Fallback: some inputs might be like "web:host/path" or malformed variants.
|
||||||
|
if ((host == null || host.isBlank()) && uri.getRawSchemeSpecificPart() != null) {
|
||||||
|
// best-effort: extract after "//"
|
||||||
|
String ssp = uri.getRawSchemeSpecificPart();
|
||||||
|
int idx = ssp.indexOf("//");
|
||||||
|
if (idx >= 0) {
|
||||||
|
String rest = ssp.substring(idx + 2);
|
||||||
|
int slash = rest.indexOf('/');
|
||||||
|
host = (slash >= 0) ? rest.substring(0, slash) : rest;
|
||||||
|
if (host != null) host = host.trim();
|
||||||
|
if (host != null && host.isEmpty()) host = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String path = uri.getPath();
|
||||||
|
if (path == null || path.isBlank()) path = "/";
|
||||||
|
|
||||||
|
String query = uri.getQuery();
|
||||||
|
|
||||||
|
return new ParsedRequestUrl(raw.trim(), scheme, host, path, query);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Minimal byte-range parser supporting "bytes=start-end", "bytes=start-", and "bytes=-suffixLen".
|
* Minimal byte-range parser supporting "bytes=start-end", "bytes=start-", and "bytes=-suffixLen".
|
||||||
*/
|
*/
|
||||||
@@ -489,20 +531,12 @@ public final class WebServer extends ProtocolWebServer {
|
|||||||
this.valid = valid;
|
this.valid = valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses a Range header for a resource of the given size.
|
|
||||||
*
|
|
||||||
* @param header Range header value
|
|
||||||
* @param size total resource size
|
|
||||||
* @return range spec or null if header missing/blank
|
|
||||||
*/
|
|
||||||
public static RangeSpec parse(String header, long size) {
|
public static RangeSpec parse(String header, long size) {
|
||||||
if (header == null || header.isBlank()) return null;
|
if (header == null || header.isBlank()) return null;
|
||||||
String h = header.trim().toLowerCase(Locale.ROOT);
|
String h = header.trim().toLowerCase(Locale.ROOT);
|
||||||
if (!h.startsWith("bytes=")) return null;
|
if (!h.startsWith("bytes=")) return null;
|
||||||
|
|
||||||
String v = h.substring("bytes=".length()).trim();
|
String v = h.substring("bytes=".length()).trim();
|
||||||
// Only single-range supported
|
|
||||||
int comma = v.indexOf(',');
|
int comma = v.indexOf(',');
|
||||||
if (comma >= 0) v = v.substring(0, comma).trim();
|
if (comma >= 0) v = v.substring(0, comma).trim();
|
||||||
|
|
||||||
@@ -522,7 +556,6 @@ public final class WebServer extends ProtocolWebServer {
|
|||||||
return new RangeSpec(start, end, true);
|
return new RangeSpec(start, end, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// suffix range: "-N" (last N bytes)
|
|
||||||
if (!b.isEmpty()) {
|
if (!b.isEmpty()) {
|
||||||
long suffix = Long.parseLong(b);
|
long suffix = Long.parseLong(b);
|
||||||
if (suffix <= 0) return new RangeSpec(0, 0, false);
|
if (suffix <= 0) return new RangeSpec(0, 0, false);
|
||||||
@@ -538,40 +571,24 @@ public final class WebServer extends ProtocolWebServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return true if range is usable
|
|
||||||
*/
|
|
||||||
public boolean isValid() {
|
public boolean isValid() {
|
||||||
return valid;
|
return valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return inclusive start
|
|
||||||
*/
|
|
||||||
public long startInclusive() {
|
public long startInclusive() {
|
||||||
return startInclusive;
|
return startInclusive;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return inclusive end
|
|
||||||
*/
|
|
||||||
public long endInclusive() {
|
public long endInclusive() {
|
||||||
return endInclusive;
|
return endInclusive;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return byte length represented by this range
|
|
||||||
*/
|
|
||||||
public long length() {
|
public long length() {
|
||||||
return (endInclusive >= startInclusive) ? (endInclusive - startInclusive + 1) : 0L;
|
return (endInclusive >= startInclusive) ? (endInclusive - startInclusive + 1) : 0L;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param total total size
|
|
||||||
* @return Content-Range header value
|
|
||||||
*/
|
|
||||||
public String toContentRange(long total) {
|
public String toContentRange(long total) {
|
||||||
return "bytes " + startInclusive + "-" + endInclusive + "/" + total;
|
return "bytes " + startInclusive + "-" + endInclusive + "/" + total;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,9 +50,7 @@ public final class JavaPageDispatcher {
|
|||||||
int q = route.indexOf('?');
|
int q = route.indexOf('?');
|
||||||
if (q >= 0) route = route.substring(0, q);
|
if (q >= 0) route = route.substring(0, q);
|
||||||
|
|
||||||
if (!route.startsWith("/")) {
|
route = WebUrlUtil.normalizeRequestPath(route);
|
||||||
route = "/" + route;
|
|
||||||
}
|
|
||||||
|
|
||||||
java.io.File contentRoot = server.getContentFolder();
|
java.io.File contentRoot = server.getContentFolder();
|
||||||
ROUTES.refreshIfNeeded(contentRoot);
|
ROUTES.refreshIfNeeded(contentRoot);
|
||||||
@@ -113,4 +111,4 @@ public final class JavaPageDispatcher {
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import java.net.URI;
|
|||||||
*/
|
*/
|
||||||
public final class WebUrlUtil {
|
public final class WebUrlUtil {
|
||||||
|
|
||||||
|
private static final String DEFAULT_DOCUMENT_PATH = "/index.html";
|
||||||
|
|
||||||
private WebUrlUtil() {
|
private WebUrlUtil() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,6 +30,25 @@ public final class WebUrlUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalizes a request path for route/rule lookup.
|
||||||
|
*
|
||||||
|
* <p>Blank paths and "/" resolve to the default document.</p>
|
||||||
|
*
|
||||||
|
* @param path request path
|
||||||
|
* @return normalized absolute path
|
||||||
|
*/
|
||||||
|
public static String normalizeRequestPath(String path) {
|
||||||
|
if (path == null) return DEFAULT_DOCUMENT_PATH;
|
||||||
|
|
||||||
|
String p = path.trim();
|
||||||
|
if (p.isEmpty() || "/".equals(p)) {
|
||||||
|
return DEFAULT_DOCUMENT_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.startsWith("/") ? p : ("/" + p);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalizes a requested path to a content-root relative path.
|
* Normalizes a requested path to a content-root relative path.
|
||||||
*
|
*
|
||||||
@@ -39,12 +60,10 @@ public final class WebUrlUtil {
|
|||||||
int q = p.indexOf('?');
|
int q = p.indexOf('?');
|
||||||
if (q >= 0) p = p.substring(0, q);
|
if (q >= 0) p = p.substring(0, q);
|
||||||
|
|
||||||
if (p == null || p.isBlank() || "/".equals(p)) {
|
p = normalizeRequestPath(p);
|
||||||
return "index.html";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p.startsWith("/")) p = p.substring(1);
|
if (p.startsWith("/")) p = p.substring(1);
|
||||||
if (p.isBlank()) return "index.html";
|
if (p.isBlank()) return "index.html";
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user