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