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