1
0
mirror of https://github.com/rkd77/elinks.git synced 2024-07-15 14:54:40 -04:00
elinks/src/viewer/text/form.c
2021-10-17 18:17:48 +02:00

2184 lines
51 KiB
C

/** Forms viewing/manipulation handling
* @file */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifndef _GNU_SOURCE
#define _GNU_SOURCE /* XXX: we want memrchr() ! */
#endif
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.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 "bfu/listmenu.h"
#include "bfu/dialog.h"
#include "config/kbdbind.h"
#include "dialogs/menu.h"
#include "document/document.h"
#include "document/forms.h"
#include "document/view.h"
#include "ecmascript/ecmascript.h"
#include "intl/libintl.h"
#include "formhist/formhist.h"
#include "mime/mime.h"
#include "osdep/ascii.h"
#include "osdep/osdep.h"
#include "protocol/uri.h"
#include "session/session.h"
#include "session/task.h"
#include "terminal/kbd.h"
#include "terminal/terminal.h"
#include "terminal/window.h"
#include "util/conv.h"
#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"
#include "viewer/text/form.h"
#include "viewer/text/link.h"
#include "viewer/text/textarea.h"
#include "viewer/text/view.h"
#include "viewer/text/vs.h"
/* 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(char *name, char *value, enum form_type type,
struct el_form_control *fc, int position)
{
struct submitted_value *sv;
sv = mem_alloc(sizeof(*sv));
if (!sv) return NULL;
sv->value = stracpy(value);
if (!sv->value) { mem_free(sv); return NULL; }
sv->name = stracpy(name);
if (!sv->name) { mem_free(sv->value); mem_free(sv); return NULL; }
sv->type = type;
sv->form_control = fc;
sv->position = position;
return sv;
}
/** @relates submitted_value */
void
done_submitted_value(struct submitted_value *sv)
{
if (!sv) return;
mem_free_if(sv->value);
mem_free_if(sv->name);
mem_free(sv);
}
static void
fixup_select_state(struct el_form_control *fc, struct form_state *fs)
{
int i;
assert(fc && fs);
if_assert_failed return;
if (fs->state >= 0
&& fs->state < fc->nvalues
&& !strcmp(fc->values[fs->state], fs->value))
return;
for (i = 0; i < fc->nvalues; i++)
if (!strcmp(fc->values[i], fs->value)) {
fs->state = i;
return;
}
fs->state = 0;
mem_free_set(&fs->value, stracpy(fc->nvalues
? fc->values[0]
: (char *) ""));
}
/* menu_func_T */
void
selected_item(struct terminal *term, void *item_, void *ses_)
{
struct session *ses = ses_;
int item = (long) item_;
struct document_view *doc_view;
struct link *link;
struct form_state *fs;
struct el_form_control *fc;
assert(term && ses);
if_assert_failed return;
doc_view = current_frame(ses);
assert(doc_view && doc_view->vs && doc_view->document);
if_assert_failed return;
link = get_current_link(doc_view);
if (!link || link->type != LINK_SELECT) return;
fc = get_link_form_control(link);
fs = find_form_state(doc_view, fc);
if (fs) {
if (item >= 0 && item < fc->nvalues) {
fs->state = item;
mem_free_set(&fs->value, stracpy(fc->values[item]));
}
fixup_select_state(fc, fs);
}
refresh_view(ses, doc_view, 0);
}
static void
init_form_state(struct document_view *doc_view,
struct el_form_control *fc, struct form_state *fs)
{
struct terminal *term;
int doc_cp, viewer_cp;
assert(fc && fs);
if_assert_failed return;
doc_cp = doc_view->document->cp;
term = doc_view->session->tab->term;
viewer_cp = get_terminal_codepage(term);
mem_free_set(&fs->value, NULL);
switch (fc->type) {
case FC_TEXT:
case FC_PASSWORD:
#ifdef CONFIG_FORMHIST
fs->value = null_or_stracpy(
get_form_history_value(
fc->form->action, fc->name));
#endif /* CONFIG_FORMHIST */
/* fall through */
case FC_TEXTAREA:
if (fs->value == NULL) {
fs->value = convert_string(
get_translation_table(doc_cp, viewer_cp),
fc->default_value,
strlen(fc->default_value),
viewer_cp, CSM_FORM,
&fs->state, NULL, NULL);
}
fs->state = fs->value ? strlen(fs->value) : 0;
#ifdef CONFIG_UTF8
if (fc->type == FC_TEXTAREA)
fs->state_cell = 0;
#endif /* CONFIG_UTF8 */
fs->vpos = 0;
break;
case FC_FILE:
fs->value = stracpy("");
fs->state = 0;
fs->vpos = 0;
break;
case FC_SELECT:
fs->value = convert_string(
get_translation_table(doc_cp, viewer_cp),
fc->default_value,
strlen(fc->default_value),
viewer_cp, CSM_FORM,
&fs->state, NULL, NULL);
fs->state = fc->default_state;
fixup_select_state(fc, fs);
break;
case FC_CHECKBOX:
case FC_RADIO:
fs->state = fc->default_state;
/* Fall-through */
case FC_SUBMIT:
case FC_IMAGE:
case FC_RESET:
case FC_BUTTON:
case FC_HIDDEN:
/* We don't want to recode hidden fields. */
fs->value = stracpy(fc->default_value);
break;
}
}
struct form_state *
find_form_state(struct document_view *doc_view, struct el_form_control *fc)
{
struct view_state *vs;
struct form_state *fs;
int n;
assert(doc_view && doc_view->vs && fc);
if_assert_failed return NULL;
vs = doc_view->vs;
n = fc->g_ctrl_num;
if (n >= vs->form_info_len) {
int nn = n + 1;
#if defined(CONFIG_ECMASCRIPT_SMJS) || defined(CONFIG_QUICKJS)
const struct form_state *const old_form_info = vs->form_info;
#endif
fs = mem_align_alloc(&vs->form_info, vs->form_info_len, nn, 0);
if (!fs) return NULL;
vs->form_info = fs;
vs->form_info_len = nn;
#if defined(CONFIG_ECMASCRIPT_SMJS) || defined(CONFIG_QUICKJS)
/* TODO: Standard C does not allow this comparison;
* if the memory to which old_form_info pointed has
* been freed, then the value of the pointer itself is
* indeterminate. Fixing this would require changing
* mem_align_alloc to tell the caller whether it did
* realloc or not. */
if (vs->form_info != old_form_info) {
/* vs->form_info[] was moved to a different address.
* Update all the ECMAScript objects that have
* pointers to its elements. */
for (nn = 0; nn < vs->form_info_len; nn++)
ecmascript_moved_form_state(&vs->form_info[nn]);
}
#endif /* CONFIG_ECMASCRIPT_SMJS */
}
fs = &vs->form_info[n];
if (fs->form_view && fs->form_view->form_num == fc->form->form_num
&& fs->g_ctrl_num == fc->g_ctrl_num
&& fs->position == fc->position
&& fs->type == fc->type)
return fs;
mem_free_if(fs->value);
memset(fs, 0, sizeof(*fs));
fs->form_view = find_form_view(doc_view, fc->form);
fs->g_ctrl_num = fc->g_ctrl_num;
fs->position = fc->position;
fs->type = fc->type;
init_form_state(doc_view, fc, fs);
return fs;
}
struct el_form_control *
find_form_control(struct document *document, struct form_state *fs)
{
struct form *form = find_form_by_form_view(document, fs->form_view);
struct el_form_control *fc;
foreach (fc, form->items) {
if (fs->g_ctrl_num == fc->g_ctrl_num
&& fs->position == fc->position
&& fs->type == fc->type)
return fc;
}
return NULL;
}
struct form_view *
find_form_view_in_vs(struct view_state *vs, int form_num)
{
struct form_view *fv;
assert(vs);
foreach (fv, vs->forms)
if (fv->form_num == form_num)
return fv;
fv = mem_calloc(1, sizeof(*fv));
fv->form_num = form_num;
add_to_list(vs->forms, fv);
return fv;
}
struct form_view *
find_form_view(struct document_view *doc_view, struct form *form)
{
return find_form_view_in_vs(doc_view->vs, form->form_num);
}
struct form *
find_form_by_form_view(struct document *document, struct form_view *fv)
{
struct form *form;
foreach (form, document->forms) {
if (form->form_num == fv->form_num)
return form;
}
return NULL;
}
/** Free any data owned by @a fs, but not the struct form_state
* itself, because that is normally allocated as part of an array.
* @relates form_state */
void
done_form_state(struct form_state *fs)
{
#if defined(CONFIG_ECMASCRIPT) || defined(CONFIG_ECMASCRIPT_SMJS)
ecmascript_detach_form_state(fs);
#endif
mem_free_if(fs->value);
}
/** Free @a fv and any data owned by it. This does not call
* del_from_list(fv), so the caller must usually do that first.
* @relates form_view */
void
done_form_view(struct form_view *fv)
{
#if defined(CONFIG_ECMASCRIPT) || defined(CONFIG_ECMASCRIPT_SMJS)
ecmascript_detach_form_view(fv);
#endif
mem_free(fv);
}
int
get_current_state(struct session *ses)
{
struct document_view *doc_view;
struct link *link;
struct form_state *fs;
assert(ses);
if_assert_failed return -1;
doc_view = current_frame(ses);
assert(doc_view && doc_view->vs && doc_view->document);
if_assert_failed return -1;
link = get_current_link(doc_view);
if (!link || link->type != LINK_SELECT) return -1;
fs = find_form_state(doc_view, get_link_form_control(link));
if (fs) return fs->state;
return -1;
}
void
draw_form_entry(struct terminal *term, struct document_view *doc_view,
struct link *link)
{
struct form_state *fs;
struct el_form_control *fc;
struct view_state *vs;
struct el_box *box;
int dx, dy;
assert(term && doc_view && doc_view->document && doc_view->vs && link);
if_assert_failed return;
fc = get_link_form_control(link);
assertm(fc != NULL, "link %d has no form control", (int) (link - doc_view->document->links));
if_assert_failed return;
fs = find_form_state(doc_view, fc);
if (!fs) return;
box = &doc_view->box;
vs = doc_view->vs;
dx = box->x - vs->x;
dy = box->y - vs->y;
switch (fc->type) {
char *s;
#ifdef CONFIG_UTF8
char *text, *end, *last_in_view;
#endif /* CONFIG_UTF8 */
int len;
int i, x, y;
case FC_TEXT:
case FC_PASSWORD:
case FC_FILE:
if (!link->npoints) break;
y = link->points[0].y + dy;
if (!row_is_in_box(box, y))
break;
x = link->points[0].x + dx;
#ifdef CONFIG_UTF8
if (term->utf8_cp) goto utf8;
#endif /* CONFIG_UTF8 */
int_bounds(&fs->vpos, fs->state - fc->size + 1, fs->state);
len = strlen(fs->value) - fs->vpos;
for (i = 0; i < fc->size; i++, x++) {
unsigned char data;
if (!col_is_in_box(box, x)) continue;
if (fs->value && i >= -fs->vpos && i < len)
data = fc->type != FC_PASSWORD
? fs->value[i + fs->vpos] : '*';
else
data = '_';
draw_char_data(term, x, y, data);
}
break;
#ifdef CONFIG_UTF8
utf8:
retry_after_scroll:
text = fs->value;
if (!text) text = "";
len = strlen(text);
int_bounds(&fs->state, 0, len);
int_bounds(&fs->vpos, 0, fs->state);
end = text + len;
text += fs->vpos;
last_in_view = NULL;
for (i = 0; i < fc->size; ) {
unicode_val_T data;
int cells, cell;
char *maybe_in_view = text;
data = utf8_to_unicode(&text, end);
if (data == UCS_NO_CHAR) /* end of string */
data = '_';
else if (fc->type == FC_PASSWORD)
data = '*';
cells = unicode_to_cell(data);
if (i + cells <= fc->size) {
last_in_view = maybe_in_view;
if (colspan_is_in_box(box, x + i, cells)) {
/* The character fits completely.
* Draw the character, and mark any
* further cells with UCS_NO_CHAR. */
draw_char_data(term, x + i, y, data);
for (cell = 1; cell < cells; cell++)
draw_char_data(term, x + i + cell,
y, UCS_NO_CHAR);
goto drew_char;
}
}
/* The character does not fit completely.
* Write UCS_ORPHAN_CELL to the cells that
* do fit. */
for (cell = 0; cell < cells; cell++) {
if (col_is_in_box(box, x + i + cell)
&& i + cell < fc->size)
draw_char_data(term,
x + i + cell, y,
UCS_ORPHAN_CELL);
}
drew_char:
i += cells;
}
/* The int_bounds calls above ensured that the
* insertion point cannot be at the left side
* of the scrolled-visible part of the text.
* However it can still be at the right side.
* Check whether we need to change fs->vpos.
*
* This implementation attempts to follow
* these rules:
* - If the insertion point is at the end of
* the string, leave at least one empty cell
* so that there is a place for the cursor.
* - If a character follows the insertion
* point, make that character fully visible;
* note the character may be double-width.
* - If fc->size < 2, it is not possible to
* make a double-width character fully
* visible. In this case, it is OK if the
* output is ugly, but ELinks must not fall
* into an infinite loop or crash.
* - The length of the string should not affect
* how long this function takes. The width
* of the widget naturally will.
* - Optimize the case where fields are drawn
* several times without being modified.
*
* It follows that:
* - If the "for i" loop above hit UCS_NO_CHAR,
* then there is no need to scroll.
* - When the string ends with a double-width
* character that fits in only partially,
* then text==end, but the field may have
* to be scrolled. */
if (fs->value && last_in_view
&& last_in_view < fs->value + fs->state) {
char *ptr = fs->value + fs->state;
int cells = fc->size;
enum utf8_step how = (fc->type == FC_PASSWORD)
? UTF8_STEP_CHARACTERS
: UTF8_STEP_CELLS_FEWER;
/* The insertion point is at the right
* side of the scrolled-visible part
* of the text. Decide a new fs->vpos
* by counting cells backwards from
* @ptr. But first advance @ptr past
* the character that follows the
* insertion point, so that it will be
* fully displayed. If there is no
* such character, reserve one cell
* for the cursor anyway. */
if (utf8_to_unicode(&ptr, end) == UCS_NO_CHAR)
--cells;
ptr = utf8_step_backward(ptr, fs->value,
cells, how, NULL);
if (fs->vpos != ptr - fs->value) {
fs->vpos = ptr - fs->value;
goto retry_after_scroll;
}
}
break;
#endif /* CONFIG_UTF8 */
case FC_TEXTAREA:
draw_textarea(term, fs, doc_view, link);
break;
case FC_CHECKBOX:
case FC_RADIO:
if (link->npoints < 2) break;
x = link->points[1].x + dx;
y = link->points[1].y + dy;
if (is_in_box(box, x, y))
draw_char_data(term, x, y, fs->state ? 'X' : ' ');
break;
case FC_SELECT:
fixup_select_state(fc, fs);
if (fs->state < fc->nvalues)
s = fc->labels[fs->state];
else
/* XXX: when can this happen? --pasky */
s = "";
/* insert preceding '[_' */
i = 0;
x = link->points[i].x + dx;
y = link->points[i].y + dy;
if (is_in_box(box, x, y))
draw_char_data(term, x, y, '[');
i++;
x = link->points[i].x + dx;
y = link->points[i].y + dy;
if (is_in_box(box, x, y))
draw_char_data(term, x, y, '_');
i++;
#ifndef CONFIG_UTF8
len = s ? strlen(s) : 0;
for (; i < link->npoints-2; i++) {
x = link->points[i].x + dx;
y = link->points[i].y + dy;
if (is_in_box(box, x, y))
draw_char_data(term, x, y, i-2 < len ? s[i] : '_');
}
#else
text = s;
end = strchr((const char *)s, '\0');
len = utf8_ptr2cells(text, end);
for (; i < link->npoints-2; i++) {
x = link->points[i].x + dx;
y = link->points[i].y + dy;
if (is_in_box(box, x, y)) {
unicode_val_T data;
if (i-2 < len) {
int cell;
data = utf8_to_unicode(&s, end);
cell = unicode_to_cell(data);
if (i - 1 < len && cell == 2) {
draw_char_data(term, x++, y, data);
data = UCS_NO_CHAR;
i++;
} else if (cell == 2) {
data = UCS_ORPHAN_CELL;
}
} else
data = '_';
draw_char_data(term, x, y, data);
}
}
#endif /* CONFIG_UTF8 */
/* insert trailing ' ]'. */
x = link->points[i].x + dx;
y = link->points[i].y + dy;
if (is_in_box(box, x, y))
draw_char_data(term, x, y, '_');
i++;
x = link->points[i].x + dx;
y = link->points[i].y + dy;
if (is_in_box(box, x, y))
draw_char_data(term, x, y, ']');
break;
case FC_SUBMIT:
case FC_IMAGE:
case FC_RESET:
case FC_BUTTON:
case FC_HIDDEN:
break;
}
}
void
draw_forms(struct terminal *term, struct document_view *doc_view)
{
struct link *l1, *l2;
assert(term && doc_view);
if_assert_failed return;
l1 = get_first_link(doc_view);
l2 = get_last_link(doc_view);
if (!l1 || !l2) {
assertm(!l1 && !l2, "get_first_link == %p, get_last_link == %p", l1, l2);
/* Return path :-). */
return;
}
do {
struct el_form_control *fc = get_link_form_control(l1);
if (!fc) continue;
draw_form_entry(term, doc_view, l1);
} while (l1++ < l2);
}
/** @relates submitted_value */
void
done_submitted_value_list(LIST_OF(struct submitted_value) *list)
{
struct submitted_value *sv, *svtmp;
assert(list);
if_assert_failed return;
foreach (sv, *list) {
svtmp = sv;
sv = sv->prev;
del_from_list(svtmp);
done_submitted_value(svtmp);
}
}
static void
add_submitted_value_to_list(struct el_form_control *fc,
struct form_state *fs,
LIST_OF(struct submitted_value) *list)
{
struct submitted_value *sub;
char *name;
enum form_type type;
int position;
assert(fc && fs && list);
name = fc->name;
position = fc->position;
type = fc->type;
switch (fc->type) {
case FC_TEXT:
case FC_PASSWORD:
case FC_FILE:
case FC_TEXTAREA:
sub = init_submitted_value(name, fs->value, type, fc, position);
if (sub) add_to_list(*list, sub);
break;
case FC_CHECKBOX:
case FC_RADIO:
if (!fs->state) break;
/* fall through */
case FC_SUBMIT:
case FC_HIDDEN:
case FC_RESET:
case FC_BUTTON:
sub = init_submitted_value(name, fs->value, type, fc,
position);
if (sub) add_to_list(*list, sub);
break;
case FC_SELECT:
if (!fc->nvalues) break;
fixup_select_state(fc, fs);
sub = init_submitted_value(name, fs->value, type, fc, position);
if (sub) add_to_list(*list, sub);
break;
case FC_IMAGE:
name = straconcat(fc->name, ".x", (char *) NULL);
if (!name) break;
sub = init_submitted_value(name, "0", type, fc, position);
mem_free(name);
if (sub) add_to_list(*list, sub);
name = straconcat(fc->name, ".y", (char *) NULL);
if (!name) break;
sub = init_submitted_value(name, "0", type, fc, position);
mem_free(name);
if (sub) add_to_list(*list, sub);
break;
}
}
static void
sort_submitted_values(LIST_OF(struct submitted_value) *list)
{
while (1) {
struct submitted_value *sub;
int changed = 0;
foreach (sub, *list) if (list_has_next(*list, sub))
if (sub->next->position < sub->position) {
struct submitted_value *next = sub->next;
del_from_list(sub);
add_at_pos(next, sub);
sub = next;
changed = 1;
}
foreachback (sub, *list) if (list_has_next(*list, sub))
if (sub->next->position < sub->position) {
struct submitted_value *next = sub->next;
del_from_list(sub);
add_at_pos(next, sub);
sub = next;
changed = 1;
}
if (!changed) break;
};
}
static void
get_successful_controls(struct document_view *doc_view,
struct el_form_control *fc,
LIST_OF(struct submitted_value) *list)
{
struct el_form_control *fc2;
assert(doc_view && fc && fc->form && list);
if_assert_failed return;
foreach (fc2, fc->form->items) {
if (((fc2->type != FC_SUBMIT &&
fc2->type != FC_IMAGE &&
fc2->type != FC_RESET &&
fc2->type != FC_BUTTON) || fc2 == fc)
&& fc2->name && fc2->name[0]) {
struct form_state *fs = find_form_state(doc_view, fc2);
if (!fs) continue;
add_submitted_value_to_list(fc2, fs, list);
}
}
sort_submitted_values(list);
}
char *
encode_crlf(struct submitted_value *sv)
{
struct string newtext;
int i;
assert(sv && sv->value);
if_assert_failed return NULL;
if (!init_string(&newtext)) return NULL;
for (i = 0; sv->value[i]; i++) {
if (sv->value[i] == '\r') {
if (sv->value[i+1] != '\n')
add_crlf_to_string(&newtext);
} else if (sv->value[i] == '\n')
add_crlf_to_string(&newtext);
else
add_char_to_string(&newtext, sv->value[i]);
}
return newtext.source;
}
static void
encode_controls(LIST_OF(struct submitted_value) *l, struct string *data,
int cp_from, int cp_to)
{
struct submitted_value *sv;
struct conv_table *convert_table = NULL;
int lst = 0;
assert(l && data);
if_assert_failed return;
foreach (sv, *l) {
char *p2 = NULL;
if (lst)
add_char_to_string(data, '&');
else
lst = 1;
encode_uri_string(data, sv->name, strlen(sv->name), 1);
add_char_to_string(data, '=');
/* Convert back to original encoding (see html_form_control()
* for the original recoding). */
if (sv->type == FC_TEXTAREA) {
char *p;
p = encode_textarea(sv);
if (p) {
if (!convert_table)
convert_table = get_translation_table(cp_from, cp_to);
p2 = convert_string(convert_table, p,
strlen(p), -1, CSM_FORM, NULL, NULL, NULL);
mem_free(p);
}
} else if (sv->type == FC_TEXT ||
sv->type == FC_PASSWORD) {
if (!convert_table)
convert_table = get_translation_table(cp_from, cp_to);
p2 = convert_string(convert_table, sv->value,
strlen(sv->value), -1, CSM_FORM, NULL, NULL, NULL);
} else if (sv->type == FC_HIDDEN) {
p2 = encode_crlf(sv);
} else {
p2 = stracpy(sv->value);
}
if (p2) {
encode_uri_string(data, p2, strlen(p2), 1);
mem_free(p2);
}
}
}
#define BOUNDARY_LENGTH 32
#define realloc_bound_ptrs(bptrs, bptrs_size) \
mem_align_alloc(bptrs, bptrs_size, bptrs_size + 1, 0xFF)
struct boundary_info {
int count;
int *offsets;
char string[BOUNDARY_LENGTH];
};
/** @relates boundary_info */
static void
randomize_boundary(char *data, int length)
{
int i;
random_nonce((unsigned char *)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));
randomize_boundary(boundary->string, BOUNDARY_LENGTH);
}
/** Add boundary to string and save the offset
* @relates boundary_info */
static inline void
add_boundary(struct string *data, struct boundary_info *boundary)
{
add_to_string(data, "--");
if (realloc_bound_ptrs(&boundary->offsets, boundary->count))
boundary->offsets[boundary->count++] = data->length;
add_bytes_to_string(data, boundary->string, BOUNDARY_LENGTH);
}
/** Format a multipart/form-data body for a POST request.
*
* @param ses
* Display an info_box() in the terminal of this session if an error
* occurs.
* @param[in] l
* List of values to be sent to the server.
* @param[out] data
* Append the body here. This is in the same format as uri.post,
* except this never has a Content-Type at the beginning, the
* literal parts are not encoded in hexadecimal, and the file names
* are not percent-encoded. Therefore the result would be ambiguous
* without @a bfs.
* @param[out] boundary
* A random boundary %string, and a list of offsets where the
* boundary was used, so that the caller can in principle change the
* %string and update all of its uses if the original one conflicts
* with some of the submitted values. However, the caller does not
* do that nowadays because reading through the attached files would
* be too expensive. It just assumes the boundary is random enough.
* @param[out] bfs
* List of offsets of names of files to be uploaded. This is how
* the caller knows which occurrences of ::FILE_CHAR in @a data
* should be encoded and which ones should not.
* @param[in] cp_from
* Codepage of the submitted-value strings in @a l.
* @param[in] cp_to
* Codepage wanted by the server.
*
* @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,
LIST_OF(struct files_offset) *bfs, int cp_from, int cp_to)
{
struct conv_table *convert_table = NULL;
struct submitted_value *sv;
assert(ses && l && data && boundary);
if_assert_failed return;
init_boundary(boundary);
foreach (sv, *l) {
add_boundary(data, boundary);
add_crlf_to_string(data);
/** @bug FIXME: name is not encoded.
* From RFC 1867:
* multipart/form-data contains a series of parts.
* Each %part is expected to contain a content-disposition
* header where the value is "form-data" and a name attribute
* specifies the field name within the form,
* e.g., 'content-disposition: form-data; name="xxxxx"',
* where xxxxx is the field name corresponding to that field.
* Field names originally in non-ASCII character sets may be
* encoded using the method outlined in RFC 1522. */
add_to_string(data, "Content-Disposition: form-data; name=\"");
add_to_string(data, sv->name);
add_char_to_string(data, '"');
if (sv->type == FC_FILE) {
char *extension;
add_to_string(data, "; filename=\"");
add_to_string(data, get_filename_position(sv->value));
/* It sends bad data if the file name contains ", but
Netscape does the same */
/* FIXME: We should follow RFCs 1522, 1867,
* 2047 (updated by rfc 2231), to provide correct support
* for non-ASCII and special characters in values. --Zas */
add_char_to_string(data, '"');
/* Add a Content-Type header if the type is configured */
extension = strrchr((const char *)sv->value, '.');
if (extension) {
char *type = get_extension_content_type(extension);
if (type) {
add_crlf_to_string(data);
add_to_string(data, "Content-Type: ");
add_to_string(data, type);
mem_free(type);
}
}
add_crlf_to_string(data);
add_crlf_to_string(data);
if (*sv->value) {
char *filename;
struct files_offset *bfs_new;
if (get_cmd_opt_bool("anonymous")) {
errno = EPERM;
goto encode_error;
}
filename = expand_tilde(sv->value);
if (!filename) 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);
}
} else {
add_crlf_to_string(data);
add_crlf_to_string(data);
/* Convert back to original encoding (see
* html_special_form_control() for the original
* recoding). */
if (sv->type == FC_TEXT || sv->type == FC_PASSWORD ||
sv->type == FC_TEXTAREA) {
char *p;
if (!convert_table)
convert_table = get_translation_table(cp_from,
cp_to);
p = convert_string(convert_table, sv->value,
strlen(sv->value), -1, CSM_FORM, NULL,
NULL, NULL);
if (p) {
add_to_string(data, p);
mem_free(p);
}
} else {
add_to_string(data, sv->value);
}
}
add_crlf_to_string(data);
}
/* End-boundary */
add_boundary(data, boundary);
add_to_string(data, "--\r\n");
mem_free_if(boundary->offsets);
return;
encode_error:
free_list(*bfs);
mem_free_if(boundary->offsets);
done_string(data);
/* XXX: This error message should move elsewhere. --Zas */
info_box(ses->tab->term, MSGBOX_FREE_TEXT,
N_("Error while posting form"), ALIGN_CENTER,
msg_text(ses->tab->term, N_("Could not load file %s: %s"),
sv->value, strerror(errno)));
}
static void
encode_newlines(struct string *string, char *data)
{
for (; *data; data++) {
if (*data == '\n' || *data == '\r') {
char buffer[3];
/* Hex it. */
buffer[0] = '%';
buffer[1] = hx((((int) *data) & 0xF0) >> 4);
buffer[2] = hx(((int) *data) & 0xF);
add_bytes_to_string(string, buffer, 3);
} else {
add_char_to_string(string, *data);
}
}
}
static void
encode_text_plain(LIST_OF(struct submitted_value) *l, struct string *data,
int cp_from, int cp_to)
{
struct submitted_value *sv;
struct conv_table *convert_table = get_translation_table(cp_from, cp_to);
assert(l && data);
if_assert_failed return;
foreach (sv, *l) {
char *area51 = NULL;
char *value = sv->value;
add_to_string(data, sv->name);
add_char_to_string(data, '=');
switch (sv->type) {
case FC_TEXTAREA:
value = area51 = encode_textarea(sv);
if (!area51) break;
/* Fall through */
case FC_HIDDEN:
if (!area51) value = area51 = encode_crlf(sv);
if (!area51) break;
/* Fall through */
case FC_TEXT:
case FC_PASSWORD:
/* Convert back to original encoding (see
* html_form_control() for the original recoding). */
value = convert_string(convert_table, value,
strlen(value), -1, CSM_FORM,
NULL, NULL, NULL);
default:
/* Falling right through to free that textarea stuff */
mem_free_if(area51);
/* Did the conversion fail? */
if (!value) break;
encode_newlines(data, value);
/* Free if we did convert something */
if (value != sv->value) mem_free(value);
}
add_crlf_to_string(data);
}
}
void
do_reset_form(struct document_view *doc_view, struct form *form)
{
struct el_form_control *fc;
assert(doc_view && doc_view->document);
if_assert_failed return;
foreach (fc, form->items) {
struct form_state *fs = find_form_state(doc_view, fc);
if (fs) init_form_state(doc_view, fc, fs);
}
}
enum frame_event_status
reset_form(struct session *ses, struct document_view *doc_view, int a)
{
struct link *link = get_current_link(doc_view);
if (!link) return FRAME_EVENT_OK;
do_reset_form(doc_view, get_link_form_control(link)->form);
draw_forms(ses->tab->term, doc_view);
/* Could be the refresh return value and then ditch the draw_forms()
* call. */
return FRAME_EVENT_OK;
}
struct uri *
get_form_uri(struct session *ses, struct document_view *doc_view,
struct el_form_control *fc)
{
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;
struct uri *uri;
struct form *form;
assert(ses && ses->tab && ses->tab->term);
if_assert_failed return NULL;
assert(doc_view && doc_view->document && fc && fc->form);
if_assert_failed return NULL;
form = fc->form;
if (fc->type == FC_RESET) {
do_reset_form(doc_view, form);
return NULL;
}
if (!form->action
|| !init_string(&data))
return NULL;
get_successful_controls(doc_view, fc, &submit);
cp_from = get_terminal_codepage(ses->tab->term);
cp_to = doc_view->document->cp;
switch (form->method) {
case FORM_METHOD_GET:
case FORM_METHOD_POST:
encode_controls(&submit, &data, cp_from, cp_to);
break;
case FORM_METHOD_POST_MP:
encode_multipart(ses, &submit, &data, &boundary,
&bfs, cp_from, cp_to);
break;
case FORM_METHOD_POST_TEXT_PLAIN:
encode_text_plain(&submit, &data, cp_from, cp_to);
}
#ifdef CONFIG_FORMHIST
/* XXX: We check data.source here because a NULL value can indicate
* not only a memory allocation failure, but also an error reading
* a file that is to be uploaded. TODO: Distinguish between
* these two classes of errors (is it worth it?). -- Miciah */
if (data.source
&& get_opt_bool("document.browse.forms.show_formhist", ses))
memorize_form(ses, &submit, form);
#endif
done_submitted_value_list(&submit);
if (!data.source
|| !init_string(&go)) {
done_string(&data);
return NULL;
}
switch (form->method) {
case FORM_METHOD_GET:
{
char *pos = strchr((const char *)form->action, '#');
if (pos) {
add_bytes_to_string(&go, form->action, pos - form->action);
} else {
add_to_string(&go, form->action);
}
if (strchr((const char *)go.source, '?'))
add_char_to_string(&go, '&');
else
add_char_to_string(&go, '?');
add_string_to_string(&go, &data);
if (pos) add_to_string(&go, pos);
break;
}
case FORM_METHOD_POST:
case FORM_METHOD_POST_MP:
case FORM_METHOD_POST_TEXT_PLAIN:
{
/* Note that we end content type here by a simple '\n',
* replaced later by correct '\r\n' in http_send_header(). */
add_to_string(&go, form->action);
add_char_to_string(&go, POST_CHAR);
if (form->method == FORM_METHOD_POST) {
add_to_string(&go, "application/x-www-form-urlencoded\n");
} else if (form->method == FORM_METHOD_POST_TEXT_PLAIN) {
/* Dunno about this one but we don't want the full
* hextcat thingy. --jonas */
add_to_string(&go, "text/plain\n");
add_to_string(&go, data.source);
break;
} else {
add_to_string(&go, "multipart/form-data; boundary=");
add_bytes_to_string(&go, boundary.string, BOUNDARY_LENGTH);
add_char_to_string(&go, '\n');
}
if (list_empty(bfs)) {
int i;
for (i = 0; i < data.length; i++) {
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++) {
char p[3];
ulonghexcat(p, NULL, (int) data.source[i], 2, '0', 0);
add_to_string(&go, p);
}
assert(i == b->begin);
assert(b->end - b->begin >= 2);
assert(data.source[b->begin] == FILE_CHAR);
assert(data.source[b->end - 1] == FILE_CHAR);
add_char_to_string(&go, FILE_CHAR);
encode_uri_string(&go, data.source + i + 1,
b->end - b->begin - 2, 0);
add_char_to_string(&go, FILE_CHAR);
i = b->end;
}
for (; i < data.length; i++) {
char p[3];
ulonghexcat(p, NULL, (int) data.source[i], 2, '0', 0);
add_to_string(&go, p);
}
}
}
}
done_string(&data);
uri = get_uri(go.source, 0);
done_string(&go);
if (uri) {
uri->form = 1;
}
free_list(bfs);
return uri;
}
#undef BOUNDARY_LENGTH
enum frame_event_status
submit_form(struct session *ses, struct document_view *doc_view, int do_reload)
{
goto_current_link(ses, doc_view, do_reload);
return FRAME_EVENT_OK;
}
void
submit_given_form(struct session *ses, struct document_view *doc_view,
struct form *form, int do_reload)
{
/* Added support for submitting forms in hidden
* links in 1.285, commented code can safely be removed once we have made sure the new
* code does the right thing. */
#if 0
struct document *document = doc_view->document;
int link;
for (link = 0; link < document->nlinks; link++) {
struct el_form_control *fc = get_link_form_control(&document->links[link]);
if (fc && fc->form == form) {
doc_view->vs->current_link = link;
submit_form(ses, doc_view, 0);
return;
}
}
#endif
if (!list_empty(form->items)) {
struct el_form_control *fc = (struct el_form_control *)form->items.next;
struct uri *uri;
enum cache_mode mode = do_reload ? CACHE_MODE_FORCE_RELOAD : CACHE_MODE_NORMAL;
if (!fc) return;
uri = get_form_uri(ses, doc_view, fc);
if (!uri) return;
goto_uri_frame(ses, uri, form->target, mode);
done_uri(uri);
}
}
void
auto_submit_form(struct session *ses)
{
struct document *document = ses->doc_view->document;
if (!list_empty(document->forms))
submit_given_form(ses, ses->doc_view, document->forms.next, 0);
}
/* menu_func_T */
static void
set_file_form_state(struct terminal *term, void *filename_, void *fs_)
{
char *filename = filename_;
struct form_state *fs = fs_;
/* The menu code doesn't free the filename data */
mem_free_set(&fs->value, filename);
fs->state = strlen(filename);
redraw_terminal(term);
}
/* menu_func_T */
static void
file_form_menu(struct terminal *term, void *path_, void *fs_)
{
char *path = path_;
struct form_state *fs = fs_;
/* FIXME: It doesn't work for ../../ */
#if 0
int valuelen = strlen(fs->value);
int pathlen = strlen(path);
int no_elevator = 0;
/* Don't add elevators for subdirs menus */
/* It is not perfect at all because fs->value is not updated for each
* newly opened file menu. Maybe it should be dropped. */
for (; valuelen < pathlen; valuelen++) {
if (dir_sep(path[valuelen - 1])) {
no_elevator = 1;
break;
}
}
#endif
auto_complete_file(term, 0 /* no_elevator */, path,
set_file_form_state,
file_form_menu, fs);
}
enum frame_event_status
field_op(struct session *ses, struct document_view *doc_view,
struct link *link, struct term_event *ev)
{
struct el_form_control *fc;
struct form_state *fs;
enum edit_action action_id;
char *text;
int length;
enum frame_event_status status = FRAME_EVENT_REFRESH;
#ifdef CONFIG_UTF8
const char *ctext;
int utf8 = ses->tab->term->utf8_cp;
#endif /* CONFIG_UTF8 */
assert(ses && doc_view && link && ev);
if_assert_failed return FRAME_EVENT_OK;
fc = get_link_form_control(link);
assertm(fc != NULL, "link has no form control");
if_assert_failed return FRAME_EVENT_OK;
if (fc->mode == FORM_MODE_DISABLED || ev->ev != EVENT_KBD
|| (ses->insert_mode == INSERT_MODE_OFF
&& !(get_kbd_modifier(ev) & KBD_MOD_PASTE)))
return FRAME_EVENT_IGNORED;
action_id = kbd_action(KEYMAP_EDIT, ev, NULL);
fs = find_form_state(doc_view, fc);
if (!fs || !fs->value) return FRAME_EVENT_OK;
switch (action_id) {
case ACT_EDIT_LEFT:
#ifdef CONFIG_UTF8
if (fc->type == FC_TEXTAREA) {
status = textarea_op_left(fs, fc, utf8);
break;
}
if (utf8) {
char *new_value;
new_value = utf8_prevchar(fs->value + fs->state, 1, fs->value);
fs->state = new_value - fs->value;
} else
#endif /* CONFIG_UTF8 */
fs->state = int_max(fs->state - 1, 0);
break;
case ACT_EDIT_RIGHT:
#ifdef CONFIG_UTF8
if (fc->type == FC_TEXTAREA) {
status = textarea_op_right(fs, fc, utf8);
break;
}
if (utf8) {
char *text = fs->value + fs->state;
char *end = strchr((const char *)text, '\0');
utf8_to_unicode(&text, end);
fs->state = (int)(text - fs->value);
} else
#endif /* CONFIG_UTF8 */
fs->state = int_min(fs->state + 1, strlen(fs->value));
break;
case ACT_EDIT_HOME:
#ifdef CONFIG_UTF8
if (fc->type == FC_TEXTAREA) {
status = textarea_op_home(fs, fc, utf8);
} else {
fs->state = 0;
}
#else
if (fc->type == FC_TEXTAREA) {
status = textarea_op_home(fs, fc);
} else {
fs->state = 0;
}
#endif /* CONFIG_UTF8 */
break;
case ACT_EDIT_UP:
if (fc->type != FC_TEXTAREA)
status = FRAME_EVENT_IGNORED;
else
#ifdef CONFIG_UTF8
status = textarea_op_up(fs, fc, utf8);
#else
status = textarea_op_up(fs, fc);
#endif /* CONFIG_UTF8 */
break;
case ACT_EDIT_DOWN:
if (fc->type != FC_TEXTAREA)
status = FRAME_EVENT_IGNORED;
else
#ifdef CONFIG_UTF8
status = textarea_op_down(fs, fc, utf8);
#else
status = textarea_op_down(fs, fc);
#endif /* CONFIG_UTF8 */
break;
case ACT_EDIT_END:
if (fc->type == FC_TEXTAREA) {
#ifdef CONFIG_UTF8
status = textarea_op_end(fs, fc, utf8);
#else
status = textarea_op_end(fs, fc);
#endif /* CONFIG_UTF8 */
} else {
fs->state = strlen(fs->value);
}
break;
case ACT_EDIT_BEGINNING_OF_BUFFER:
if (fc->type == FC_TEXTAREA) {
#ifdef CONFIG_UTF8
status = textarea_op_bob(fs, fc, utf8);
fs->state_cell = 0;
#else
status = textarea_op_bob(fs, fc);
#endif /* CONFIG_UTF8 */
} else {
fs->state = 0;
}
break;
case ACT_EDIT_END_OF_BUFFER:
if (fc->type == FC_TEXTAREA) {
#ifdef CONFIG_UTF8
status = textarea_op_eob(fs, fc, utf8);
#else
status = textarea_op_eob(fs, fc);
#endif /* CONFIG_UTF8 */
} else {
fs->state = strlen(fs->value);
}
break;
case ACT_EDIT_OPEN_EXTERNAL:
if (form_field_is_readonly(fc))
status = FRAME_EVENT_IGNORED;
else if (fc->type == FC_TEXTAREA)
textarea_edit(0, ses->tab->term, fs, doc_view, link);
break;
case ACT_EDIT_COPY_CLIPBOARD:
set_clipboard_text(fs->value);
status = FRAME_EVENT_OK;
break;
case ACT_EDIT_CUT_CLIPBOARD:
set_clipboard_text(fs->value);
if (!form_field_is_readonly(fc))
fs->value[0] = 0;
fs->state = 0;
#ifdef CONFIG_UTF8
if (fc->type == FC_TEXTAREA)
fs->state_cell = 0;
#endif /* CONFIG_UTF8 */
break;
case ACT_EDIT_PASTE_CLIPBOARD:
if (form_field_is_readonly(fc)) break;
text = get_clipboard_text();
if (!text) break;
length = strlen(text);
if (length <= fc->maxlength) {
char *v = mem_realloc(fs->value, length + 1);
if (v) {
fs->value = v;
memmove(v, text, length + 1);
fs->state = strlen(fs->value);
#ifdef CONFIG_UTF8
if (utf8 && fc->type == FC_TEXTAREA)
fs->state_cell = 0;
#endif /* CONFIG_UTF8 */
}
}
mem_free(text);
break;
case ACT_EDIT_ENTER:
if (fc->type == FC_TEXTAREA) {
#ifdef CONFIG_UTF8
status = textarea_op_enter(fs, fc, utf8);
#else
status = textarea_op_enter(fs, fc);
#endif /* CONFIG_UTF8 */
break;
}
/* Set status to ok if either it is not possible to
* submit the form or the posting fails. */
/* FIXME: We should maybe have ACT_EDIT_ENTER_RELOAD */
if ((has_form_submit(fc->form)
&& !get_opt_bool("document.browse.forms.auto_submit", ses))
|| goto_link(ses, doc_view, link, 0)) {
if (ses->insert_mode == INSERT_MODE_ON)
ses->insert_mode = INSERT_MODE_OFF;
status = FRAME_EVENT_OK;
}
break;
case ACT_EDIT_BACKSPACE:
if (form_field_is_readonly(fc)) {
status = FRAME_EVENT_IGNORED;
break;
}
if (!fs->state) {
status = FRAME_EVENT_OK;
break;
}
#ifdef CONFIG_UTF8
if (utf8) {
int old_state = fs->state;
char *new_value;
new_value = utf8_prevchar(fs->value + fs->state, 1, fs->value);
fs->state = new_value - fs->value;
if (old_state != fs->state) {
if (fc->type == FC_TEXTAREA)
fs->state_cell = 0;
length = strlen(fs->value + old_state) + 1;
memmove(new_value, fs->value + old_state, length);
}
} else
#endif /* CONFIG_UTF8 */
{
length = strlen(fs->value + fs->state) + 1;
text = fs->value + fs->state;
memmove(text - 1, text, length);
fs->state--;
}
break;
case ACT_EDIT_DELETE:
if (form_field_is_readonly(fc)) {
status = FRAME_EVENT_IGNORED;
break;
}
length = strlen(fs->value);
if (fs->state >= length) {
status = FRAME_EVENT_OK;
break;
}
#ifdef CONFIG_UTF8
if (utf8) {
char *end = fs->value + length;
char *text = fs->value + fs->state;
char *old = text;
utf8_to_unicode(&text, end);
if (old != text) {
memmove(old, text,
(int)(end - text) + 1);
}
break;
}
#endif /* CONFIG_UTF8 */
text = fs->value + fs->state;
memmove(text, text + 1, length - fs->state);
break;
case ACT_EDIT_KILL_TO_BOL:
if (form_field_is_readonly(fc)) {
status = FRAME_EVENT_IGNORED;
break;
}
if (fs->state <= 0) {
status = FRAME_EVENT_OK;
break;
}
text = memrchr(fs->value, ASCII_LF, fs->state);
if (text) {
/* Leave the new-line character if it does not
* immediately precede the cursor. */
if (text != &fs->value[fs->state - 1])
text++;
} else {
text = fs->value;
}
length = strlen(fs->value + fs->state) + 1;
memmove(text, fs->value + fs->state, length);
fs->state = (int) (text - fs->value);
#ifdef CONFIG_UTF8
if (utf8) {
if (fc->type == FC_TEXTAREA)
fs->state_cell = 0;
}
#endif /* CONFIG_UTF8 */
break;
case ACT_EDIT_KILL_TO_EOL:
if (form_field_is_readonly(fc)) {
status = FRAME_EVENT_IGNORED;
break;
}
if (!fs->value[fs->state]) {
status = FRAME_EVENT_OK;
break;
}
text = strchr((const char *)(fs->value + fs->state), ASCII_LF);
if (!text) {
fs->value[fs->state] = '\0';
break;
}
if (fs->value[fs->state] == ASCII_LF)
++text;
memmove(fs->value + fs->state, text, strlen(text) + 1);
break;
case ACT_EDIT_KILL_WORD_BACK:
if (form_field_is_readonly(fc)) {
status = FRAME_EVENT_IGNORED;
break;
}
if (fs->state <= 0) {
status = FRAME_EVENT_OK;
break;
}
text = &fs->value[fs->state];
while (text > fs->value && isspace(*(text - 1)))
--text;
while (text > fs->value && !isspace(*(text - 1)))
--text;
if (*text == ASCII_LF
&& text != &fs->value[fs->state - 1])
text++;
length = strlen(fs->value + fs->state) + 1;
memmove(text, fs->value + fs->state, length);
fs->state = (int) (text - fs->value);
break;
case ACT_EDIT_MOVE_BACKWARD_WORD:
while (fs->state > 0
&& isspace(fs->value[fs->state - 1]))
--fs->state;
while (fs->state > 0
&& !isspace(fs->value[fs->state - 1]))
--fs->state;
break;
case ACT_EDIT_MOVE_FORWARD_WORD:
while (isspace(fs->value[fs->state]))
++fs->state;
while (fs->value[fs->state]
&& !isspace(fs->value[fs->state]))
++fs->state;
while (isspace(fs->value[fs->state]))
++fs->state;
break;
case ACT_EDIT_AUTO_COMPLETE:
if (fc->type != FC_FILE
|| form_field_is_readonly(fc)) {
status = FRAME_EVENT_IGNORED;
break;
}
file_form_menu(ses->tab->term, fs->value, fs);
break;
case ACT_EDIT_CANCEL:
if (ses->insert_mode == INSERT_MODE_ON)
ses->insert_mode = INSERT_MODE_OFF;
else
status = FRAME_EVENT_IGNORED;
break;
case ACT_EDIT_REDRAW:
redraw_terminal_cls(ses->tab->term);
status = FRAME_EVENT_OK;
break;
default:
if (!check_kbd_textinput_key(ev)) {
status = FRAME_EVENT_IGNORED;
break;
}
if (form_field_is_readonly(fc)
#ifndef CONFIG_UTF8
|| strlen(fs->value) >= fc->maxlength
|| !insert_in_string(&fs->value, fs->state, "?", 1)
#endif /* CONFIG_UTF8 */
)
{
status = FRAME_EVENT_OK;
break;
}
#ifdef CONFIG_UTF8
/* fs->value is in the charset of the terminal. */
ctext = u2cp_no_nbsp(get_kbd_key(ev),
get_terminal_codepage(ses->tab->term));
length = strlen(ctext);
if (strlen(fs->value) + length > fc->maxlength
|| !insert_in_string(&fs->value, fs->state, ctext, length)) {
status = FRAME_EVENT_OK;
break;
}
fs->state += length;
if (fc->type == FC_TEXTAREA)
fs->state_cell = 0;
#else
fs->value[fs->state++] = get_kbd_key(ev);
#endif /* CONFIG_UTF8 */
break;
}
return status;
}
static char *
get_form_label(struct el_form_control *fc)
{
assert(fc->form);
switch (fc->type) {
case FC_RESET:
return N_("Reset form");
case FC_BUTTON:
return N_("Harmless button");
case FC_HIDDEN:
return NULL;
case FC_SUBMIT:
case FC_IMAGE:
if (!fc->form->action) return NULL;
if (fc->form->method == FORM_METHOD_GET)
return N_("Submit form to");
return N_("Post form to");
case FC_RADIO:
return N_("Radio button");
case FC_CHECKBOX:
return N_("Checkbox");
case FC_SELECT:
return N_("Select field");
case FC_TEXT:
return N_("Text field");
case FC_TEXTAREA:
return N_("Text area");
case FC_FILE:
return N_("File upload");
case FC_PASSWORD:
return N_("Password field");
}
return NULL;
}
static inline void
add_form_attr_to_string(struct string *string, struct terminal *term,
char *name, char *value)
{
add_to_string(string, ", ");
add_to_string(string, _(name, term));
if (value) {
add_char_to_string(string, ' ');
add_to_string(string, value);
}
}
char *
get_form_info(struct session *ses, struct document_view *doc_view)
{
struct terminal *term = ses->tab->term;
struct link *link = get_current_link(doc_view);
struct el_form_control *fc;
char *label, *key;
struct string str;
assert(link);
fc = get_link_form_control(link);
label = get_form_label(fc);
if (!label) return NULL;
if (!init_string(&str)) return NULL;
add_to_string(&str, _(label, term));
if (link->type != LINK_BUTTON && fc->name && fc->name[0]) {
add_form_attr_to_string(&str, term, N_("name"), fc->name);
}
switch (fc->type) {
case FC_CHECKBOX:
case FC_RADIO:
{
struct form_state *fs = find_form_state(doc_view, fc);
if (!fs->value || !fs->value[0])
break;
add_form_attr_to_string(&str, term, N_("value"), fs->value);
break;
}
case FC_TEXT:
case FC_PASSWORD:
case FC_FILE:
case FC_TEXTAREA:
{
struct uri *uri;
char *uristring;
if (form_field_is_readonly(fc)) {
add_form_attr_to_string(&str, term, N_("read only"), NULL);
}
/* Should we add info about entering insert mode or add info
* about submitting the form? */
if (ses->insert_mode == INSERT_MODE_OFF) {
key = get_keystroke(ACT_EDIT_ENTER, KEYMAP_EDIT);
if (!key) break;
if (form_field_is_readonly(fc))
label = N_("press %s to navigate");
else
label = N_("press %s to edit");
add_to_string(&str, " (");
add_format_to_string(&str, _(label, term), key);
add_char_to_string(&str, ')');
mem_free(key);
break;
}
if (fc->type == FC_TEXTAREA)
break;
assert(fc->form);
if (!fc->form->action
|| (has_form_submit(fc->form)
&& !get_opt_bool("document.browse.forms.auto_submit",
ses)))
break;
uri = get_uri(fc->form->action, 0);
if (!uri) break;
/* Add the uri with password and post info stripped */
uristring = get_uri_string(uri, URI_PUBLIC);
done_uri(uri);
if (!uristring) break;
key = get_keystroke(ACT_EDIT_ENTER, KEYMAP_EDIT);
if (!key) {
mem_free(uristring);
break;
}
if (fc->form->method == FORM_METHOD_GET)
label = N_("press %s to submit to %s");
else
label = N_("press %s to post to %s");
add_to_string(&str, " (");
add_format_to_string(&str, _(label, term), key, uristring);
mem_free(uristring);
mem_free(key);
add_char_to_string(&str, ')');
break;
}
case FC_SUBMIT:
case FC_IMAGE:
add_char_to_string(&str, ' ');
assert(fc->form);
/* Add the uri with password and post info stripped */
add_string_uri_to_string(&str, fc->form->action, URI_PUBLIC);
break;
case FC_HIDDEN:
case FC_RESET:
case FC_BUTTON:
case FC_SELECT:
break;
}
if (link->accesskey
&& get_opt_bool("document.browse.accesskey.display", ses)) {
add_to_string(&str, " (");
add_accesskey_to_string(&str, link->accesskey);
add_char_to_string(&str, ')');
}
return str.source;
}
static void
link_form_menu_func(struct terminal *term, void *link_number_, void *ses_)
{
struct session *ses = ses_;
struct document_view *doc_view;
int link_number = *(int *) link_number_;
mem_free(link_number_);
assert(term && ses);
if_assert_failed return;
doc_view = current_frame(ses);
if (!doc_view) return;
assert(doc_view->vs && doc_view->document);
if_assert_failed return;
jump_to_link_number(ses, doc_view, link_number);
refresh_view(ses, doc_view, 0);
}
void
link_form_menu(struct session *ses)
{
struct document_view *doc_view;
struct link *link;
struct menu_item *mi;
struct el_form_control *fc;
struct form *form;
assert(ses);
if_assert_failed return;
doc_view = current_frame(ses);
if (!doc_view) return;
assert(doc_view->vs && doc_view->document);
if_assert_failed return;
link = get_current_link(doc_view);
if (!link) return;
assert(link_is_form(link));
fc = get_link_form_control(link);
if (!fc) return;
form = fc->form;
mi = new_menu(FREE_LIST | FREE_TEXT | NO_INTL);
if (!mi) return;
foreach (fc, form->items) {
char *text;
char *rtext;
int link_number;
struct string str;
switch (fc->type) {
case FC_HIDDEN:
continue;
case FC_SUBMIT:
case FC_IMAGE:
if (!form->action)
text = N_("Useless button");
else
text = N_("Submit button");
break;
default:
text = get_form_label(fc);
}
link_number = get_form_control_link(doc_view->document, fc);
if (link_number < 0
|| !init_string(&str))
continue;
assert(text);
add_to_string(&str, _(text, ses->tab->term));
rtext = fc->name;
if (!rtext) rtext = fc->alt;
add_to_menu(&mi, str.source, rtext, ACT_MAIN_NONE,
link_form_menu_func, intdup(link_number),
FREE_DATA);
}
do_menu(ses->tab->term, mi, ses, 1);
}