diff --git a/lib/handshake.c b/lib/handshake.c
index f1e7dadb..1935c25d 100644
--- a/lib/handshake.c
+++ b/lib/handshake.c
@@ -139,6 +139,7 @@ libwebsocket_read(struct libwebsocket_context *context,
memset(&wsi->u, 0, sizeof(wsi->u));
wsi->mode = LWS_CONNMODE_HTTP_SERVING_ACCEPTED;
wsi->state = WSI_STATE_HTTP;
+ wsi->u.http.fd = -1;
/* expose it at the same offset as u.hdr */
wsi->u.http.ah = ah;
diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c
index 475162b7..2cb3ab0c 100644
--- a/lib/libwebsockets.c
+++ b/lib/libwebsockets.c
@@ -217,10 +217,10 @@ libwebsocket_close_and_free_session(struct libwebsocket_context *context,
}
- if (wsi->mode == LWS_CONNMODE_HTTP_SERVING_ACCEPTED && wsi->u.http.fd) {
+ if (wsi->mode == LWS_CONNMODE_HTTP_SERVING_ACCEPTED && wsi->u.http.fd >= 0) {
lwsl_debug("closing http fd %d\n", wsi->u.http.fd);
close(wsi->u.http.fd);
- wsi->u.http.fd = 0;
+ wsi->u.http.fd = -1;
context->protocols[0].callback(context, wsi,
LWS_CALLBACK_CLOSED_HTTP, wsi->user_space, NULL, 0);
}
diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h
index 54d485ab..4c121af3 100644
--- a/lib/libwebsockets.h
+++ b/lib/libwebsockets.h
@@ -127,6 +127,8 @@ LWS_VISIBLE LWS_EXTERN void lwsl_hexdump(void *buf, size_t len);
#endif
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
+
enum libwebsocket_context_options {
LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT = 2,
LWS_SERVER_OPTION_SKIP_SERVER_CANONICAL_NAME = 4,
@@ -394,6 +396,34 @@ enum lws_close_status {
LWS_CLOSE_STATUS_TLS_FAILURE = 1015,
};
+enum http_status {
+ HTTP_STATUS_BAD_REQUEST = 400,
+ HTTP_STATUS_UNAUTHORIZED,
+ HTTP_STATUS_PAYMENT_REQUIRED,
+ HTTP_STATUS_FORBIDDEN,
+ HTTP_STATUS_NOT_FOUND,
+ HTTP_STATUS_METHOD_NOT_ALLOWED,
+ HTTP_STATUS_NOT_ACCEPTABLE,
+ HTTP_STATUS_PROXY_AUTH_REQUIRED,
+ HTTP_STATUS_REQUEST_TIMEOUT,
+ HTTP_STATUS_CONFLICT,
+ HTTP_STATUS_GONE,
+ HTTP_STATUS_LENGTH_REQUIRED,
+ HTTP_STATUS_PRECONDITION_FAILED,
+ HTTP_STATUS_REQ_ENTITY_TOO_LARGE,
+ HTTP_STATUS_REQ_URI_TOO_LONG,
+ HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE,
+ HTTP_STATUS_REQ_RANGE_NOT_SATISFIABLE,
+ HTTP_STATUS_EXPECTATION_FAILED,
+
+ HTTP_STATUS_INTERNAL_SERVER_ERROR = 500,
+ HTTP_STATUS_NOT_IMPLEMENTED,
+ HTTP_STATUS_BAD_GATEWAY,
+ HTTP_STATUS_SERVICE_UNAVAILABLE,
+ HTTP_STATUS_GATEWAY_TIMEOUT,
+ HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED,
+};
+
struct libwebsocket;
struct libwebsocket_context;
/* needed even with extensions disabled for create context */
@@ -940,6 +970,11 @@ LWS_VISIBLE LWS_EXTERN int
libwebsockets_serve_http_file_fragment(struct libwebsocket_context *context,
struct libwebsocket *wsi);
+LWS_VISIBLE int libwebsockets_return_http_status(
+ struct libwebsocket_context *context,
+ struct libwebsocket *wsi, unsigned int code,
+ const char *html_body);
+
LWS_VISIBLE LWS_EXTERN const struct libwebsocket_protocols *
libwebsockets_get_protocol(struct libwebsocket *wsi);
diff --git a/lib/output.c b/lib/output.c
index 9abe0873..5a661b05 100644
--- a/lib/output.c
+++ b/lib/output.c
@@ -641,78 +641,3 @@ LWS_VISIBLE int libwebsockets_serve_http_file_fragment(
return 0; /* indicates further processing must be done */
}
-
-/**
- * libwebsockets_serve_http_file() - Send a file back to the client using http
- * @context: libwebsockets context
- * @wsi: Websocket instance (available from user callback)
- * @file: The file to issue over http
- * @content_type: The http content type, eg, text/html
- * @other_headers: NULL or pointer to \0-terminated other header string
- *
- * This function is intended to be called from the callback in response
- * to http requests from the client. It allows the callback to issue
- * local files down the http link in a single step.
- *
- * Returning <0 indicates error and the wsi should be closed. Returning
- * >0 indicates the file was completely sent and the wsi should be closed.
- * ==0 indicates the file transfer is started and needs more service later,
- * the wsi should be left alone.
- */
-
-LWS_VISIBLE int libwebsockets_serve_http_file(
- struct libwebsocket_context *context,
- struct libwebsocket *wsi, const char *file,
- const char *content_type, const char *other_headers)
-{
- struct stat stat_buf;
- unsigned char *p = context->service_buffer;
- int ret = 0;
- int n;
-
- wsi->u.http.fd = open(file, O_RDONLY
-#ifdef WIN32
- | _O_BINARY
-#endif
- );
-
- if (wsi->u.http.fd < 1) {
- lwsl_err("Unable to open '%s'\n", file);
- p += sprintf((char *)p,
- "HTTP/1.0 400 Bad\x0d\x0aServer: libwebsockets\x0d\x0a\x0d\x0a"
- );
- wsi->u.http.fd = 0;
- /* too small to care about partial, closing anyway */
- libwebsocket_write(wsi, context->service_buffer,
- p - context->service_buffer, LWS_WRITE_HTTP);
-
- return -1;
- }
-
- fstat(wsi->u.http.fd, &stat_buf);
- wsi->u.http.filelen = stat_buf.st_size;
- p += sprintf((char *)p,
-"HTTP/1.0 200 OK\x0d\x0aServer: libwebsockets\x0d\x0a""Content-Type: %s\x0d\x0a",
- content_type);
- if (other_headers) {
- n = strlen(other_headers);
- memcpy(p, other_headers, n);
- p += n;
- }
- p += sprintf((char *)p,
- "Content-Length: %u\x0d\x0a\x0d\x0a",
- (unsigned int)stat_buf.st_size);
-
- ret = libwebsocket_write(wsi, context->service_buffer,
- p - context->service_buffer, LWS_WRITE_HTTP);
- if (ret != (p - context->service_buffer)) {
- lwsl_err("_write returned %d from %d\n", ret, (p - context->service_buffer));
- return -1;
- }
-
- wsi->u.http.filepos = 0;
- wsi->state = WSI_STATE_HTTP_ISSUING_FILE;
-
- return libwebsockets_serve_http_file_fragment(context, wsi);
-}
-
diff --git a/lib/server.c b/lib/server.c
index bc8b497f..7c0421a9 100644
--- a/lib/server.c
+++ b/lib/server.c
@@ -389,3 +389,142 @@ int lws_server_socket_service(struct libwebsocket_context *context,
return 0;
}
+
+static const char *err400[] = {
+ "Bad Request",
+ "Unauthorized",
+ "Payment Required",
+ "Forbidden",
+ "Not Found",
+ "Method Not Allowed",
+ "Not Acceptable",
+ "Proxy Auth Required",
+ "Request Timeout",
+ "Conflict",
+ "Gone",
+ "Length Required",
+ "Precondition Failed",
+ "Request Entity Too Large",
+ "Request URI too Long",
+ "Unsupported Media Type",
+ "Requested Range Not Satisfiable",
+ "Expectation Failed"
+};
+
+static const char *err500[] = {
+ "Internal Server Error",
+ "Not Implemented",
+ "Bad Gateway",
+ "Service Unavailable",
+ "Gateway Timeout",
+ "HTTP Version Not Supported"
+};
+
+/**
+ * libwebsockets_return_http_status() - Return simple http status
+ * @context: libwebsockets context
+ * @wsi: Websocket instance (available from user callback)
+ * @code: Status index, eg, 404
+ * @html_body: User-readable HTML description, or NULL
+ *
+ * Helper to report HTTP errors back to the client cleanly and
+ * consistently
+ */
+LWS_VISIBLE int libwebsockets_return_http_status(
+ struct libwebsocket_context *context, struct libwebsocket *wsi,
+ unsigned int code, const char *html_body)
+{
+ int n, m;
+ const char *description = "";
+
+ if (!html_body)
+ html_body = "";
+
+ if (code >= 400 && code < (400 + ARRAY_SIZE(err400)))
+ description = err400[code - 400];
+ if (code >= 500 && code < (500 + ARRAY_SIZE(err500)))
+ description = err500[code - 500];
+
+ n = sprintf((char *)context->service_buffer,
+ "HTTP/1.0 %u %s\x0d\x0a"
+ "Server: libwebsockets\x0d\x0a"
+ "Mime-Type: text/html\x0d\x0a\x0d\x0a"
+ "
%u %s
%s",
+ code, description, code, description, html_body);
+
+ lwsl_info((const char *)context->service_buffer);
+
+ m = libwebsocket_write(wsi, context->service_buffer, n, LWS_WRITE_HTTP);
+
+ return m;
+}
+
+/**
+ * libwebsockets_serve_http_file() - Send a file back to the client using http
+ * @context: libwebsockets context
+ * @wsi: Websocket instance (available from user callback)
+ * @file: The file to issue over http
+ * @content_type: The http content type, eg, text/html
+ * @other_headers: NULL or pointer to \0-terminated other header string
+ *
+ * This function is intended to be called from the callback in response
+ * to http requests from the client. It allows the callback to issue
+ * local files down the http link in a single step.
+ *
+ * Returning <0 indicates error and the wsi should be closed. Returning
+ * >0 indicates the file was completely sent and the wsi should be closed.
+ * ==0 indicates the file transfer is started and needs more service later,
+ * the wsi should be left alone.
+ */
+
+LWS_VISIBLE int libwebsockets_serve_http_file(
+ struct libwebsocket_context *context,
+ struct libwebsocket *wsi, const char *file,
+ const char *content_type, const char *other_headers)
+{
+ struct stat stat_buf;
+ unsigned char *p = context->service_buffer;
+ int ret = 0;
+ int n;
+
+ wsi->u.http.fd = open(file, O_RDONLY
+#ifdef WIN32
+ | _O_BINARY
+#endif
+ );
+
+ if (wsi->u.http.fd < 1) {
+ lwsl_err("Unable to open '%s'\n", file);
+ libwebsockets_return_http_status(context, wsi,
+ HTTP_STATUS_NOT_FOUND, NULL);
+ wsi->u.http.fd = -1;
+ return -1;
+ }
+
+ fstat(wsi->u.http.fd, &stat_buf);
+ wsi->u.http.filelen = stat_buf.st_size;
+ p += sprintf((char *)p,
+"HTTP/1.0 200 OK\x0d\x0aServer: libwebsockets\x0d\x0a""Content-Type: %s\x0d\x0a",
+ content_type);
+ if (other_headers) {
+ n = strlen(other_headers);
+ memcpy(p, other_headers, n);
+ p += n;
+ }
+ p += sprintf((char *)p,
+ "Content-Length: %u\x0d\x0a\x0d\x0a",
+ (unsigned int)stat_buf.st_size);
+
+ ret = libwebsocket_write(wsi, context->service_buffer,
+ p - context->service_buffer, LWS_WRITE_HTTP);
+ if (ret != (p - context->service_buffer)) {
+ lwsl_err("_write returned %d from %d\n", ret, (p - context->service_buffer));
+ return -1;
+ }
+
+ wsi->u.http.filepos = 0;
+ wsi->state = WSI_STATE_HTTP_ISSUING_FILE;
+
+ return libwebsockets_serve_http_file_fragment(context, wsi);
+}
+
diff --git a/test-server/attack.sh b/test-server/attack.sh
index 84328ac8..c1279077 100755
--- a/test-server/attack.sh
+++ b/test-server/attack.sh
@@ -107,7 +107,7 @@ check
echo
echo "---- good request but http payload coming too (should be ignored and test.html served)"
-echo -e "GET blah HTTP/1.1\x0d\x0a\x0d\x0aILLEGAL-PAYLOAD........................................" \
+echo -e "GET /test.html HTTP/1.1\x0d\x0a\x0d\x0aILLEGAL-PAYLOAD........................................" \
"......................................................................................................................." \
"......................................................................................................................." \
"......................................................................................................................." \
@@ -125,10 +125,9 @@ echo -e "GET blah HTTP/1.1\x0d\x0a\x0d\x0aILLEGAL-PAYLOAD.......................
"......................................................................................................................." \
| nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap
check
-size=`stat /tmp/lwscap | grep Size: | tr -s ' ' | cut -d' ' -f3`
-if [ $size -ne 0 ] ; then
- echo "FAIL: got something back when should have hung up"
- cat /tmp/lwscap
+diff /tmp/lwscap /usr/share/libwebsockets-test-server/test.html > /dev/null
+if [ $? -ne 0 ] ; then
+ echo "FAIL: got something other than test.html back"
exit 1
fi
@@ -137,9 +136,8 @@ echo "---- directory attack 1 (/../../../../etc/passwd should be /etc/passswd)"
rm -f /tmp/lwscap
echo -e "GET /../../../../etc/passwd HTTP/1.1\x0d\x0a\x0d\x0a" | nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap
check
-size=`stat /tmp/lwscap | grep Size: | tr -s ' ' | cut -d' ' -f3`
-if [ $size -ne 0 ] ; then
- echo "FAIL: got something back when should have hung up"
+if [ -z "`grep '403 Forbidden
' /tmp/lwscap`" ] ; then
+ echo "FAIL: should have told forbidden (test server has no dirs)"
exit 1
fi
@@ -170,9 +168,8 @@ echo "---- directory attack 4 (/blah/.. should be /blah/)"
rm -f /tmp/lwscap
echo -e "GET /blah/.. HTTP/1.1\x0d\x0a\x0d\x0a" | nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap
check
-size=`stat /tmp/lwscap | grep Size: | tr -s ' ' | cut -d' ' -f3`
-if [ $size -ne 0 ] ; then
- echo "FAIL: got something back when should have hung up"
+if [ -z "`grep '403 Forbidden
' /tmp/lwscap`" ] ; then
+ echo "FAIL: should have told forbidden (test server has no dirs)"
exit 1
fi
@@ -181,9 +178,8 @@ echo "---- directory attack 5 (/blah/../ should be /blah/)"
rm -f /tmp/lwscap
echo -e "GET /blah/../ HTTP/1.1\x0d\x0a\x0d\x0a" | nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap
check
-size=`stat /tmp/lwscap | grep Size: | tr -s ' ' | cut -d' ' -f3`
-if [ $size -ne 0 ] ; then
- echo "FAIL: got something back when should have hung up"
+if [ -z "`grep '403 Forbidden
' /tmp/lwscap`" ] ; then
+ echo "FAIL: should have told forbidden (test server has no dirs)"
exit 1
fi
@@ -192,9 +188,8 @@ echo "---- directory attack 6 (/blah/../. should be /blah/)"
rm -f /tmp/lwscap
echo -e "GET /blah/../. HTTP/1.1\x0d\x0a\x0d\x0a" | nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap
check
-size=`stat /tmp/lwscap | grep Size: | tr -s ' ' | cut -d' ' -f3`
-if [ $size -ne 0 ] ; then
- echo "FAIL: got something back when should have hung up"
+if [ -z "`grep '403 Forbidden
' /tmp/lwscap`" ] ; then
+ echo "FAIL: should have told forbidden (test server has no dirs)"
exit 1
fi
@@ -203,9 +198,8 @@ echo "---- directory attack 7 (/%2e%2e%2f../../../etc/passwd should be /etc/pass
rm -f /tmp/lwscap
echo -e "GET /%2e%2e%2f../../../etc/passwd HTTP/1.1\x0d\x0a\x0d\x0a" | nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap
check
-size=`stat /tmp/lwscap | grep Size: | tr -s ' ' | cut -d' ' -f3`
-if [ $size -ne 0 ] ; then
- echo "FAIL: got something back when should have hung up"
+if [ -z "`grep '403 Forbidden
' /tmp/lwscap`" ] ; then
+ echo "FAIL: should have told forbidden (test server has no dirs)"
exit 1
fi
@@ -214,13 +208,11 @@ echo "---- directory attack 7 (%2f%2e%2e%2f%2e./.%2e/.%2e%2fetc/passwd should be
rm -f /tmp/lwscap
echo -e "GET %2f%2e%2e%2f%2e./.%2e/.%2e%2fetc/passwd HTTP/1.1\x0d\x0a\x0d\x0a" | nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap
check
-size=`stat /tmp/lwscap | grep Size: | tr -s ' ' | cut -d' ' -f3`
-if [ $size -ne 0 ] ; then
- echo "FAIL: got something back when should have hung up"
+if [ -z "`grep '403 Forbidden
' /tmp/lwscap`" ] ; then
+ echo "FAIL: should have told forbidden (test server has no dirs)"
exit 1
fi
-
echo
echo "--- survived"
kill -2 $CPID
diff --git a/test-server/test-server.c b/test-server/test-server.c
index 35150def..899314c8 100644
--- a/test-server/test-server.c
+++ b/test-server/test-server.c
@@ -152,6 +152,19 @@ static int callback_http(struct libwebsocket_context *context,
switch (reason) {
case LWS_CALLBACK_HTTP:
+ if (len < 1) {
+ libwebsockets_return_http_status(context, wsi,
+ HTTP_STATUS_BAD_REQUEST, NULL);
+ return -1;
+ }
+
+ /* this server has no concept of directories */
+ if (strchr((const char *)in + 1, '/')) {
+ libwebsockets_return_http_status(context, wsi,
+ HTTP_STATUS_FORBIDDEN, NULL);
+ return -1;
+ }
+
/* check for the "send a big file by hand" example case */
if (!strcmp((const char *)in, "/leaf.jpg")) {
@@ -217,9 +230,13 @@ static int callback_http(struct libwebsocket_context *context,
} else /* default file to serve */
strcat(buf, "/test.html");
buf[sizeof(buf) - 1] = '\0';
+
+ /* refuse to serve files we don't understand */
mimetype = get_mimetype(buf);
if (!mimetype) {
lwsl_err("Unknown mimetype for %s\n", buf);
+ libwebsockets_return_http_status(context, wsi,
+ HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, NULL);
return -1;
}