1/* Get the contents of an URL.
2   Copyright (C) 2001-2003, 2005-2006 Free Software Foundation, Inc.
3   Written by Bruno Haible <haible@clisp.cons.org>, 2001.
4
5   This program is free software; you can redistribute it and/or modify
6   it under the terms of the GNU General Public License as published by
7   the Free Software Foundation; either version 2, or (at your option)
8   any later version.
9
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14
15   You should have received a copy of the GNU General Public License
16   along with this program; if not, write to the Free Software Foundation,
17   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
18
19
20#ifdef HAVE_CONFIG_H
21# include "config.h"
22#endif
23
24#include <errno.h>
25#include <fcntl.h>
26#include <getopt.h>
27#include <stdbool.h>
28#include <stdio.h>
29#include <stdlib.h>
30#include <locale.h>
31#include <unistd.h>
32
33#include "closeout.h"
34#include "error.h"
35#include "error-progname.h"
36#include "progname.h"
37#include "relocatable.h"
38#include "basename.h"
39#include "full-write.h"
40#include "execute.h"
41#include "javaexec.h"
42#include "exit.h"
43#include "binary-io.h"
44#include "propername.h"
45#include "gettext.h"
46
47#define _(str) gettext (str)
48
49#ifndef STDOUT_FILENO
50# define STDOUT_FILENO 1
51#endif
52
53
54/* Only high-level toolkits, written in languages with exception handling,
55   have an URL datatype and operations to fetch an URL's contents.  Such
56   toolkits are Java (class java.net.URL), Qt (classes QUrl and QUrlOperator).
57   We use the Java toolkit.
58   Note that this program doesn't handle redirection pages; programs which
59   wish to process HTML redirection tags need to include a HTML parser,
60   and only full-fledged browsers like w3m, lynx, links have have both
61   an URL fetcher (which covers at least the protocols "http", "ftp", "file")
62   and a HTML parser.  */
63
64
65/* Long options.  */
66static const struct option long_options[] =
67{
68  { "help", no_argument, NULL, 'h' },
69  { "version", no_argument, NULL, 'V' },
70  { NULL, 0, NULL, 0 }
71};
72
73
74/* Forward declaration of local functions.  */
75static void usage (int status)
76#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2)
77     __attribute__ ((noreturn))
78#endif
79;
80static void fetch (const char *url, const char *file);
81
82
83int
84main (int argc, char *argv[])
85{
86  int optchar;
87  bool do_help;
88  bool do_version;
89
90  /* Set program name for messages.  */
91  set_program_name (argv[0]);
92  error_print_progname = maybe_print_progname;
93
94#ifdef HAVE_SETLOCALE
95  /* Set locale via LC_ALL.  */
96  setlocale (LC_ALL, "");
97#endif
98
99  /* Set the text message domain.  */
100  bindtextdomain (PACKAGE, relocate (LOCALEDIR));
101  textdomain (PACKAGE);
102
103  /* Ensure that write errors on stdout are detected.  */
104  atexit (close_stdout);
105
106  /* Set default values for variables.  */
107  do_help = false;
108  do_version = false;
109
110  /* Parse command line options.  */
111  while ((optchar = getopt_long (argc, argv, "hV", long_options, NULL)) != EOF)
112    switch (optchar)
113    {
114    case '\0':		/* Long option.  */
115      break;
116    case 'h':
117      do_help = true;
118      break;
119    case 'V':
120      do_version = true;
121      break;
122    default:
123      usage (EXIT_FAILURE);
124      /* NOTREACHED */
125    }
126
127  /* Version information requested.  */
128  if (do_version)
129    {
130      printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION);
131      /* xgettext: no-wrap */
132      printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
133This is free software; see the source for copying conditions.  There is NO\n\
134warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
135"),
136	      "2001-2003");
137      printf (_("Written by %s.\n"), proper_name ("Bruno Haible"));
138      exit (EXIT_SUCCESS);
139    }
140
141  /* Help is requested.  */
142  if (do_help)
143    usage (EXIT_SUCCESS);
144
145  /* Test argument count.  */
146  if (optind + 2 != argc)
147    error (EXIT_FAILURE, 0, _("expected two arguments"));
148
149  /* Fetch the contents.  */
150  fetch (argv[optind], argv[optind + 1]);
151
152  exit (EXIT_SUCCESS);
153}
154
155/* Display usage information and exit.  */
156static void
157usage (int status)
158{
159  if (status != EXIT_SUCCESS)
160    fprintf (stderr, _("Try `%s --help' for more information.\n"),
161	     program_name);
162  else
163    {
164      printf (_("\
165Usage: %s [OPTION] URL FILE\n\
166"), program_name);
167      printf ("\n");
168      /* xgettext: no-wrap */
169      printf (_("\
170Fetches and outputs the contents of an URL.  If the URL cannot be accessed,\n\
171the locally accessible FILE is used instead.\n\
172"));
173      printf ("\n");
174      printf (_("\
175Informative output:\n"));
176      printf (_("\
177  -h, --help                  display this help and exit\n"));
178      printf (_("\
179  -V, --version               output version information and exit\n"));
180      printf ("\n");
181      fputs (_("Report bugs to <bug-gnu-gettext@gnu.org>.\n"),
182	     stdout);
183    }
184
185  exit (status);
186}
187
188/* Copy a file's contents to stdout.  */
189static void
190cat_file (const char *src_filename)
191{
192  int src_fd;
193  char buf[4096];
194  const int buf_size = sizeof (buf);
195
196  src_fd = open (src_filename, O_RDONLY | O_BINARY);
197  if (src_fd < 0)
198    error (EXIT_FAILURE, errno, _("error while opening \"%s\" for reading"),
199	   src_filename);
200
201  for (;;)
202    {
203      ssize_t n_read = read (src_fd, buf, buf_size);
204      if (n_read < 0)
205	{
206#ifdef EINTR
207	  if (errno == EINTR)
208	    continue;
209#endif
210	  error (EXIT_FAILURE, errno, _("error reading \"%s\""), src_filename);
211	}
212      if (n_read == 0)
213	break;
214
215      if (full_write (STDOUT_FILENO, buf, n_read) < n_read)
216	error (EXIT_FAILURE, errno, _("error writing stdout"));
217    }
218
219  if (close (src_fd) < 0)
220    error (EXIT_FAILURE, errno, _("error after reading \"%s\""), src_filename);
221}
222
223static bool
224execute_it (const char *progname,
225	    const char *prog_path, char **prog_argv,
226	    void *private_data)
227{
228  (void) private_data;
229
230  return execute (progname, prog_path, prog_argv, true, true, false, false,
231		  true, false)
232	 != 0;
233}
234
235/* Fetch the URL.  Upon error, use the FILE as fallback.  */
236static void
237fetch (const char *url, const char *file)
238{
239  /* First try: using Java.  */
240  {
241    const char *class_name = "gnu.gettext.GetURL";
242    const char *gettextjexedir;
243    const char *gettextjar;
244    const char *args[2];
245
246#if USEJEXE
247    /* Make it possible to override the executable's location.  This is
248       necessary for running the testsuite before "make install".  */
249    gettextjexedir = getenv ("GETTEXTJEXEDIR");
250    if (gettextjexedir == NULL || gettextjexedir[0] == '\0')
251      gettextjexedir = relocate (GETTEXTJEXEDIR);
252#else
253    gettextjexedir = NULL;
254#endif
255
256    /* Make it possible to override the gettext.jar location.  This is
257       necessary for running the testsuite before "make install".  */
258    gettextjar = getenv ("GETTEXTJAR");
259    if (gettextjar == NULL || gettextjar[0] == '\0')
260      gettextjar = relocate (GETTEXTJAR);
261
262    /* Prepare arguments.  */
263    args[0] = url;
264    args[1] = NULL;
265
266    /* Fetch the URL's contents.  */
267    if (execute_java_class (class_name, &gettextjar, 1, true, gettextjexedir,
268			    args,
269			    false, true,
270			    execute_it, NULL) == 0)
271      return;
272  }
273
274  /* Second try: using "wget -q -O - url".  */
275  {
276    static bool wget_tested;
277    static bool wget_present;
278
279    if (!wget_tested)
280      {
281	/* Test for presence of wget: "wget --version > /dev/null"  */
282	char *argv[3];
283	int exitstatus;
284
285	argv[0] = "wget";
286	argv[1] = "--version";
287	argv[2] = NULL;
288	exitstatus = execute ("wget", "wget", argv, false, false, true, true,
289			      true, false);
290	wget_present = (exitstatus == 0);
291	wget_tested = true;
292      }
293
294    if (wget_present)
295      {
296	char *argv[8];
297	int exitstatus;
298
299	argv[0] = "wget";
300	argv[1] = "-q";
301	argv[2] = "-O"; argv[3] = "-";
302	argv[4] = "-T"; argv[5] = "30";
303	argv[6] = (char *) url;
304	argv[7] = NULL;
305	exitstatus = execute ("wget", "wget", argv, true, false, false, false,
306			      true, false);
307	if (exitstatus != 127)
308	  {
309	    if (exitstatus != 0)
310	      /* Use the file as fallback.  */
311	      cat_file (file);
312	    return;
313	  }
314      }
315  }
316
317  /* Third try: using "lynx -source url".  */
318  {
319    static bool lynx_tested;
320    static bool lynx_present;
321
322    if (!lynx_tested)
323      {
324	/* Test for presence of lynx: "lynx --version > /dev/null"  */
325	char *argv[3];
326	int exitstatus;
327
328	argv[0] = "lynx";
329	argv[1] = "--version";
330	argv[2] = NULL;
331	exitstatus = execute ("lynx", "lynx", argv, false, false, true, true,
332			      true, false);
333	lynx_present = (exitstatus == 0);
334	lynx_tested = true;
335      }
336
337    if (lynx_present)
338      {
339	char *argv[4];
340	int exitstatus;
341
342	argv[0] = "lynx";
343	argv[1] = "-source";
344	argv[2] = (char *) url;
345	argv[3] = NULL;
346	exitstatus = execute ("lynx", "lynx", argv, true, false, false, false,
347			      true, false);
348	if (exitstatus != 127)
349	  {
350	    if (exitstatus != 0)
351	      /* Use the file as fallback.  */
352	      cat_file (file);
353	    return;
354	  }
355      }
356  }
357
358  /* Fourth try: using "curl --silent url".  */
359  {
360    static bool curl_tested;
361    static bool curl_present;
362
363    if (!curl_tested)
364      {
365	/* Test for presence of curl: "curl --version > /dev/null"  */
366	char *argv[3];
367	int exitstatus;
368
369	argv[0] = "curl";
370	argv[1] = "--version";
371	argv[2] = NULL;
372	exitstatus = execute ("curl", "curl", argv, false, false, true, true,
373			      true, false);
374	curl_present = (exitstatus == 0 || exitstatus == 2);
375	curl_tested = true;
376      }
377
378    if (curl_present)
379      {
380	char *argv[4];
381	int exitstatus;
382
383	argv[0] = "curl";
384	argv[1] = "--silent";
385	argv[2] = (char *) url;
386	argv[3] = NULL;
387	exitstatus = execute ("curl", "curl", argv, true, false, false, false,
388			      true, false);
389	if (exitstatus != 127)
390	  {
391	    if (exitstatus != 0)
392	      /* Use the file as fallback.  */
393	      cat_file (file);
394	    return;
395	  }
396      }
397  }
398
399  /* Use the file as fallback.  */
400  cat_file (file);
401}
402