undefect. CWE-407 — 63 sites patched across 27 ecosystems

Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com

Patches, unit tests, benchmarks, whitepaper, and outreach briefs.
Public domain — no copyright claimed. Use freely.
This commit is contained in:
russell@unturf.com 2026-03-26 17:11:57 -04:00
commit 0a580b313d
70422 changed files with 17213626 additions and 0 deletions

View file

@ -0,0 +1,416 @@
/*
* Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.net.httpserver.simpleserver;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.lang.System.Logger;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.function.UnaryOperator;
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpHandlers;
import static java.nio.charset.StandardCharsets.UTF_8;
import static com.sun.net.httpserver.HttpExchange.RSPBODY_EMPTY;
/**
* A basic HTTP file server handler for static content.
*
* <p> Must be given an absolute pathname to the directory to be served.
* Supports only HEAD and GET requests. Directory listings and files can be
* served, content types are supported on a best-guess basis.
*/
public final class FileServerHandler implements HttpHandler {
private static final List<String> SUPPORTED_METHODS = List.of("HEAD", "GET");
private static final List<String> UNSUPPORTED_METHODS =
List.of("CONNECT", "DELETE", "OPTIONS", "PATCH", "POST", "PUT", "TRACE");
private static final String FAVICON_RESOURCE_PATH =
"/sun/net/httpserver/simpleserver/resources/favicon.ico";
private static final String FAVICON_LAST_MODIFIED = "Mon, 23 May 1995 11:11:11 GMT";
private final Path root;
private final UnaryOperator<String> mimeTable;
private final Logger logger;
private FileServerHandler(Path root, UnaryOperator<String> mimeTable) {
root = root.normalize();
if (!Files.exists(root))
throw new IllegalArgumentException("Path does not exist: " + root);
if (!root.isAbsolute())
throw new IllegalArgumentException("Path is not absolute: " + root);
if (!Files.isDirectory(root))
throw new IllegalArgumentException("Path is not a directory: " + root);
if (!Files.isReadable(root))
throw new IllegalArgumentException("Path is not readable: " + root);
this.root = root;
this.mimeTable = mimeTable;
this.logger = System.getLogger("com.sun.net.httpserver");
}
private static final HttpHandler NOT_IMPLEMENTED_HANDLER =
HttpHandlers.of(501, Headers.of(), "");
private static final HttpHandler METHOD_NOT_ALLOWED_HANDLER =
HttpHandlers.of(405, Headers.of("Allow", "HEAD, GET"), "");
public static HttpHandler create(Path root, UnaryOperator<String> mimeTable) {
var fallbackHandler = HttpHandlers.handleOrElse(
r -> UNSUPPORTED_METHODS.contains(r.getRequestMethod()),
METHOD_NOT_ALLOWED_HANDLER,
NOT_IMPLEMENTED_HANDLER);
return HttpHandlers.handleOrElse(
r -> SUPPORTED_METHODS.contains(r.getRequestMethod()),
new FileServerHandler(root, mimeTable), fallbackHandler);
}
private void handleHEAD(HttpExchange exchange, Path path) throws IOException {
handleSupportedMethod(exchange, path, false);
}
private void handleGET(HttpExchange exchange, Path path) throws IOException {
handleSupportedMethod(exchange, path, true);
}
private void handleSupportedMethod(HttpExchange exchange, Path path, boolean writeBody)
throws IOException {
if (Files.isDirectory(path)) {
if (missingSlash(exchange)) {
handleMovedPermanently(exchange);
return;
}
if (indexFile(path) != null) {
serveFile(exchange, indexFile(path), writeBody);
} else {
listFiles(exchange, path, writeBody);
}
} else {
serveFile(exchange, path, writeBody);
}
}
private void handleMovedPermanently(HttpExchange exchange) throws IOException {
exchange.getResponseHeaders().set("Location", getRedirectURI(exchange.getRequestURI()));
exchange.sendResponseHeaders(301, RSPBODY_EMPTY);
}
private void handleForbidden(HttpExchange exchange) throws IOException {
exchange.sendResponseHeaders(403, RSPBODY_EMPTY);
}
private void handleNotFound(HttpExchange exchange) throws IOException {
String fileNotFound = ResourceBundleHelper.getMessage("html.not.found");
var bytes = (openHTML
+ "<h1>" + fileNotFound + "</h1>\n"
+ "<p>" + sanitize.apply(exchange.getRequestURI().getPath()) + "</p>\n"
+ closeHTML).getBytes(UTF_8);
exchange.getResponseHeaders().set("Content-Type", "text/html; charset=UTF-8");
if (exchange.getRequestMethod().equals("HEAD")) {
exchange.getResponseHeaders().set("Content-Length", Integer.toString(bytes.length));
exchange.sendResponseHeaders(404, RSPBODY_EMPTY);
} else {
exchange.sendResponseHeaders(404, bytes.length);
try (OutputStream os = exchange.getResponseBody()) {
os.write(bytes);
}
}
}
private static void discardRequestBody(HttpExchange exchange) throws IOException {
try (InputStream is = exchange.getRequestBody()) {
is.skip(Integer.MAX_VALUE);
}
}
private String getRedirectURI(URI uri) {
String query = uri.getRawQuery();
String redirectPath = uri.getRawPath() + "/";
return query == null ? redirectPath : redirectPath + "?" + query;
}
private static boolean missingSlash(HttpExchange exchange) {
return !exchange.getRequestURI().getPath().endsWith("/");
}
private static String contextPath(HttpExchange exchange) {
String context = exchange.getHttpContext().getPath();
if (!context.startsWith("/")) {
throw new IllegalArgumentException("Context path invalid: " + context);
}
return context;
}
private static String requestPath(HttpExchange exchange) {
String request = exchange.getRequestURI().getPath();
if (!request.startsWith("/")) {
throw new IllegalArgumentException("Request path invalid: " + request);
}
return request;
}
// Checks that the request does not escape context.
private static void checkRequestWithinContext(String requestPath,
String contextPath) {
if (requestPath.equals(contextPath)) {
return; // context path requested, e.g. context /foo, request /foo
}
String contextPathWithTrailingSlash = contextPath.endsWith("/")
? contextPath : contextPath + "/";
if (!requestPath.startsWith(contextPathWithTrailingSlash)) {
throw new IllegalArgumentException("Request not in context: " + contextPath);
}
}
// Checks that path is, or is within, the root.
private static Path checkPathWithinRoot(Path path, Path root) {
if (!path.startsWith(root)) {
throw new IllegalArgumentException("Request not in root");
}
return path;
}
// Returns the request URI path relative to the context.
private static String relativeRequestPath(HttpExchange exchange) {
String context = contextPath(exchange);
String request = requestPath(exchange);
checkRequestWithinContext(request, context);
return request.substring(context.length());
}
private Path mapToPath(HttpExchange exchange, Path root) {
try {
assert root.isAbsolute() && Files.isDirectory(root); // checked during creation
String uriPath = relativeRequestPath(exchange);
String[] pathSegment = uriPath.split("/");
// resolve each path segment against the root
Path path = root;
for (var segment : pathSegment) {
if (!URIPathSegment.isSupported(segment)) {
return null; // stop resolution, null results in 404 response
}
path = path.resolve(segment);
if (!Files.isReadable(path) || isHiddenOrSymLink(path)) {
return null; // stop resolution
}
}
path = path.normalize();
return checkPathWithinRoot(path, root);
} catch (Exception e) {
logger.log(System.Logger.Level.TRACE,
"FileServerHandler: request URI path resolution failed", e);
return null; // could not resolve request URI path
}
}
private static Path indexFile(Path path) {
Path html = path.resolve("index.html");
Path htm = path.resolve("index.htm");
return Files.exists(html) ? html : Files.exists(htm) ? htm : null;
}
private static boolean isFavIconRequest(HttpExchange exchange) {
return "/favicon.ico".equals(exchange.getRequestURI().getPath());
}
private void serveDefaultFavIcon(HttpExchange exchange, boolean writeBody)
throws IOException
{
var respHdrs = exchange.getResponseHeaders();
try (var stream = getClass().getModule().getResourceAsStream(FAVICON_RESOURCE_PATH)) {
var bytes = stream.readAllBytes();
respHdrs.set("Content-Type", "image/x-icon");
respHdrs.set("Last-Modified", FAVICON_LAST_MODIFIED);
if (writeBody) {
exchange.sendResponseHeaders(200, bytes.length);
try (OutputStream os = exchange.getResponseBody()) {
os.write(bytes);
}
} else {
respHdrs.set("Content-Length", Integer.toString(bytes.length));
exchange.sendResponseHeaders(200, RSPBODY_EMPTY);
}
}
}
private void serveFile(HttpExchange exchange, Path path, boolean writeBody)
throws IOException
{
var respHdrs = exchange.getResponseHeaders();
respHdrs.set("Content-Type", mediaType(path.toString()));
respHdrs.set("Last-Modified", getLastModified(path));
if (writeBody) {
exchange.sendResponseHeaders(200, Files.size(path));
try (InputStream fis = Files.newInputStream(path);
OutputStream os = exchange.getResponseBody()) {
fis.transferTo(os);
}
} else {
respHdrs.set("Content-Length", Long.toString(Files.size(path)));
exchange.sendResponseHeaders(200, RSPBODY_EMPTY);
}
}
private void listFiles(HttpExchange exchange, Path path, boolean writeBody)
throws IOException
{
var respHdrs = exchange.getResponseHeaders();
respHdrs.set("Content-Type", "text/html; charset=UTF-8");
respHdrs.set("Last-Modified", getLastModified(path));
var bodyBytes = dirListing(exchange, path).getBytes(UTF_8);
if (writeBody) {
exchange.sendResponseHeaders(200, bodyBytes.length);
try (OutputStream os = exchange.getResponseBody()) {
os.write(bodyBytes);
}
} else {
respHdrs.set("Content-Length", Integer.toString(bodyBytes.length));
exchange.sendResponseHeaders(200, RSPBODY_EMPTY);
}
}
private static final String openHTML = """
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
</head>
<body>
""";
private static final String closeHTML = """
</body>
</html>
""";
private static final String hrefListItemTemplate = """
<li><a href="%s">%s</a></li>
""";
private static String hrefListItemFor(URI uri) {
return hrefListItemTemplate.formatted(uri.toASCIIString(), sanitize.apply(uri.getPath()));
}
private static String dirListing(HttpExchange exchange, Path path) throws IOException {
String dirListing = ResourceBundleHelper.getMessage("html.dir.list");
var sb = new StringBuilder(openHTML
+ "<h1>" + dirListing + " "
+ sanitize.apply(exchange.getRequestURI().getPath())
+ "</h1>\n"
+ "<ul>\n");
try (var paths = Files.list(path)) {
paths.filter(p -> Files.isReadable(p) && !isHiddenOrSymLink(p))
.map(p -> path.toUri().relativize(p.toUri()))
.forEach(uri -> sb.append(hrefListItemFor(uri)));
}
sb.append("</ul>\n");
sb.append(closeHTML);
return sb.toString();
}
private static String getLastModified(Path path) throws IOException {
var fileTime = Files.getLastModifiedTime(path);
return fileTime.toInstant().atZone(ZoneId.of("GMT"))
.format(DateTimeFormatter.RFC_1123_DATE_TIME);
}
private static boolean isHiddenOrSymLink(Path path) {
try {
return Files.isHidden(path) || Files.isSymbolicLink(path);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
// Default for unknown content types, as per RFC 2046
private static final String DEFAULT_CONTENT_TYPE = "application/octet-stream";
private String mediaType(String file) {
String type = mimeTable.apply(file);
return type != null ? type : DEFAULT_CONTENT_TYPE;
}
// A non-exhaustive map of reserved-HTML and special characters to their
// equivalent entity.
private static final Map<Integer, String> RESERVED_CHARS = Map.of(
(int) '&' , "&amp;" ,
(int) '<' , "&lt;" ,
(int) '>' , "&gt;" ,
(int) '"' , "&quot;" ,
(int) '\'' , "&#x27;" ,
(int) '/' , "&#x2F;" );
// A function that takes a string and returns a sanitized version of that
// string with the reserved-HTML and special characters replaced with their
// equivalent entity.
private static final UnaryOperator<String> sanitize =
file -> file.chars().collect(StringBuilder::new,
(sb, c) -> sb.append(RESERVED_CHARS.getOrDefault(c, Character.toString(c))),
StringBuilder::append).toString();
@Override
public void handle(HttpExchange exchange) throws IOException {
assert List.of("GET", "HEAD").contains(exchange.getRequestMethod());
try (exchange) {
discardRequestBody(exchange);
boolean isHeadRequest = exchange.getRequestMethod().equals("HEAD");
Path path = mapToPath(exchange, root);
if (path != null) {
exchange.setAttribute("request-path", path.toString()); // store for OutputFilter
if (!Files.exists(path) || !Files.isReadable(path) || isHiddenOrSymLink(path)) {
handleNotFound(exchange);
} else if (isHeadRequest) {
handleHEAD(exchange, path);
} else {
handleGET(exchange, path);
}
} else {
if (isFavIconRequest(exchange)) {
try {
serveDefaultFavIcon(exchange, !isHeadRequest);
return;
} catch (IOException ignore) {
// fall through to send the not-found response
}
}
exchange.setAttribute("request-path", "could not resolve request URI path");
handleNotFound(exchange);
}
}
}
}

View file

@ -0,0 +1,99 @@
/*
* Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.net.httpserver.simpleserver;
import java.io.PrintWriter;
/**
* Programmatic entry point to start the jwebserver tool.
*/
public class JWebServer {
private static final String SYS_PROP_MAX_CONNECTIONS = "jdk.httpserver.maxConnections";
private static final String DEFAULT_JWEBSERVER_MAX_CONNECTIONS = "200";
private static final String SYS_PROP_ENHANCED_EXCEP = "jdk.includeInExceptions";
private static final String DEFAULT_ENHANCED_EXCEP = "net";
/**
* This constructor should never be called.
*/
private JWebServer() { throw new AssertionError(); }
/**
* The main entry point.
*
* <p> The command line arguments are parsed and the server is started. If
* started successfully, the server will run on a new non-daemon thread,
* and this method will return. Otherwise, if the server is not started
* successfully, e.g. an error is encountered while parsing the arguments
* or an I/O error occurs, the server is not started and this method invokes
* System::exit with an appropriate exit code.
*
* <p> If the system property "sun.net.httpserver.maxReqTime" has not been
* set by the user, it is set to a value of 5 seconds. This is to prevent
* the server from hanging indefinitely, for example in the case of an HTTPS
* request.
*
* @param args the command-line options
* @throws NullPointerException if {@code args} is {@code null}, or if there
* are any {@code null} values in the {@code args} array
*/
public static void main(String... args) {
setMaxReqTime();
setEnhancedExceptions();
setMaxConnectionsIfNotSet();
int ec = SimpleFileServerImpl.start(new PrintWriter(System.out, true), "jwebserver", args);
if (ec != 0) {
System.exit(ec);
} // otherwise, the server has either been started successfully and
// runs in another non-daemon thread, or -h or -version have been
// passed and the main thread has exited normally.
}
public static final String MAXREQTIME_KEY = "sun.net.httpserver.maxReqTime";
public static final String MAXREQTIME_VAL = "5";
private static void setMaxReqTime() {
if (System.getProperty(MAXREQTIME_KEY) == null) {
System.setProperty(MAXREQTIME_KEY, MAXREQTIME_VAL);
}
}
static void setEnhancedExceptions() {
if (System.getProperty(SYS_PROP_ENHANCED_EXCEP) != null) {
// an explicit value has already been set, so we don't override it
return;
}
System.setProperty(SYS_PROP_ENHANCED_EXCEP, DEFAULT_ENHANCED_EXCEP);
}
static void setMaxConnectionsIfNotSet() {
if (System.getProperty(SYS_PROP_MAX_CONNECTIONS) == null) {
System.setProperty(SYS_PROP_MAX_CONNECTIONS, DEFAULT_JWEBSERVER_MAX_CONNECTIONS);
}
}
}

View file

@ -0,0 +1,79 @@
/*
* Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.net.httpserver.simpleserver;
import java.io.PrintWriter;
/**
* Programmatic entry point to start "java -m jdk.httpserver".
*/
public class Main {
/**
* This constructor should never be called.
*/
private Main() { throw new AssertionError(); }
/**
* The main entry point.
*
* <p> The command line arguments are parsed and the server is started. If
* started successfully, the server will run on a new non-daemon thread,
* and this method will return. Otherwise, if the server is not started
* successfully, e.g. an error is encountered while parsing the arguments
* or an I/O error occurs, the server is not started and this method invokes
* System::exit with an appropriate exit code.
*
* <p> If the system property "sun.net.httpserver.maxReqTime" has not been
* set by the user, it is set to a value of 5 seconds. This is to prevent
* the server from hanging indefinitely, for example in the case of an HTTPS
* request.
*
* @param args the command-line options
* @throws NullPointerException if {@code args} is {@code null}, or if there
* are any {@code null} values in the {@code args} array
*/
public static void main(String... args) {
setMaxReqTime();
JWebServer.setMaxConnectionsIfNotSet();
int ec = SimpleFileServerImpl.start(new PrintWriter(System.out, true), "java", args);
if (ec != 0) {
System.exit(ec);
} // otherwise, the server has either been started successfully and
// runs in another non-daemon thread, or -h or -version have been
// passed and the main thread has exited normally.
}
public static final String MAXREQTIME_KEY = "sun.net.httpserver.maxReqTime";
public static final String MAXREQTIME_VAL = "5";
private static void setMaxReqTime() {
if (System.getProperty(MAXREQTIME_KEY) == null) {
System.setProperty(MAXREQTIME_KEY, MAXREQTIME_VAL);
}
}
}

View file

@ -0,0 +1,135 @@
/*
* Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.net.httpserver.simpleserver;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.util.function.Consumer;
import com.sun.net.httpserver.Filter;
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.SimpleFileServer.OutputLevel;
import static java.nio.charset.StandardCharsets.UTF_8;
/**
* A Filter that outputs log messages about an HttpExchange. The implementation
* uses a {@link Filter#afterHandler(String, Consumer) post-processing filter}.
*
* <p> If the outputLevel is INFO, the format is based on the
* <a href='https://www.w3.org/Daemon/User/Config/Logging.html#common-logfile-format'>Common Logfile Format</a>.
* In this case the output includes the following information about an exchange:
*
* <p> remotehost rfc931 authuser [date] "request line" status bytes
*
* <p> Example:
* 127.0.0.1 - - [22/Jun/2000:13:55:36 -0700] "GET /example.txt HTTP/1.1" 200 -
*
* <p> The fields rfc931, authuser and bytes are not captured in the implementation
* and are always represented as '-'.
*
* <p> If the outputLevel is VERBOSE, the output additionally includes the
* absolute path of the resource requested, if it has been
* {@linkplain HttpExchange#setAttribute(String, Object) provided} via the
* attribute {@code "request-path"}, as well as the request and response headers
* of the exchange.
*/
public final class OutputFilter extends Filter {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("dd/MMM/yyyy:HH:mm:ss Z");
private final PrintStream printStream;
private final OutputLevel outputLevel;
private final Filter filter;
private OutputFilter(OutputStream os, OutputLevel outputLevel) {
printStream = new PrintStream(os, true, UTF_8);
this.outputLevel = outputLevel;
var description = "HttpExchange OutputFilter (outputLevel: " + outputLevel + ")";
this.filter = Filter.afterHandler(description, operation());
}
public static OutputFilter create(OutputStream os, OutputLevel outputLevel) {
if (outputLevel.equals(OutputLevel.NONE)) {
throw new IllegalArgumentException("Not a valid outputLevel: " + outputLevel);
}
return new OutputFilter(os, outputLevel);
}
private Consumer<HttpExchange> operation() {
return e -> {
String s = e.getRemoteAddress().getHostString() + " "
+ "- - " // rfc931 and authuser
+ "[" + OffsetDateTime.now().format(FORMATTER) + "] "
+ "\"" + e.getRequestMethod() + " " + e.getRequestURI() + " " + e.getProtocol() + "\" "
+ e.getResponseCode() + " -"; // bytes
printStream.println(s);
if (outputLevel.equals(OutputLevel.VERBOSE)) {
if (e.getAttribute("request-path") instanceof String requestPath) {
printStream.println("Resource requested: " + requestPath);
}
logHeaders(">", e.getRequestHeaders());
logHeaders("<", e.getResponseHeaders());
}
};
}
private void logHeaders(String sign, Headers headers) {
headers.forEach((name, values) -> {
var sb = new StringBuilder();
var it = values.iterator();
while (it.hasNext()) {
sb.append(it.next());
if (it.hasNext()) {
sb.append(", ");
}
}
printStream.println(sign + " " + name + ": " + sb);
});
printStream.println(sign);
}
@Override
public void doFilter(HttpExchange exchange, Chain chain) throws IOException {
try {
filter.doFilter(exchange, chain);
} catch (Throwable t) {
if (!outputLevel.equals(OutputLevel.NONE)) {
reportError(ResourceBundleHelper.getMessage("err.server.handle.failed",
t.getMessage()));
}
throw t;
}
}
@Override
public String description() { return filter.description(); }
private void reportError(String message) {
printStream.println(ResourceBundleHelper.getMessage("error.prefix") + " " + message);
}
}

View file

@ -0,0 +1,51 @@
/*
* Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.net.httpserver.simpleserver;
import java.text.MessageFormat;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
class ResourceBundleHelper {
static final ResourceBundle bundle;
static {
try {
bundle = ResourceBundle.getBundle("sun.net.httpserver.simpleserver.resources.simpleserver");
} catch (MissingResourceException e) {
throw new InternalError("Cannot find simpleserver resource bundle for locale " + Locale.getDefault());
}
}
static String getMessage(String key, Object... args) {
try {
return MessageFormat.format(bundle.getString(key), args);
} catch (MissingResourceException e) {
throw new InternalError("Missing message: " + key);
}
}
}

View file

@ -0,0 +1,257 @@
/*
* Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.net.httpserver.simpleserver;
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.SimpleFileServer;
import com.sun.net.httpserver.SimpleFileServer.OutputLevel;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Locale;
import java.util.NoSuchElementException;
import java.util.Objects;
/**
* A class that provides a simple HTTP file server to serve the content of
* a given directory.
*
* <p> The server is an HttpServer bound to a given address. It comes with an
* HttpHandler that serves files from a given directory path
* (and its subdirectories) on the default file system, and an optional Filter
* that prints log messages related to the exchanges handled by the server to
* a given output stream.
*
* <p> Unless specified as arguments, the default values are:<ul>
* <li>bind address: 127.0.0.1 or ::1 (loopback)</li>
* <li>directory: current working directory</li>
* <li>outputLevel: info</li></ul>
* <li>port: 8000</li>
* <p>
* The implementation is provided via the main entry point of the jdk.httpserver
* module.
*/
final class SimpleFileServerImpl {
private static final InetAddress LOOPBACK_ADDR = InetAddress.getLoopbackAddress();
private static final int DEFAULT_PORT = 8000;
private static final Path DEFAULT_ROOT = Path.of("").toAbsolutePath();
private static final OutputLevel DEFAULT_OUTPUT_LEVEL = OutputLevel.INFO;
private static boolean addrSpecified = false;
private SimpleFileServerImpl() { throw new AssertionError(); }
/**
* Starts a simple HTTP file server created on a directory.
*
* @param writer the writer to which output should be written
* @param args the command line options
* @param launcher the launcher the server is started from
* @throws NullPointerException if any of the arguments are {@code null},
* or if there are any {@code null} values in the {@code args} array
* @return startup status code
*/
static int start(PrintWriter writer, String launcher, String[] args) {
Objects.requireNonNull(args);
for (var arg : args) {
Objects.requireNonNull(arg);
}
Out out = new Out(writer);
InetAddress addr = LOOPBACK_ADDR;
int port = DEFAULT_PORT;
Path root = DEFAULT_ROOT;
OutputLevel outputLevel = DEFAULT_OUTPUT_LEVEL;
// parse options
Iterator<String> options = Arrays.asList(args).iterator();
String option = null;
String optionArg = null;
try {
while (options.hasNext()) {
option = options.next();
switch (option) {
case "-h", "-?", "--help" -> {
out.showHelp(launcher);
return Startup.OK.statusCode;
}
case "-version", "--version" -> {
out.showVersion(launcher);
return Startup.OK.statusCode;
}
case "-b", "--bind-address" -> {
addr = InetAddress.getByName(optionArg = options.next());
addrSpecified = true;
}
case "-d", "--directory" ->
root = Path.of(optionArg = options.next());
case "-o", "--output" ->
outputLevel = Enum.valueOf(OutputLevel.class,
(optionArg = options.next()).toUpperCase(Locale.ROOT));
case "-p", "--port" ->
port = Integer.parseInt(optionArg = options.next());
default -> throw new AssertionError();
}
}
} catch (AssertionError ae) {
out.reportError(ResourceBundleHelper.getMessage("err.unknown.option", option));
out.showUsage(launcher);
return Startup.CMDERR.statusCode;
} catch (NoSuchElementException nsee) {
out.reportError(ResourceBundleHelper.getMessage("err.missing.arg", option));
out.showOption(option);
return Startup.CMDERR.statusCode;
} catch (Exception e) {
out.reportError(ResourceBundleHelper.getMessage("err.invalid.arg", option, optionArg));
e.printStackTrace(out.writer);
return Startup.CMDERR.statusCode;
} finally {
out.flush();
}
// configure and start server
try {
root = realPath(root);
var socketAddr = new InetSocketAddress(addr, port);
var server = SimpleFileServer.createFileServer(socketAddr, root, outputLevel);
server.start();
out.printStartMessage(root, server);
} catch (Throwable t) {
out.reportError(ResourceBundleHelper.getMessage("err.server.config.failed", t.getMessage()));
return Startup.SYSERR.statusCode;
} finally {
out.flush();
}
return Startup.OK.statusCode;
}
private static Path realPath(Path root) {
// `toRealPath()` invocation below already checks if file exists, though
// there is no way to figure out if it fails due to a non-existent file.
// Hence, checking the existence here first to deliver the user a more
// descriptive message.
if (!Files.exists(root)) {
throw new IllegalArgumentException("Path does not exist: " + root);
}
// Obtain the real path
try {
return root.toRealPath();
} catch (IOException exception) {
throw new IllegalArgumentException("Path is invalid: " + root, exception);
}
}
private static final class Out {
private final PrintWriter writer;
private Out() { throw new AssertionError(); }
Out(PrintWriter writer) {
this.writer = Objects.requireNonNull(writer);
}
void printStartMessage(Path root, HttpServer server)
throws UnknownHostException
{
String port = Integer.toString(server.getAddress().getPort());
var inetAddr = server.getAddress().getAddress();
var isAnyLocal = inetAddr.isAnyLocalAddress();
var addr = isAnyLocal ? InetAddress.getLocalHost().getHostAddress() : inetAddr.getHostAddress();
if (!addrSpecified) {
writer.println(ResourceBundleHelper.getMessage("loopback.info"));
}
if (inetAddr instanceof Inet6Address && addr.contains(":") && !addr.startsWith("[")) {
// we use the "addr" when printing the URL, so make sure it
// conforms to RFC-2732, section 2:
// To use a literal IPv6 address in a URL, the literal
// address should be enclosed in "[" and "]" characters.
addr = "[" + addr + "]";
}
if (isAnyLocal) {
writer.println(ResourceBundleHelper.getMessage("msg.start.anylocal", root, addr, port));
} else {
writer.println(ResourceBundleHelper.getMessage("msg.start.other", root, addr, port));
}
}
void showUsage(String launcher) {
writer.println(ResourceBundleHelper.getMessage("usage." + launcher));
}
void showVersion(String launcher) {
writer.println(ResourceBundleHelper.getMessage("version", launcher, System.getProperty("java.version")));
}
void showHelp(String launcher) {
writer.println(ResourceBundleHelper.getMessage("usage." + launcher));
writer.println(ResourceBundleHelper.getMessage("options", LOOPBACK_ADDR.getHostAddress()));
}
void showOption(String option) {
switch (option) {
case "-b", "--bind-address" ->
writer.println(ResourceBundleHelper.getMessage("opt.bindaddress", LOOPBACK_ADDR.getHostAddress()));
case "-d", "--directory" ->
writer.println(ResourceBundleHelper.getMessage("opt.directory"));
case "-o", "--output" ->
writer.println(ResourceBundleHelper.getMessage("opt.output"));
case "-p", "--port" ->
writer.println(ResourceBundleHelper.getMessage("opt.port"));
}
}
void reportError(String message) {
writer.println(ResourceBundleHelper.getMessage("error.prefix") + " " + message);
}
void flush() {
writer.flush();
}
}
private enum Startup {
/** Started with no errors */
OK(0),
/** Not started, bad command-line arguments */
CMDERR(1),
/** Not started, system error or resource exhaustion */
SYSERR(2);
Startup(int statusCode) {
this.statusCode = statusCode;
}
public final int statusCode;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -0,0 +1,80 @@
#
# Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# This code is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 2 only, as
# published by the Free Software Foundation. Oracle designates this
# particular file as subject to the "Classpath" exception as provided
# by Oracle in the LICENSE file that accompanied this code.
#
# This code is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# version 2 for more details (a copy is included in the LICENSE file that
# accompanied this code).
#
# You should have received a copy of the GNU General Public License version
# 2 along with this work; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
# or visit www.oracle.com if you need additional information or have any
# questions.
#
usage.java=\
Usage: java -m jdk.httpserver [-b bind address] [-p port] [-d directory]\n\
\ [-o none|info|verbose] [-h to show options]\n\
\ [-version to show version information]
usage.jwebserver=\
Usage: jwebserver [-b bind address] [-p port] [-d directory]\n\
\ [-o none|info|verbose] [-h to show options]\n\
\ [-version to show version information]
version=\
{0} {1}
options=\
Options:\n\
-b, --bind-address - Address to bind to. Default: {0} (loopback).\n\
\ For all interfaces use "-b 0.0.0.0" or "-b ::".\n\
-d, --directory - Directory to serve. Default: current directory.\n\
-o, --output - Output format. none|info|verbose. Default: info.\n\
-p, --port - Port to listen on. Default: 8000.\n\
-h, -?, --help - Prints this help message and exits.\n\
-version, --version - Prints version information and exits.\n\
To stop the server, press Ctrl + C.
opt.bindaddress=\
-b, --bind-address - Address to bind to. Default: {0} (loopback).\n\
\ For all interfaces use "-b 0.0.0.0" or "-b ::".
opt.directory=\
-d, --directory - Directory to serve. Default: current directory.
opt.output=\
-o, --output - Output format. none|info|verbose. Default: info.
opt.port=\
-p, --port - Port to listen on. Default: 8000.
loopback.info=\
Binding to loopback by default. For all interfaces use "-b 0.0.0.0" or "-b ::".
msg.start.anylocal=\
Serving {0} and subdirectories on 0.0.0.0 (all interfaces) port {2}\n\
URL http://{1}:{2}/
msg.start.other=\
Serving {0} and subdirectories on {1} port {2}\n\
URL http://{1}:{2}/
error.prefix=Error:
err.unknown.option=unknown option: {0}
err.missing.arg=no value given for {0}
err.invalid.arg=invalid value given for {0}: {1}
err.server.config.failed=server config failed: {0}
err.server.handle.failed=server exchange handling failed: {0}
html.dir.list=Directory listing for
html.not.found=File not found

View file

@ -0,0 +1,54 @@
#
# Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# This code is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 2 only, as
# published by the Free Software Foundation. Oracle designates this
# particular file as subject to the "Classpath" exception as provided
# by Oracle in the LICENSE file that accompanied this code.
#
# This code is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# version 2 for more details (a copy is included in the LICENSE file that
# accompanied this code).
#
# You should have received a copy of the GNU General Public License version
# 2 along with this work; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
# or visit www.oracle.com if you need additional information or have any
# questions.
#
usage.java=Verwendung: java -m jdk.httpserver [-b bind address] [-p port] [-d directory]\n [-o none|info|verbose] [-h zum Anzeigen von Optionen]\n [-version zum Anzeigen der Versionsinformationen]
usage.jwebserver=Verwendung: jwebserver [-b bind address] [-p port] [-d directory]\n [-o none|info|verbose] [-h zum Anzeigen von Optionen]\n [-version zum Anzeigen von Versionsinformationen]
version={0} {1}
options=Optionen:\n-b, --bind-address - Adresse, an die das Binding erfolgt. Standard: {0} (Loopback).\n Verwenden Sie für alle Schnittstellen "-b 0.0.0.0" oder "-b ::".\n-d, --directory - Zu bedienendes Verzeichnis. Standard: Aktuelles Verzeichnis.\n-o, --output - Ausgabeformat. none|info|verbose. Standard: info.\n-p, --port - Port, auf dem gehorcht wird. Standard: 8000.\n-h, -?, --help - Gibt diese Hilfemeldung aus und beendet.\n-version, --version - Gibt Versionsinformationen aus und beendet.\nDrücken Sie zum Stoppen des Servers Strg+C.
opt.bindaddress=-b, --bind-address - Adresse, an die das Binding erfolgt. Standard: {0} (Loopback).\n Verwenden Sie für alle Schnittstellen "-b 0.0.0.0" oder "-b ::".
opt.directory=-d, --directory - Zu bedienendes Verzeichnis. Standard: Aktuelles Verzeichnis.
opt.output=-o, --output - Ausgabeformat. none|info|verbose. Standard: info.
opt.port=-p, --port - Port, auf dem gehorcht wird. Standard: 8000.
loopback.info=Binding an Loopback als Standard. Verwenden Sie für alle Schnittstellen "-b 0.0.0.0" oder "-b ::".
msg.start.anylocal=Bedient {0} und Unterverzeichnisse auf 0.0.0.0 (alle Schnittstellen) Port {2}\nURL http://{1}:{2}/
msg.start.other=Bedient {0} und Unterverzeichnisse auf {1} Port {2}\nURL http://{1}:{2}/
error.prefix=Fehler:
err.unknown.option=unbekannte Option: {0}
err.missing.arg=kein Wert angegeben für {0}
err.invalid.arg=ungültiger Wert angegeben für {0}: {1}
err.server.config.failed=Serverkonfiguration nicht erfolgreich: {0}
err.server.handle.failed=Handling des Serveraustauschs nicht erfolgreich: {0}
html.dir.list=Verzeichnisliste für
html.not.found=Datei wurde nicht gefunden

View file

@ -0,0 +1,54 @@
#
# Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# This code is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 2 only, as
# published by the Free Software Foundation. Oracle designates this
# particular file as subject to the "Classpath" exception as provided
# by Oracle in the LICENSE file that accompanied this code.
#
# This code is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# version 2 for more details (a copy is included in the LICENSE file that
# accompanied this code).
#
# You should have received a copy of the GNU General Public License version
# 2 along with this work; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
# or visit www.oracle.com if you need additional information or have any
# questions.
#
usage.java=使用方法: java -m jdk.httpserver [-b bind address] [-p port] [-d directory]\n [-o none|info|verbose] [-h to show options]\n [-version to show version information]
usage.jwebserver=使用方法: jwebserver [-b bind address] [-p port] [-d directory]\n [-o none|info|verbose] [-h to show options]\n [-version to show version information]
version={0} {1}
options=オプション:\n-b, --bind-address - バインド先アドレス。デフォルト: {0} (ループバック)。\n すべてのインタフェースで"-b 0.0.0.0"または"-b ::"を使用します。\n-d, --directory - 使用するディレクトリ。デフォルト: 現在のディレクトリ。\n-o, --output - 出力形式。none|info|verbose。デフォルト: info。\n-p, --port - リスニングするポート。デフォルト: 8000。\n-h, -?, --help - ヘルプ・メッセージを出力して終了します。\n-version, --version - バージョン情報を出力して終了します。\nサーバーを停止するには、[Ctrl]+[C]を押します。
opt.bindaddress=-b, --bind-address - バインド先アドレス。デフォルト: {0} (ループバック).\n すべてのインタフェースで"-b 0.0.0.0"または"-b ::"を使用します。
opt.directory=-d, --directory - 使用するディレクトリ。デフォルト: 現在のディレクトリ。
opt.output=-o, --output - 出力形式。none|info|verbose. デフォルト: info。
opt.port=-p, --port - リスニングするポート。デフォルト: 8000。
loopback.info=デフォルトでループバックにバインドします。すべてのインタフェースで"-b 0.0.0.0"または"-b ::"を使用します。
msg.start.anylocal={0}およびサブディレクトリを0.0.0.0 (すべてのインタフェース)ポート{2}で使用します\nURL http://{1}:{2}/
msg.start.other={0}およびサブディレクトリを{1}ポート{2}で使用します\nURL http://{1}:{2}/
error.prefix=エラー:
err.unknown.option=不明なオプション: {0}
err.missing.arg={0}に値が指定されていません
err.invalid.arg={0}に無効な値が指定されました: {1}
err.server.config.failed=サーバーの構成に失敗しました: {0}
err.server.handle.failed=サーバー交換処理が失敗しました: {0}
html.dir.list=次のディレクトリ・リスト
html.not.found=ファイルが見つかりませんでした

View file

@ -0,0 +1,54 @@
#
# Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# This code is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 2 only, as
# published by the Free Software Foundation. Oracle designates this
# particular file as subject to the "Classpath" exception as provided
# by Oracle in the LICENSE file that accompanied this code.
#
# This code is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# version 2 for more details (a copy is included in the LICENSE file that
# accompanied this code).
#
# You should have received a copy of the GNU General Public License version
# 2 along with this work; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
# or visit www.oracle.com if you need additional information or have any
# questions.
#
usage.java=用法java -m jdk.httpserver [-b 绑定地址] [-p 端口] [-d 目录]\n [-o none|info|verbose] [-h 显示选项]\n [-version 显示版本信息]
usage.jwebserver=用法jwebserver [-b 绑定地址] [-p 端口] [-d 目录]\n [-o none|info|verbose] [-h 显示选项]\n [-version 显示版本信息]
version={0} {1}
options=选项:\n-b, --bind-address - 要绑定到的地址。默认值:{0}(环回)。\n 如果要表示所有接口,请使用 "-b 0.0.0.0" 或 "-b ::"。\n-d, --directory - 要为其提供服务的目录。默认值:当前目录。\n-o, --output - 输出格式。none|info|verbose。默认值info。\n-p, --port - 要监听的端口。默认值8000。\n-h, -?, --help - 输出此帮助消息并退出。\n-version, --version - 输出版本信息并退出。\n要停止服务器请按 Ctrl + C。
opt.bindaddress=-b, --bind-address - 要绑定到的地址。默认值:{0}(环回)。\n 如果要表示所有接口,请使用 "-b 0.0.0.0" 或 "-b ::"。
opt.directory=-d, --directory - 要为其提供服务的目录。默认值:当前目录。
opt.output=-o, --output - 输出格式。none|info|verbose。默认值info。
opt.port=-p, --port - 要监听的端口。默认值8000。
loopback.info=默认情况下绑定到环回。如果要表示所有接口,请使用 "-b 0.0.0.0" 或 "-b ::"。
msg.start.anylocal=为 0.0.0.0(所有接口)端口 {2} 上的 {0} 及子目录提供服务\nURL http://{1}:{2}/
msg.start.other=为 {1} 端口 {2} 上的 {0} 及子目录提供服务\nURL http://{1}:{2}/
error.prefix=错误:
err.unknown.option=未知选项: {0}
err.missing.arg=没有为{0}指定值
err.invalid.arg=为 {0} 提供的值无效:{1}
err.server.config.failed=服务器配置失败:{0}
err.server.handle.failed=服务器交换处理失败:{0}
html.dir.list=以下项的目录列表
html.not.found=找不到文件