0
0
mirror of https://github.com/rkd77/elinks.git synced 2025-06-30 22:19:29 -04:00

Merge branch 'top/0.13/bug1008' into elinks-0.13

This commit is contained in:
Kalle Olavi Niemitalo 2008-06-08 19:51:00 +03:00 committed by Kalle Olavi Niemitalo
commit b8b54a5325
27 changed files with 905 additions and 230 deletions

6
NEWS
View File

@ -25,6 +25,12 @@ Miscellaneous:
* bug 963: New option document.css.ignore_display_none.
* bug 977: Fixed crash when opening in new tab a non link with onclick
attribute.
* bug 1008: File upload fields in HTML forms now stream the files to
the server, instead of reading them to memory in advance. This lets
you upload larger files. The downsides are that ELinks may use a
cached response even if you have modified a file between requests,
and that ELinks can send inconsistent data if you modify a file
while it is being uploaded.
* Really retry forever when connection.retries = 0.
* enhancement: Session-specific options. Any options changed with
toggle-* actions no longer affect other tabs or other terminals.

View File

@ -15,10 +15,9 @@
#include "util/memory.h"
#include "util/string.h"
unsigned char *
get_progress_msg(struct progress *progress, struct terminal *term,
int wide, int full, unsigned char *separator)
static unsigned char *
get_progress_msg_2(struct progress *progress, struct terminal *term,
int wide, int full, unsigned char *separator, unsigned char *type)
{
struct string msg;
int newlines = separator[strlen(separator) - 1] == '\n';
@ -29,7 +28,7 @@ get_progress_msg(struct progress *progress, struct terminal *term,
* one, _("of")-like pearls are a nightmare. Format strings need to
* be introduced to this fuggy corner of code as well. --pasky */
add_to_string(&msg, _("Received", term));
add_to_string(&msg, type);
add_char_to_string(&msg, ' ');
add_xnum_to_string(&msg, progress->pos);
if (progress->size >= 0) {
@ -90,6 +89,20 @@ get_progress_msg(struct progress *progress, struct terminal *term,
return msg.source;
}
unsigned char *
get_upload_progress_msg(struct progress *progress, struct terminal *term,
int wide, int full, unsigned char *separator)
{
return get_progress_msg_2(progress, term, wide, full, separator, _("Sent", term));
}
unsigned char *
get_progress_msg(struct progress *progress, struct terminal *term,
int wide, int full, unsigned char *separator)
{
return get_progress_msg_2(progress, term, wide, full, separator, _("Received", term));
}
void
draw_progress_bar(struct progress *progress, struct terminal *term,
int x, int y, int width,

View File

@ -8,6 +8,11 @@ unsigned char *
get_progress_msg(struct progress *progress, struct terminal *term,
int wide, int full, unsigned char *separator);
unsigned char *
get_upload_progress_msg(struct progress *progress, struct terminal *term,
int wide, int full, unsigned char *separator);
/* Draws a progress bar meter or progress coloured text depending on whether
* @text is NULL. If @meter_color is NULL dialog.meter color is used. */
void

View File

@ -55,6 +55,9 @@ get_download_msg(struct download *download, struct terminal *term,
&& download->conn->uri->protocol == PROTOCOL_BITTORRENT)
return get_bittorrent_message(download, term, wide, full, separator);
#endif
if (download->conn && download->conn->http_upload_progress)
return get_upload_progress_msg(download->conn->http_upload_progress,
term, wide, full, separator);
return get_progress_msg(download->progress, term, wide, full, separator);
}
@ -229,6 +232,7 @@ display_status_bar(struct session *ses, struct terminal *term, int tabs_count)
}
}
if (!msg) {
int full = term->width > 130;
int wide = term->width > 80;

View File

@ -26,6 +26,7 @@
#include "network/progress.h"
#include "network/socket.h"
#include "network/ssl/ssl.h"
#include "protocol/http/http.h"
#include "protocol/protocol.h"
#include "protocol/proxy.h"
#include "protocol/uri.h"
@ -64,9 +65,8 @@ static INIT_LIST_OF(struct host_connection, host_connections);
static INIT_LIST_OF(struct keepalive_connection, keepalive_connections);
/* Prototypes */
static void notify_connection_callbacks(struct connection *conn);
static void check_keepalive_connections(void);
static void notify_connection_callbacks(struct connection *conn);
static /* inline */ enum connection_priority
get_priority(struct connection *conn)
@ -327,9 +327,9 @@ update_connection_progress(struct connection *conn)
update_progress(conn->progress, conn->received, conn->est_length, conn->from);
}
/* Progress timer callback for @conn->progress. As explained in
* @start_update_progress, this function must erase the expired timer
* ID from @conn->progress->timer. */
/** Progress timer callback for @a conn->progress. As explained in
* start_update_progress(), this function must erase the expired timer
* ID from @a conn->progress->timer. */
static void
stat_timer(struct connection *conn)
{
@ -338,17 +338,48 @@ stat_timer(struct connection *conn)
notify_connection_callbacks(conn);
}
/** Progress timer callback for @a conn->upload_progress. As explained
* in start_update_progress(), this function must erase the expired timer
* ID from @a conn->upload_progress->timer. */
static void
upload_stat_timer(struct connection *conn)
{
struct http_connection_info *http = conn->info;
assert(conn->http_upload_progress);
if_assert_failed return;
assert(http);
if_assert_failed {
conn->http_upload_progress->timer = TIMER_ID_UNDEF;
/* The expired timer ID has now been erased. */
return;
}
update_progress(conn->http_upload_progress, http->post.uploaded,
http->post.total_upload_length, http->post.uploaded);
/* The expired timer ID has now been erased. */
notify_connection_callbacks(conn);
}
void
set_connection_state(struct connection *conn, enum connection_state state)
{
struct download *download;
struct progress *progress = conn->progress;
struct progress *upload_progress = conn->http_upload_progress;
if (is_in_result_state(conn->state) && is_in_progress_state(state))
conn->prev_error = conn->state;
conn->state = state;
if (conn->state == S_TRANS) {
if (upload_progress && upload_progress->timer == TIMER_ID_UNDEF) {
start_update_progress(upload_progress,
(void (*)(void *)) upload_stat_timer, conn);
upload_stat_timer(conn);
if (connection_disappeared(conn))
return;
}
if (progress->timer == TIMER_ID_UNDEF) {
start_update_progress(progress, (void (*)(void *)) stat_timer, conn);
update_connection_progress(conn);
@ -358,6 +389,7 @@ set_connection_state(struct connection *conn, enum connection_state state)
} else {
kill_timer(&progress->timer);
if (upload_progress) kill_timer(&upload_progress->timer);
}
foreach (download, conn->downloads) {
@ -430,6 +462,15 @@ free_connection_data(struct connection *conn)
shutdown_connection_stream(conn);
mem_free_set(&conn->info, NULL);
/* If conn->done is not NULL, it probably points to a function
* that expects conn->info to be a specific kind of structure.
* Such a function should not be called if a different pointer
* is later assigned to conn->info. Actually though, each
* free_connection_data() call seems to be soon followed by
* done_connection() so that conn->done would not be called
* again in any case. However, this assignment costs little
* and may make things a bit safer. */
conn->done = NULL;
kill_timer(&conn->timer);
@ -437,7 +478,7 @@ free_connection_data(struct connection *conn)
done_host_connection(conn);
}
void
static void
notify_connection_callbacks(struct connection *conn)
{
enum connection_state state = conn->state;
@ -470,6 +511,8 @@ done_connection(struct connection *conn)
mem_free(conn->socket);
mem_free(conn->data_socket);
done_progress(conn->progress);
if (conn->http_upload_progress)
done_progress(conn->http_upload_progress);
mem_free(conn);
check_queue_bugs();
}

View File

@ -19,6 +19,15 @@ struct connection {
LIST_OF(struct download) downloads;
struct progress *progress;
/** Progress of sending the request and attached files to the
* server. This happens before any download.
*
* Currently, ELinks supports file uploads only in HTTP and
* local CGI. Therefore, upload_stat_timer() in connection.c
* assumes that #info points to struct http_connection_info
* whenever @c http_upload_progress is not NULL. */
struct progress *http_upload_progress;
/* If no proxy is used uri and proxied_uri are the same. */
struct uri *uri;
struct uri *proxied_uri;

View File

@ -8,6 +8,7 @@
#include <openssl/ssl.h>
#include <openssl/rand.h>
#elif defined(CONFIG_GNUTLS)
#include <gcrypt.h>
#include <gnutls/gnutls.h>
#else
#error "Huh?! You have SSL enabled, but not OPENSSL nor GNUTLS!! And then you want exactly *what* from me?"
@ -27,6 +28,7 @@
#include "util/conv.h"
#include "util/error.h"
#include "util/string.h"
#include "util/random.h"
/* FIXME: As you can see, SSL is currently implemented in very, erm,
@ -282,3 +284,17 @@ get_ssl_connection_cipher(struct socket *socket)
return str.source;
}
/* When CONFIG_SSL is defined, this implementation replaces the one in
* src/util/random.c. */
void
random_nonce(unsigned char buf[], size_t size)
{
#ifdef CONFIG_OPENSSL
RAND_pseudo_bytes(buf, size);
#elif defined(CONFIG_GNUTLS)
gcry_create_nonce(buf, size);
#else
# error unsupported SSL library
#endif
}

View File

@ -70,6 +70,7 @@ static const struct s_msg_dsc msg_dsc[] = {
{S_HTTP_ERROR, N_("Bad HTTP response")},
{S_HTTP_204, N_("No content")},
{S_HTTP_UPLOAD_RESIZED, N_("File was resized during upload")},
{S_FILE_TYPE, N_("Unknown file type")},
{S_FILE_ERROR, N_("Error opening file")},

View File

@ -72,6 +72,7 @@ enum connection_state {
S_HTTP_ERROR = -100100,
S_HTTP_204 = -100101,
S_HTTP_UPLOAD_RESIZED = -100102,
S_FILE_TYPE = -100200,
S_FILE_ERROR = -100201,

View File

@ -15,6 +15,7 @@
#include "util/conv.h"
#include "util/md5.h"
#include "util/memory.h"
#include "util/random.h"
/* Hexes a binary md5 digest. Taken from RFC 2617 */
@ -31,18 +32,14 @@ convert_to_md5_digest_hex_T(md5_digest_bin_T bin, md5_digest_hex_T hex)
}
}
/* Initializes a random cnonce that is also a hexed md5 digest. */
/* Initializes a random cnonce that has the same format as a hexed md5
* digest. */
static void
init_cnonce_digest(md5_digest_hex_T cnonce)
{
md5_digest_bin_T md5;
int random;
srand(time(NULL));
random = rand();
MD5((const unsigned char *) &random, sizeof(random), md5);
random_nonce(md5, MD5_DIGEST_LENGTH);
convert_to_md5_digest_hex_T(md5, cnonce);
}

View File

@ -17,6 +17,7 @@
#include "util/error.h"
#include "util/lists.h"
#include "util/memory.h"
#include "util/random.h"
#include "util/sha1.h"
#include "util/string.h"
#include "util/snprintf.h"
@ -167,13 +168,10 @@ init_bittorrent_peer_id(bittorrent_id_T peer_id)
}
/* Hmm, sizeof(peer_id) don't work here. */
random_nonce(peer_id + i, sizeof(bittorrent_id_T) - i);
while (i < sizeof(bittorrent_id_T)) {
int random = rand();
while (i < sizeof(bittorrent_id_T) && (random & 0xF)) {
peer_id[i++] = hx(random & 0xF);
random >>= 4;
}
peer_id[i] = hx(peer_id[i] & 0xF);
i++;
}
}

View File

@ -250,6 +250,7 @@ done_bittorrent_connection(struct connection *conn)
struct bittorrent_peer_connection *peer, *next;
assert(bittorrent);
assert(conn->done == done_bittorrent_connection);
/* We don't want the tracker to see the fetch. */
if (bittorrent->fetch)
@ -270,6 +271,7 @@ done_bittorrent_connection(struct connection *conn)
free_list(bittorrent->peer_pool);
mem_free_set(&conn->info, NULL);
conn->done = NULL;
}
static struct bittorrent_connection *
@ -277,12 +279,17 @@ init_bittorrent_connection(struct connection *conn)
{
struct bittorrent_connection *bittorrent;
assert(conn->info == NULL);
assert(conn->done == NULL);
if_assert_failed return NULL;
bittorrent = mem_calloc(1, sizeof(*bittorrent));
if (!bittorrent) return NULL;
init_list(bittorrent->peers);
init_list(bittorrent->peer_pool);
/* conn->info and conn->done were asserted as NULL above. */
conn->info = bittorrent;
conn->done = done_bittorrent_connection;

View File

@ -35,6 +35,7 @@
#include "util/file.h"
#include "util/lists.h"
#include "util/memory.h"
#include "util/random.h"
#include "util/string.h"
@ -165,7 +166,7 @@ find_random_in_bittorrent_piece_cache(struct bittorrent_piece_cache *cache,
assert(peer->bitfield->bitsize == peer->bittorrent->meta.pieces);
srand(time(NULL));
seed_rand_once();
foreachback_bitfield_set (piece, peer->bitfield) {
assertm(cache->entries[piece].rarity,
@ -238,7 +239,7 @@ find_rarest_in_bittorrent_piece_cache(struct bittorrent_piece_cache *cache,
assert(peer->bitfield->bitsize == peer->bittorrent->meta.pieces);
srand(time(NULL));
seed_rand_once();
/* Try to randomize the piece picking using the strategy from the random
* piece selection. */

View File

@ -23,6 +23,7 @@
#include "intl/gettext/libintl.h"
#include "mime/backend/common.h"
#include "network/connection.h"
#include "network/progress.h"
#include "network/socket.h"
#include "osdep/osdep.h"
#include "osdep/sysname.h"
@ -80,57 +81,49 @@ close_pipe_and_read(struct socket *data_socket)
read_from_socket(conn->socket, rb, S_SENT, http_got_header);
}
#define POST_BUFFER_SIZE 32768
static void
send_more_post_data(struct socket *socket)
{
struct connection *conn = socket->conn;
struct http_connection_info *http = conn->info;
unsigned char buffer[POST_BUFFER_SIZE];
int got;
enum connection_state error;
got = read_http_post(&http->post, buffer, POST_BUFFER_SIZE, &error);
if (got < 0) {
abort_connection(conn, error);
} else if (got > 0) {
write_to_socket(socket, buffer, got, S_TRANS,
send_more_post_data);
} else { /* got == 0, meaning end of data */
close_pipe_and_read(socket);
}
}
#undef POST_BUFFER_SIZE
static void
send_post_data(struct connection *conn)
{
#define POST_BUFFER_SIZE 4096
struct http_connection_info *http = conn->info;
unsigned char *post = conn->uri->post;
unsigned char *postend;
unsigned char buffer[POST_BUFFER_SIZE];
struct string data;
int n = 0;
enum connection_state error;
if (!init_string(&data)) {
abort_connection(conn, S_OUT_OF_MEM);
return;
}
postend = strchr(post, '\n');
if (postend) post = postend + 1;
/* FIXME: Code duplication with protocol/http/http.c! --witekfl */
while (post[0] && post[1]) {
int h1, h2;
h1 = unhx(post[0]);
assert(h1 >= 0 && h1 < 16);
if_assert_failed h1 = 0;
h2 = unhx(post[1]);
assert(h2 >= 0 && h2 < 16);
if_assert_failed h2 = 0;
buffer[n++] = (h1<<4) + h2;
post += 2;
if (n == POST_BUFFER_SIZE) {
add_bytes_to_string(&data, buffer, n);
n = 0;
}
if (!open_http_post(&http->post, post, &error))
abort_connection(conn, error);
else {
if (!conn->http_upload_progress && http->post.file_count)
conn->http_upload_progress = init_progress(0);
send_more_post_data(conn->data_socket);
}
if (n)
add_bytes_to_string(&data, buffer, n);
/* If we're submitting a form whose controls do not have
* names, then the POST has a Content-Type but empty data,
* and an assertion would fail in write_to_socket. */
if (data.length)
write_to_socket(conn->data_socket, data.source, data.length,
S_SENT, close_pipe_and_read);
else
close_pipe_and_read(conn->data_socket);
done_string(&data);
#undef POST_BUFFER_SIZE
}
static void

View File

@ -678,6 +678,13 @@ add_file_cmd_to_str(struct connection *conn)
goto ret;
}
assert(conn->info == NULL);
assert(conn->done == NULL);
if_assert_failed {
abort_connection(conn, S_INTERNAL);
goto ret;
}
/* This will be reallocated below when we know how long the
* command string should be. Error handling could be
* simplified a little by allocating this initial structure on
@ -689,6 +696,7 @@ add_file_cmd_to_str(struct connection *conn)
goto ret;
}
/* conn->info and conn->done were asserted as NULL above. */
conn->info = ftp; /* Freed when connection is destroyed. */
if (!init_string(&command)

View File

@ -310,6 +310,13 @@ init_gopher_connection_info(struct connection *conn)
* wazzup! */
assert(command.length >= 2);
assert(conn->info == NULL);
assert(conn->done == NULL);
if_assert_failed {
done_string(&command);
return S_INTERNAL;
}
size = sizeof(*gopher) + command.length;
gopher = mem_calloc(1, size);
if (!gopher) {

View File

@ -3,6 +3,6 @@ include $(top_builddir)/Makefile.config
OBJS-$(CONFIG_GSSAPI) += http_negotiate.o
OBJS = blacklist.o codes.o http.o
OBJS = blacklist.o codes.o http.o post.o
include $(top_srcdir)/Makefile.lib

View File

@ -8,12 +8,6 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h> /* OS/2 needs this after sys/types.h */
#endif
#ifdef HAVE_LIMITS_H
#include <limits.h>
#endif
@ -51,11 +45,7 @@
#include "http_negotiate.h"
#endif
struct http_version {
int major;
int minor;
};
/* These macros concern the struct http_version defined in the http.h */
#define HTTP_0_9(x) ((x).major == 0 && (x).minor == 9)
#define HTTP_1_0(x) ((x).major == 1 && (x).minor == 0)
#define HTTP_1_1(x) ((x).major == 1 && (x).minor == 1)
@ -65,26 +55,13 @@ struct http_version {
#define POST_HTTP_1_1(x) ((x).major > 1 || ((x).major == 1 && (x).minor > 1))
struct http_connection_info {
enum blacklist_flags bl_flags;
struct http_version recv_version;
struct http_version sent_version;
int close;
#define LEN_CHUNKED -2 /* == we get data in unknown number of chunks */
#define LEN_FINISHED 0
int length;
/* Either bytes coming in this chunk yet or "parser state". */
#define CHUNK_DATA_END -3
#define CHUNK_ZERO_SIZE -2
#define CHUNK_SIZE -1
int chunk_remaining;
int code;
};
static struct auth_entry proxy_auth;
@ -487,9 +464,17 @@ static void
http_end_request(struct connection *conn, enum connection_state state,
int notrunc)
{
struct http_connection_info *http;
shutdown_connection_stream(conn);
if (conn->info && !((struct http_connection_info *) conn->info)->close
/* shutdown_connection_stream() should not change conn->info,
* but in case it does, read conn->info only after the call. */
http = conn->info;
if (http)
done_http_post(&http->post);
if (http && !http->close
&& (!conn->socket->ssl) /* We won't keep alive ssl connections */
&& (!get_opt_bool("protocol.http.bugs.post_no_keepalive", NULL)
|| !conn->uri->post)) {
@ -528,6 +513,19 @@ proxy_protocol_handler(struct connection *conn)
#define connection_is_https_proxy(conn) \
(IS_PROXY_URI((conn)->uri) && (conn)->proxied_uri->protocol == PROTOCOL_HTTPS)
/** connection.done points to this function if connection.info points
* to a struct http_connection_info. */
static void
done_http_connection(struct connection *conn)
{
struct http_connection_info *http = conn->info;
done_http_post(&http->post);
mem_free(http);
conn->info = NULL;
conn->done = NULL;
}
struct http_connection_info *
init_http_connection_info(struct connection *conn, int major, int minor, int close)
{
@ -543,6 +541,8 @@ init_http_connection_info(struct connection *conn, int major, int minor, int clo
http->sent_version.minor = minor;
http->close = close;
init_http_post(&http->post);
/* The CGI code uses this too and blacklisting expects a host name. */
if (conn->proxied_uri->protocol != PROTOCOL_FILE)
http->bl_flags = get_blacklist_flags(conn->proxied_uri);
@ -555,7 +555,12 @@ init_http_connection_info(struct connection *conn, int major, int minor, int clo
/* If called from HTTPS proxy connection the connection info might have
* already been allocated. */
if (conn->done) {
conn->done(conn);
conn->done = NULL;
}
mem_free_set(&conn->info, http);
conn->done = done_http_connection;
return http;
}
@ -587,6 +592,39 @@ accept_encoding_header(struct string *header)
#endif
}
#define POST_BUFFER_SIZE 32768
#define BIG_READ 655360
static void
send_more_post_data(struct socket *socket)
{
struct connection *conn = socket->conn;
struct http_connection_info *http = conn->info;
unsigned char buffer[POST_BUFFER_SIZE];
int got;
enum connection_state error;
got = read_http_post(&http->post, buffer, POST_BUFFER_SIZE, &error);
if (got < 0) {
http_end_request(conn, error, 0);
} else if (got > 0) {
write_to_socket(socket, buffer, got, S_TRANS,
send_more_post_data);
} else { /* got == 0, meaning end of data */
/* Can't use request_from_socket() because there's no
* more data to write. */
struct read_buffer *rb = alloc_read_buffer(socket);
socket->state = SOCKET_END_ONCLOSE;
if (rb)
read_from_socket(socket, rb, S_SENT, http_got_header);
else
http_end_request(conn, S_OUT_OF_MEM, 0);
}
}
static void
http_send_header(struct socket *socket)
{
@ -932,6 +970,7 @@ http_send_header(struct socket *socket)
* as set by get_form_uri(). This '\n' is dropped if any
* and replaced by correct '\r\n' termination here. */
unsigned char *postend = strchr(uri->post, '\n');
enum connection_state error;
if (postend) {
add_to_string(&header, "Content-Type: ");
@ -940,9 +979,15 @@ http_send_header(struct socket *socket)
}
post_data = postend ? postend + 1 : uri->post;
add_to_string(&header, "Content-Length: ");
add_long_to_string(&header, strlen(post_data) / 2);
add_crlf_to_string(&header);
if (!open_http_post(&http->post, post_data, &error)) {
http_end_request(conn, error, 0);
done_string(&header);
return;
}
add_format_to_string(&header, "Content-Length: "
"%" OFF_PRINT_FORMAT "\x0D\x0A",
(off_print_T)
http->post.total_upload_length);
}
#ifdef CONFIG_COOKIES
@ -965,42 +1010,21 @@ http_send_header(struct socket *socket)
* This was already checked above and post_data is NULL
* in that case. Verified with an assertion below. */
if (post_data) {
#define POST_BUFFER_SIZE 4096
unsigned char *post = post_data;
unsigned char buffer[POST_BUFFER_SIZE];
int n = 0;
assert(!use_connect); /* see comment above */
while (post[0] && post[1]) {
int h1, h2;
h1 = unhx(post[0]);
assertm(h1 >= 0 && h1 < 16, "h1 in the POST buffer is %d (%d/%c)", h1, post[0], post[0]);
if_assert_failed h1 = 0;
h2 = unhx(post[1]);
assertm(h2 >= 0 && h2 < 16, "h2 in the POST buffer is %d (%d/%c)", h2, post[1], post[1]);
if_assert_failed h2 = 0;
buffer[n++] = (h1<<4) + h2;
post += 2;
if (n == POST_BUFFER_SIZE) {
add_bytes_to_string(&header, buffer, n);
n = 0;
}
}
if (n)
add_bytes_to_string(&header, buffer, n);
#undef POST_BUFFER_SIZE
}
request_from_socket(socket, header.source, header.length, S_SENT,
SOCKET_END_ONCLOSE, http_got_header);
socket->state = SOCKET_END_ONCLOSE;
if (!conn->http_upload_progress && http->post.file_count)
conn->http_upload_progress = init_progress(0);
write_to_socket(socket, header.source, header.length, S_TRANS,
send_more_post_data);
} else
request_from_socket(socket, header.source, header.length, S_SENT,
SOCKET_END_ONCLOSE, http_got_header);
done_string(&header);
}
#undef POST_BUFFER_SIZE
/* This function decompresses the data block given in @data (if it was
* compressed), which is long @len bytes. The decompressed data block is given
@ -1027,7 +1051,6 @@ decompress_data(struct connection *conn, unsigned char *data, int len,
int *length_of_block;
unsigned char *output = NULL;
#define BIG_READ 65536
if (http->length == LEN_CHUNKED) {
if (http->chunk_remaining == CHUNK_ZERO_SIZE)
@ -1116,6 +1139,7 @@ decompress_data(struct connection *conn, unsigned char *data, int len,
if (state == FINISHING) shutdown_connection_stream(conn);
return output;
}
#undef BIG_READ
static int
is_line_in_buffer(struct read_buffer *rb)

View File

@ -3,13 +3,34 @@
#define EL__PROTOCOL_HTTP_HTTP_H
#include "main/module.h"
#include "protocol/http/blacklist.h"
#include "protocol/http/post.h"
#include "protocol/protocol.h"
struct connection;
struct http_connection_info;
struct read_buffer;
struct socket;
/* Macros related to this struct are defined in the http.c. */
struct http_version {
int major;
int minor;
};
/** connection.info points to this in HTTP and local CGI connections. */
struct http_connection_info {
enum blacklist_flags bl_flags;
struct http_version recv_version;
struct http_version sent_version;
int close;
int length;
int chunk_remaining;
int code;
struct http_post post;
};
extern struct module http_protocol_module;
extern protocol_handler_T http_protocol_handler;

305
src/protocol/http/post.c Normal file
View File

@ -0,0 +1,305 @@
/** Parsing uri.post and uploading files in a POST request.
* @file */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h> /* OS/2 needs this after sys/types.h */
#endif
#include "elinks.h"
#include "protocol/http/post.h"
#include "protocol/uri.h"
#include "util/conv.h"
#include "util/error.h"
/** Initialize *@a http_post so that done_http_post() can be safely
* called.
*
* @relates http_post */
void
init_http_post(struct http_post *http_post)
{
http_post->total_upload_length = 0;
http_post->uploaded = 0;
http_post->post_data = NULL;
http_post->post_fd = -1;
http_post->file_index = 0;
http_post->file_count = 0;
http_post->file_read = 0;
http_post->files = NULL;
}
/** Free all resources owned by *@a http_post, but do not free the
* structure itself. It is safe to call this multiple times.
*
* @relates http_post */
void
done_http_post(struct http_post *http_post)
{
http_post->total_upload_length = 0;
http_post->uploaded = 0;
http_post->post_data = NULL;
if (http_post->post_fd != -1) {
close(http_post->post_fd);
http_post->post_fd = -1;
}
http_post->file_index = 0;
http_post->file_count = 0;
http_post->file_read = 0;
mem_free_set(&http_post->files, NULL);
}
/** Prepare to read POST data from a URI and possibly to upload files.
*
* @param http_post
* Must have been initialized with init_http_post().
* @param[in] post_data
* The body of the POST request as formatted by get_form_uri().
* However, unlike uri.post, @a post_data must not contain any
* Content-Type. The caller must ensure that the @a post_data
* pointer remains valid until done_http_post().
* @param[out] error
* If the function fails, it writes the error state here so that
* the caller can pass that on to abort_connection(). If the
* function succeeds, the value of *@a error is undefined.
*
* This function does not parse the Content-Type from uri.post; the
* caller must do that. This is because in local CGI, the child
* process handles the Content-Type (saving it to an environment
* variable before exec) but the parent process handles the body of
* the request (feeding it to the child process via a pipe).
*
* @return nonzero on success, zero on error.
*
* @relates http_post */
int
open_http_post(struct http_post *http_post, unsigned char *post_data,
enum connection_state *error)
{
off_t size = 0;
size_t length = strlen(post_data);
unsigned char *end = post_data;
done_http_post(http_post);
http_post->post_data = end;
while (1) {
struct stat sb;
unsigned char *begin;
int res;
struct http_post_file *new_files;
begin = strchr(end, FILE_CHAR);
if (!begin) break;
end = strchr(begin + 1, FILE_CHAR);
if (!end) break;
*end = '\0';
res = stat(begin + 1, &sb);
*end = FILE_CHAR;
if (res) break;
/* This use of mem_realloc() in a loop consumes O(n^2)
* time but how many files are you really going to
* upload in one request? */
new_files = mem_realloc(http_post->files,
(http_post->file_count + 1)
* sizeof(*new_files));
if (new_files == NULL) {
done_http_post(http_post);
*error = S_OUT_OF_MEM;
return 0;
}
http_post->files = new_files;
new_files[http_post->file_count].size = sb.st_size;
http_post->file_count++;
size += sb.st_size;
length -= (end - begin + 1);
end++;
}
size += (length / 2);
http_post->total_upload_length = size;
return 1;
}
/** @return -2 if no data was read but the caller should retry;
* -1 if an error occurred and *@a error was set; 0 at end of data;
* a positive number if that many bytes were read.
*
* @relates http_post */
static int
read_http_post_inline(struct http_post *http_post,
unsigned char buffer[], int max,
enum connection_state *error)
{
unsigned char *post = http_post->post_data;
unsigned char *end = strchr(post, FILE_CHAR);
int total = 0;
assert(http_post->post_fd < 0);
if_assert_failed { *error = S_INTERNAL; return -1; }
if (!end)
end = strchr(post, '\0');
while (post < end && total < max) {
int h1, h2;
h1 = unhx(post[0]);
assertm(h1 >= 0 && h1 < 16, "h1 in the POST buffer is %d (%d/%c)", h1, post[0], post[0]);
if_assert_failed h1 = 0;
h2 = unhx(post[1]);
assertm(h2 >= 0 && h2 < 16, "h2 in the POST buffer is %d (%d/%c)", h2, post[0], post[0]);
if_assert_failed h2 = 0;
buffer[total++] = (h1<<4) + h2;
post += 2;
}
if (post != end || *end != FILE_CHAR) {
http_post->post_data = post;
return total;
}
http_post->file_read = 0;
end = strchr(post + 1, FILE_CHAR);
assert(end);
*end = '\0';
http_post->post_fd = open(post + 1, O_RDONLY);
/* Be careful not to change errno here. */
*end = FILE_CHAR;
if (http_post->post_fd < 0) {
http_post->post_data = post;
if (total > 0)
return total; /* retry the open on the next call */
else {
*error = -errno;
return -1;
}
}
http_post->post_data = end + 1;
return total ? total : -2;
}
/** @return -2 if no data was read but the caller should retry;
* -1 if an error occurred and *@a error was set; 0 at end of data;
* a positive number if that many bytes were read.
*
* @relates http_post */
static int
read_http_post_fd(struct http_post *http_post,
unsigned char buffer[], int max,
enum connection_state *error)
{
const struct http_post_file *const file
= &http_post->files[http_post->file_index];
int ret;
/* safe_read() would set errno = EBADF anyway, but check this
* explicitly to make any such bugs easier to detect. */
assert(http_post->post_fd >= 0);
if_assert_failed { *error = S_INTERNAL; return -1; }
ret = safe_read(http_post->post_fd, buffer, max);
if (ret <= 0) {
const int errno_from_read = errno;
close(http_post->post_fd);
http_post->post_fd = -1;
http_post->file_index++;
/* http_post->file_read is used below so don't clear it here.
* It will be cleared when the next file is opened. */
if (ret == -1) {
*error = -errno_from_read;
return -1;
} else if (http_post->file_read != file->size) {
/* ELinks already sent a Content-Length header
* based on the size of this file, but the
* file has since been shrunk. Abort the
* connection because ELinks can no longer get
* enough data to fill the Content-Length.
* (Well, it could pad with zeroes, but that
* would be just weird.) */
*error = S_HTTP_UPLOAD_RESIZED;
return -1;
} else {
/* The upload file ended but there may still
* be more data in uri.post. If not,
* read_http_post_inline() will return 0 to
* indicate the final end of file. */
return -2;
}
}
http_post->file_read += ret;
if (http_post->file_read > file->size) {
/* ELinks already sent a Content-Length header based
* on the size of this file, but the file has since
* been extended. Abort the connection because ELinks
* can no longer fit the entire file in the original
* Content-Length. */
*error = S_HTTP_UPLOAD_RESIZED;
return -1;
}
return ret;
}
/** Read data from connection.uri->post or from the files to which it
* refers.
*
* @return >0 if read that many bytes; 0 if EOF; -1 on error and set
* *@a error.
*
* @relates http_post */
int
read_http_post(struct http_post *http_post,
unsigned char buffer[], int max,
enum connection_state *error)
{
int total = 0;
while (total < max) {
int chunk;
if (http_post->post_fd < 0)
chunk = read_http_post_inline(http_post,
buffer + total,
max - total,
error);
else
chunk = read_http_post_fd(http_post,
buffer + total,
max - total,
error);
if (chunk > 0) {
total += chunk;
http_post->uploaded += chunk;
} else if (chunk != -2) {
assert(chunk == -1 || chunk == 0);
/* If some data has already been successfully
* read to buffer[], tell the caller about
* that and forget about the error. The next
* read_http_post() call will retry the
* operation that failed. */
if (total != 0)
return total;
else
return chunk;
}
}
return total;
}

62
src/protocol/http/post.h Normal file
View File

@ -0,0 +1,62 @@
/** Parsing uri.post and uploading files in a POST request.
* @file */
#ifndef EL__PROTOCOL_HTTP_POST_H
#define EL__PROTOCOL_HTTP_POST_H
#include "network/state.h"
/** Information about a file to be uploaded in a POST request.
* open_http_post() collects this information and done_http_post()
* discards it. */
struct http_post_file {
/** The size of the file. */
off_t size;
};
/** State of reading POST data from connection.uri->post and related
* files. */
struct http_post {
/** Total size of the POST body to be uploaded */
off_t total_upload_length;
/** Amount of POST body data uploaded so far.
* read_http_post() increments this. */
off_t uploaded;
/** Points to the next byte to be read from connection.uri->post.
* Does not point to const because http_read_post() momentarily
* substitutes a null character for the FILE_CHAR at the end of
* each file name. */
unsigned char *post_data;
/** File descriptor from which data is being read, or -1 if
* none. */
int post_fd;
/** Current position in the #files array. This is the file
* that is currently being read (when post_fd != -1) or would
* be read next (when post_fd == -1). */
size_t file_index;
/** Number of files to be uploaded, i.e. the number of
* elements in the #files array. */
size_t file_count;
/** Number of bytes read from the current file so far.
* The value makes sense only when post_fd != -1. */
off_t file_read;
/** Array of information about files to be uploaded. */
struct http_post_file *files;
};
void init_http_post(struct http_post *http_post);
void done_http_post(struct http_post *http_post);
int open_http_post(struct http_post *http_post, unsigned char *post_data,
enum connection_state *error);
int read_http_post(struct http_post *http_post,
unsigned char buffer[], int max,
enum connection_state *error);
#endif

View File

@ -103,6 +103,13 @@ init_nntp_connection_info(struct connection *conn)
unsigned char *data;
int datalen;
assert(conn->info == NULL);
assert(conn->done == NULL);
if_assert_failed {
abort_connection(conn, S_INTERNAL);
return NULL;
}
nntp = mem_calloc(1, sizeof(*nntp));
if (!nntp) {
abort_connection(conn, S_OUT_OF_MEM);
@ -194,6 +201,13 @@ nntp_quit(struct connection *conn)
{
struct nntp_connection_info *info;
assert(conn->info == NULL);
assert(conn->done == NULL);
if_assert_failed {
abort_connection(conn, S_INTERNAL);
return;
}
info = mem_calloc(1, sizeof(*info));
if (!info) {
abort_connection(conn, S_OUT_OF_MEM);

View File

@ -7,6 +7,7 @@ struct string;
#define POST_CHAR 1
#define POST_CHAR_S "\001"
#define FILE_CHAR '\002'
/* The uri structure is used to store the start position and length of commonly
* used uri fields. It is initialized by parse_uri(). It is possible that the
@ -54,6 +55,9 @@ struct uri {
unsigned int datalen:16;
unsigned int fragmentlen:16;
/* Number of POSTED files */
unsigned int big_files:8;
/* Flags */
unsigned int ipv6:1; /* URI contains IPv6 host */
unsigned int form:1; /* URI originated from form */

View File

@ -32,6 +32,7 @@ OBJS = \
hash.o \
memlist.o \
memory.o \
random.o \
secsave.o \
snprintf.o \
string.o \

114
src/util/random.c Normal file
View File

@ -0,0 +1,114 @@
/** Random numbers.
* @file */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdlib.h>
#include <time.h>
#include "elinks.h"
#include "util/random.h"
void
seed_rand_once(void)
{
static int seeded = 0;
if (!seeded) {
srand(time(NULL));
seeded = 1;
}
}
#ifndef CONFIG_SSL
static void
pseudorandom_nonce(unsigned char buf[], size_t size)
{
static int initialized = 0;
static int accept_bits;
static int accept_mask;
unsigned int got_mask;
unsigned int got_random;
size_t index;
if (!initialized) {
unsigned int shift;
seed_rand_once();
/* 32767 <= RAND_MAX <= INT_MAX. Find the largest
* accept_mask such that accept_mask <= RAND_MAX and
* accept_mask + 1 is a power of two. */
shift = RAND_MAX;
accept_bits = 0U;
accept_mask = 0U;
while (shift != 0U) {
shift >>= 1;
accept_bits++;
accept_mask = (accept_mask << 1) + 1U;
}
if (accept_mask > (unsigned int) RAND_MAX) {
accept_bits--;
accept_mask >>= 1;
}
initialized = 1;
}
got_mask = got_random = 0U;
for (index = 0; index < size; ) {
if (got_mask >= UCHAR_MAX) {
buf[index++] = (unsigned char) got_random;
got_mask >>= CHAR_BIT;
got_random >>= CHAR_BIT;
} else {
unsigned int candidate;
do {
candidate = rand();
} while (candidate > accept_mask);
/* These shifts can discard some bits. */
got_mask = (got_mask << accept_bits) | accept_mask;
got_random = (got_random << accept_bits) | candidate;
}
}
}
/** Fill a buffer with random bytes. The bytes are not
* cryptographically random enough to be used in a key, but they
* should be good enough for a nonce or boundary string that may
* be sent in cleartext.
*
* If CONFIG_SSL is defined, then this function is instead defined in
* src/network/ssl/ssl.c, and it gets random numbers directly from the
* selected SSL library. */
void
random_nonce(unsigned char buf[], size_t size)
{
size_t i = 0;
FILE *f = fopen("/dev/urandom", "rb");
if (!f) f = fopen("/dev/prandom", "rb"); /* OpenBSD */
if (f) {
i = fread(data, 1, length, f);
fclose(f);
}
/* If the random device did not exist or could not provide
* enough data, then fill the buffer with rand(). The
* resulting numbers may be predictable but they provide
* ELinks with at least some way to generate boundary strings
* for multipart uploads. A more secure algorithm and entropy
* collection could be implemented, but there doesn't seem to
* be much point as SSL libraries already provide this
* facility. */
if (i < size)
pseudorandom_nonce(buf + i, size - i);
}
#endif /* ndef CONFIG_SSL */

11
src/util/random.h Normal file
View File

@ -0,0 +1,11 @@
/** Random numbers.
* @file */
#ifndef EL__UTIL_RANDOM_H
#define EL__UTIL_RANDOM_H
void seed_rand_once(void);
void random_nonce(unsigned char buf[], size_t size);
#endif

View File

@ -45,6 +45,7 @@
#include "util/error.h"
#include "util/file.h"
#include "util/memory.h"
#include "util/random.h"
#include "util/string.h"
#include "viewer/action.h"
#include "viewer/text/draw.h"
@ -58,7 +59,19 @@
/* TODO: Some of these (particulary those encoding routines) would feel better
* in viewer/common/. --pasky */
struct files_offset {
LIST_HEAD(struct files_offset);
/* offset of the filename in the data generated by encode_multipart.
* data[begin] is the FILE_CHAR, data + begin + 1 is the filename. */
int begin;
/* end of filename. data[end] is the FILE_CHAR. In normal strings
* it would be a nul char. */
int end;
};
/** @relates submitted_value */
struct submitted_value *
init_submitted_value(unsigned char *name, unsigned char *value, enum form_type type,
struct form_control *fc, int position)
@ -817,12 +830,29 @@ struct boundary_info {
unsigned char string[BOUNDARY_LENGTH];
};
/** @relates boundary_info */
static void
randomize_boundary(unsigned char *data, int length)
{
int i;
random_nonce(data, length);
for (i = 0; i < length; i++) {
/* Only [0-9A-Za-z]. */
data[i] = data[i] & 63;
if (data[i] < 10) data[i] += '0';
else if (data[i] < 36) data[i] = data[i] - 10 + 'A';
else if (data[i] < 62) data[i] = data[i] - 36 + 'a';
else data[i] = '0';
}
}
/** @relates boundary_info */
static inline void
init_boundary(struct boundary_info *boundary)
{
memset(boundary, 0, sizeof(*boundary));
memset(boundary->string, '0', BOUNDARY_LENGTH);
randomize_boundary(boundary->string, BOUNDARY_LENGTH);
}
/** Add boundary to string and save the offset
@ -838,76 +868,12 @@ add_boundary(struct string *data, struct boundary_info *boundary)
add_bytes_to_string(data, boundary->string, BOUNDARY_LENGTH);
}
/** @relates boundary_info */
static inline unsigned char *
increment_boundary_counter(struct boundary_info *boundary)
{
int j;
/* This is just a decimal string incrementation */
for (j = BOUNDARY_LENGTH - 1; j >= 0; j--) {
if (boundary->string[j]++ < '9')
return boundary->string;
boundary->string[j] = '0';
}
INTERNAL("Form data boundary counter overflow");
return NULL;
}
/** @relates boundary_info */
static inline void
check_boundary(struct string *data, struct boundary_info *boundary)
{
unsigned char *bound = boundary->string;
int i;
/* Search between all boundaries. There is a starting and an ending
* boundary so only check the range of chars after the current offset
* and before the next offset. If some string in the form data matches
* the boundary string it is changed. */
for (i = 0; i < boundary->count - 1; i++) {
/* Start after the boundary string and also jump past the
* "\r\nContent-Disposition: form-data; name=\"" string added
* before any form data. */
int start_offset = boundary->offsets[i] + BOUNDARY_LENGTH + 40;
/* End so that there is atleast BOUNDARY_LENGTH chars to
* compare. Subtract 2 char because there is no need to also
* compare the '--' prefix that is part of the boundary. */
int end_offset = boundary->offsets[i + 1] - BOUNDARY_LENGTH - 2;
unsigned char *pos = data->source + start_offset;
unsigned char *end = data->source + end_offset;
for (; pos <= end; pos++) {
if (memcmp(pos, bound, BOUNDARY_LENGTH))
continue;
/* If incrementing causes overflow bail out. There is
* no need to reset the boundary string with '0' since
* that is already done when incrementing. */
if (!increment_boundary_counter(boundary))
return;
/* Else start checking all boundaries using the new
* boundary string */
i = 0;
break;
}
}
/* Now update all the boundaries with the unique boundary string */
for (i = 0; i < boundary->count; i++)
memcpy(data->source + boundary->offsets[i], bound, BOUNDARY_LENGTH);
}
/** @todo FIXME: shouldn't we encode data at send time (in http.c) ? --Zas */
static void
encode_multipart(struct session *ses, LIST_OF(struct submitted_value) *l,
struct string *data,
struct boundary_info *boundary, int cp_from, int cp_to)
struct string *data, struct boundary_info *boundary,
LIST_OF(struct files_offset) *bfs, int cp_from, int cp_to)
{
struct conv_table *convert_table = NULL;
struct submitted_value *sv;
@ -936,9 +902,6 @@ encode_multipart(struct session *ses, LIST_OF(struct submitted_value) *l,
add_char_to_string(data, '"');
if (sv->type == FC_FILE) {
#define F_BUFLEN 1024
int fh;
unsigned char buffer[F_BUFLEN];
unsigned char *extension;
add_to_string(data, "; filename=\"");
@ -968,39 +931,59 @@ encode_multipart(struct session *ses, LIST_OF(struct submitted_value) *l,
if (*sv->value) {
unsigned char *filename;
struct files_offset *bfs_new;
if (get_cmd_opt_bool("anonymous")) {
errno = EPERM;
goto encode_error;
}
/* FIXME: DO NOT COPY FILE IN MEMORY !! --Zas */
filename = expand_tilde(sv->value);
if (!filename) goto encode_error;
fh = open(filename, O_RDONLY);
/* Do not allow FILE_CHAR in file
* names. It would make the resulting
* *data string ambiguous.
*
* Because FILE_CHAR is a control
* character, the user cannot directly
* type it in a file upload field.
* ELinks also does not let scripts
* modify such fields, for security
* reasons. It seems impossible to
* get FILE_CHAR here, so use assert.
*
* In uri.post, the first '\n' also
* has special meaning. However, '\n'
* in a file name does not cause any
* ambiguity, because get_form_uri()
* always adds a content-type and '\n'
* to the beginning of the encoded
* POST data. */
assert(strchr(filename, FILE_CHAR) == NULL);
if_assert_failed {
mem_free(filename);
errno = EINVAL;
goto encode_error;
}
if (access(filename, R_OK)) {
mem_free(filename);
goto encode_error;
}
bfs_new = mem_calloc(1, sizeof(*bfs_new));
if (!bfs_new) {
mem_free(filename);
goto encode_error;
}
bfs_new->begin = data->length;
add_char_to_string(data, FILE_CHAR);
add_to_string(data, filename);
add_char_to_string(data, FILE_CHAR);
bfs_new->end = data->length;
add_to_list_end(*bfs, bfs_new);
mem_free(filename);
if (fh == -1) goto encode_error;
set_bin(fh);
while (1) {
ssize_t rd = safe_read(fh, buffer, F_BUFLEN);
if (rd) {
if (rd == -1) {
close(fh);
goto encode_error;
}
add_bytes_to_string(data, buffer, rd);
} else {
break;
}
};
close(fh);
}
#undef F_BUFLEN
} else {
add_crlf_to_string(data);
add_crlf_to_string(data);
@ -1035,12 +1018,11 @@ encode_multipart(struct session *ses, LIST_OF(struct submitted_value) *l,
add_boundary(data, boundary);
add_to_string(data, "--\r\n");
check_boundary(data, boundary);
mem_free_if(boundary->offsets);
return;
encode_error:
free_list(*bfs);
mem_free_if(boundary->offsets);
done_string(data);
@ -1151,6 +1133,7 @@ get_form_uri(struct session *ses, struct document_view *doc_view,
{
struct boundary_info boundary;
INIT_LIST_OF(struct submitted_value, submit);
INIT_LIST_OF(struct files_offset, bfs);
struct string data;
struct string go;
int cp_from, cp_to;
@ -1184,7 +1167,8 @@ get_form_uri(struct session *ses, struct document_view *doc_view,
break;
case FORM_METHOD_POST_MP:
encode_multipart(ses, &submit, &data, &boundary, cp_from, cp_to);
encode_multipart(ses, &submit, &data, &boundary,
&bfs, cp_from, cp_to);
break;
case FORM_METHOD_POST_TEXT_PLAIN:
@ -1236,7 +1220,6 @@ get_form_uri(struct session *ses, struct document_view *doc_view,
{
/* Note that we end content type here by a simple '\n',
* replaced later by correct '\r\n' in http_send_header(). */
int i;
add_to_string(&go, form->action);
add_char_to_string(&go, POST_CHAR);
@ -1256,11 +1239,35 @@ get_form_uri(struct session *ses, struct document_view *doc_view,
add_char_to_string(&go, '\n');
}
for (i = 0; i < data.length; i++) {
unsigned char p[3];
if (list_empty(bfs)) {
int i;
ulonghexcat(p, NULL, (int) data.source[i], 2, '0', 0);
add_to_string(&go, p);
for (i = 0; i < data.length; i++) {
unsigned char p[3];
ulonghexcat(p, NULL, (int) data.source[i], 2, '0', 0);
add_to_string(&go, p);
}
} else {
struct files_offset *b;
int i = 0;
foreach (b, bfs) {
for (; i < b->begin; i++) {
unsigned char p[3];
ulonghexcat(p, NULL, (int) data.source[i], 2, '0', 0);
add_to_string(&go, p);
}
add_bytes_to_string(&go, data.source + i, b->end - b->begin);
i = b->end;
}
for (; i < data.length; i++) {
unsigned char p[3];
ulonghexcat(p, NULL, (int) data.source[i], 2, '0', 0);
add_to_string(&go, p);
}
}
}
}
@ -1269,7 +1276,10 @@ get_form_uri(struct session *ses, struct document_view *doc_view,
uri = get_uri(go.source, 0);
done_string(&go);
if (uri) uri->form = 1;
if (uri) {
uri->form = 1;
}
free_list(bfs);
return uri;
}