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