X-Git-Url: https://arthur.barton.de/cgi-bin/gitweb.cgi?p=netatalk.git;a=blobdiff_plain;f=libevent%2Fhttp.c;h=b9687df67196903938fd58d493bcc949d341645f;hp=f8de3b37f808e32cf85fd118cb1e40f410e035ca;hb=d2968df026cd9971caeab102f9348152b08b9132;hpb=caad56fb97ea14d8b64df8879f68fe82175aa54f diff --git a/libevent/http.c b/libevent/http.c index f8de3b37..b9687df6 100644 --- a/libevent/http.c +++ b/libevent/http.c @@ -1,6 +1,6 @@ /* * Copyright (c) 2002-2007 Niels Provos - * Copyright (c) 2007-2010 Niels Provos and Nick Mathewson + * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -56,6 +56,9 @@ #ifdef _EVENT_HAVE_NETINET_IN_H #include #endif +#ifdef _EVENT_HAVE_ARPA_INET_H +#include +#endif #ifdef _EVENT_HAVE_NETDB_H #include #endif @@ -97,6 +100,7 @@ #include "util-internal.h" #include "http-internal.h" #include "mm-internal.h" +#include "bufferevent-internal.h" #ifndef _EVENT_HAVE_GETNAMEINFO #define NI_MAXSERV 32 @@ -215,29 +219,30 @@ strsep(char **s, const char *del) } #endif -static const char * -html_replace(char ch, char *buf) +static size_t +html_replace(const char ch, const char **escaped) { switch (ch) { case '<': - return "<"; + *escaped = "<"; + return 4; case '>': - return ">"; + *escaped = ">"; + return 4; case '"': - return """; + *escaped = """; + return 6; case '\'': - return "'"; + *escaped = "'"; + return 6; case '&': - return "&"; + *escaped = "&"; + return 5; default: break; } - /* Echo the character back */ - buf[0] = ch; - buf[1] = '\0'; - - return buf; + return 1; } /* @@ -251,21 +256,34 @@ char * evhttp_htmlescape(const char *html) { size_t i; - size_t new_size = 0, old_size = strlen(html); + size_t new_size = 0, old_size = 0; char *escaped_html, *p; - char scratch_space[2]; - for (i = 0; i < old_size; ++i) - new_size += strlen(html_replace(html[i], scratch_space)); + if (html == NULL) + return (NULL); + old_size = strlen(html); + for (i = 0; i < old_size; ++i) { + const char *replaced = NULL; + const size_t replace_size = html_replace(html[i], &replaced); + if (replace_size > EV_SIZE_MAX - new_size) { + event_warn("%s: html_replace overflow", __func__); + return (NULL); + } + new_size += replace_size; + } + + if (new_size == EV_SIZE_MAX) + return (NULL); p = escaped_html = mm_malloc(new_size + 1); if (escaped_html == NULL) { - event_warn("%s: malloc(%ld)", __func__, (long)(new_size + 1)); + event_warn("%s: malloc(%lu)", __func__, + (unsigned long)(new_size + 1)); return (NULL); } for (i = 0; i < old_size; ++i) { - const char *replaced = html_replace(html[i], scratch_space); - size_t len = strlen(replaced); + const char *replaced = &html[i]; + const size_t len = html_replace(html[i], &replaced); memcpy(p, replaced, len); p += len; } @@ -441,8 +459,8 @@ evhttp_make_header_request(struct evhttp_connection *evcon, if ((req->type == EVHTTP_REQ_POST || req->type == EVHTTP_REQ_PUT) && evhttp_find_header(req->output_headers, "Content-Length") == NULL){ char size[22]; - evutil_snprintf(size, sizeof(size), "%ld", - (long)evbuffer_get_length(req->output_buffer)); + evutil_snprintf(size, sizeof(size), EV_SIZE_FMT, + EV_SIZE_ARG(evbuffer_get_length(req->output_buffer))); evhttp_add_header(req->output_headers, "Content-Length", size); } } @@ -500,12 +518,13 @@ evhttp_maybe_add_date_header(struct evkeyvalq *headers) * unless it already has a content-length or transfer-encoding header. */ static void evhttp_maybe_add_content_length_header(struct evkeyvalq *headers, - long content_length) /* XXX use size_t or int64, not long. */ + size_t content_length) { if (evhttp_find_header(headers, "Transfer-Encoding") == NULL && evhttp_find_header(headers, "Content-Length") == NULL) { char len[22]; - evutil_snprintf(len, sizeof(len), "%ld", content_length); + evutil_snprintf(len, sizeof(len), EV_SIZE_FMT, + EV_SIZE_ARG(content_length)); evhttp_add_header(headers, "Content-Length", len); } } @@ -545,7 +564,7 @@ evhttp_make_header_response(struct evhttp_connection *evcon, */ evhttp_maybe_add_content_length_header( req->output_headers, - (long)evbuffer_get_length(req->output_buffer)); + evbuffer_get_length(req->output_buffer)); } } @@ -822,9 +841,23 @@ evhttp_connection_done(struct evhttp_connection *evcon) static enum message_read_status evhttp_handle_chunked_read(struct evhttp_request *req, struct evbuffer *buf) { - ev_ssize_t len; + if (req == NULL || buf == NULL) { + return DATA_CORRUPTED; + } + + while (1) { + size_t buflen; + + if ((buflen = evbuffer_get_length(buf)) == 0) { + break; + } + + /* evbuffer_get_length returns size_t, but len variable is ssize_t, + * check for overflow conditions */ + if (buflen > EV_SSIZE_MAX) { + return DATA_CORRUPTED; + } - while ((len = evbuffer_get_length(buf)) > 0) { if (req->ntoread < 0) { /* Read chunk size */ ev_int64_t ntoread; @@ -847,11 +880,18 @@ evhttp_handle_chunked_read(struct evhttp_request *req, struct evbuffer *buf) /* could not get chunk size */ return (DATA_CORRUPTED); } + + /* ntoread is signed int64, body_size is unsigned size_t, check for under/overflow conditions */ + if ((ev_uint64_t)ntoread > EV_SIZE_MAX - req->body_size) { + return DATA_CORRUPTED; + } + if (req->body_size + (size_t)ntoread > req->evcon->max_body_size) { /* failed body length test */ event_debug(("Request body is too long")); return (DATA_TOO_LONG); } + req->body_size += (size_t)ntoread; req->ntoread = ntoread; if (req->ntoread == 0) { @@ -861,12 +901,17 @@ evhttp_handle_chunked_read(struct evhttp_request *req, struct evbuffer *buf) continue; } + /* req->ntoread is signed int64, len is ssize_t, based on arch, + * ssize_t could only be 32b, check for these conditions */ + if (req->ntoread > EV_SSIZE_MAX) { + return DATA_CORRUPTED; + } + /* don't have enough to complete a chunk; wait for more */ - if (len < req->ntoread) + if (req->ntoread > 0 && buflen < (ev_uint64_t)req->ntoread) return (MORE_DATA_EXPECTED); /* Completed chunk */ - /* XXXX fixme: what if req->ntoread is > SIZE_T_MAX? */ evbuffer_remove_buffer(buf, req->input_buffer, (size_t)req->ntoread); req->ntoread = -1; if (req->chunk_cb != NULL) { @@ -934,13 +979,19 @@ evhttp_read_body(struct evhttp_connection *evcon, struct evhttp_request *req) } } else if (req->ntoread < 0) { /* Read until connection close. */ + if ((size_t)(req->body_size + evbuffer_get_length(buf)) < req->body_size) { + evhttp_connection_fail(evcon, EVCON_HTTP_INVALID_HEADER); + return; + } + req->body_size += evbuffer_get_length(buf); evbuffer_add_buffer(req->input_buffer, buf); - } else if (req->chunk_cb != NULL || - evbuffer_get_length(buf) >= (size_t)req->ntoread) { + } else if (req->chunk_cb != NULL || evbuffer_get_length(buf) >= (size_t)req->ntoread) { + /* XXX: the above get_length comparison has to be fixed for overflow conditions! */ /* We've postponed moving the data until now, but we're * about to use it. */ size_t n = evbuffer_get_length(buf); + if (n > (size_t) req->ntoread) n = (size_t) req->ntoread; req->ntoread -= n; @@ -948,7 +999,10 @@ evhttp_read_body(struct evhttp_connection *evcon, struct evhttp_request *req) evbuffer_remove_buffer(buf, req->input_buffer, n); } - if (req->body_size > req->evcon->max_body_size) { + if (req->body_size > req->evcon->max_body_size || + (!req->chunked && req->ntoread >= 0 && + (size_t)req->ntoread > req->evcon->max_body_size)) { + /* XXX: The above casted comparison must checked for overflow */ /* failed body length test */ event_debug(("Request body is too long")); evhttp_connection_fail(evcon, @@ -1015,9 +1069,25 @@ evhttp_read_cb(struct bufferevent *bufev, void *arg) case EVCON_READING_TRAILER: evhttp_read_trailer(evcon, req); break; + case EVCON_IDLE: + { +#ifdef USE_DEBUG + struct evbuffer *input; + size_t total_len; + + input = bufferevent_get_input(evcon->bufev); + total_len = evbuffer_get_length(input); + event_debug(("%s: read "EV_SIZE_FMT + " bytes in EVCON_IDLE state," + " resetting connection", + __func__, EV_SIZE_ARG(total_len))); +#endif + + evhttp_connection_reset(evcon); + } + break; case EVCON_DISCONNECTED: case EVCON_CONNECTING: - case EVCON_IDLE: case EVCON_WRITING: default: event_errx(1, "%s: illegal connection state %d", @@ -1110,7 +1180,7 @@ evhttp_connection_set_local_address(struct evhttp_connection *evcon, if (evcon->bind_address) mm_free(evcon->bind_address); if ((evcon->bind_address = mm_strdup(address)) == NULL) - event_err(1, "%s: strdup", __func__); + event_warn("%s: strdup", __func__); } void @@ -1151,7 +1221,18 @@ evhttp_connection_reset(struct evhttp_connection *evcon) { struct evbuffer *tmp; - bufferevent_disable(evcon->bufev, EV_READ|EV_WRITE); + /* XXXX This is not actually an optimal fix. Instead we ought to have + an API for "stop connecting", or use bufferevent_setfd to turn off + connecting. But for Libevent 2.0, this seems like a minimal change + least likely to disrupt the rest of the bufferevent and http code. + + Why is this here? If the fd is set in the bufferevent, and the + bufferevent is connecting, then you can't actually stop the + bufferevent from trying to connect with bufferevent_disable(). The + connect will never trigger, since we close the fd, but the timeout + might. That caused an assertion failure in evhttp_connection_fail. + */ + bufferevent_disable_hard(evcon->bufev, EV_READ|EV_WRITE); if (evcon->fd != -1) { /* inform interested parties about connection close */ @@ -1200,6 +1281,8 @@ evhttp_connection_retry(evutil_socket_t fd, short what, void *arg) static void evhttp_connection_cb_cleanup(struct evhttp_connection *evcon) { + struct evcon_requestq requests; + if (evcon->retry_max < 0 || evcon->retry_cnt < evcon->retry_max) { evtimer_assign(&evcon->retry_ev, evcon->base, evhttp_connection_retry, evcon); /* XXXX handle failure from evhttp_add_event */ @@ -1211,10 +1294,23 @@ evhttp_connection_cb_cleanup(struct evhttp_connection *evcon) } evhttp_connection_reset(evcon); - /* for now, we just signal all requests by executing their callbacks */ + /* + * User callback can do evhttp_make_request() on the same + * evcon so new request will be added to evcon->requests. To + * avoid freeing it prematurely we iterate over the copy of + * the queue. + */ + TAILQ_INIT(&requests); while (TAILQ_FIRST(&evcon->requests) != NULL) { struct evhttp_request *request = TAILQ_FIRST(&evcon->requests); TAILQ_REMOVE(&evcon->requests, request, next); + TAILQ_INSERT_TAIL(&requests, request, next); + } + + /* for now, we just signal all requests by executing their callbacks */ + while (TAILQ_FIRST(&requests) != NULL) { + struct evhttp_request *request = TAILQ_FIRST(&requests); + TAILQ_REMOVE(&requests, request, next); request->evcon = NULL; /* we might want to set an error here */ @@ -1373,7 +1469,7 @@ evhttp_parse_http_version(const char *version, struct evhttp_request *req) int major, minor; char ch; int n = sscanf(version, "HTTP/%d.%d%c", &major, &minor, &ch); - if (n > 2 || major > 1) { + if (n != 2 || major > 1) { event_debug(("%s: bad version %s on message %p from %s", __func__, version, req, req->remote_host)); return (-1); @@ -1472,7 +1568,8 @@ evhttp_parse_request_line(struct evhttp_request *req, char *line) return (-1); } - if ((req->uri_elems = evhttp_uri_parse(req->uri)) == NULL) { + if ((req->uri_elems = evhttp_uri_parse_with_flags(req->uri, + EVHTTP_URI_NONCONFORMANT)) == NULL) { return -1; } @@ -1731,7 +1828,8 @@ evhttp_parse_headers(struct evhttp_request *req, struct evbuffer* buffer) } if (status == MORE_DATA_EXPECTED) { - if (req->headers_size + evbuffer_get_length(buffer) > req->evcon->max_headers_size) + if (req->evcon != NULL && + req->headers_size + evbuffer_get_length(buffer) > req->evcon->max_headers_size) return (DATA_TOO_LONG); } @@ -1774,9 +1872,9 @@ evhttp_get_body_length(struct evhttp_request *req) req->ntoread = ntoread; } - event_debug(("%s: bytes to read: %ld (in buffer %ld)\n", - __func__, (long)req->ntoread, - evbuffer_get_length(bufferevent_get_input(req->evcon->bufev)))); + event_debug(("%s: bytes to read: "EV_I64_FMT" (in buffer "EV_SIZE_FMT")\n", + __func__, EV_I64_ARG(req->ntoread), + EV_SIZE_ARG(evbuffer_get_length(bufferevent_get_input(req->evcon->bufev))))); return (0); } @@ -1846,11 +1944,12 @@ evhttp_get_body(struct evhttp_connection *evcon, struct evhttp_request *req) no, we should respond with an error. For now, just optimistically tell the client to send their message body. */ - if (req->ntoread > 0 && - (size_t)req->ntoread > req->evcon->max_body_size) { - evhttp_send_error(req, HTTP_ENTITYTOOLARGE, - NULL); - return; + if (req->ntoread > 0) { + /* ntoread is ev_int64_t, max_body_size is ev_uint64_t */ + if ((req->evcon->max_body_size <= EV_INT64_MAX) && (ev_uint64_t)req->ntoread > req->evcon->max_body_size) { + evhttp_send_error(req, HTTP_ENTITYTOOLARGE, NULL); + return; + } } if (!evbuffer_get_length(bufferevent_get_input(evcon->bufev))) evhttp_send_continue(evcon, req); @@ -2018,6 +2117,12 @@ evhttp_connection_base_new(struct event_base *base, struct evdns_base *dnsbase, return (NULL); } +struct bufferevent * +evhttp_connection_get_bufferevent(struct evhttp_connection *evcon) +{ + return evcon->bufev; +} + void evhttp_connection_set_base(struct evhttp_connection *evcon, struct event_base *base) @@ -2145,11 +2250,20 @@ evhttp_make_request(struct evhttp_connection *evcon, req->evcon = evcon; EVUTIL_ASSERT(!(req->flags & EVHTTP_REQ_OWN_CONNECTION)); - TAILQ_INSERT_TAIL(&evcon->requests, req, next); + TAILQ_INSERT_TAIL(&evcon->requests, req, next); /* If the connection object is not connected; make it so */ - if (!evhttp_connected(evcon)) - return (evhttp_connection_connect(evcon)); + if (!evhttp_connected(evcon)) { + int res = evhttp_connection_connect(evcon); + /* evhttp_connection_fail(), which is called through + * evhttp_connection_connect(), assumes that req lies in + * evcon->requests. Thus, enqueue the request in advance and r + * it in the error case. */ + if (res != 0) + TAILQ_REMOVE(&evcon->requests, req, next); + + return res; + } /* * If it's connected already and we are the first in the queue, @@ -2491,6 +2605,10 @@ evhttp_response_code(struct evhttp_request *req, int code, const char *reason) if (reason == NULL) reason = evhttp_response_phrase_internal(code); req->response_code_line = mm_strdup(reason); + if (req->response_code_line == NULL) { + event_warn("%s: strdup", __func__); + /* XXX what else can we do? */ + } } void @@ -3093,8 +3211,11 @@ evhttp_new_object(void) struct evhttp * evhttp_new(struct event_base *base) { - struct evhttp *http = evhttp_new_object(); + struct evhttp *http = NULL; + http = evhttp_new_object(); + if (http == NULL) + return (NULL); http->base = base; return (http); @@ -3107,8 +3228,11 @@ evhttp_new(struct event_base *base) struct evhttp * evhttp_start(const char *address, unsigned short port) { - struct evhttp *http = evhttp_new_object(); + struct evhttp *http = NULL; + http = evhttp_new_object(); + if (http == NULL) + return (NULL); if (evhttp_bind_socket(http, address, port) == -1) { mm_free(http); return (NULL); @@ -3280,6 +3404,11 @@ evhttp_set_cb(struct evhttp *http, const char *uri, } http_cb->what = mm_strdup(uri); + if (http_cb->what == NULL) { + event_warn("%s: strdup", __func__); + mm_free(http_cb); + return (-3); + } http_cb->cb = cb; http_cb->cbarg = cbarg; @@ -3768,6 +3897,7 @@ bind_socket(const char *address, ev_uint16_t port, int reuse) } struct evhttp_uri { + unsigned flags; char *scheme; /* scheme; e.g http, ftp etc */ char *userinfo; /* userinfo (typically username:pass), or NULL */ char *host; /* hostname, IP address, or NULL */ @@ -3786,7 +3916,13 @@ evhttp_uri_new(void) return uri; } -/* Return true of the string starting at s and ending immediately before eos +void +evhttp_uri_set_flags(struct evhttp_uri *uri, unsigned flags) +{ + uri->flags = flags; +} + +/* Return true if the string starting at s and ending immediately before eos * is a valid URI scheme according to RFC3986 */ static int @@ -3911,6 +4047,10 @@ parse_authority(struct evhttp_uri *uri, char *s, char *eos) EVUTIL_ASSERT(eos); if (eos == s) { uri->host = mm_strdup(""); + if (uri->host == NULL) { + event_warn("%s: strdup", __func__); + return -1; + } return 0; } @@ -3922,6 +4062,10 @@ parse_authority(struct evhttp_uri *uri, char *s, char *eos) return -1; *cp++ = '\0'; uri->userinfo = mm_strdup(s); + if (uri->userinfo == NULL) { + event_warn("%s: strdup", __func__); + return -1; + } } else { cp = s; } @@ -3949,6 +4093,10 @@ parse_authority(struct evhttp_uri *uri, char *s, char *eos) return -1; } uri->host = mm_malloc(eos-cp+1); + if (uri->host == NULL) { + event_warn("%s: malloc", __func__); + return -1; + } memcpy(uri->host, cp, eos-cp); uri->host[eos-cp] = '\0'; return 0; @@ -3966,13 +4114,41 @@ end_of_authority(char *cp) return cp; } +enum uri_part { + PART_PATH, + PART_QUERY, + PART_FRAGMENT +}; + /* Return the character after the longest prefix of 'cp' that matches... * *pchar / "/" if allow_qchars is false, or - * *(pchar / "/" / "?") if allow_chars is true. + * *(pchar / "/" / "?") if allow_qchars is true. */ static char * -end_of_path(char *cp, int allow_qchars) +end_of_path(char *cp, enum uri_part part, unsigned flags) { + if (flags & EVHTTP_URI_NONCONFORMANT) { + /* If NONCONFORMANT: + * Path is everything up to a # or ? or nul. + * Query is everything up a # or nul + * Fragment is everything up to a nul. + */ + switch (part) { + case PART_PATH: + while (*cp && *cp != '#' && *cp != '?') + ++cp; + break; + case PART_QUERY: + while (*cp && *cp != '#') + ++cp; + break; + case PART_FRAGMENT: + cp += strlen(cp); + break; + }; + return cp; + } + while (*cp) { if (CHAR_IS_UNRESERVED(*cp) || strchr(SUBDELIMS, *cp) || @@ -3981,7 +4157,7 @@ end_of_path(char *cp, int allow_qchars) else if (*cp == '%' && EVUTIL_ISXDIGIT(cp[1]) && EVUTIL_ISXDIGIT(cp[2])) cp += 3; - else if (*cp == '?' && allow_qchars) + else if (*cp == '?' && part != PART_PATH) ++cp; else return cp; @@ -4004,6 +4180,12 @@ path_matches_noscheme(const char *cp) struct evhttp_uri * evhttp_uri_parse(const char *source_uri) +{ + return evhttp_uri_parse_with_flags(source_uri, 0); +} + +struct evhttp_uri * +evhttp_uri_parse_with_flags(const char *source_uri, unsigned flags) { char *readbuf = NULL, *readp = NULL, *token = NULL, *query = NULL; char *path = NULL, *fragment = NULL; @@ -4011,14 +4193,15 @@ evhttp_uri_parse(const char *source_uri) struct evhttp_uri *uri = mm_calloc(1, sizeof(struct evhttp_uri)); if (uri == NULL) { - event_err(1, "%s: calloc", __func__); + event_warn("%s: calloc", __func__); goto err; } uri->port = -1; + uri->flags = flags; readbuf = mm_strdup(source_uri); if (readbuf == NULL) { - event_err(1, "%s: strdup", __func__); + event_warn("%s: strdup", __func__); goto err; } @@ -4031,7 +4214,6 @@ evhttp_uri_parse(const char *source_uri) URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] relative-ref = relative-part [ "?" query ] [ "#" fragment ] - */ /* 1. scheme: */ @@ -4039,7 +4221,10 @@ evhttp_uri_parse(const char *source_uri) if (token && scheme_ok(readp,token)) { *token = '\0'; uri->scheme = mm_strdup(readp); - + if (uri->scheme == NULL) { + event_warn("%s: strdup", __func__); + goto err; + } readp = token+1; /* eat : */ } @@ -4058,21 +4243,21 @@ evhttp_uri_parse(const char *source_uri) /* 3. Query: path-abempty, path-absolute, path-rootless, or path-empty */ path = readp; - readp = end_of_path(path, 0); + readp = end_of_path(path, PART_PATH, flags); /* Query */ if (*readp == '?') { *readp = '\0'; ++readp; query = readp; - readp = end_of_path(readp, 1); + readp = end_of_path(readp, PART_QUERY, flags); } /* fragment */ if (*readp == '#') { *readp = '\0'; ++readp; fragment = readp; - readp = end_of_path(readp, 1); + readp = end_of_path(readp, PART_FRAGMENT, flags); } if (*readp != '\0') { goto err; @@ -4096,11 +4281,25 @@ evhttp_uri_parse(const char *source_uri) EVUTIL_ASSERT(path); uri->path = mm_strdup(path); + if (uri->path == NULL) { + event_warn("%s: strdup", __func__); + goto err; + } - if (query) + if (query) { uri->query = mm_strdup(query); - if (fragment) + if (uri->query == NULL) { + event_warn("%s: strdup", __func__); + goto err; + } + } + if (fragment) { uri->fragment = mm_strdup(fragment); + if (uri->fragment == NULL) { + event_warn("%s: strdup", __func__); + goto err; + } + } mm_free(readbuf); @@ -4286,12 +4485,13 @@ evhttp_uri_set_port(struct evhttp_uri *uri, int port) uri->port = port; return 0; } -#define end_of_cpath(cp,aq) ((const char*)(end_of_path(((char*)(cp)), (aq)))) +#define end_of_cpath(cp,p,f) \ + ((const char*)(end_of_path(((char*)(cp)), (p), (f)))) int evhttp_uri_set_path(struct evhttp_uri *uri, const char *path) { - if (path && end_of_cpath(path, 0) != path+strlen(path)) + if (path && end_of_cpath(path, PART_PATH, uri->flags) != path+strlen(path)) return -1; _URI_SET_STR(path); @@ -4300,7 +4500,7 @@ evhttp_uri_set_path(struct evhttp_uri *uri, const char *path) int evhttp_uri_set_query(struct evhttp_uri *uri, const char *query) { - if (query && end_of_cpath(query, 1) != query+strlen(query)) + if (query && end_of_cpath(query, PART_QUERY, uri->flags) != query+strlen(query)) return -1; _URI_SET_STR(query); return 0; @@ -4308,7 +4508,7 @@ evhttp_uri_set_query(struct evhttp_uri *uri, const char *query) int evhttp_uri_set_fragment(struct evhttp_uri *uri, const char *fragment) { - if (fragment && end_of_cpath(fragment, 1) != fragment+strlen(fragment)) + if (fragment && end_of_cpath(fragment, PART_FRAGMENT, uri->flags) != fragment+strlen(fragment)) return -1; _URI_SET_STR(fragment); return 0;