1 /* Support for dumping to the file on startup (w/o bfu) */
2 
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
6 
7 #include <errno.h>
8 #include <stdio.h>
9 #include <string.h>
10 #include <sys/types.h> /* NetBSD flavour */
11 #ifdef HAVE_SYS_SIGNAL_H
12 #include <sys/signal.h>
13 #endif
14 #ifdef HAVE_FCNTL_H
15 #include <fcntl.h> /* OS/2 needs this after sys/types.h */
16 #endif
17 #ifdef HAVE_UNISTD_H
18 #include <unistd.h>
19 #endif
20 
21 #include "elinks.h"
22 
23 #include "cache/cache.h"
24 #include "config/options.h"
25 #include "document/document.h"
26 #include "document/options.h"
27 #include "document/renderer.h"
28 #include "document/view.h"
29 #include "intl/gettext/libintl.h"
30 #include "main/select.h"
31 #include "main/main.h"
32 #include "network/connection.h"
33 #include "network/state.h"
34 #include "osdep/ascii.h"
35 #include "osdep/osdep.h"
36 #include "protocol/protocol.h"
37 #include "protocol/uri.h"
38 #include "session/download.h"
39 #include "terminal/color.h"
40 #include "terminal/hardio.h"
41 #include "terminal/terminal.h"
42 #include "util/memory.h"
43 #include "util/string.h"
44 #include "viewer/dump/dump.h"
45 #include "viewer/text/view.h"
46 #include "viewer/text/vs.h"
47 
48 
49 static int dump_pos;
50 static struct download dump_download;
51 static int dump_redir_count = 0;
52 
53 
54 /* This dumps the given @cached's source onto @fd nothing more. It returns 0 if it
55  * all went fine and 1 if something isn't quite right and we should terminate
56  * ourselves ASAP. */
57 static int
dump_source(int fd,struct download * download,struct cache_entry * cached)58 dump_source(int fd, struct download *download, struct cache_entry *cached)
59 {
60 	struct fragment *frag;
61 
62 	if (!cached) return 0;
63 
64 nextfrag:
65 	foreach (frag, cached->frag) {
66 		int d = dump_pos - frag->offset;
67 		int l, w;
68 
69 		if (d < 0 || frag->length <= d)
70 			continue;
71 
72 		l = frag->length - d;
73 		w = hard_write(fd, frag->data + d, l);
74 
75 		if (w != l) {
76 			detach_connection(download, dump_pos);
77 
78 			if (w < 0)
79 				ERROR(gettext("Can't write to stdout: %s"),
80 				      (unsigned char *) strerror(errno));
81 			else
82 				ERROR(gettext("Can't write to stdout."));
83 
84 			program.retval = RET_ERROR;
85 			return 1;
86 		}
87 
88 		dump_pos += w;
89 		detach_connection(download, dump_pos);
90 		goto nextfrag;
91 	}
92 
93 	return 0;
94 }
95 
96 /* This dumps the given @cached's formatted output onto @fd. */
97 static void
dump_formatted(int fd,struct download * download,struct cache_entry * cached)98 dump_formatted(int fd, struct download *download, struct cache_entry *cached)
99 {
100 	struct document_options o;
101 	struct document_view formatted;
102 	struct view_state vs;
103 	int width;
104 
105 	if (!cached) return;
106 
107 	memset(&formatted, 0, sizeof(formatted));
108 
109 	init_document_options(&o);
110 	width = get_opt_int("document.dump.width");
111 	set_box(&o.box, 0, 1, width, DEFAULT_TERMINAL_HEIGHT);
112 
113 	o.cp = get_opt_codepage("document.dump.codepage");
114 	o.color_mode = COLOR_MODE_DUMP;
115 	o.plain = 0;
116 	o.frames = 0;
117 	o.links_numbering = get_opt_bool("document.dump.numbering");
118 
119 	init_vs(&vs, cached->uri, -1);
120 
121 	render_document(&vs, &formatted, &o);
122 	dump_to_file(formatted.document, fd);
123 
124 	detach_formatted(&formatted);
125 	destroy_vs(&vs, 1);
126 }
127 
128 static unsigned char *
subst_url(unsigned char * str,struct string * url)129 subst_url(unsigned char *str, struct string *url)
130 {
131 	struct string string;
132 
133 	if (!init_string(&string)) return NULL;
134 
135 	while (*str) {
136 		int p;
137 
138 		for (p = 0; str[p] && str[p] != '%' && str[p] != '\\'; p++);
139 
140 		add_bytes_to_string(&string, str, p);
141 		str += p;
142 
143 		if (*str == '\\') {
144 			unsigned char ch;
145 
146 			str++;
147 			switch (*str) {
148 				case 'f':
149 					ch = '\f';
150 					break;
151 				case 'n':
152 					ch = '\n';
153 					break;
154 				case 't':
155 					ch = '\t';
156 					break;
157 				default:
158 					ch = *str;
159 			}
160 			if (*str) {
161 				add_char_to_string(&string, ch);
162 				str++;
163 			}
164 			continue;
165 
166 		} else if (*str != '%') {
167 			break;
168 		}
169 
170 		str++;
171 		switch (*str) {
172 			case 'u':
173 				if (url) add_string_to_string(&string, url);
174 				break;
175 		}
176 
177 		if (*str) str++;
178 	}
179 
180 	return string.source;
181 }
182 
183 static void
dump_print(unsigned char * option,struct string * url)184 dump_print(unsigned char *option, struct string *url)
185 {
186 	unsigned char *str = get_opt_str(option);
187 
188 	if (str) {
189 		unsigned char *realstr = subst_url(str, url);
190 
191 		if (realstr) {
192 			printf("%s", realstr);
193 			fflush(stdout);
194 			mem_free(realstr);
195 		}
196 	}
197 }
198 
199 static void
dump_loading_callback(struct download * download,void * p)200 dump_loading_callback(struct download *download, void *p)
201 {
202 	struct cache_entry *cached = download->cached;
203 	int fd = get_output_handle();
204 
205 	if (fd == -1) return;
206 	if (cached && cached->redirect && dump_redir_count++ < MAX_REDIRECTS) {
207 		struct uri *uri = cached->redirect;
208 
209 		if (is_in_progress_state(download->state))
210 			change_connection(download, NULL, PRI_CANCEL, 0);
211 
212 		load_uri(uri, cached->uri, download, PRI_MAIN, 0, -1);
213 		return;
214 	}
215 
216 	if (is_in_queued_state(download->state)) return;
217 
218 	if (get_cmd_opt_bool("dump")) {
219 		if (is_in_transfering_state(download->state))
220 			return;
221 
222 		dump_formatted(fd, download, cached);
223 
224 	} else {
225 		if (dump_source(fd, download, cached) > 0)
226 			goto terminate;
227 
228 		if (is_in_progress_state(download->state))
229 			return;
230 
231 	}
232 
233 	if (download->state != S_OK) {
234 		usrerror(get_state_message(download->state, NULL));
235 		program.retval = RET_ERROR;
236 		goto terminate;
237 	}
238 
239 terminate:
240 	program.terminate = 1;
241 	dump_next(NULL);
242 }
243 
244 static void
dump_start(unsigned char * url)245 dump_start(unsigned char *url)
246 {
247 	unsigned char *wd = get_cwd();
248 	struct uri *uri = get_translated_uri(url, wd);
249 
250 	mem_free_if(wd);
251 
252 	if (!uri || get_protocol_external_handler(NULL, uri)) {
253 		usrerror(gettext("URL protocol not supported (%s)."), url);
254 		goto terminate;
255 	}
256 
257 	dump_download.callback = (download_callback_T *) dump_loading_callback;
258 	dump_pos = 0;
259 
260 	if (load_uri(uri, NULL, &dump_download, PRI_MAIN, 0, -1)) {
261 terminate:
262 		dump_next(NULL);
263 		program.terminate = 1;
264 		program.retval = RET_SYNTAX;
265 	}
266 
267 	if (uri) done_uri(uri);
268 }
269 
270 void
dump_next(struct list_head * url_list)271 dump_next(struct list_head *url_list)
272 {
273 	static INIT_LIST_HEAD(todo_list);
274 	static INIT_LIST_HEAD(done_list);
275 	struct string_list_item *item;
276 
277 	if (url_list) {
278 		/* Steal all them nice list items but keep the same order */
279 		while (!list_empty(*url_list)) {
280 			item = url_list->next;
281 			del_from_list(item);
282 			add_to_list_end(todo_list, item);
283 		}
284 	}
285 
286 	/* Dump each url list item one at a time */
287 	if (!list_empty(todo_list)) {
288 		static int first = 1;
289 
290 		program.terminate = 0;
291 
292 		item = todo_list.next;
293 		del_from_list(item);
294 		add_to_list(done_list, item);
295 
296 		if (!first) {
297 			dump_print("document.dump.separator", NULL);
298 		} else {
299 			first = 0;
300 		}
301 
302 		dump_print("document.dump.header", &item->string);
303 		dump_start(item->string.source);
304 		/* XXX: I think it ought to print footer at the end of
305 		 * the whole dump (not only this file). Testing required.
306 		 * --pasky */
307 		dump_print("document.dump.footer", &item->string);
308 
309 	} else {
310 		free_string_list(&done_list);
311 		program.terminate = 1;
312 	}
313 }
314 
315 /* Using this function in dump_to_file() is unfortunately slightly slower than
316  * the current code.  However having this here instead of in the scripting
317  * backends is better. */
318 struct string *
add_document_to_string(struct string * string,struct document * document)319 add_document_to_string(struct string *string, struct document *document)
320 {
321 	int y;
322 
323 	assert(string && document);
324 	if_assert_failed return NULL;
325 
326 	for (y = 0; y < document->height; y++) {
327 		int white = 0;
328 		int x;
329 
330 		for (x = 0; x < document->data[y].length; x++) {
331 			struct screen_char *pos = &document->data[y].chars[x];
332 			unsigned char data = pos->data;
333 			unsigned int frame = (pos->attr & SCREEN_ATTR_FRAME);
334 
335 			if (!isscreensafe(data)) {
336 				white++;
337 				continue;
338 			} else {
339 				if (frame && data >= 176 && data < 224)
340 					data = frame_dumb[data - 176];
341 
342 				if (data <= ' ') {
343 					/* Count spaces. */
344 					white++;
345 				} else {
346 					/* Print spaces if any. */
347 					if (white) {
348 						add_xchar_to_string(string, ' ', white);
349 						white = 0;
350 					}
351 					add_char_to_string(string, data);
352 				}
353 			}
354 		}
355 
356 		add_char_to_string(string, '\n');
357 	}
358 
359 	return string;
360 }
361 
362 #define D_BUF	65536
363 
364 static int
write_char(unsigned char c,int fd,unsigned char * buf,int * bptr)365 write_char(unsigned char c, int fd, unsigned char *buf, int *bptr)
366 {
367 	buf[(*bptr)++] = c;
368 	if ((*bptr) >= D_BUF) {
369 		if (hard_write(fd, buf, (*bptr)) != (*bptr))
370 			return -1;
371 		(*bptr) = 0;
372 	}
373 
374 	return 0;
375 }
376 
377 int
dump_to_file(struct document * document,int fd)378 dump_to_file(struct document *document, int fd)
379 {
380 	int y;
381 	int bptr = 0;
382 	unsigned char *buf = mem_alloc(D_BUF);
383 
384 	if (!buf) return -1;
385 
386 	for (y = 0; y < document->height; y++) {
387 		int white = 0;
388 		int x;
389 
390 		for (x = 0; x < document->data[y].length; x++) {
391 			unsigned char c;
392 			unsigned char attr = document->data[y].chars[x].attr;
393 
394 			c = document->data[y].chars[x].data;
395 
396 			if ((attr & SCREEN_ATTR_FRAME)
397 			    && c >= 176 && c < 224)
398 				c = frame_dumb[c - 176];
399 
400 			if (c <= ' ') {
401 				/* Count spaces. */
402 				white++;
403 				continue;
404 			}
405 
406 			/* Print spaces if any. */
407 			while (white) {
408 				if (write_char(' ', fd, buf, &bptr))
409 					goto fail;
410 				white--;
411 			}
412 
413 			/* Print normal char. */
414 			if (write_char(c, fd, buf, &bptr))
415 				goto fail;
416 		}
417 
418 		/* Print end of line. */
419 		if (write_char('\n', fd, buf, &bptr))
420 			goto fail;
421 	}
422 
423 	if (hard_write(fd, buf, bptr) != bptr) {
424 fail:
425 		mem_free(buf);
426 		return -1;
427 	}
428 
429 	if (document->nlinks && get_opt_bool("document.dump.references")) {
430 		int x;
431 		unsigned char *header = "\nReferences\n\n   Visible links\n";
432 		int headlen = strlen(header);
433 
434 		if (hard_write(fd, header, headlen) != headlen)
435 			goto fail;
436 
437 		for (x = 0; x < document->nlinks; x++) {
438 			struct link *link = &document->links[x];
439 			unsigned char *where = link->where;
440 
441 			if (!where) continue;
442 
443 			if (document->options.links_numbering) {
444 				if (link->title && *link->title)
445 					snprintf(buf, D_BUF, "%4d. %s\n\t%s\n",
446 						 x + 1, link->title, where);
447 				else
448 					snprintf(buf, D_BUF, "%4d. %s\n",
449 						 x + 1, where);
450 			} else {
451 				if (link->title && *link->title)
452 					snprintf(buf, D_BUF, "   . %s\n\t%s\n",
453 						 link->title, where);
454 				else
455 					snprintf(buf, D_BUF, "   . %s\n", where);
456 			}
457 
458 			bptr = strlen(buf);
459 			if (hard_write(fd, buf, bptr) != bptr)
460 				goto fail;
461 		}
462 	}
463 
464 	mem_free(buf);
465 	return 0;
466 }
467 
468 #undef D_BUF
469