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:
commit
b8b54a5325
6
NEWS
6
NEWS
@ -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.
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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")},
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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++;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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. */
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
305
src/protocol/http/post.c
Normal 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
62
src/protocol/http/post.h
Normal 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
|
@ -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);
|
||||
|
@ -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 */
|
||||
|
@ -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
114
src/util/random.c
Normal 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
11
src/util/random.h
Normal 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
|
@ -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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user