Skip to content

Commit

Permalink
Rather than appling a maximum count, apply an overall timeout to
Browse files Browse the repository at this point in the history
reading interim responses to match the read timeout, if a read timeout
is configured and the NE_SESSFLAG_1XXTIMEOUT flag is set - which
defaults to set (issue #94):

* src/ne_session.h (NE_SESSFLAG_1XXTIMEOUT): New flag.

* src/ne_session.c (ne_session_create): Set NE_SESSFLAG_1XXTIMEOUT to
  default on.

* src/ne_request.c (send_request): Use the session read timeout to
  avoid looping reading interim responses indefinitely, rather than
  hard-coding a maximum number of responses.

* test/request.c (fail_request_with_error): Set read timeout.
  (fail_excess_1xx): Adjust expected error, send more interim
  responses.
  • Loading branch information
notroj committed Jan 27, 2024
1 parent 3e71541 commit f4cf972
Show file tree
Hide file tree
Showing 4 changed files with 28 additions and 14 deletions.
28 changes: 16 additions & 12 deletions src/ne_request.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <time.h>

#include "ne_internal.h"

Expand Down Expand Up @@ -78,8 +79,6 @@ struct field {
struct field *next;
};

/* Maximum number of interim responses. */
#define MAX_INTERIM_RESPONSES (128)
/* Maximum number of header fields per response: */
#define MAX_HEADER_FIELDS (100)
/* Size of hash table; 43 is the smallest prime for which the common
Expand Down Expand Up @@ -1016,6 +1015,10 @@ static int read_status_line(ne_request *req, ne_status *status, int retry)
return 0;
}

#define INTERIM_TIMEOUT(sess_) \
(((sess_)->flags[NE_SESSFLAG_1XXTIMEOUT] && (sess_)->rdtimeout) \
? time(NULL) + (sess_)->rdtimeout : 0)

/* Send the request, and read the response Status-Line. Returns:
* NE_RETRY connection closed by server; persistent connection
* timeout
Expand All @@ -1030,7 +1033,7 @@ static int send_request(ne_request *req, const ne_buffer *request)
ne_status *const status = &req->status;
int sentbody = 0; /* zero until body has been sent. */
int ret, retry; /* retry non-zero whilst the request should be retried */
unsigned count;
time_t timeout = INTERIM_TIMEOUT(sess);
ssize_t sret;

/* Send the Request-Line and headers */
Expand Down Expand Up @@ -1061,13 +1064,11 @@ static int send_request(ne_request *req, const ne_buffer *request)

/* Loop eating interim 1xx responses; RFC 7231§6.2 says clients
* MUST be able to parse unsolicited interim responses. */
for (count = 0; count < MAX_INTERIM_RESPONSES
&& (ret = read_status_line(req, status, retry)) == NE_OK
&& status->klass == 1; count++) {
while ((ret = read_status_line(req, status, retry)) == NE_OK
&& status->klass == 1) {
struct interim_handler *ih;

NE_DEBUG(NE_DBG_HTTP, "[req] Interim %d response %d.\n",
status->code, count);
NE_DEBUG(NE_DBG_HTTP, "[req] Interim %d response.\n", status->code);
retry = 0; /* successful read() => never retry now. */

/* Discard headers with the interim response. */
Expand All @@ -1083,11 +1084,14 @@ static int send_request(ne_request *req, const ne_buffer *request)
/* Send the body after receiving the first 100 Continue */
if ((ret = send_request_body(req, 0)) != NE_OK) break;
sentbody = 1;
/* Reset read timeout. */
timeout = INTERIM_TIMEOUT(sess);
}
}

if (count == MAX_INTERIM_RESPONSES) {
return aborted(req, _("Too many interim responses"), 0);
else if (sess->flags[NE_SESSFLAG_1XXTIMEOUT] && sess->rdtimeout
&& time(NULL) > timeout) {
NE_DEBUG(NE_DBG_HTTP, "[req] Timeout after %d\n", sess->rdtimeout);
return aborted(req, _("Timed out reading interim responses"), 0);
}
}

/* Per RFC 9110ẞ15.5.9 a client MAY retry an outstanding request
Expand Down
1 change: 1 addition & 0 deletions src/ne_session.c
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ ne_session *ne_session_create(const char *scheme,

/* Set flags which default to on: */
sess->flags[NE_SESSFLAG_PERSIST] = 1;
sess->flags[NE_SESSFLAG_1XXTIMEOUT] = 1;

#ifdef NE_ENABLE_AUTO_LIBPROXY
ne_session_system_proxy(sess, 0);
Expand Down
4 changes: 4 additions & 0 deletions src/ne_session.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ typedef enum ne_session_flag_e {
* to improve interoperability with
* SharePoint */

NE_SESSFLAG_1XXTIMEOUT, /* disable this flag to apply no overall
* timeout when reading interim
* responses. */

NE_SESSFLAG_LAST /* enum sentinel value */
} ne_session_flag;

Expand Down
9 changes: 7 additions & 2 deletions test/request.c
Original file line number Diff line number Diff line change
Expand Up @@ -1057,6 +1057,10 @@ static int fail_request_with_error(int with_body, server_fn fn, void *ud,

CALL(new_spawn_server(forever ? 100 : 1, fn, ud, &port));
sess = ne_session_create("http", "localhost", port);

/* Set default timeout, required by e.g. fail_excess_1xx. */
ne_set_read_timeout(sess, 2);

req = ne_request_create(sess, "GET", "/");

if (with_body) {
Expand Down Expand Up @@ -2267,11 +2271,12 @@ static int safe_flags(void)
return OK;
}

/* Hit the timeout (2 seconds) for reading interim responses. */
static int fail_excess_1xx(void)
{
struct s1xx_args args = {200, 0};
struct s1xx_args args = {2000000, 0};
return fail_request_with_error(0, serve_1xx, &args, 0,
"Too many interim responses");
"Timed out reading interim responses");
}

static int serve_check_reqline(ne_socket *sock, void *userdata)
Expand Down

0 comments on commit f4cf972

Please sign in to comment.