Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add request target API and use it in Location handling #165

Merged
merged 3 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 64 additions & 11 deletions src/ne_request.c
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ struct ne_request_s {
struct interim_handler *interim_handler;

/*** Miscellaneous ***/
ne_uri *target_uri;
unsigned int method_is_head;
unsigned int can_persist;

Expand Down Expand Up @@ -556,6 +557,49 @@ ne_request *ne_request_create(ne_session *sess, const char *method,
return req;
}

/* Reconstruct the request target URI following RFC 9112ẞ3.3. Returns
* zero on success or non-zero on error. */
static int get_request_target_uri(ne_request *req, ne_uri *uri)
{
if (strcmp(req->target, "*") == 0
|| strcmp(req->method, "CONNECT") == 0) {
/* asterisk-form or authority-form. Since neon only ever uses
* authority-form with a CONNECT to the origin server (which
* is the session host) there is no need to re-parse
* req->target to extract it. */
ne_fill_server_uri(req->session, uri);
uri->path = ne_strdup("");
return 0;
}
else if (req->target[0] == '/') {
/* origin-form. */
ne_fill_server_uri(req->session, uri);
uri->path = ne_strdup(req->target);
return 0;
}
else {
/* absolute-form */
return ne_uri_parse(req->target, uri);
}
}

const ne_uri *ne_get_request_target(ne_request *req)
{
if (req->target_uri == NULL) {
ne_uri *uri = ne_calloc(sizeof *uri);

if (get_request_target_uri(req, uri) == 0) {
req->target_uri = uri;
}
else {
ne_uri_free(uri);
ne_free(uri);
}
}

return req->target_uri;
}

/* Set the request body length to 'length' */
static void set_body_length(ne_request *req, ne_off_t length)
{
Expand Down Expand Up @@ -724,35 +768,40 @@ static void free_response_headers(ne_request *req)
ne_uri *ne_get_response_location(ne_request *req, const char *fragment)
{
const char *location;
ne_uri dest, base, *ret;
ne_uri dest, *ret = NULL;
const ne_uri *base;

location = get_response_header_hv(req, HH_HV_LOCATION, "location");
if (location == NULL)
return NULL;

memset(&dest, 0, sizeof dest);

/* Location is a URI-reference (RFC9110ẞ10.2.2) relative to the
* request target URI; determine each of these then resolve. */

/* Parse the Location header */
if (ne_uri_parse(location, &dest) || !dest.path) {
ne_set_error(req->session, _("Could not parse redirect "
ne_set_error(req->session, _("Could not parse redirect "
"destination URL"));
return NULL;
goto fail;
}

/* Location is a URI-reference (RFC9110ẞ10.2.2) relative to the
* request target URI; create that base URI: */
memset(&base, 0, sizeof base);
ne_fill_server_uri(req->session, &base);
base.path = req->target;
if ((base = ne_get_request_target(req)) == NULL) {
ne_set_error(req->session, _("Could not parse request "
"target URI"));
goto fail;
}

ret = ne_malloc(sizeof *ret);
ne_uri_resolve(&base, &dest, ret);
ne_uri_resolve(base, &dest, ret);

/* HTTP-specific fragment handling is a MUST in RFC9110ẞ10.2.2: */
if (fragment && !dest.fragment) {
ret->fragment = ne_strdup(fragment);
}

base.path = NULL; /* owned by ne_request object, don't free */
ne_uri_free(&base);
fail:
ne_uri_free(&dest);

return ret;
Expand Down Expand Up @@ -788,6 +837,10 @@ void ne_request_destroy(ne_request *req)

ne_free(req->target);
ne_free(req->method);
if (req->target_uri) {
ne_uri_free(req->target_uri);
ne_free(req->target_uri);
}

for (rdr = req->body_readers; rdr != NULL; rdr = next_rdr) {
next_rdr = rdr->next;
Expand Down
5 changes: 5 additions & 0 deletions src/ne_request.h
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,11 @@ void ne_print_request_header(ne_request *req, const char *name,
const char *format, ...)
ne_attribute((format(printf, 3, 4)));

/* Returns the request target URI as a malloc-allocated ne_uri object,
* or NULL on error if the request target cannot be determined. The
* session error string is not changed on error. */
const ne_uri *ne_get_request_target(ne_request *req);

/* If the response includes a Location header, this function parses
* and resolves the URI-reference relative to the request target. If
* a fragment ("#fragment") is used for the request target, it can be
Expand Down
1 change: 1 addition & 0 deletions src/neon.vers
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ NEON_0_33 {

NEON_0_34 {
ne_get_response_location;
ne_get_request_target;
ne_iaddr_set_scope;
ne_iaddr_get_scope;
};
Expand Down
4 changes: 4 additions & 0 deletions test/redirect.c
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ static int redirects(void)
/* all 3xx should get NE_REDIRECT. */
{PATH, 399, DEST, DEST, NULL},

/* Handling of various request-target cases. */
{"*", 307, "/fish#food", "/fish#food", NULL},
{"ftp://example.com/fish", 307, "/fish#food", "ftp://example.com/fish#food", NULL},

/* More relative URIs: */
{"/blah", 307, "//example.com:8080/fish#food", "http://example.com:8080/fish#food", NULL},
{"/blah", 307, "#food", "/blah#food", NULL},
Expand Down
45 changes: 45 additions & 0 deletions test/request.c
Original file line number Diff line number Diff line change
Expand Up @@ -2435,6 +2435,50 @@ static int ipv6_literal(void)
return destroy_and_wait(sess);
}

static int targets(void)
{
struct {
const char *scheme;
const char *host;
int port;
const char *method;
const char *target;
const char *expected;
} ts[] = {
{ "http", "example.com", 80, "GET", "/fish", "http://example.com/fish" },
{ "http", "example.com", 8080, "GET", "/fish", "http://example.com:8080/fish" },
{ "https", "example.com", 443, "GET", "/", "https://example.com/" },
{ "http", "proxy.example.com", 80, "GET", "ftp://example.com/fishfood", "ftp://example.com/fishfood" },
{ "https", "example.com", 443, "OPTIONS", "*", "https://example.com" },
{ NULL }
};
unsigned n;

for (n = 0; ts[n].scheme != NULL; n++ ) {
const ne_uri *uri, *uri2;
ne_session *sess;
ne_request *req;
char *actual;

sess = ne_session_create(ts[n].scheme, ts[n].host, ts[n].port);
req = ne_request_create(sess, ts[n].method, ts[n].target);
uri = ne_get_request_target(req);
uri2 = ne_get_request_target(req);
actual = uri ? ne_uri_unparse(uri) : NULL;

ONCMP(ts[n].expected, actual, "request target", "URI");

ONN("caching failed, different rv on second call", uri != uri2);

if (actual) ne_free(actual);

ne_request_destroy(req);
ne_session_destroy(sess);
}

return OK;
}

/* TODO: test that ne_set_notifier(, NULL, NULL) DTRT too. */

ne_test tests[] = {
Expand Down Expand Up @@ -2511,5 +2555,6 @@ ne_test tests[] = {
T(dont_retry_408),
T(ipv6_literal),
T(redirect_error),
T(targets),
T(NULL)
};
Loading