EOF
lines=$(grep -c "^<statistics version" nc.out$n)
test "$lines" = 2 || ret=1
+ # keep-alive not needed in HTTP/1.1, second response has close
+ lines=$(grep -c "^Connection: Keep-Alive" nc.out$n)
+ test "$lines" = 0 || ret=1
+ lines=$(grep -c "^Connection: close" nc.out$n)
+ test "$lines" = 1 || ret=1
else
echo_i "skipping test as nc not found"
fi
EOF
lines=$(grep -c "^<statistics version" nc.out$n)
test "$lines" = 2 || ret=1
+ # keep-alive not needed in HTTP/1.1, second response has close
+ lines=$(grep -c "^Connection: Keep-Alive" nc.out$n)
+ test "$lines" = 0 || ret=1
+ lines=$(grep -c "^Connection: close" nc.out$n)
+ test "$lines" = 1 || ret=1
+else
+ echo_i "skipping test as nc not found"
+fi
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+n=$((n + 1))
+
+echo_i "Check HTTP/1.0 keep-alive ($n)"
+ret=0
+if [ -x "${NC}" ]; then
+ "${NC}" 10.53.0.3 "${EXTRAPORT1}" << EOF > nc.out$n || ret=1
+GET /xml/v3/status HTTP/1.0
+Connection: keep-alive
+
+GET /xml/v3/status HTTP/1.0
+
+EOF
+ # should be two responses
+ lines=$(grep -c "^<statistics version" nc.out$n)
+ test "$lines" = 2 || ret=1
+ # first response has keep-alive, second has close
+ lines=$(grep -c "^Connection: Keep-Alive" nc.out$n)
+ test "$lines" = 1 || ret=1
+ lines=$(grep -c "^Connection: close" nc.out$n)
+ test "$lines" = 1 || ret=1
+else
+ echo_i "skipping test as nc not found"
+fi
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+n=$((n + 1))
+
+echo_i "Check inconsistent Connection: headers ($n)"
+ret=0
+if [ -x "${NC}" ]; then
+ "${NC}" 10.53.0.3 "${EXTRAPORT1}" << EOF > nc.out$n || ret=1
+GET /xml/v3/status HTTP/1.0
+Connection: keep-alive
+Connection: close
+
+GET /xml/v3/status HTTP/1.0
+
+EOF
+ # should be one response (second is ignored)
+ lines=$(grep -c "^<statistics version" nc.out$n)
+ test "$lines" = 1 || ret=1
+ # no keep-alive, one close
+ lines=$(grep -c "^Connection: Keep-Alive" nc.out$n)
+ test "$lines" = 0 || ret=1
+ lines=$(grep -c "^Connection: close" nc.out$n)
+ test "$lines" = 1 || ret=1
else
echo_i "skipping test as nc not found"
fi
#define HTTP_HEADERS_NUM 100
#define HTTP_MAX_REQUEST_LEN 4096
-#define HTTPD_CLOSE 0x0001 /* Got a Connection: close header */
-#define HTTPD_FOUNDHOST 0x0002 /* Got a Host: header */
-#define HTTPD_KEEPALIVE 0x0004 /* Got a Connection: Keep-Alive */
-#define HTTPD_ACCEPT_DEFLATE 0x0008
+typedef enum httpd_flags {
+ CONNECTION_CLOSE = 1 << 0, /* connection must close */
+ CONNECTION_KEEP_ALIVE = 1 << 1, /* response needs a keep-alive header */
+ ACCEPT_DEFLATE = 1 << 2, /* response can be compressed */
+} httpd_flags_t;
#define HTTPD_MAGIC ISC_MAGIC('H', 't', 'p', 'd')
#define VALID_HTTPD(m) ISC_MAGIC_VALID(m, HTTPD_MAGIC)
isc_nmhandle_t *handle; /* Permanent pointer to handle */
isc_nmhandle_t *readhandle; /* Waiting for a read callback */
- int flags;
-
/*%
* Received data state.
*/
method_t method;
int minor_version;
+ httpd_flags_t flags;
const char *path;
isc_url_parser_t up;
isc_time_t if_modified_since;
}
httpd->path = path;
+ /*
+ * Examine headers that can affect this request's response
+ */
+ httpd->flags = 0;
+
ssize_t content_len = 0;
bool keep_alive = false;
+ bool host_header = false;
isc_time_set(&httpd->if_modified_since, 0, 0);
return (ISC_R_RANGE);
}
} else if (name_match(header, "Connection")) {
+ /* multiple fields in a connection header are allowed */
if (value_match(header, "close")) {
- httpd->flags |= HTTPD_CLOSE;
- } else if (value_match(header, "keep-alive")) {
+ httpd->flags |= CONNECTION_CLOSE;
+ }
+ if (value_match(header, "keep-alive")) {
keep_alive = true;
}
} else if (name_match(header, "Host")) {
- httpd->flags |= HTTPD_FOUNDHOST;
+ host_header = true;
} else if (name_match(header, "Accept-Encoding")) {
if (value_match(header, "deflate")) {
- httpd->flags |= HTTPD_ACCEPT_DEFLATE;
+ httpd->flags |= ACCEPT_DEFLATE;
}
} else if (name_match(header, "If-Modified-Since")) {
char timestamp[ISC_FORMATHTTPTIMESTAMP_SIZE + 1];
switch (httpd->minor_version) {
case 0:
- if (keep_alive == true) {
- httpd->flags |= HTTPD_KEEPALIVE;
+ /*
+ * RFC 9112 section 9.3 says close takes priority if
+ * keep-alive is also present
+ */
+ if ((httpd->flags & CONNECTION_CLOSE) == 0 && keep_alive) {
+ httpd->flags |= CONNECTION_KEEP_ALIVE;
} else {
- httpd->flags |= HTTPD_CLOSE;
+ httpd->flags |= CONNECTION_CLOSE;
}
break;
case 1:
- if ((httpd->flags & HTTPD_FOUNDHOST) == 0) {
+ if (!host_header) {
return (ISC_R_RANGE);
}
break;
}
#ifdef HAVE_ZLIB
- if ((httpd->flags & HTTPD_ACCEPT_DEFLATE) != 0) {
+ if ((httpd->flags & ACCEPT_DEFLATE) != 0) {
result = httpd_compress(req);
if (result == ISC_R_SUCCESS) {
is_compressed = true;
#endif /* ifdef HAVE_ZLIB */
httpd_response(httpd, req);
- if ((httpd->flags & HTTPD_KEEPALIVE) != 0) {
+ /* RFC 9112 ยง 9.6: SHOULD send Connection: close in last response */
+ if ((httpd->flags & CONNECTION_CLOSE) != 0) {
+ httpd_addheader(req, "Connection", "close");
+ } else if ((httpd->flags & CONNECTION_KEEP_ALIVE) != 0) {
httpd_addheader(req, "Connection", "Keep-Alive");
}
httpd_addheader(req, "Content-Type", req->mimetype);
return;
}
+ /* XXXFANF it would be more polite to reply 400 bad request */
if (result != ISC_R_SUCCESS) {
goto close_readhandle;
}
goto detach;
}
- if (eresult == ISC_R_SUCCESS && (httpd->flags & HTTPD_CLOSE) == 0) {
+ if (eresult == ISC_R_SUCCESS && (httpd->flags & CONNECTION_CLOSE) == 0)
+ {
/*
* Calling httpd_request() with region NULL restarts
* reading.