Skip to content

Commit

Permalink
Move Location: handling into ne_request, mostly obsoleting the
Browse files Browse the repository at this point in the history
ne_redirect.h API. Add ne_get_response_location(). Set descriptive
errors for 3xx responses with a Location header. NE_REDIRECT
is returned for any 3xx response if ne_redirect API is used.

* src/ne_request.c (ne_get_response_location): New function.
  (ne_end_request): Set descriptive error for a redirect.

* src/ne_redirect.c (create, post_send, free_redirect,
  ne_redirect_register): Rewritten as a simple cache of the URI
  returned by ne_get_response_location. Handle any 3xx response.

* src/ne_redirect.h: Note API is deprecated.

* src/ne_request.h: Describe ne_get_response_location.

* test/request.c (redirect_error): New test.

* test/redirect.c (serve_redir): Removed.
  (check_redir, redirects): Simplify, and enhance to test
  ne_get_response_location() directly. Add more relative URI
  checks and fragment handling.
  • Loading branch information
notroj committed Apr 27, 2024
1 parent 1a3e46d commit 2931acd
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 124 deletions.
87 changes: 20 additions & 67 deletions src/ne_redirect.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
HTTP-redirect support
Copyright (C) 1999-2021, Joe Orton <joe@manyfish.co.uk>
Copyright (C) 1999-2024, Joe Orton <joe@manyfish.co.uk>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
Expand Down Expand Up @@ -39,89 +39,45 @@
#define REDIRECT_ID "http://www.webdav.org/neon/hooks/http-redirect"

struct redirect {
char *requri;
int valid; /* non-zero if .uri contains a redirect */
ne_uri uri;
ne_session *sess;
ne_uri *uri;
};

static void
create(ne_request *req, void *session, const char *method, const char *uri)
#define uri_free_clear(r_) do { if ((r_)->uri) { ne_uri_free((r_)->uri); ne_free((r_)->uri); (r_)->uri = NULL; }} while (0)

static void create(ne_request *req, void *userdata,
const char *method, const char *target)
{
struct redirect *red = session;
if (red->requri) ne_free(red->requri);
red->requri = ne_strdup(uri);
}
struct redirect *red = userdata;

#define REDIR(n) ((n) == 301 || (n) == 302 || (n) == 303 || \
(n) == 307)
uri_free_clear(red);
}

static int post_send(ne_request *req, void *private, const ne_status *status)
static int post_send(ne_request *req, void *userdata, const ne_status *status)
{
struct redirect *red = private;
const char *location = ne_get_response_header(req, "Location");
ne_buffer *path = NULL;
int ret;

/* Don't do anything for non-redirect status or no Location header. */
if (!REDIR(status->code) || location == NULL)
return NE_OK;

if (strstr(location, "://") == NULL && location[0] != '/') {
char *pnt;

path = ne_buffer_create();
ne_buffer_zappend(path, red->requri);
pnt = strrchr(path->data, '/');

if (pnt && pnt[1] != '\0') {
/* Chop off last path segment. */
pnt[1] = '\0';
ne_buffer_altered(path);
}
ne_buffer_zappend(path, location);
location = path->data;
}
struct redirect *red = userdata;
ne_uri *loc = ne_get_response_location(req, NULL);

/* free last uri. */
ne_uri_free(&red->uri);

/* Parse the Location header */
if (ne_uri_parse(location, &red->uri) || red->uri.path == NULL) {
red->valid = 0;
ne_set_error(red->sess, _("Could not parse redirect destination URL"));
ret = NE_ERROR;
} else {
/* got a valid redirect. */
red->valid = 1;
ret = NE_REDIRECT;

if (!red->uri.host) {
/* Not an absoluteURI: breaks 2616 but everybody does it. */
ne_fill_server_uri(red->sess, &red->uri);
}
}
uri_free_clear(red);

if (path) ne_buffer_destroy(path);
if (status->klass != 3 || loc == NULL) {
return NE_OK;
}

return ret;
red->uri = loc;
return NE_REDIRECT;
}

static void free_redirect(void *cookie)
{
struct redirect *red = cookie;
ne_uri_free(&red->uri);
if (red->requri)
ne_free(red->requri);
uri_free_clear(red);
ne_free(red);
}

void ne_redirect_register(ne_session *sess)
{
struct redirect *red = ne_calloc(sizeof *red);

red->sess = sess;

ne_hook_create_request(sess, create, red);
ne_hook_post_send(sess, post_send, red);
ne_hook_destroy_session(sess, free_redirect, red);
Expand All @@ -133,9 +89,6 @@ const ne_uri *ne_redirect_location(ne_session *sess)
{
struct redirect *red = ne_get_session_private(sess, REDIRECT_ID);

if (red && red->valid)
return &red->uri;
else
return NULL;
return red ? red->uri : NULL;
}

51 changes: 49 additions & 2 deletions src/ne_request.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
HTTP request/response handling
Copyright (C) 1999-2021, Joe Orton <joe@manyfish.co.uk>
Copyright (C) 1999-2024, Joe Orton <joe@manyfish.co.uk>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
Expand Down Expand Up @@ -92,6 +92,7 @@ struct field {
#define HH_HV_PROXY_CONNECTION (0x1A)
#define HH_HV_CONTENT_LENGTH (0x13)
#define HH_HV_TRANSFER_ENCODING (0x07)
#define HH_HV_LOCATION (0x05)

struct ne_request_s {
char *method, *target; /* method and request-target */
Expand Down Expand Up @@ -720,6 +721,43 @@ 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;

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

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

/* 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;

ret = ne_malloc(sizeof *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);
ne_uri_free(&dest);

return ret;
}

void ne_add_response_body_reader(ne_request *req, ne_accept_response acpt,
ne_block_reader rdr, void *userdata)
{
Expand Down Expand Up @@ -1459,7 +1497,16 @@ int ne_end_request(ne_request *req)
ne_post_send_fn fn = (ne_post_send_fn)hk->fn;
ret = fn(req, hk->userdata, &req->status);
}


if (ret == NE_OK && req->status.klass == 3) {
const char *dest;

dest = get_response_header_hv(req, HH_HV_LOCATION, "location");
if (dest) {
ne_set_error(req->session, _("Redirected to %s"), dest);
}
}

/* Close the connection if persistent connections are disabled or
* not supported by the server. */
if (!req->session->flags[NE_SESSFLAG_PERSIST] || !req->can_persist)
Expand Down
11 changes: 10 additions & 1 deletion src/ne_request.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
HTTP Request Handling
Copyright (C) 1999-2021, Joe Orton <joe@manyfish.co.uk>
Copyright (C) 1999-2024, Joe Orton <joe@manyfish.co.uk>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
Expand Down Expand Up @@ -166,6 +166,15 @@ void ne_print_request_header(ne_request *req, const char *name,
const char *format, ...)
ne_attribute((format(printf, 3, 4)));

/* If the response includes a Location header, this function parses
* and resolves the URI-references relative to the request target URI.
* If a fragment ("#fragment") is used for the request target, it can
* be passed as an argument to allow relative resolution. Returns a
* malloc-allocated ne_uri object, or NULL if the URI in the Location
* header could not be parsed, or the Location header was not
* present. */
ne_uri *ne_get_response_location(ne_request *req, const char *fragment);

/* ne_request_dispatch: Sends the given request, and reads the
* response. Returns:
* - NE_OK if the request was sent and response read successfully
Expand Down
Loading

0 comments on commit 2931acd

Please sign in to comment.