/* objreq.c * Object Requester * (c) 2002 Mikulas Patocka * This file is a part of the Links program, released under GPL. */ #include "links.h" static void objreq_end(struct status *, void *); static void object_timer(void *); static struct list_head requests = {&requests, &requests}; static tcount obj_req_count = 1; #define LL gf_val(1, G_BFU_FONT_SIZE) #define MAX_UID_LEN 256 struct auth_dialog { unsigned char uid[MAX_UID_LEN]; unsigned char passwd[MAX_UID_LEN]; unsigned char *realm; int proxy; tcount count; unsigned char msg[1]; }; static inline struct object_request *find_rq(tcount c) { struct object_request *rq; struct list_head *lrq; foreach(struct object_request, rq, lrq, requests) if (rq->count == c) return rq; return NULL; } static void auth_fn(struct dialog_data *dlg) { struct terminal *term = dlg->win->term; struct auth_dialog *a = dlg->dlg->udata; int max = 0, min = 0; int w, rw; int y = 0; max_text_width(term, a->msg, &max, AL_LEFT); min_text_width(term, a->msg, &min, AL_LEFT); max_text_width(term, TEXT_(T_USERID), &max, AL_LEFT); min_text_width(term, TEXT_(T_USERID), &min, AL_LEFT); max_text_width(term, TEXT_(T_PASSWORD), &max, AL_LEFT); min_text_width(term, TEXT_(T_PASSWORD), &min, AL_LEFT); max_buttons_width(term, dlg->items + 2, 2, &max); min_buttons_width(term, dlg->items + 2, 2, &min); w = term->x * 9 / 10 - 2 * DIALOG_LB; if (w > max) w = max; if (w < min) w = min; rw = w; dlg_format_text(dlg, NULL, a->msg, 0, &y, w, &rw, COLOR_DIALOG_TEXT, AL_LEFT); y += LL; dlg_format_text_and_field(dlg, NULL, TEXT_(T_USERID), dlg->items, 0, &y, w, &rw, COLOR_DIALOG_TEXT, AL_LEFT); y += LL; dlg_format_text_and_field(dlg, NULL, TEXT_(T_PASSWORD), dlg->items + 1, 0, &y, w, &rw, COLOR_DIALOG_TEXT, AL_LEFT); y += LL; dlg_format_buttons(dlg, NULL, dlg->items + 2, 2, 0, &y, w, &rw, AL_CENTER); w = rw; dlg->xw = rw + 2 * DIALOG_LB; dlg->yw = y + 2 * DIALOG_TB; center_dlg(dlg); draw_dlg(dlg); y = dlg->y + DIALOG_TB; y += LL; dlg_format_text(dlg, term, a->msg, dlg->x + DIALOG_LB, &y, w, NULL, COLOR_DIALOG_TEXT, AL_LEFT); y += LL; dlg_format_text_and_field(dlg, term, TEXT_(T_USERID), dlg->items, dlg->x + DIALOG_LB, &y, w, NULL, COLOR_DIALOG_TEXT, AL_LEFT); y += LL; dlg_format_text_and_field(dlg, term, TEXT_(T_PASSWORD), dlg->items + 1, dlg->x + DIALOG_LB, &y, w, NULL, COLOR_DIALOG_TEXT, AL_LEFT); y += LL; dlg_format_buttons(dlg, term, dlg->items + 2, 2, dlg->x + DIALOG_LB, &y, w, NULL, AL_CENTER); } static int auth_cancel(struct dialog_data *dlg, struct dialog_item_data *item) { struct auth_dialog *a = dlg->dlg->udata; struct object_request *rq = find_rq(a->count); if (rq) { rq->hold = 0; rq->state = O_OK; if (rq->timer != NULL) kill_timer(rq->timer); rq->timer = install_timer(0, object_timer, rq); if (!rq->ce) (rq->ce = rq->ce_internal)->refcount++; } cancel_dialog(dlg, item); return 0; } static int auth_ok(struct dialog_data *dlg, struct dialog_item_data *item) { struct auth_dialog *a = dlg->dlg->udata; struct object_request *rq = find_rq(a->count); if (rq) { struct session *ses; int net_cp; unsigned char *uid, *passwd; get_dialog_data(dlg); ses = list_struct(dlg->win->term->windows.prev, struct window)->data; get_convert_table(rq->ce_internal->head, term_charset(dlg->win->term), ses->ds.assume_cp, &net_cp, NULL, ses->ds.hard_assume); uid = convert(term_charset(dlg->win->term), net_cp, a->uid, NULL); passwd = convert(term_charset(dlg->win->term), net_cp, a->passwd, NULL); add_auth(rq->url, a->realm, uid, passwd, a->proxy); mem_free(uid); mem_free(passwd); rq->hold = 0; change_connection(&rq->stat, NULL, PRI_CANCEL); load_url(rq->url, rq->prev_url, &rq->stat, rq->pri, NC_RELOAD, 0, 0, 0, 0); } cancel_dialog(dlg, item); return 0; } static int auth_window(struct object_request *rq, unsigned char *realm) { unsigned char *host, *port; struct dialog *d; struct auth_dialog * volatile a; /* avoid gcc-10 warning */ struct terminal *term; unsigned char *urealm; struct session *ses; int net_cp; if (!(term = find_terminal(rq->term))) return -1; ses = list_struct(term->windows.prev, struct window)->data; get_convert_table(rq->ce_internal->head, term_charset(term), ses->ds.assume_cp, &net_cp, NULL, ses->ds.hard_assume); if (rq->ce_internal->http_code == 407) { unsigned char *h = get_proxy_string(rq->url); if (!h) h = cast_uchar ""; host = display_host(term, h); } else { unsigned char *h = get_host_name(rq->url); if (!h) return -1; host = display_host(term, h); mem_free(h); if ((port = get_port_str(rq->url))) { add_to_strn(&host, cast_uchar ":"); add_to_strn(&host, port); mem_free(port); } } urealm = convert(term_charset(term), net_cp, realm, NULL); d = mem_alloc(sizeof(struct dialog) + 5 * sizeof(struct dialog_item) + sizeof(struct auth_dialog) + strlen(cast_const_char get_text_translation(TEXT_(T_ENTER_USERNAME), term)) + strlen(cast_const_char urealm) + 1 + strlen(cast_const_char get_text_translation(TEXT_(T_AT), term)) + strlen(cast_const_char host)); memset(d, 0, sizeof(struct dialog) + 5 * sizeof(struct dialog_item) + sizeof(struct auth_dialog)); a = (struct auth_dialog *)((unsigned char *)d + sizeof(struct dialog) + 5 * sizeof(struct dialog_item)); strcpy(cast_char a->msg, cast_const_char get_text_translation(TEXT_(T_ENTER_USERNAME), term)); strcat(cast_char a->msg, cast_const_char urealm); if (*host) { strcat(cast_char a->msg, "\n"); strcat(cast_char a->msg, cast_const_char get_text_translation(TEXT_(T_AT), term)); strcat(cast_char a->msg, cast_const_char host); } mem_free(host); mem_free(urealm); a->proxy = rq->ce_internal->http_code == 407; a->realm = stracpy(realm); a->count = rq->count; d->udata = a; if (rq->ce_internal->http_code == 401) d->title = TEXT_(T_AUTHORIZATION_REQUIRED); else d->title = TEXT_(T_PROXY_AUTHORIZATION_REQUIRED); d->fn = auth_fn; d->items[0].type = D_FIELD; d->items[0].dlen = MAX_UID_LEN; d->items[0].data = a->uid; d->items[1].type = D_FIELD_PASS; d->items[1].dlen = MAX_UID_LEN; d->items[1].data = a->passwd; d->items[2].type = D_BUTTON; d->items[2].gid = B_ENTER; d->items[2].fn = auth_ok; d->items[2].text = TEXT_(T_OK); d->items[3].type = D_BUTTON; d->items[3].gid = B_ESC; d->items[3].fn = auth_cancel; d->items[3].text = TEXT_(T_CANCEL); do_dialog(term, d, getml(d, a->realm, NULL)); return 0; } #ifdef HAVE_SSL_CERTIFICATES struct cert_dialog { tcount term; unsigned char *host; int bl; int state; }; static void cert_action(struct object_request *rq, int yes) { if (yes > 0) { rq->hold = 0; change_connection(&rq->stat, NULL, PRI_CANCEL); load_url(rq->url, rq->prev_url, &rq->stat, rq->pri, NC_CACHE, 0, 0, 0, 0); } else { rq->hold = 0; rq->dont_print_error = 1; rq->state = O_FAILED; if (rq->timer != NULL) kill_timer(rq->timer); rq->timer = install_timer(0, object_timer, rq); } } static void cert_forall(struct cert_dialog *cs, int yes) { struct object_request *rq; struct list_head *lrq; if (yes > 0) { add_blacklist_entry(cs->host, cs->bl); del_blacklist_entry(cs->host, BL_AVOID_INSECURE); } if (yes < 0) { add_blacklist_entry(cs->host, BL_AVOID_INSECURE); del_blacklist_entry(cs->host, BL_IGNORE_CERTIFICATE); del_blacklist_entry(cs->host, BL_IGNORE_DOWNGRADE); del_blacklist_entry(cs->host, BL_IGNORE_CIPHER); } foreach(struct object_request, rq, lrq, requests) if (rq->term == cs->term && rq->hold == HOLD_CERT && rq->stat.state == cs->state) { unsigned char *host = get_host_name(rq->url); if (!strcmp(cast_const_char host, cast_const_char cs->host)) cert_action(rq, yes); mem_free(host); } } static void cert_yes(void *data) { cert_forall((struct cert_dialog *)data, 1); } static void cert_no(void *data) { cert_forall((struct cert_dialog *)data, 0); } static void cert_never(void *data) { cert_forall((struct cert_dialog *)data, -1); } static int cert_compare(void *data1, void *data2) { struct cert_dialog *cs1 = (struct cert_dialog *)data1; struct cert_dialog *cs2 = (struct cert_dialog *)data2; return !strcmp(cast_const_char cs1->host, cast_const_char cs2->host) && cs1->state == cs2->state; } static int cert_window(struct object_request *rq) { struct terminal *term; unsigned char *h, *host, *title, *text; struct cert_dialog *cs; struct memory_list *ml; if (!(term = find_terminal(rq->term))) return -1; h = get_host_name(rq->url); if (get_blacklist_flags(h) & BL_AVOID_INSECURE) { mem_free(h); return -1; } cs = mem_alloc(sizeof(struct cert_dialog)); cs->term = rq->term; cs->host = h; cs->state = rq->stat.state; if (rq->stat.state == S_INVALID_CERTIFICATE) { title = TEXT_(T_INVALID_CERTIFICATE); text = TEXT_(T_DOESNT_HAVE_A_VALID_CERTIFICATE); cs->bl = BL_IGNORE_CERTIFICATE; } else if (rq->stat.state == S_DOWNGRADED_METHOD) { title = TEXT_(T_DOWNGRADED_METHOD); text = TEXT_(T_USES_DOWNGRADED_METHOD); cs->bl = BL_IGNORE_DOWNGRADE; } else { title = TEXT_(T_INSECURE_CIPHER); text = TEXT_(T_USES_INSECURE_CIPHER); cs->bl = BL_IGNORE_CIPHER; } host = display_host(term, h); ml = getml(cs, h, host, NULL); if (find_msg_box(term, title, cert_compare, cs)) { freeml(ml); return 0; } msg_box(term, ml, title, AL_CENTER, TEXT_(T_THE_SERVER_), host, text, MSG_BOX_END, (void *)cs, 3, TEXT_(T_NO), cert_no, B_ESC, TEXT_(T_YES), cert_yes, B_ENTER, TEXT_(T_NEVER), cert_never, 0); return 0; } #endif /* prev_url is a pointer to previous url or NULL */ /* prev_url will NOT be deallocated */ void request_object(struct terminal *term, unsigned char *url, unsigned char *prev_url, int pri, int cache, int allow_flags, void (*upcall)(struct object_request *, void *), void *data, struct object_request **rqp) { struct object_request *rq; rq = mem_calloc(sizeof(struct object_request)); rq->state = O_WAITING; rq->refcount = 1; rq->term = term ? term->count : 0; rq->stat.end = objreq_end; rq->stat.data = rq; rq->orig_url = stracpy(url); rq->url = stracpy(url); rq->pri = pri; rq->cache = cache; rq->upcall = upcall; rq->data = data; rq->timer = NULL; rq->last_update = get_time() - STAT_UPDATE_MAX; if (rq->prev_url) mem_free(rq->prev_url); rq->prev_url = stracpy(prev_url); if (rqp) *rqp = rq; rq->count = obj_req_count++; add_to_list(requests, rq); load_url(url, prev_url, &rq->stat, pri, cache, 0, 0, allow_flags, 0); } static void set_ce_internal(struct object_request *rq) { if (rq->stat.ce != rq->ce_internal) { if (!rq->stat.ce) { rq->ce_internal->refcount--; rq->ce_internal = NULL; } else { if (rq->ce_internal) rq->ce_internal->refcount--; rq->ce_internal = rq->stat.ce; rq->ce_internal->refcount++; } } } static void objreq_end(struct status *stat, void *data) { struct object_request *rq = (struct object_request *)data; set_ce_internal(rq); if (stat->state < 0) { #ifdef HAVE_SSL_CERTIFICATES if (!stat->ce && rq->state == O_WAITING && (stat->state == S_INVALID_CERTIFICATE || stat->state == S_DOWNGRADED_METHOD || stat->state == S_INSECURE_CIPHER) && ssl_options.certificates == SSL_WARN_ON_INVALID_CERTIFICATE) { if (!cert_window(rq)) { rq->hold = HOLD_CERT; rq->redirect_cnt = 0; goto tm; } } #endif if (stat->ce && rq->state == O_WAITING && stat->ce->redirect) { if (rq->redirect_cnt++ < MAX_REDIRECTS) { int cache, allow_flags; unsigned char *u, *pos, *url_host; change_connection(stat, NULL, PRI_CANCEL); u = join_urls(rq->url, stat->ce->redirect); if ((pos = extract_position(u))) { if (rq->goto_position) mem_free(rq->goto_position); rq->goto_position = pos; } cache = rq->cache; url_host = get_host_name(rq->url); if (cache < NC_RELOAD && (!strcmp(cast_const_char u, cast_const_char rq->url) || !strcmp(cast_const_char u, cast_const_char rq->orig_url) || (url_host && casestrstr(url_host, cast_uchar "consent.google")) || rq->redirect_cnt >= MAX_CACHED_REDIRECTS)) cache = NC_RELOAD; if (url_host) mem_free(url_host); allow_flags = get_allow_flags(rq->url); mem_free(rq->url); rq->url = u; load_url(u, rq->prev_url, &rq->stat, rq->pri, cache, 0, 0, allow_flags, 0); return; } else { maxrd: rq->stat.state = S_CYCLIC_REDIRECT; } } if (stat->ce && rq->state == O_WAITING && (stat->ce->http_code == 401 || stat->ce->http_code == 407)) { unsigned char *realm = get_auth_realm(rq->url, stat->ce->head, stat->ce->http_code == 407); unsigned char *user; if (!realm) goto xx; if (stat->ce->http_code == 401 && !find_auth(rq->url, realm)) { mem_free(realm); if (rq->redirect_cnt++ >= MAX_REDIRECTS) goto maxrd; change_connection(stat, NULL, PRI_CANCEL); load_url(rq->url, rq->prev_url, &rq->stat, rq->pri, NC_RELOAD, 0, 0, 0, 0); return; } user = get_user_name(rq->url); if (stat->ce->http_code == 401 && user && *user) { mem_free(user); mem_free(realm); goto xx; } mem_free(user); if (!auth_window(rq, realm)) { rq->hold = HOLD_AUTH; rq->redirect_cnt = 0; mem_free(realm); goto tm; } mem_free(realm); goto xx; } } if ((stat->state < 0 || stat->state == S_TRANS) && stat->ce && !stat->ce->redirect && stat->ce->http_code != 401 && stat->ce->http_code != 407) { rq->state = O_LOADING; if (0) { xx: rq->state = O_OK; } if (!rq->ce) (rq->ce = stat->ce)->refcount++; } tm: if (rq->timer != NULL) kill_timer(rq->timer); rq->timer = install_timer(0, object_timer, rq); } static void object_timer(void *rq_) { struct object_request *rq = (struct object_request *)rq_; off_t last; rq->timer = NULL; set_ce_internal(rq); last = rq->last_bytes; if (rq->ce) rq->last_bytes = rq->ce->length; if (rq->stat.state < 0 && !rq->hold && (!rq->ce_internal || !rq->ce_internal->redirect || rq->stat.state == S_CYCLIC_REDIRECT)) { if (rq->ce_internal && rq->stat.state != S_CYCLIC_REDIRECT) { rq->state = rq->stat.state != S__OK ? O_INCOMPLETE : O_OK; } else rq->state = O_FAILED; } if (rq->stat.state != S_TRANS) { if (rq->stat.state >= 0) rq->timer = install_timer(STAT_UPDATE_MAX, object_timer, rq); rq->last_update = get_time() - STAT_UPDATE_MAX; if (rq->upcall) rq->upcall(rq, rq->data); } else { uttime ct = get_time(); uttime t = ct - rq->last_update; rq->timer = install_timer(STAT_UPDATE_MIN, object_timer, rq); if (t >= STAT_UPDATE_MAX || (t >= STAT_UPDATE_MIN && rq->ce && rq->last_bytes > last)) { rq->last_update = ct; if (rq->upcall) rq->upcall(rq, rq->data); } } } void release_object_get_stat(struct object_request **rqq, struct status *news, int pri) { struct object_request *rq = *rqq; if (!rq) return; *rqq = NULL; if (--rq->refcount) return; change_connection(&rq->stat, news, pri); if (rq->timer != NULL) kill_timer(rq->timer); if (rq->ce_internal) rq->ce_internal->refcount--; if (rq->ce) rq->ce->refcount--; mem_free(rq->orig_url); mem_free(rq->url); if (rq->prev_url) mem_free(rq->prev_url); if (rq->goto_position) mem_free(rq->goto_position); del_from_list(rq); mem_free(rq); } void release_object(struct object_request **rqq) { release_object_get_stat(rqq, NULL, PRI_CANCEL); } void detach_object_connection(struct object_request *rq, off_t pos) { if (rq->state == O_WAITING || rq->state == O_FAILED) { internal_error("detach_object_connection: no data received"); return; } if (rq->refcount == 1) { detach_connection(&rq->stat, pos, 0, 1); } } void clone_object(struct object_request *rq, struct object_request **rqq) { (*rqq = rq)->refcount++; }