1182 lines
28 KiB
C
1182 lines
28 KiB
C
/* sched.c
|
|
* Links internal scheduler
|
|
* (c) 2002 Mikulas Patocka
|
|
* This file is a part of the Links program, released under GPL.
|
|
*/
|
|
|
|
#include "links.h"
|
|
|
|
static tcount connection_count = 0;
|
|
|
|
static int active_connections = 0;
|
|
|
|
tcount netcfg_stamp = 0;
|
|
|
|
struct list_head queue = {&queue, &queue};
|
|
|
|
struct h_conn {
|
|
list_entry_1st
|
|
unsigned char *host;
|
|
int conn;
|
|
list_entry_last
|
|
};
|
|
|
|
static struct list_head h_conns = {&h_conns, &h_conns};
|
|
|
|
struct list_head keepalive_connections = {&keepalive_connections, &keepalive_connections};
|
|
|
|
/* prototypes */
|
|
static void send_connection_info(struct connection *c);
|
|
static void del_keepalive_socket(struct k_conn *kc);
|
|
static void check_keepalive_connections(void);
|
|
#ifdef DEBUG
|
|
static void check_queue_bugs(void);
|
|
#else
|
|
#define check_queue_bugs() do { } while (0)
|
|
#endif
|
|
|
|
unsigned long connect_info(int type)
|
|
{
|
|
int i = 0;
|
|
struct connection *ce;
|
|
struct list_head *lce;
|
|
switch (type) {
|
|
case CI_FILES:
|
|
return list_size(&queue);
|
|
case CI_CONNECTING:
|
|
foreach(struct connection, ce, lce, queue) i += ce->state > S_WAIT && ce->state < S_TRANS;
|
|
return i;
|
|
case CI_TRANSFER:
|
|
foreach(struct connection, ce, lce, queue) i += ce->state == S_TRANS;
|
|
return i;
|
|
case CI_KEEP:
|
|
#ifndef DOS
|
|
/* this is very slow on DOS */
|
|
check_keepalive_connections();
|
|
#endif
|
|
return list_size(&keepalive_connections);
|
|
default:
|
|
internal_error("connect_info: bad request");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int getpri(struct connection *c)
|
|
{
|
|
int i;
|
|
for (i = 0; i < N_PRI; i++) if (c->pri[i]) return i;
|
|
internal_error("connection has no owner");
|
|
return N_PRI;
|
|
}
|
|
|
|
static int connection_disappeared(struct connection *c, tcount count)
|
|
{
|
|
struct connection *d;
|
|
struct list_head *ld;
|
|
foreach(struct connection, d, ld, queue) if (c == d && count == d->count) return 0;
|
|
return 1;
|
|
}
|
|
|
|
static struct h_conn *is_host_on_list(struct connection *c)
|
|
{
|
|
unsigned char *ho;
|
|
struct h_conn *h;
|
|
struct list_head *lh;
|
|
if (!(ho = get_host_name(c->url))) return NULL;
|
|
foreach(struct h_conn, h, lh, h_conns) if (!strcmp(cast_const_char h->host, cast_const_char ho)) {
|
|
mem_free(ho);
|
|
return h;
|
|
}
|
|
mem_free(ho);
|
|
return NULL;
|
|
}
|
|
|
|
static int st_r = 0;
|
|
|
|
static void stat_timer(void *c_)
|
|
{
|
|
struct connection *c = (struct connection *)c_;
|
|
struct remaining_info *r = &c->prg;
|
|
uttime now = get_time();
|
|
uttime a = now - r->last_time;
|
|
if (a > SPD_DISP_TIME * 100)
|
|
a = SPD_DISP_TIME * 100;
|
|
if (getpri(c) == PRI_CANCEL && (c->est_length > (longlong)memory_cache_size * MAX_CACHED_OBJECT || c->from > (longlong)memory_cache_size * MAX_CACHED_OBJECT)) register_bottom_half(check_queue, NULL);
|
|
if (c->state > S_WAIT) {
|
|
r->loaded = c->received;
|
|
if ((r->size = c->est_length) < (r->pos = c->from) && r->size != -1)
|
|
r->size = c->from;
|
|
r->dis_b += a;
|
|
while (r->dis_b >= SPD_DISP_TIME * CURRENT_SPD_SEC) {
|
|
r->cur_loaded -= r->data_in_secs[0];
|
|
memmove(r->data_in_secs, r->data_in_secs + 1, sizeof(off_t) * (CURRENT_SPD_SEC - 1));
|
|
r->data_in_secs[CURRENT_SPD_SEC - 1] = 0;
|
|
r->dis_b -= SPD_DISP_TIME;
|
|
}
|
|
r->data_in_secs[CURRENT_SPD_SEC - 1] += r->loaded - r->last_loaded;
|
|
r->cur_loaded += r->loaded - r->last_loaded;
|
|
r->last_loaded = r->loaded;
|
|
r->elapsed += a;
|
|
}
|
|
r->last_time = now;
|
|
r->timer = install_timer(SPD_DISP_TIME, stat_timer, c);
|
|
if (!st_r) send_connection_info(c);
|
|
}
|
|
|
|
void setcstate(struct connection *c, int state)
|
|
{
|
|
struct status *stat;
|
|
struct list_head *lstat;
|
|
if (state < 0)
|
|
if (c->cache)
|
|
finish_cache_entry(c->cache);
|
|
if (c->state < 0 && state >= 0) c->prev_error = c->state;
|
|
if ((c->state = state) == S_TRANS) {
|
|
struct remaining_info *r = &c->prg;
|
|
if (r->timer == NULL) {
|
|
tcount count = c->count;
|
|
if (!r->valid) {
|
|
memset(r, 0, sizeof(struct remaining_info));
|
|
r->valid = 1;
|
|
}
|
|
r->last_time = get_time();
|
|
r->last_loaded = r->loaded;
|
|
st_r = 1;
|
|
stat_timer(c);
|
|
st_r = 0;
|
|
if (connection_disappeared(c, count)) return;
|
|
}
|
|
} else {
|
|
struct remaining_info *r = &c->prg;
|
|
if (r->timer != NULL) kill_timer(r->timer), r->timer = NULL;
|
|
}
|
|
foreach(struct status, stat, lstat, c->statuss) {
|
|
stat->state = state;
|
|
stat->prev_error = c->prev_error;
|
|
}
|
|
if (state >= 0) send_connection_info(c);
|
|
}
|
|
|
|
static struct k_conn *is_host_on_keepalive_list(struct connection *c)
|
|
{
|
|
unsigned char *ho;
|
|
int po;
|
|
void (*ph)(struct connection *);
|
|
struct k_conn *h;
|
|
struct list_head *lh;
|
|
if ((po = get_port(c->url)) == -1) return NULL;
|
|
if (!(ph = get_protocol_handle(c->url))) return NULL;
|
|
if (!(ho = get_keepalive_id(c->url))) return NULL;
|
|
foreach(struct k_conn, h, lh, keepalive_connections)
|
|
if (h->protocol == ph && h->port == po && !strcmp(cast_const_char h->host, cast_const_char ho)) {
|
|
mem_free(ho);
|
|
return h;
|
|
}
|
|
mem_free(ho);
|
|
return NULL;
|
|
}
|
|
|
|
int get_keepalive_socket(struct connection *c, int *protocol_data)
|
|
{
|
|
struct k_conn *k;
|
|
int cc;
|
|
if (c->tries > 0 || c->unrestartable) return -1;
|
|
if (!(k = is_host_on_keepalive_list(c))) return -1;
|
|
cc = k->conn;
|
|
if (protocol_data) *protocol_data = k->protocol_data;
|
|
#ifdef HAVE_SSL
|
|
freeSSL(c->ssl);
|
|
c->ssl = k->ssl;
|
|
#endif
|
|
memcpy(&c->last_lookup_state, &k->last_lookup_state, sizeof(struct lookup_state));
|
|
del_from_list(k);
|
|
mem_free(k->host);
|
|
mem_free(k);
|
|
c->sock1 = cc;
|
|
if (max_tries == 1) c->tries = -1;
|
|
c->keepalive = 1;
|
|
return 0;
|
|
}
|
|
|
|
static int abort_all_keepalive_connections(void)
|
|
{
|
|
int did_something = 0;
|
|
while (!list_empty(keepalive_connections)) {
|
|
del_keepalive_socket(list_struct(keepalive_connections.next, struct k_conn));
|
|
did_something = 1;
|
|
}
|
|
check_keepalive_connections();
|
|
return did_something;
|
|
}
|
|
|
|
static void free_connection_data(struct connection *c)
|
|
{
|
|
struct h_conn *h;
|
|
int rs;
|
|
if (c->sock1 != -1) set_handlers(c->sock1, NULL, NULL, NULL);
|
|
close_socket(&c->sock2);
|
|
if (c->pid) {
|
|
EINTRLOOP(rs, kill(c->pid, SIGINT));
|
|
EINTRLOOP(rs, kill(c->pid, SIGTERM));
|
|
EINTRLOOP(rs, kill(c->pid, SIGKILL));
|
|
c->pid = 0;
|
|
}
|
|
if (!c->running) {
|
|
internal_error("connection already suspended");
|
|
}
|
|
c->running = 0;
|
|
if (c->dnsquery) kill_dns_request(&c->dnsquery);
|
|
if (c->buffer) {
|
|
mem_free(c->buffer);
|
|
c->buffer = NULL;
|
|
}
|
|
if (c->newconn) {
|
|
mem_free(c->newconn);
|
|
c->newconn = NULL;
|
|
}
|
|
if (c->info) {
|
|
mem_free(c->info);
|
|
c->info = NULL;
|
|
}
|
|
clear_connection_timeout(c);
|
|
if (!c->doh) {
|
|
if (--active_connections < 0) {
|
|
internal_error("active connections underflow");
|
|
active_connections = 0;
|
|
}
|
|
}
|
|
if (c->state != S_WAIT && !c->doh) {
|
|
if ((h = is_host_on_list(c))) {
|
|
if (!--h->conn) {
|
|
del_from_list(h);
|
|
mem_free(h->host);
|
|
mem_free(h);
|
|
}
|
|
} else internal_error("suspending connection that is not on the list (state %d)", c->state);
|
|
}
|
|
}
|
|
|
|
static void send_connection_info(struct connection *c)
|
|
{
|
|
#if 0
|
|
int st = c->state;
|
|
tcount count = c->count;
|
|
struct status *stat;
|
|
struct list_head *lstat;
|
|
/*debug("reporting conn info %s", c->url);*/
|
|
foreach(struct status, stat, lstat, c->statuss) {
|
|
/*debug("status list &%p %p %p, status %p %p %p", &c->statuss, c->statuss.next, c->statuss.prev, lstat, lstat->next, lstat->prev);*/
|
|
stat->ce = c->cache;
|
|
verify_list_entry(lstat);
|
|
lstat = lstat->next;
|
|
if (stat->end) stat->end(stat, stat->data);
|
|
if (st >= 0 && connection_disappeared(c, count)) return;
|
|
verify_list_entry(lstat);
|
|
lstat = lstat->prev;
|
|
}
|
|
/*debug("end report %s", c->url);*/
|
|
#else
|
|
if (!list_empty(c->statuss)) {
|
|
int st = c->state;
|
|
tcount count = c->count;
|
|
struct list_head *lstat = c->statuss.next;
|
|
while (1) {
|
|
int e;
|
|
struct status *xstat = list_struct(lstat, struct status);
|
|
xstat->ce = c->cache;
|
|
lstat = lstat->next;
|
|
e = lstat == &c->statuss;
|
|
if (xstat->end) xstat->end(xstat, xstat->data);
|
|
if (e) return;
|
|
if (st >= 0 && connection_disappeared(c, count)) return;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void del_connection(struct connection *c)
|
|
{
|
|
struct cache_entry *ce = c->cache;
|
|
if (ce) ce->refcount++;
|
|
del_from_list(c);
|
|
send_connection_info(c);
|
|
if (ce) ce->refcount--;
|
|
if (c->detached) {
|
|
if (ce && !ce->url[0] && !is_entry_used(ce) && !ce->refcount)
|
|
delete_cache_entry(ce);
|
|
} else {
|
|
if (ce)
|
|
trim_cache_entry(ce);
|
|
}
|
|
mem_free(c->url);
|
|
if (c->prev_url) mem_free(c->prev_url);
|
|
#ifdef HAVE_SSL
|
|
freeSSL(c->ssl);
|
|
#endif
|
|
mem_free(c);
|
|
}
|
|
|
|
void add_keepalive_socket(struct connection *c, uttime timeout, int protocol_data)
|
|
{
|
|
struct k_conn *k;
|
|
int rs;
|
|
free_connection_data(c);
|
|
if (c->sock1 == -1) {
|
|
internal_error("keepalive connection not connected");
|
|
goto del;
|
|
}
|
|
k = mem_alloc(sizeof(struct k_conn));
|
|
if (c->netcfg_stamp != netcfg_stamp ||
|
|
#ifdef HAVE_SSL
|
|
ssl_not_reusable(c->ssl) ||
|
|
#endif
|
|
(k->port = get_port(c->url)) == -1 ||
|
|
!(k->protocol = get_protocol_handle(c->url)) ||
|
|
!(k->host = get_keepalive_id(c->url))) {
|
|
mem_free(k);
|
|
del_connection(c);
|
|
goto clos;
|
|
}
|
|
k->conn = c->sock1;
|
|
k->timeout = timeout;
|
|
k->add_time = get_absolute_time();
|
|
k->protocol_data = protocol_data;
|
|
#ifdef HAVE_SSL
|
|
k->ssl = c->ssl;
|
|
c->ssl = NULL;
|
|
#endif
|
|
memcpy(&k->last_lookup_state, &c->last_lookup_state, sizeof(struct lookup_state));
|
|
add_to_list(keepalive_connections, k);
|
|
del:
|
|
del_connection(c);
|
|
check_queue_bugs();
|
|
register_bottom_half(check_queue, NULL);
|
|
return;
|
|
clos:
|
|
EINTRLOOP(rs, close(c->sock1));
|
|
check_queue_bugs();
|
|
register_bottom_half(check_queue, NULL);
|
|
}
|
|
|
|
static void del_keepalive_socket(struct k_conn *kc)
|
|
{
|
|
int rs;
|
|
del_from_list(kc);
|
|
#ifdef HAVE_SSL
|
|
freeSSL(kc->ssl);
|
|
#endif
|
|
EINTRLOOP(rs, close(kc->conn));
|
|
mem_free(kc->host);
|
|
mem_free(kc);
|
|
}
|
|
|
|
static struct timer *keepalive_timeout = NULL;
|
|
|
|
static void keepalive_timer(void *x)
|
|
{
|
|
keepalive_timeout = NULL;
|
|
check_keepalive_connections();
|
|
}
|
|
|
|
static void check_keepalive_connections(void)
|
|
{
|
|
struct k_conn *kc;
|
|
struct list_head *lkc;
|
|
uttime ct = get_absolute_time();
|
|
int p = 0;
|
|
if (keepalive_timeout != NULL)
|
|
kill_timer(keepalive_timeout), keepalive_timeout = NULL;
|
|
foreach(struct k_conn, kc, lkc, keepalive_connections) {
|
|
if (can_read(kc->conn) || ct - kc->add_time > kc->timeout) {
|
|
lkc = lkc->prev;
|
|
del_keepalive_socket(kc);
|
|
} else
|
|
p++;
|
|
}
|
|
for (; p > MAX_KEEPALIVE_CONNECTIONS; p--)
|
|
if (!list_empty(keepalive_connections))
|
|
del_keepalive_socket(list_struct(keepalive_connections.prev, struct k_conn));
|
|
else
|
|
internal_error("keepalive list empty");
|
|
if (!list_empty(keepalive_connections))
|
|
keepalive_timeout = install_timer(KEEPALIVE_CHECK_TIME, keepalive_timer, NULL);
|
|
}
|
|
|
|
static void add_to_queue(struct connection *c)
|
|
{
|
|
struct connection *cc;
|
|
struct list_head *lcc;
|
|
int pri = getpri(c);
|
|
foreach(struct connection, cc, lcc, queue) if (getpri(cc) > pri) break;
|
|
add_before_list_entry(lcc, &c->list_entry);
|
|
}
|
|
|
|
static void sort_queue(void)
|
|
{
|
|
struct connection *c, *n;
|
|
struct list_head *lc;
|
|
int swp;
|
|
do {
|
|
swp = 0;
|
|
foreach(struct connection, c, lc, queue) if (c->list_entry.next != &queue) {
|
|
n = list_struct(c->list_entry.next, struct connection);
|
|
if (getpri(n) < getpri(c)) {
|
|
del_from_list(c);
|
|
add_after_pos(n, c);
|
|
swp = 1;
|
|
}
|
|
}
|
|
} while (swp);
|
|
}
|
|
|
|
static void interrupt_connection(struct connection *c)
|
|
{
|
|
#ifdef HAVE_SSL
|
|
freeSSL(c->ssl);
|
|
c->ssl = NULL;
|
|
#endif
|
|
close_socket(&c->sock1);
|
|
free_connection_data(c);
|
|
}
|
|
|
|
static void suspend_connection(struct connection *c)
|
|
{
|
|
interrupt_connection(c);
|
|
setcstate(c, S_WAIT);
|
|
}
|
|
|
|
static int try_to_suspend_connection(struct connection *c, unsigned char *ho)
|
|
{
|
|
int pri = getpri(c);
|
|
struct connection *d;
|
|
struct list_head *ld;
|
|
foreachback(struct connection, d, ld, queue) {
|
|
if (getpri(d) <= pri) return -1;
|
|
if (d->state == S_WAIT) continue;
|
|
if (d->unrestartable == 2 && getpri(d) < PRI_CANCEL) continue;
|
|
if (ho) {
|
|
unsigned char *h;
|
|
if (!(h = get_host_name(d->url))) continue;
|
|
if (strcmp(cast_const_char h, cast_const_char ho)) {
|
|
mem_free(h);
|
|
continue;
|
|
}
|
|
mem_free(h);
|
|
}
|
|
suspend_connection(d);
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int is_noproxy_host(unsigned char *host)
|
|
{
|
|
if (!proxies.only_proxies) {
|
|
unsigned char *np = proxies.no_proxy;
|
|
int host_l = (int)strlen(cast_const_char host);
|
|
if (*np) while (1) {
|
|
int l = (int)strcspn(cast_const_char np, ",");
|
|
if (l > host_l)
|
|
goto no_match;
|
|
if (l < host_l && host[host_l - l - 1] != '.')
|
|
goto no_match;
|
|
if (casecmp(np, host + (host_l - l), l))
|
|
goto no_match;
|
|
return 1;
|
|
no_match:
|
|
if (!np[l])
|
|
break;
|
|
np += l + 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int is_noproxy_url(unsigned char *url)
|
|
{
|
|
unsigned char *host = get_host_name(url);
|
|
int r = is_noproxy_host(host);
|
|
mem_free(host);
|
|
return r;
|
|
}
|
|
|
|
static void run_connection(struct connection *c)
|
|
{
|
|
struct h_conn *hc;
|
|
void (*func)(struct connection *);
|
|
if (c->running) {
|
|
internal_error("connection already running");
|
|
return;
|
|
}
|
|
|
|
memset(&c->last_lookup_state, 0, sizeof(struct lookup_state));
|
|
|
|
if (is_noproxy_url(remove_proxy_prefix(c->url))) {
|
|
c->socks_proxy[0] = 0;
|
|
c->dns_append[0] = 0;
|
|
} else {
|
|
safe_strncpy(c->socks_proxy, proxies.socks_proxy, sizeof c->socks_proxy);
|
|
safe_strncpy(c->dns_append, proxies.dns_append, sizeof c->dns_append);
|
|
}
|
|
|
|
if (proxies.only_proxies && !is_proxy_url(c->url) && casecmp(c->url, cast_uchar "data:", 5) && (!*c->socks_proxy || url_bypasses_socks(c->url))) {
|
|
setcstate(c, S_NO_PROXY);
|
|
del_connection(c);
|
|
return;
|
|
}
|
|
|
|
if (!(func = get_protocol_handle(c->url))) {
|
|
s_bad_url:
|
|
if (is_proxy_url(c->url)) setcstate(c, S_BAD_PROXY);
|
|
else setcstate(c, S_BAD_URL);
|
|
del_connection(c);
|
|
return;
|
|
}
|
|
if (c->doh && func != http_func && func != https_func && func != proxy_func)
|
|
goto s_bad_url;
|
|
if (!c->doh) {
|
|
if (!(hc = is_host_on_list(c))) {
|
|
hc = mem_alloc(sizeof(struct h_conn));
|
|
if (!(hc->host = get_host_name(c->url))) {
|
|
mem_free(hc);
|
|
goto s_bad_url;
|
|
}
|
|
hc->conn = 0;
|
|
add_to_list(h_conns, hc);
|
|
}
|
|
hc->conn++;
|
|
active_connections++;
|
|
}
|
|
c->keepalive = 0;
|
|
c->running = 1;
|
|
func(c);
|
|
}
|
|
|
|
static int is_connection_seekable(struct connection *c)
|
|
{
|
|
unsigned char *protocol = get_protocol_name(c->url);
|
|
if (!casestrcmp(protocol, cast_uchar "http") ||
|
|
!casestrcmp(protocol, cast_uchar "https") ||
|
|
!casestrcmp(protocol, cast_uchar "proxy")) {
|
|
unsigned char *d;
|
|
mem_free(protocol);
|
|
if (!c->cache || !c->cache->head)
|
|
return 1;
|
|
d = parse_http_header(c->cache->head, cast_uchar "Accept-Ranges", NULL);
|
|
if (d) {
|
|
mem_free(d);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
if (!casestrcmp(protocol, cast_uchar "ftp")) {
|
|
mem_free(protocol);
|
|
return 1;
|
|
}
|
|
mem_free(protocol);
|
|
return 0;
|
|
}
|
|
|
|
int is_connection_restartable(struct connection *c)
|
|
{
|
|
return !(c->unrestartable >= 2 || (c->tries + 1 >= (max_tries ? max_tries : 1000)));
|
|
}
|
|
|
|
int is_last_try(struct connection *c)
|
|
{
|
|
int is_restartable;
|
|
c->tries++;
|
|
is_restartable = is_connection_restartable(c) && c->tries < 10;
|
|
c->tries--;
|
|
return !is_restartable;
|
|
}
|
|
|
|
void retry_connection(struct connection *c)
|
|
{
|
|
interrupt_connection(c);
|
|
if (!is_connection_restartable(c)) {
|
|
del_connection(c);
|
|
check_queue_bugs();
|
|
register_bottom_half(check_queue, NULL);
|
|
} else {
|
|
c->tries++;
|
|
c->prev_error = c->state;
|
|
run_connection(c);
|
|
}
|
|
}
|
|
|
|
void abort_connection(struct connection *c)
|
|
{
|
|
if (c->running) interrupt_connection(c);
|
|
del_connection(c);
|
|
check_queue_bugs();
|
|
register_bottom_half(check_queue, NULL);
|
|
}
|
|
|
|
static int try_connection(struct connection *c)
|
|
{
|
|
struct h_conn *hc;
|
|
if (!c->doh) {
|
|
if ((hc = is_host_on_list(c))) {
|
|
if (hc->conn >= max_connections_to_host) {
|
|
if (try_to_suspend_connection(c, hc->host)) return 0;
|
|
else return -1;
|
|
}
|
|
}
|
|
if (active_connections >= max_connections) {
|
|
if (try_to_suspend_connection(c, NULL)) return 0;
|
|
else return -1;
|
|
}
|
|
}
|
|
run_connection(c);
|
|
return 1;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
static void check_queue_bugs(void)
|
|
{
|
|
struct connection *d;
|
|
struct list_head *ld;
|
|
int p = 0, ps = 0;
|
|
int cc;
|
|
again:
|
|
cc = 0;
|
|
foreach(struct connection, d, ld, queue) {
|
|
int q = getpri(d);
|
|
cc += d->running && !d->doh;
|
|
if (q < p) {
|
|
if (!ps) {
|
|
internal_error("queue is not sorted");
|
|
sort_queue();
|
|
ps = 1;
|
|
goto again;
|
|
} else {
|
|
internal_error("queue is not sorted even after sort_queue!");
|
|
break;
|
|
}
|
|
} else p = q;
|
|
if (d->state < 0) {
|
|
internal_error("interrupted connection on queue (conn %s, state %d)", d->url, d->state);
|
|
ld = ld->prev;
|
|
abort_connection(d);
|
|
}
|
|
}
|
|
if (cc != active_connections) {
|
|
internal_error("bad number of active connections (counted %d, stored %d)", cc, active_connections);
|
|
active_connections = cc;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void check_queue(void *dummy)
|
|
{
|
|
struct connection *c;
|
|
struct list_head *lc;
|
|
again:
|
|
check_queue_bugs();
|
|
check_keepalive_connections();
|
|
foreach(struct connection, c, lc, queue) {
|
|
struct connection *d;
|
|
struct list_head *ld;
|
|
int cp = getpri(c);
|
|
foreachfrom(struct connection, d, ld, queue, &c->list_entry) {
|
|
if (getpri(d) != cp)
|
|
break;
|
|
if (!d->state && is_host_on_keepalive_list(d))
|
|
if (try_connection(d))
|
|
goto again;
|
|
}
|
|
foreachfrom(struct connection, d, ld, queue, &c->list_entry) {
|
|
if (getpri(d) != cp)
|
|
break;
|
|
if (!d->state)
|
|
if (try_connection(d))
|
|
goto again;
|
|
}
|
|
lc = ld->prev;
|
|
}
|
|
again2:
|
|
foreachback(struct connection, c, lc, queue) {
|
|
if (getpri(c) < PRI_CANCEL) break;
|
|
if (c->state == S_WAIT) {
|
|
setcstate(c, S_INTERRUPTED);
|
|
del_connection(c);
|
|
goto again2;
|
|
} else if (c->est_length > (longlong)memory_cache_size * MAX_CACHED_OBJECT || c->from > (longlong)memory_cache_size * MAX_CACHED_OBJECT) {
|
|
setcstate(c, S_INTERRUPTED);
|
|
abort_connection(c);
|
|
goto again2;
|
|
}
|
|
}
|
|
check_queue_bugs();
|
|
}
|
|
|
|
unsigned char *get_proxy_string(unsigned char *url)
|
|
{
|
|
if (is_noproxy_url(url))
|
|
return NULL;
|
|
if (*proxies.http_proxy && !casecmp(url, cast_uchar "http://", 7)) return proxies.http_proxy;
|
|
if (*proxies.ftp_proxy && !casecmp(url, cast_uchar "ftp://", 6)) return proxies.ftp_proxy;
|
|
#ifdef HAVE_SSL
|
|
if (*proxies.https_proxy && !casecmp(url, cast_uchar "https://", 8)) return proxies.https_proxy;
|
|
#endif
|
|
return NULL;
|
|
}
|
|
|
|
unsigned char *get_proxy(unsigned char *url)
|
|
{
|
|
unsigned char *proxy = get_proxy_string(url);
|
|
unsigned char *u;
|
|
u = stracpy(cast_uchar "");
|
|
if (proxy) {
|
|
add_to_strn(&u, cast_uchar "proxy://");
|
|
add_to_strn(&u, proxy);
|
|
add_to_strn(&u, cast_uchar "/");
|
|
}
|
|
add_to_strn(&u, url);
|
|
return u;
|
|
}
|
|
|
|
int is_proxy_url(unsigned char *url)
|
|
{
|
|
return !casecmp(url, cast_uchar "proxy://", 8);
|
|
}
|
|
|
|
unsigned char *remove_proxy_prefix(unsigned char *url)
|
|
{
|
|
unsigned char *x = NULL;
|
|
if (is_proxy_url(url))
|
|
x = get_url_data(url);
|
|
if (!x)
|
|
x = url;
|
|
return x;
|
|
}
|
|
|
|
int get_allow_flags(unsigned char *url)
|
|
{
|
|
return !casecmp(url, cast_uchar "smb://", 6) ? ALLOW_SMB :
|
|
!casecmp(url, cast_uchar "file://", 7) ? ALLOW_FILE : 0;
|
|
}
|
|
|
|
int disallow_url(unsigned char *url, int allow_flags)
|
|
{
|
|
if (!casecmp(url, cast_uchar "smb://", 6) && !(allow_flags & ALLOW_SMB) && !smb_options.allow_hyperlinks_to_smb) {
|
|
return S_SMB_NOT_ALLOWED;
|
|
}
|
|
if (!casecmp(url, cast_uchar "file://", 7) && !(allow_flags & ALLOW_FILE)) {
|
|
return S_FILE_NOT_ALLOWED;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* prev_url is a pointer to previous url or NULL */
|
|
/* prev_url will NOT be deallocated */
|
|
void load_url(unsigned char *url, unsigned char *prev_url, struct status *stat, int pri, int no_cache, int no_compress, int doh, int allow_flags, off_t position)
|
|
{
|
|
struct cache_entry *e = NULL;
|
|
struct connection *c;
|
|
struct list_head *lc;
|
|
unsigned char *u;
|
|
int must_detach = 0;
|
|
int err;
|
|
|
|
if (stat) {
|
|
stat->c = NULL;
|
|
stat->ce = NULL;
|
|
stat->state = S_OUT_OF_MEM;
|
|
stat->prev_error = 0;
|
|
stat->pri = pri;
|
|
}
|
|
#ifdef DEBUG
|
|
foreach(struct connection, c, lc, queue) {
|
|
struct status *st;
|
|
struct list_head *lst;
|
|
foreach(struct status, st, lst, c->statuss) {
|
|
if (st == stat) {
|
|
internal_error("status already assigned to '%s'", c->url);
|
|
stat->state = S_INTERNAL;
|
|
if (stat->end) stat->end(stat, stat->data);
|
|
goto ret;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
if (!doh && is_url_blocked(url)) {
|
|
if (stat) {
|
|
stat->state = S_BLOCKED_URL;
|
|
if (stat->end) stat->end(stat, stat->data);
|
|
}
|
|
goto ret;
|
|
}
|
|
err = disallow_url(url, allow_flags);
|
|
if (err) {
|
|
if (stat) {
|
|
stat->state = err;
|
|
if (stat->end) stat->end(stat, stat->data);
|
|
}
|
|
goto ret;
|
|
}
|
|
if (no_cache <= NC_CACHE && !find_in_cache(url, &e)) {
|
|
if (e->incomplete) {
|
|
e->refcount--;
|
|
goto skip_cache;
|
|
}
|
|
if (!aggressive_cache && no_cache > NC_ALWAYS_CACHE) {
|
|
if (e->expire_time && e->expire_time < get_absolute_seconds()) {
|
|
if (no_cache < NC_IF_MOD) no_cache = NC_IF_MOD;
|
|
e->refcount--;
|
|
goto skip_cache;
|
|
}
|
|
}
|
|
if (no_compress) {
|
|
unsigned char *enc;
|
|
enc = parse_http_header(e->head, cast_uchar "Content-Encoding", NULL);
|
|
if (enc) {
|
|
mem_free(enc);
|
|
e->refcount--;
|
|
must_detach = 1;
|
|
goto skip_cache;
|
|
}
|
|
}
|
|
if (stat) {
|
|
stat->ce = e;
|
|
stat->state = S__OK;
|
|
if (stat->end) stat->end(stat, stat->data);
|
|
}
|
|
e->refcount--;
|
|
goto ret;
|
|
}
|
|
skip_cache:
|
|
if (is_proxy_url(url)) {
|
|
if (stat) {
|
|
stat->state = S_BAD_URL;
|
|
if (stat->end) stat->end(stat, stat->data);
|
|
}
|
|
goto ret;
|
|
}
|
|
u = get_proxy(url);
|
|
foreach(struct connection, c, lc, queue) if (!c->detached && !strcmp(cast_const_char c->url, cast_const_char u)) {
|
|
if (c->from < position) continue;
|
|
if (c->doh != doh) {
|
|
must_detach = 1;
|
|
break;
|
|
}
|
|
if (no_compress && !c->no_compress) {
|
|
unsigned char *enc;
|
|
if ((c->state >= S_WAIT && c->state < S_TRANS) || !c->cache) {
|
|
must_detach = 1;
|
|
break;
|
|
}
|
|
enc = parse_http_header(c->cache->head, cast_uchar "Content-Encoding", NULL);
|
|
if (enc) {
|
|
mem_free(enc);
|
|
must_detach = 1;
|
|
break;
|
|
}
|
|
}
|
|
mem_free(u);
|
|
if (getpri(c) > pri) {
|
|
del_from_list(c);
|
|
c->pri[pri]++;
|
|
add_to_queue(c);
|
|
register_bottom_half(check_queue, NULL);
|
|
} else c->pri[pri]++;
|
|
if (stat) {
|
|
stat->prg = &c->prg;
|
|
stat->c = c;
|
|
stat->ce = c->cache;
|
|
add_to_list(c->statuss, stat);
|
|
setcstate(c, c->state);
|
|
}
|
|
check_queue_bugs();
|
|
goto ret;
|
|
}
|
|
c = mem_calloc(sizeof(struct connection));
|
|
c->count = connection_count++;
|
|
c->url = u;
|
|
c->prev_url = stracpy(prev_url);
|
|
c->running = 0;
|
|
c->prev_error = 0;
|
|
if (position || must_detach) {
|
|
c->from = position;
|
|
} else if (no_cache >= NC_IF_MOD || !e) {
|
|
c->from = 0;
|
|
} else {
|
|
struct fragment *frag;
|
|
struct list_head *lfrag;
|
|
c->from = 0;
|
|
foreach(struct fragment, frag, lfrag, e->frag) {
|
|
if (frag->offset != c->from)
|
|
break;
|
|
c->from += frag->length;
|
|
}
|
|
}
|
|
memset(c->pri, 0, sizeof c->pri);
|
|
c->pri[pri] = 1;
|
|
c->no_cache = no_cache;
|
|
c->sock1 = c->sock2 = -1;
|
|
c->dnsquery = NULL;
|
|
c->tries = 0;
|
|
c->netcfg_stamp = netcfg_stamp;
|
|
init_list(c->statuss);
|
|
c->info = NULL;
|
|
c->buffer = NULL;
|
|
c->newconn = NULL;
|
|
c->cache = NULL;
|
|
c->est_length = -1;
|
|
c->unrestartable = 0;
|
|
#ifdef HAVE_ANY_COMPRESSION
|
|
c->no_compress = http_options.no_compression || no_compress || dmp == D_SOURCE;
|
|
#else
|
|
c->no_compress = 1;
|
|
#endif
|
|
c->doh = doh;
|
|
c->prg.timer = NULL;
|
|
c->timer = NULL;
|
|
if (position || must_detach) {
|
|
if (new_cache_entry(cast_uchar "", &c->cache)) {
|
|
mem_free(c->url);
|
|
if (c->prev_url) mem_free(c->prev_url);
|
|
mem_free(c);
|
|
if (stat) {
|
|
stat->state = S_OUT_OF_MEM;
|
|
if (stat->end) stat->end(stat, stat->data);
|
|
}
|
|
goto ret;
|
|
}
|
|
c->cache->refcount--;
|
|
detach_cache_entry(c->cache);
|
|
c->detached = 2;
|
|
}
|
|
if (stat) {
|
|
stat->prg = &c->prg;
|
|
stat->c = c;
|
|
stat->ce = NULL;
|
|
add_to_list(c->statuss, stat);
|
|
}
|
|
add_to_queue(c);
|
|
setcstate(c, S_WAIT);
|
|
check_queue_bugs();
|
|
register_bottom_half(check_queue, NULL);
|
|
|
|
ret:
|
|
return;
|
|
}
|
|
|
|
void change_connection(struct status *oldstat, struct status *newstat, int newpri)
|
|
{
|
|
struct connection *c;
|
|
int oldpri;
|
|
oldpri = oldstat->pri;
|
|
if (oldstat->state < 0) {
|
|
if (newstat) {
|
|
struct cache_entry *ce = oldstat->ce;
|
|
if (ce) ce->refcount++;
|
|
newstat->ce = oldstat->ce;
|
|
newstat->state = oldstat->state;
|
|
newstat->prev_error = oldstat->prev_error;
|
|
if (newstat->end) newstat->end(newstat, newstat->data);
|
|
if (ce) ce->refcount--;
|
|
}
|
|
return;
|
|
}
|
|
check_queue_bugs();
|
|
c = oldstat->c;
|
|
if (--c->pri[oldpri] < 0) {
|
|
internal_error("priority counter underflow");
|
|
c->pri[oldpri] = 0;
|
|
}
|
|
c->pri[newpri]++;
|
|
del_from_list(oldstat);
|
|
oldstat->state = S_INTERRUPTED;
|
|
if (newstat) {
|
|
newstat->prg = &c->prg;
|
|
add_to_list(c->statuss, newstat);
|
|
newstat->state = c->state;
|
|
newstat->prev_error = c->prev_error;
|
|
newstat->pri = newpri;
|
|
newstat->c = c;
|
|
newstat->ce = c->cache;
|
|
}
|
|
if (c->detached && !newstat) {
|
|
setcstate(c, S_INTERRUPTED);
|
|
abort_connection(c);
|
|
}
|
|
sort_queue();
|
|
check_queue_bugs();
|
|
register_bottom_half(check_queue, NULL);
|
|
}
|
|
|
|
void detach_connection(struct status *stat, off_t pos, int want_to_restart, int dont_check_refcount)
|
|
{
|
|
struct connection *c;
|
|
int i, n_users;
|
|
off_t l;
|
|
if (stat->state < 0) return;
|
|
c = stat->c;
|
|
if (c->no_compress && want_to_restart) return;
|
|
if (!c->cache || (!dont_check_refcount && c->cache->refcount)) return;
|
|
want_to_restart |= pos > c->from && is_connection_seekable(c);
|
|
if (c->detached) goto detach_done;
|
|
if (c->est_length == -1) l = c->from;
|
|
else l = c->est_length;
|
|
if (!dont_check_refcount && l < (longlong)memory_cache_size * MAX_CACHED_OBJECT && !want_to_restart) return;
|
|
n_users = 0;
|
|
for (i = 0; i < PRI_CANCEL; i++) n_users += c->pri[i];
|
|
if (!n_users) internal_error("detaching free connection");
|
|
if (n_users != 1) return;
|
|
shrink_memory(SH_CHECK_QUOTA, 0);
|
|
detach_cache_entry(c->cache);
|
|
c->detached = 1;
|
|
detach_done:
|
|
free_entry_to(c->cache, pos);
|
|
|
|
if (c->detached < 2 && want_to_restart) {
|
|
int running = c->running;
|
|
c->no_compress = 1;
|
|
if (running) interrupt_connection(c);
|
|
c->from = pos;
|
|
if (running) run_connection(c);
|
|
c->detached = 2;
|
|
}
|
|
}
|
|
|
|
static uttime get_timeout_value(struct connection *c)
|
|
{
|
|
uttime t;
|
|
if (c->state == S_CONN || c->state == S_CONN_ANOTHER)
|
|
t = timeout_multiple_addresses * (c->tries < 1 ? 1 : c->tries + 1);
|
|
else if (c->unrestartable)
|
|
t = unrestartable_receive_timeout;
|
|
else
|
|
t = receive_timeout;
|
|
t *= 500;
|
|
return t;
|
|
}
|
|
|
|
static void connection_timeout(void *c_)
|
|
{
|
|
struct connection *c = (struct connection *)c_;
|
|
c->timer = NULL;
|
|
if (c->state == S_CONN || c->state == S_CONN_ANOTHER) {
|
|
retry_connect(c, get_error_from_errno(ETIMEDOUT), 0);
|
|
return;
|
|
}
|
|
setcstate(c, S_TIMEOUT);
|
|
retry_connection(c);
|
|
}
|
|
|
|
static void connection_timeout_1(void *c_)
|
|
{
|
|
struct connection *c = (struct connection *)c_;
|
|
c->timer = install_timer(get_timeout_value(c), connection_timeout, c);
|
|
}
|
|
|
|
void clear_connection_timeout(struct connection *c)
|
|
{
|
|
if (c->timer != NULL) kill_timer(c->timer), c->timer = NULL;
|
|
}
|
|
|
|
void set_connection_timeout(struct connection *c)
|
|
{
|
|
clear_connection_timeout(c);
|
|
c->timer = install_timer(get_timeout_value(c), connection_timeout_1, c);
|
|
}
|
|
|
|
void set_connection_timeout_keepal(struct connection *c)
|
|
{
|
|
if (c->keepalive && !c->unrestartable) {
|
|
clear_connection_timeout(c);
|
|
c->timer = install_timer(timeout_multiple_addresses * 1000, connection_timeout, c);
|
|
return;
|
|
}
|
|
set_connection_timeout(c);
|
|
}
|
|
|
|
void abort_all_connections(void)
|
|
{
|
|
while (!list_empty(queue)) {
|
|
struct connection *c = list_struct(queue.next, struct connection);
|
|
setcstate(c, S_INTERRUPTED);
|
|
abort_connection(c);
|
|
}
|
|
abort_all_keepalive_connections();
|
|
}
|
|
|
|
int abort_background_connections(void)
|
|
{
|
|
int did_something = 0;
|
|
struct connection *c;
|
|
struct list_head *lc;
|
|
again:
|
|
foreach(struct connection, c, lc, queue) {
|
|
if (getpri(c) >= PRI_CANCEL) {
|
|
setcstate(c, S_INTERRUPTED);
|
|
abort_connection(c);
|
|
did_something = 1;
|
|
goto again;
|
|
}
|
|
}
|
|
return did_something | abort_all_keepalive_connections();
|
|
}
|
|
|
|
int is_entry_used(struct cache_entry *e)
|
|
{
|
|
struct connection *c;
|
|
struct list_head *lc;
|
|
foreach(struct connection, c, lc, queue) if (c->cache == e) return 1;
|
|
return 0;
|
|
}
|
|
|
|
struct blacklist_entry {
|
|
list_entry_1st
|
|
int flags;
|
|
list_entry_last
|
|
unsigned char host[1];
|
|
};
|
|
|
|
static struct list_head blacklist = { &blacklist, &blacklist };
|
|
|
|
void add_blacklist_entry(unsigned char *host, int flags)
|
|
{
|
|
struct blacklist_entry *b;
|
|
struct list_head *lb;
|
|
size_t sl;
|
|
foreach(struct blacklist_entry, b, lb, blacklist) if (!casestrcmp(host, b->host)) {
|
|
b->flags |= flags;
|
|
return;
|
|
}
|
|
sl = strlen(cast_const_char host);
|
|
if (sl > MAXINT - sizeof(struct blacklist_entry)) overalloc();
|
|
b = mem_alloc(sizeof(struct blacklist_entry) + sl);
|
|
b->flags = flags;
|
|
strcpy(cast_char b->host, cast_const_char host);
|
|
add_to_list(blacklist, b);
|
|
}
|
|
|
|
void del_blacklist_entry(unsigned char *host, int flags)
|
|
{
|
|
struct blacklist_entry *b;
|
|
struct list_head *lb;
|
|
foreach(struct blacklist_entry, b, lb, blacklist) if (!casestrcmp(host, b->host)) {
|
|
b->flags &= ~flags;
|
|
if (!b->flags) {
|
|
del_from_list(b);
|
|
mem_free(b);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
int get_blacklist_flags(unsigned char *host)
|
|
{
|
|
struct blacklist_entry *b;
|
|
struct list_head *lb;
|
|
foreach(struct blacklist_entry, b, lb, blacklist) if (!casestrcmp(host, b->host)) return b->flags;
|
|
return 0;
|
|
}
|
|
|
|
void free_blacklist(void)
|
|
{
|
|
free_list(struct blacklist_entry, blacklist);
|
|
}
|