1 /* Textarea form item handlers */
2 
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
6 
7 #ifndef _GNU_SOURCE
8 #define _GNU_SOURCE /* XXX: we want memrchr() ! */
9 #endif
10 
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #ifdef HAVE_UNISTD_H
15 #include <unistd.h>
16 #endif
17 
18 #include "elinks.h"
19 
20 #include "bfu/dialog.h"
21 #include "document/document.h"
22 #include "document/forms.h"
23 #include "document/html/parser.h"
24 #include "document/html/renderer.h"
25 #include "document/view.h"
26 #include "intl/gettext/libintl.h"
27 #include "session/session.h"
28 #include "terminal/draw.h"
29 #include "terminal/window.h"
30 #include "util/error.h"
31 #include "util/file.h"
32 #include "util/memory.h"
33 #include "util/string.h"
34 #include "viewer/action.h"
35 #include "viewer/text/form.h"
36 #include "viewer/text/textarea.h"
37 #include "viewer/text/view.h"
38 
39 
40 struct line_info {
41 	int start;
42 	int end;
43 };
44 
45 /* We add two extra entries to the table so the ending info can be added
46  * without reallocating. */
47 #define realloc_line_info(info, size) \
48 	mem_align_alloc(info, size, (size) + 3, struct line_info, 0xFF)
49 
50 /* Allocates a line_info table describing the layout of the textarea buffer.
51  *
52  * @width	is max width and the offset at which text will be wrapped
53  * @wrap	controls how the wrapping of text is performed
54  * @format	is non zero the @text will be modified to make it suitable for
55  *		encoding it for form posting
56  */
57 static struct line_info *
format_text(unsigned char * text,int width,enum form_wrap wrap,int format)58 format_text(unsigned char *text, int width, enum form_wrap wrap, int format)
59 {
60 	struct line_info *line = NULL;
61 	int line_number = 0;
62 	int begin = 0;
63 	int pos = 0;
64 	int skip;
65 
66 	assert(text);
67 	if_assert_failed return NULL;
68 
69 	/* Allocate the ending entries */
70 	if (!realloc_line_info(&line, 0))
71 		return NULL;
72 
73 	while (text[pos]) {
74 		if (text[pos] == '\n') {
75 			skip = 1;
76 
77 		} else if (wrap == FORM_WRAP_NONE || pos - begin < width) {
78 			pos++;
79 			continue;
80 
81 		} else {
82 			unsigned char *wrappos;
83 
84 			/* Find a place to wrap the text */
85 			wrappos = memrchr(&text[begin], ' ', pos - begin);
86 			if (wrappos) {
87 				/* When formatting text for form submitting we
88 				 * have to apply the wrapping mode. */
89 				if (wrap == FORM_WRAP_HARD && format)
90 					*wrappos = '\n';
91 				pos = wrappos - text;
92 			}
93 			skip = !!wrappos;
94 		}
95 
96 		if (!realloc_line_info(&line, line_number)) {
97 			mem_free_if(line);
98 			return NULL;
99 		}
100 
101 		line[line_number].start = begin;
102 		line[line_number++].end = pos;
103 		begin = pos += skip;
104 	}
105 
106 	/* Flush the last text before the loop ended */
107 	line[line_number].start = begin;
108 	line[line_number++].end = pos;
109 
110 	/* Add end marker */
111 	line[line_number].start = line[line_number].end = -1;
112 
113 	return line;
114 }
115 
116 /* Searches for @cursor_position (aka. position in the fs->value string) for
117  * the corresponding entry in the @line info. Returns the index or -1 if
118  * position is not found. */
119 static int
get_textarea_line_number(struct line_info * line,int cursor_position)120 get_textarea_line_number(struct line_info *line, int cursor_position)
121 {
122 	int idx;
123 
124 	for (idx = 0; line[idx].start != -1; idx++) {
125 		int wrap;
126 
127 		if (cursor_position < line[idx].start) continue;
128 
129 		wrap = (line[idx + 1].start == line[idx].end);
130 		if (cursor_position >= line[idx].end + !wrap) continue;
131 
132 		return idx;
133 	}
134 
135 	return -1;
136 }
137 
138 /* Fixes up the vpos and vypos members of the form_state. Returns the
139  * logical position in the textarea view. */
140 int
area_cursor(struct form_control * fc,struct form_state * fs)141 area_cursor(struct form_control *fc, struct form_state *fs)
142 {
143 	struct line_info *line;
144 	int x, y;
145 
146 	assert(fc && fs);
147 	if_assert_failed return 0;
148 
149 	line = format_text(fs->value, fc->cols, fc->wrap, 0);
150 	if (!line) return 0;
151 
152 	y = get_textarea_line_number(line, fs->state);
153 	if (y == -1) {
154 		mem_free(line);
155 		return 0;
156 	}
157 
158 	x = fs->state - line[y].start;
159 
160 	mem_free(line);
161 
162 	if (fc->wrap && x == fc->cols) x--;
163 
164 	int_bounds(&fs->vpos, x - fc->cols + 1, x);
165 	int_bounds(&fs->vypos, y - fc->rows + 1, y);
166 
167 	x -= fs->vpos;
168 	y -= fs->vypos;
169 
170 	return y * fc->cols + x;
171 }
172 
173 void
draw_textarea(struct terminal * term,struct form_state * fs,struct document_view * doc_view,struct link * link)174 draw_textarea(struct terminal *term, struct form_state *fs,
175 	      struct document_view *doc_view, struct link *link)
176 {
177 	struct line_info *line, *linex;
178 	struct form_control *fc;
179 	struct box *box;
180 	int vx, vy;
181 	int sl, ye;
182 	int x, y;
183 
184 	assert(term && doc_view && doc_view->document && doc_view->vs && link);
185 	if_assert_failed return;
186 	fc = get_link_form_control(link);
187 	assertm(fc, "link %d has no form control", (int) (link - doc_view->document->links));
188 	if_assert_failed return;
189 
190 	box = &doc_view->box;
191 	vx = doc_view->vs->x;
192 	vy = doc_view->vs->y;
193 
194 	if (!link->npoints) return;
195 	area_cursor(fc, fs);
196 	linex = format_text(fs->value, fc->cols, fc->wrap, 0);
197 	if (!linex) return;
198 	line = linex;
199 	sl = fs->vypos;
200 	while (line->start != -1 && sl) sl--, line++;
201 
202 	x = link->points[0].x + box->x - vx;
203 	y = link->points[0].y + box->y - vy;
204 	ye = y + fc->rows;
205 
206 	for (; line->start != -1 && y < ye; line++, y++) {
207 		int i;
208 
209 		if (!row_is_in_box(box, y)) continue;
210 
211 		for (i = 0; i < fc->cols; i++) {
212 			unsigned char data;
213 			int xi = x + i;
214 
215 			if (!col_is_in_box(box, xi))
216 				continue;
217 
218 			if (i >= -fs->vpos
219 			    && i + fs->vpos < line->end - line->start)
220 				data = fs->value[line->start + i + fs->vpos];
221 			else
222 				data = '_';
223 
224 			draw_char_data(term, xi, y, data);
225 		}
226 	}
227 
228 	for (; y < ye; y++) {
229 		int i;
230 
231 		if (!row_is_in_box(box, y)) continue;
232 
233 		for (i = 0; i < fc->cols; i++) {
234 			int xi = x + i;
235 
236 			if (col_is_in_box(box, xi))
237 				draw_char_data(term, xi, y, '_');
238 		}
239 	}
240 
241 	mem_free(linex);
242 }
243 
244 
245 unsigned char *
encode_textarea(struct submitted_value * sv)246 encode_textarea(struct submitted_value *sv)
247 {
248 	struct form_control *fc;
249 	struct string newtext;
250 	void *blabla;
251 	int i;
252 
253 	assert(sv && sv->value);
254 	if_assert_failed return NULL;
255 
256 	fc = sv->form_control;
257 
258 	/* We need to reformat text now if it has to be wrapped hard, just
259 	 * before encoding it. */
260 	blabla = format_text(sv->value, fc->cols, fc->wrap, 1);
261 	mem_free_if(blabla);
262 
263 	if (!init_string(&newtext)) return NULL;
264 
265 	for (i = 0; sv->value[i]; i++) {
266 		if (sv->value[i] != '\n')
267 			add_char_to_string(&newtext, sv->value[i]);
268 		else
269 			add_crlf_to_string(&newtext);
270 	}
271 
272 	return newtext.source;
273 }
274 
275 
276 /* We use some evil hacking in order to make external textarea editor working.
277  * We need to have some way how to be notified that the editor finished and we
278  * should reload content of the textarea.  So we use global variable
279  * textarea_editor as a flag whether we have one running, and if we have, we
280  * just call textarea_edit(1, ...).  Then we recover our state from static
281  * variables, reload content of textarea back from file and clean up.
282  *
283  * Unfortunately, we can't support calling of editor from non-master links
284  * session, as it would be extremely ugly to hack (you would have to transfer
285  * the content of it back to master somehow, add special flags for not deleting
286  * of 'delete' etc) and I'm not going to do that now. Inter-links communication
287  * *NEEDS* rewrite, as it looks just like quick messy hack now. --pasky */
288 
289 int textarea_editor = 0;
290 
291 static unsigned char *
save_textarea_file(unsigned char * value)292 save_textarea_file(unsigned char *value)
293 {
294 	unsigned char *filename;
295 	FILE *file = NULL;
296 	int h;
297 
298 	filename = get_tempdir_filename("elinks-area-XXXXXX");
299 	if (!filename) return NULL;
300 
301 	h = safe_mkstemp(filename);
302 	if (h >= 0) file = fdopen(h, "w");
303 
304 	if (file) {
305 		fwrite(value, strlen(value), 1, file);
306 		fclose(file);
307 	} else {
308 		mem_free_set(&filename, NULL);
309 	}
310 
311 	return filename;
312 }
313 
314 void
textarea_edit(int op,struct terminal * term_,struct form_state * fs_,struct document_view * doc_view_,struct link * link_)315 textarea_edit(int op, struct terminal *term_, struct form_state *fs_,
316 	      struct document_view *doc_view_, struct link *link_)
317 {
318 	static size_t fc_maxlength;
319 	static struct form_state *fs;
320 	static struct terminal *term;
321 	static struct document_view *doc_view;
322 	static struct link *link;
323 	static unsigned char *fn;
324 
325 	assert (op == 0 || op == 1);
326 	if_assert_failed return;
327 	assert (op == 1 || term_);
328 	if_assert_failed return;
329 
330 	if (op == 0 && get_cmd_opt_bool("anonymous")) {
331 		info_box(term_, 0, N_("Error"), ALIGN_CENTER,
332 			 N_("You cannot launch an external"
333 			    " editor in the anonymous mode."));
334 		return;
335 	}
336 
337 	if (op == 0 && !term_->master) {
338 		info_box(term_, 0, N_("Error"), ALIGN_CENTER,
339 			 N_("You can do this only on the master terminal"));
340 		return;
341 	}
342 
343 	if (op == 0 && !textarea_editor) {
344 		unsigned char *ed = get_opt_str("document.browse.forms.editor");
345 		unsigned char *ex;
346 
347 		fn = save_textarea_file(fs_->value);
348 		if (!fn) return;
349 
350 		if (!ed || !*ed) {
351 			ed = getenv("EDITOR");
352 			if (!ed || !*ed) ed = "vi";
353 		}
354 
355 		ex = straconcat(ed, " ", fn, NULL);
356 		if (!ex) {
357 			unlink(fn);
358 			goto free_and_return;
359 		}
360 
361 		if (fs_) fs = fs_;
362 		if (doc_view_) doc_view = doc_view_;
363 		if (link_) {
364 			link = link_;
365 			fc_maxlength = get_link_form_control(link_)->maxlength;
366 		}
367 		if (term_) term = term_;
368 
369 		exec_on_terminal(term, ex, "", 1);
370 		mem_free(ex);
371 
372 		textarea_editor = 1;
373 
374 	} else if (op == 1 && fs) {
375 		struct string file;
376 
377 		if (!init_string(&file)
378 		     || !add_file_to_string(&file, fn)) {
379 			textarea_editor = 0;
380 			goto free_and_return;
381 		}
382 
383 		if (file.length > fc_maxlength) {
384 			file.source[fc_maxlength] = '\0';
385 			/* Casting size_t fc_maxlength to unsigned int
386 			 * and formatting it with "%u" is safe,
387 			 * because fc_maxlength is smaller than
388 			 * file.length, which is an int.  */
389 			info_box(term, MSGBOX_FREE_TEXT, N_("Warning"),
390 			         ALIGN_CENTER,
391 			         msg_text(term,
392 				          N_("You have exceeded the textarea's"
393 				             " size limit: your input is %d"
394 					     " bytes, but the maximum is %u"
395 					     " bytes.\n\n"
396 					     "Your input has been truncated,"
397 					     " but you can still recover the"
398 					     " text that you entered from"
399 					     " this file: %s"), file.length,
400 				             (unsigned int) fc_maxlength, fn));
401 		} else {
402 			unlink(fn);
403 		}
404 
405 		mem_free(fs->value);
406 		fs->value = file.source;
407 		fs->state = file.length;
408 
409 		if (doc_view && link)
410 			draw_form_entry(term, doc_view, link);
411 
412 		textarea_editor = 0;
413 		goto free_and_return;
414 	}
415 
416 	return;
417 
418 free_and_return:
419 	mem_free_set(&fn, NULL);
420 	fs = NULL;
421 }
422 
423 /* menu_func_T */
424 void
menu_textarea_edit(struct terminal * term,void * xxx,void * ses_)425 menu_textarea_edit(struct terminal *term, void *xxx, void *ses_)
426 {
427 	struct session *ses = ses_;
428 	struct document_view *doc_view;
429 	struct link *link;
430 	struct form_state *fs;
431 	struct form_control *fc;
432 
433 	assert(term && ses);
434 	if_assert_failed return;
435 
436 	doc_view = current_frame(ses);
437 
438 	assert(doc_view && doc_view->vs && doc_view->document);
439 	if_assert_failed return;
440 
441 	link = get_current_link(doc_view);
442 	if (!link) return;
443 
444 	fc = get_link_form_control(link);
445 	if (form_field_is_readonly(fc))
446 		return;
447 
448 	fs = find_form_state(doc_view, fc);
449 	if (!fs) return;
450 
451 	textarea_edit(0, term, fs, doc_view, link);
452 }
453 
454 static enum frame_event_status
textarea_op(struct form_state * fs,struct form_control * fc,int (* do_op)(struct form_state *,struct line_info *,int))455 textarea_op(struct form_state *fs, struct form_control *fc,
456 	    int (*do_op)(struct form_state *, struct line_info *, int))
457 {
458 	struct line_info *line;
459 	int current, state;
460 
461 	assert(fs && fs->value && fc);
462 	if_assert_failed return FRAME_EVENT_OK;
463 
464 	line = format_text(fs->value, fc->cols, fc->wrap, 0);
465 	if (!line) return FRAME_EVENT_OK;
466 
467 	current = get_textarea_line_number(line, fs->state);
468 	state = fs->state;
469 	if (do_op(fs, line, current)) {
470 		mem_free(line);
471 		return FRAME_EVENT_IGNORED;
472 	}
473 
474 	mem_free(line);
475 
476 	return fs->state == state ? FRAME_EVENT_OK : FRAME_EVENT_REFRESH;
477 }
478 
479 static int
do_op_home(struct form_state * fs,struct line_info * line,int current)480 do_op_home(struct form_state *fs, struct line_info *line, int current)
481 {
482 	if (current != -1) fs->state = line[current].start;
483 	return 0;
484 }
485 
486 static int
do_op_up(struct form_state * fs,struct line_info * line,int current)487 do_op_up(struct form_state *fs, struct line_info *line, int current)
488 {
489 	if (current == -1) return 0;
490 	if (!current) return 1;
491 
492 	fs->state -= line[current].start - line[current-1].start;
493 	int_upper_bound(&fs->state, line[current-1].end);
494 	return 0;
495 }
496 
497 static int
do_op_down(struct form_state * fs,struct line_info * line,int current)498 do_op_down(struct form_state *fs, struct line_info *line, int current)
499 {
500 	if (current == -1) return 0;
501 	if (line[current+1].start == -1) return 1;
502 
503 	fs->state += line[current+1].start - line[current].start;
504 	int_upper_bound(&fs->state, line[current+1].end);
505 	return 0;
506 }
507 
508 static int
do_op_end(struct form_state * fs,struct line_info * line,int current)509 do_op_end(struct form_state *fs, struct line_info *line, int current)
510 {
511 	if (current == -1) {
512 		fs->state = strlen(fs->value);
513 
514 	} else {
515 		int wrap = line[current + 1].start == line[current].end;
516 
517 		/* Don't jump to next line when wrapping. */
518 		fs->state = int_max(0, line[current].end - wrap);
519 	}
520 	return 0;
521 }
522 
523 static int
do_op_bob(struct form_state * fs,struct line_info * line,int current)524 do_op_bob(struct form_state *fs, struct line_info *line, int current)
525 {
526 	if (current == -1) return 0;
527 
528 	fs->state -= line[current].start;
529 	int_upper_bound(&fs->state, line[0].end);
530 	return 0;
531 }
532 
533 static int
do_op_eob(struct form_state * fs,struct line_info * line,int current)534 do_op_eob(struct form_state *fs, struct line_info *line, int current)
535 {
536 	if (current == -1) {
537 		fs->state = strlen(fs->value);
538 
539 	} else {
540 		int last = get_textarea_line_number(line, strlen(fs->value));
541 
542 		assertm(last != -1, "line info corrupt");
543 
544 		fs->state += line[last].start - line[current].start;
545 		int_upper_bound(&fs->state, line[last].end);
546 	}
547 	return 0;
548 }
549 
550 enum frame_event_status
textarea_op_home(struct form_state * fs,struct form_control * fc)551 textarea_op_home(struct form_state *fs, struct form_control *fc)
552 {
553 	return textarea_op(fs, fc, do_op_home);
554 }
555 
556 enum frame_event_status
textarea_op_up(struct form_state * fs,struct form_control * fc)557 textarea_op_up(struct form_state *fs, struct form_control *fc)
558 {
559 	return textarea_op(fs, fc, do_op_up);
560 }
561 
562 enum frame_event_status
textarea_op_down(struct form_state * fs,struct form_control * fc)563 textarea_op_down(struct form_state *fs, struct form_control *fc)
564 {
565 	return textarea_op(fs, fc, do_op_down);
566 }
567 
568 enum frame_event_status
textarea_op_end(struct form_state * fs,struct form_control * fc)569 textarea_op_end(struct form_state *fs, struct form_control *fc)
570 {
571 	return textarea_op(fs, fc, do_op_end);
572 }
573 
574 /* Set the form state so the cursor is on the first line of the buffer.
575  * Preserve the column if possible. */
576 enum frame_event_status
textarea_op_bob(struct form_state * fs,struct form_control * fc)577 textarea_op_bob(struct form_state *fs, struct form_control *fc)
578 {
579 	return textarea_op(fs, fc, do_op_bob);
580 }
581 
582 /* Set the form state so the cursor is on the last line of the buffer. Preserve
583  * the column if possible. This is done by getting current and last line and
584  * then shifting the state by the delta of both lines start position bounding
585  * the whole thing to the end of the last line. */
586 enum frame_event_status
textarea_op_eob(struct form_state * fs,struct form_control * fc)587 textarea_op_eob(struct form_state *fs, struct form_control *fc)
588 {
589 	return textarea_op(fs, fc, do_op_eob);
590 }
591 
592 enum frame_event_status
textarea_op_enter(struct form_state * fs,struct form_control * fc)593 textarea_op_enter(struct form_state *fs, struct form_control *fc)
594 {
595 	assert(fs && fs->value && fc);
596 	if_assert_failed return FRAME_EVENT_OK;
597 
598 	if (form_field_is_readonly(fc)
599 	    || strlen(fs->value) >= fc->maxlength
600 	    || !insert_in_string(&fs->value, fs->state, "\n", 1))
601 		return FRAME_EVENT_OK;
602 
603 	fs->state++;
604 	return FRAME_EVENT_REFRESH;
605 }
606 
607 
608 void
set_textarea(struct document_view * doc_view,int direction)609 set_textarea(struct document_view *doc_view, int direction)
610 {
611 	struct form_control *fc;
612 	struct form_state *fs;
613 	struct link *link;
614 
615 	assert(doc_view && doc_view->vs && doc_view->document);
616 	assert(direction == 1 || direction == -1);
617 	if_assert_failed return;
618 
619 	link = get_current_link(doc_view);
620 	if (!link || link->type != LINK_AREA)
621 		return;
622 
623 	fc = get_link_form_control(link);
624 	assertm(fc, "link has no form control");
625 	if_assert_failed return;
626 
627 	if (fc->mode == FORM_MODE_DISABLED) return;
628 
629 	fs = find_form_state(doc_view, fc);
630 	if (!fs || !fs->value) return;
631 
632 	/* Depending on which way we entered the textarea move cursor so that
633 	 * it is available at end or start. */
634 	if (direction == 1)
635 		textarea_op_eob(fs, fc);
636 	else
637 		textarea_op_bob(fs, fc);
638 }
639