1 /* $LynxId: LYDownload.c,v 1.66 2012/02/09 18:55:26 tom Exp $ */
2 #include <HTUtils.h>
3 #include <HTParse.h>
4 #include <HTList.h>
5 #include <HTAlert.h>
6 #include <LYCurses.h>
7 #include <LYUtils.h>
8 #include <LYGlobalDefs.h>
9 #include <LYStrings.h>
10 #include <LYDownload.h>
11 
12 #include <LYLeaks.h>
13 
14 /*
15  * LYDownload takes a URL and downloads it using a user selected download
16  * program
17  *
18  * It parses an incoming link that looks like
19  *
20  * LYNXDOWNLOAD://Method=<#>/File=<STRING>/SugFile=<STRING>
21  */
22 #ifdef VMS
23 BOOLEAN LYDidRename = FALSE;
24 #endif /* VMS */
25 
26 static char LYValidDownloadFile[LY_MAXPATH] = "\0";
27 
LYDownload(char * line)28 void LYDownload(char *line)
29 {
30     char *Line = NULL, *method, *file, *sug_file = NULL;
31     int method_number;
32     int count;
33     char *the_command = 0;
34     bstring *buffer = NULL;
35     bstring *command = NULL;
36     char *cp;
37     lynx_list_item_type *download_command = 0;
38     int ch;
39     RecallType recall;
40     int FnameTotal;
41     int FnameNum;
42     BOOLEAN FirstRecall = TRUE;
43     BOOLEAN SecondS = FALSE;
44 
45 #ifdef VMS
46     LYDidRename = FALSE;
47 #endif /* VMS */
48 
49     /*
50      * Make sure we have a valid download file comparison string loaded via the
51      * download options menu.  - FM
52      */
53     if (LYValidDownloadFile[0] == '\0') {
54 	goto failed;
55     }
56 
57     /*
58      * Make a copy of the LYNXDOWNLOAD internal URL for parsing.  - FM
59      */
60     StrAllocCopy(Line, line);
61 
62     /*
63      * Parse out the File, sug_file, and the Method.
64      */
65     if ((file = strstr(Line, "/File=")) == NULL)
66 	goto failed;
67     *file = '\0';
68     /*
69      * Go past "File=".
70      */
71     file += 6;
72 
73     if ((sug_file = strstr(file + 1, "/SugFile=")) != NULL) {
74 	*sug_file = '\0';
75 	/*
76 	 * Go past "SugFile=".
77 	 */
78 	sug_file += 9;
79 	HTUnEscape(sug_file);
80     }
81 
82     /*
83      * Make sure that the file string is the one from the last displayed
84      * download options menu.  - FM
85      */
86     if (strcmp(file, LYValidDownloadFile)) {
87 	goto failed;
88     }
89 #if defined(DIRED_SUPPORT)
90     /* FIXME: use HTLocalName */
91     if (!StrNCmp(file, "file://localhost", 16)) {
92 #ifdef __DJGPP__
93 	if (!StrNCmp(file + 16, "/dev/", 5))
94 	    file += 16;
95 	else {
96 	    file += 17;
97 	    file = HTDOS_name(file);
98 	}
99 #else
100 	file += 16;
101 #endif /* __DJGPP__ */
102     } else if (isFILE_URL(file))
103 	file += LEN_FILE_URL;
104     HTUnEscape(file);
105 #else
106 #if defined(_WINDOWS)		/* 1997/10/15 (Wed) 16:27:38 */
107     if (!StrNCmp(file, "file://localhost/", 17))
108 	file += 17;
109     else if (!StrNCmp(file, "file:/", 6))
110 	file += 6;
111     HTUnEscape(file);
112 #endif /* _WINDOWS */
113 #endif /* DIRED_SUPPORT */
114 
115     if ((method = strstr(Line, "Method=")) == NULL)
116 	goto failed;
117     /*
118      * Go past "Method=".
119      */
120     method += 7;
121     method_number = atoi(method);
122 
123     /*
124      * Set up the sug_filenames recall buffer.
125      */
126     FnameTotal = (sug_filenames ? HTList_count(sug_filenames) : 0);
127     recall = ((FnameTotal >= 1) ? RECALL_URL : NORECALL);
128     FnameNum = FnameTotal;
129 
130     if (method_number < 0) {
131 	/*
132 	 * Write to local file.
133 	 */
134 	_statusline(FILENAME_PROMPT);
135       retry:
136 	if (sug_file) {
137 	    BStrCopy0(buffer, sug_file);
138 	} else {
139 	    BStrCopy0(buffer, "");
140 	}
141 
142       check_recall:
143 	if ((ch = LYgetBString(&buffer, VISIBLE, 0, recall)) < 0 ||
144 	    isBEmpty(buffer) ||
145 	    ch == UPARROW ||
146 	    ch == DNARROW) {
147 
148 	    if (recall && ch == UPARROW) {
149 		if (FirstRecall) {
150 		    FirstRecall = FALSE;
151 		    /*
152 		     * Use the last Fname in the list.  - FM
153 		     */
154 		    FnameNum = 0;
155 		} else {
156 		    /*
157 		     * Go back to the previous Fname in the list.  - FM
158 		     */
159 		    FnameNum++;
160 		}
161 		if (FnameNum >= FnameTotal) {
162 		    /*
163 		     * Reset the FirstRecall flag, and use sug_file or a blank.
164 		     * - FM
165 		     */
166 		    FirstRecall = TRUE;
167 		    FnameNum = FnameTotal;
168 		    _statusline(FILENAME_PROMPT);
169 		    goto retry;
170 		} else if ((cp = (char *) HTList_objectAt(sug_filenames,
171 							  FnameNum)) != NULL) {
172 		    BStrCopy0(buffer, cp);
173 		    if (FnameTotal == 1) {
174 			_statusline(EDIT_THE_PREV_FILENAME);
175 		    } else {
176 			_statusline(EDIT_A_PREV_FILENAME);
177 		    }
178 		    goto check_recall;
179 		}
180 	    } else if (recall && ch == DNARROW) {
181 		if (FirstRecall) {
182 		    FirstRecall = FALSE;
183 		    /*
184 		     * Use the first Fname in the list.  - FM
185 		     */
186 		    FnameNum = FnameTotal - 1;
187 		} else {
188 		    /*
189 		     * Advance to the next Fname in the list.  - FM
190 		     */
191 		    FnameNum--;
192 		}
193 		if (FnameNum < 0) {
194 		    /*
195 		     * Set the FirstRecall flag, and use sug_file or a blank.
196 		     * - FM
197 		     */
198 		    FirstRecall = TRUE;
199 		    FnameNum = FnameTotal;
200 		    _statusline(FILENAME_PROMPT);
201 		    goto retry;
202 		} else if ((cp = (char *) HTList_objectAt(sug_filenames,
203 							  FnameNum)) != NULL) {
204 		    BStrCopy0(buffer, cp);
205 		    if (FnameTotal == 1) {
206 			_statusline(EDIT_THE_PREV_FILENAME);
207 		    } else {
208 			_statusline(EDIT_A_PREV_FILENAME);
209 		    }
210 		    goto check_recall;
211 		}
212 	    }
213 
214 	    /*
215 	     * Save cancelled.
216 	     */
217 	    goto cancelled;
218 	}
219 
220 	BStrCopy(command, buffer);
221 	if (!LYValidateFilename(&buffer, &command))
222 	    goto cancelled;
223 #ifdef HAVE_POPEN
224 	else if (LYIsPipeCommand(buffer->str)) {
225 	    /* I don't know how to download to a pipe */
226 	    HTAlert(CANNOT_WRITE_TO_FILE);
227 	    _statusline(NEW_FILENAME_PROMPT);
228 	    FirstRecall = TRUE;
229 	    FnameNum = FnameTotal;
230 	    goto retry;
231 	}
232 #endif
233 
234 	/*
235 	 * See if it already exists.
236 	 */
237 	switch (LYValidateOutput(buffer->str)) {
238 	case 'Y':
239 	    break;
240 	case 'N':
241 	    _statusline(NEW_FILENAME_PROMPT);
242 	    FirstRecall = TRUE;
243 	    FnameNum = FnameTotal;
244 	    goto retry;
245 	default:
246 	    goto cleanup;
247 	}
248 
249 	/*
250 	 * See if we can write to it.
251 	 */
252 	CTRACE((tfp, "LYDownload: filename is %s\n", buffer->str));
253 
254 	SecondS = TRUE;
255 
256 	HTInfoMsg(SAVING);
257 #ifdef VMS
258 	/*
259 	 * Try rename() first.  - FM
260 	 */
261 	CTRACE((tfp, "command: rename(%s, %s)\n", file, buffer->str));
262 	if (rename(file, buffer->str)) {
263 	    /*
264 	     * Failed.  Use spawned COPY_COMMAND.  - FM
265 	     */
266 	    CTRACE((tfp, "         FAILED!\n"));
267 	    LYCopyFile(file, buffer->str);
268 	} else {
269 	    /*
270 	     * We don't have the temporary file (it was renamed to a permanent
271 	     * file), so set a flag to pop out of the download menu.  - FM
272 	     */
273 	    LYDidRename = TRUE;
274 	}
275 	chmod(buffer->str, HIDE_CHMOD);
276 #else /* Unix: */
277 
278 	LYCopyFile(file, buffer->str);
279 	LYRelaxFilePermissions(buffer->str);
280 #endif /* VMS */
281 
282     } else {
283 	/*
284 	 * Use configured download commands.
285 	 */
286 	BStrCopy0(buffer, "");
287 	for (count = 0, download_command = downloaders;
288 	     count < method_number;
289 	     count++, download_command = download_command->next) ;	/* null body */
290 
291 	/*
292 	 * Commands have the form "command %s [etc]" where %s is the filename.
293 	 */
294 	if (download_command->command != NULL) {
295 	    /*
296 	     * Check for two '%s' and ask for the local filename if there is.
297 	     */
298 	    if (HTCountCommandArgs(download_command->command) >= 2) {
299 		_statusline(FILENAME_PROMPT);
300 
301 	      again:
302 		if (sug_file) {
303 		    BStrCopy0(buffer, sug_file);
304 		} else {
305 		    BStrCopy0(buffer, "");
306 		}
307 
308 	      check_again:
309 		if ((ch = LYgetBString(&buffer, VISIBLE, 0, recall)) < 0 ||
310 		    isBEmpty(buffer) ||
311 		    ch == UPARROW ||
312 		    ch == DNARROW) {
313 
314 		    if (recall && ch == UPARROW) {
315 			if (FirstRecall) {
316 			    FirstRecall = FALSE;
317 			    /*
318 			     * Use the last Fname in the list.  - FM
319 			     */
320 			    FnameNum = 0;
321 			} else {
322 			    /*
323 			     * Go back to the previous Fname in the list.  - FM
324 			     */
325 			    FnameNum++;
326 			}
327 			if (FnameNum >= FnameTotal) {
328 			    /*
329 			     * Reset the FirstRecall flag, and use sug_file or
330 			     * a blank.  - FM
331 			     */
332 			    FirstRecall = TRUE;
333 			    FnameNum = FnameTotal;
334 			    _statusline(FILENAME_PROMPT);
335 			    goto again;
336 			} else if ((cp = (char *) HTList_objectAt(sug_filenames,
337 								  FnameNum))
338 				   != NULL) {
339 			    BStrCopy0(buffer, cp);
340 			    if (FnameTotal == 1) {
341 				_statusline(EDIT_THE_PREV_FILENAME);
342 			    } else {
343 				_statusline(EDIT_A_PREV_FILENAME);
344 			    }
345 			    goto check_again;
346 			}
347 		    } else if (recall && ch == DNARROW) {
348 			if (FirstRecall) {
349 			    FirstRecall = FALSE;
350 			    /*
351 			     * Use the first Fname in the list.  - FM
352 			     */
353 			    FnameNum = FnameTotal - 1;
354 			} else {
355 			    /*
356 			     * Advance to the next Fname in the list.  - FM
357 			     */
358 			    FnameNum--;
359 			}
360 			if (FnameNum < 0) {
361 			    /*
362 			     * Set the FirstRecall flag, and use sug_file or a
363 			     * blank.  - FM
364 			     */
365 			    FirstRecall = TRUE;
366 			    FnameNum = FnameTotal;
367 			    _statusline(FILENAME_PROMPT);
368 			    goto again;
369 			} else if ((cp = (char *) HTList_objectAt(sug_filenames,
370 								  FnameNum))
371 				   != NULL) {
372 			    BStrCopy0(buffer, cp);
373 			    if (FnameTotal == 1) {
374 				_statusline(EDIT_THE_PREV_FILENAME);
375 			    } else {
376 				_statusline(EDIT_A_PREV_FILENAME);
377 			    }
378 			    goto check_again;
379 			}
380 		    }
381 
382 		    /*
383 		     * Download cancelled.
384 		     */
385 		    goto cancelled;
386 		}
387 
388 		if (no_dotfiles || !show_dotfiles) {
389 		    if (*LYPathLeaf(buffer->str) == '.') {
390 			HTAlert(FILENAME_CANNOT_BE_DOT);
391 			_statusline(NEW_FILENAME_PROMPT);
392 			goto again;
393 		    }
394 		}
395 		/*
396 		 * Cancel if the user entered "/dev/null" on Unix, or an "nl:"
397 		 * path on VMS.  - FM
398 		 */
399 		if (LYIsNullDevice(buffer->str)) {
400 		    goto cancelled;
401 		}
402 		SecondS = TRUE;
403 	    }
404 
405 	    /*
406 	     * The following is considered a bug by the community.  If the
407 	     * command only takes one argument on the command line, then the
408 	     * suggested file name is not used.  It actually is not a bug at
409 	     * all and does as it should, putting both names on the command
410 	     * line.
411 	     */
412 	    count = 1;
413 	    HTAddParam(&the_command, download_command->command, count, file);
414 	    if (HTCountCommandArgs(download_command->command) > 1)
415 		HTAddParam(&the_command, download_command->command, ++count, buffer->str);
416 	    HTEndParam(&the_command, download_command->command, count);
417 
418 	} else {
419 	    HTAlert(MISCONF_DOWNLOAD_COMMAND);
420 	    goto failed;
421 	}
422 
423 	CTRACE((tfp, "command: %s\n", the_command));
424 	stop_curses();
425 	LYSystem(the_command);
426 	FREE(the_command);
427 	start_curses();
428 	/* don't remove(file); */
429     }
430 
431     if (SecondS == TRUE) {
432 #ifdef VMS
433 	if (0 == strncasecomp(buffer->str, "sys$disk:", 9)) {
434 	    if (0 == StrNCmp((buffer->str + 9), "[]", 2)) {
435 		HTAddSugFilename(buffer->str + 11);
436 	    } else {
437 		HTAddSugFilename(buffer->str + 9);
438 	    }
439 	} else {
440 	    HTAddSugFilename(buffer->str);
441 	}
442 #else
443 	HTAddSugFilename(buffer->str);
444 #endif /* VMS */
445     }
446     goto cleanup;
447 
448   failed:
449     HTAlert(CANNOT_DOWNLOAD_FILE);
450     goto cleanup;
451 
452   cancelled:
453     HTInfoMsg(CANCELLING);
454 
455   cleanup:
456     FREE(Line);
457     BStrFree(buffer);
458     return;
459 }
460 
461 /*
462  * Compare a filename with a given suffix, which we have set to give a rough
463  * idea of its content.
464  */
SuffixIs(char * filename,const char * suffix)465 static int SuffixIs(char *filename, const char *suffix)
466 {
467     size_t have = strlen(filename);
468     size_t need = strlen(suffix);
469 
470     return have > need && !strcmp(filename + have - need, suffix);
471 }
472 
473 /*
474  * LYdownload_options writes out the current download choices to a file so that
475  * the user can select downloaders in the same way that they select all other
476  * links.  Download links look like:
477  * LYNXDOWNLOAD://Method=<#>/File=<STRING>/SugFile=<STRING>
478  */
LYdownload_options(char ** newfile,char * data_file)479 int LYdownload_options(char **newfile, char *data_file)
480 {
481     static char tempfile[LY_MAXPATH] = "\0";
482     char *downloaded_url = NULL;
483     char *sug_filename = NULL;
484     FILE *fp0;
485     lynx_list_item_type *cur_download;
486     int count;
487 
488     /*
489      * Get a suggested filename.
490      */
491     StrAllocCopy(sug_filename, *newfile);
492     change_sug_filename(sug_filename);
493 
494     if ((fp0 = InternalPageFP(tempfile, TRUE)) == 0)
495 	return (-1);
496 
497     StrAllocCopy(downloaded_url, *newfile);
498     LYLocalFileToURL(newfile, tempfile);
499 
500     LYStrNCpy(LYValidDownloadFile,
501 	      data_file,
502 	      (sizeof(LYValidDownloadFile) - 1));
503     LYforce_no_cache = TRUE;	/* don't cache this doc */
504 
505     BeginInternalPage(fp0, DOWNLOAD_OPTIONS_TITLE, DOWNLOAD_OPTIONS_HELP);
506 
507     fprintf(fp0, "<pre>\n");
508     fprintf(fp0, "<em>%s</em> %s\n",
509 	    gettext("Downloaded link:"),
510 	    downloaded_url);
511     FREE(downloaded_url);
512 
513     fprintf(fp0, "<em>%s</em> %s\n",
514 	    gettext("Suggested file name:"),
515 	    sug_filename);
516 
517     fprintf(fp0, "\n%s\n",
518 	    (user_mode == NOVICE_MODE)
519 	    ? gettext("Standard download options:")
520 	    : gettext("Download options:"));
521 
522     if (!no_disk_save) {
523 #if defined(DIRED_SUPPORT)
524 	/*
525 	 * Disable save to disk option for local files.
526 	 */
527 	if (!lynx_edit_mode)
528 #endif /* DIRED_SUPPORT */
529 	{
530 	    fprintf(fp0,
531 		    "   <a href=\"%s//Method=-1/File=%s/SugFile=%s%s\">%s</a>\n",
532 		    STR_LYNXDOWNLOAD,
533 		    data_file,
534 		    NonNull(lynx_save_space),
535 		    sug_filename,
536 		    gettext("Save to disk"));
537 	    /*
538 	     * If it is not a binary file, offer the opportunity to view the
539 	     * downloaded temporary file (see HTSaveToFile).
540 	     */
541 	    if (SuffixIs(data_file, HTML_SUFFIX)
542 		|| SuffixIs(data_file, TEXT_SUFFIX)) {
543 		char *target = NULL;
544 		char *source = LYAddPathToSave(data_file);
545 
546 		LYLocalFileToURL(&target, source);
547 		fprintf(fp0,
548 			"   <a href=\"%s\">%s</a>\n",
549 			target,
550 			gettext("View temporary file"));
551 
552 		FREE(source);
553 		FREE(target);
554 	    }
555 	}
556     } else {
557 	fprintf(fp0, "   <em>%s</em>\n", gettext("Save to disk disabled."));
558     }
559 
560     if (user_mode == NOVICE_MODE)
561 	fprintf(fp0, "\n%s\n", gettext("Local additions:"));
562 
563     if (downloaders != NULL) {
564 	for (count = 0, cur_download = downloaders; cur_download != NULL;
565 	     cur_download = cur_download->next, count++) {
566 	    if (!no_download || cur_download->always_enabled) {
567 		fprintf(fp0,
568 			"   <a href=\"%s//Method=%d/File=%s/SugFile=%s\">",
569 			STR_LYNXDOWNLOAD, count, data_file, sug_filename);
570 		fprintf(fp0, "%s", (cur_download->name
571 				    ? cur_download->name
572 				    : gettext("No Name Given")));
573 		fprintf(fp0, "</a>\n");
574 	    }
575 	}
576     }
577 
578     fprintf(fp0, "</pre>\n");
579     EndInternalPage(fp0);
580     LYCloseTempFP(fp0);
581     LYRegisterUIPage(*newfile, UIP_DOWNLOAD_OPTIONS);
582 
583     /*
584      * Free off temp copy.
585      */
586     FREE(sug_filename);
587 
588     return (0);
589 }
590