/* Messages logging.
- Copyright (C) 1998, 2000, 2001 Free Software Foundation, Inc.
+ Copyright (C) 1998-2006 Free Software Foundation, Inc.
This file is part of GNU Wget.
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
-along with Wget; if not, write to the Free Software
-Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+along with Wget; if not, write to the Free Software Foundation, Inc.,
+51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
In addition, as a special exception, the Free Software Foundation
gives permission to link the code of its release of Wget with the
#include <config.h>
-/* This allows the architecture-specific .h files to specify the use
- of stdargs regardless of __STDC__. */
-#ifndef WGET_USE_STDARG
-/* Use stdarg only if the compiler supports ANSI C and stdarg.h is
- present. We check for both because there are configurations where
- stdarg.h exists, but doesn't work. */
-# ifdef __STDC__
-# ifdef HAVE_STDARG_H
-# define WGET_USE_STDARG
-# endif
-# endif
-#endif /* not WGET_USE_STDARG */
-
#include <stdio.h>
-#ifdef HAVE_STRING_H
-# include <string.h>
-#else
-# include <strings.h>
-#endif
+#include <string.h>
#include <stdlib.h>
-#ifdef WGET_USE_STDARG
-# include <stdarg.h>
-#else
-# include <varargs.h>
-#endif
+#include <stdarg.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#include "wget.h"
#include "utils.h"
-
-#ifndef errno
-extern int errno;
-#endif
+#include "log.h"
/* This file impplement support for "logging". Logging means printing
output, plus several additional features:
logging is inhibited, logfp is set back to NULL. */
static FILE *logfp;
-/* If non-zero, it means logging is inhibited, i.e. nothing is printed
- or stored. */
-static int inhibit_logging;
+/* If true, it means logging is inhibited, i.e. nothing is printed or
+ stored. */
+static bool inhibit_logging;
/* Whether the last output lines are stored for use as context. */
-static int save_context_p;
+static bool save_context_p;
/* Whether the log is flushed after each command. */
-static int flush_log_p = 1;
+static bool flush_log_p = true;
/* Whether any output has been received while flush_log_p was 0. */
-static int needs_flushing;
+static bool needs_flushing;
/* In the event of a hang-up, and if its output was on a TTY, Wget
redirects its output to `wget-log'.
finish with \n. This is an important piece of information because
the code is always careful to append data to trailing lines, rather
than create new ones. */
-static int trailing_line;
+static bool trailing_line;
-static void check_redirect_output PARAMS ((void));
+static void check_redirect_output (void);
\f
#define ROT_ADVANCE(num) do { \
if (++num >= SAVED_LOG_LINES) \
FILE *fp;
check_redirect_output ();
- if (!(fp = get_log_fp ()))
+ if ((fp = get_log_fp ()) == NULL)
return;
CHECK_VERBOSE (o);
if (flush_log_p)
logflush ();
else
- needs_flushing = 1;
+ needs_flushing = true;
}
struct logvprintf_state {
/* Print a message to the log. A copy of message will be saved to
saved_log, for later reusal by log_dump_context().
- It is not possible to code this function in a "natural" way, using
- a loop, because of the braindeadness of the varargs API.
- Specifically, each call to vsnprintf() must be preceded by va_start
- and followed by va_end. And this is possible only in the function
- that contains the `...' declaration. The alternative would be to
- use va_copy, but that's not portable. */
+ Normally we'd want this function to loop around vsnprintf until
+ sufficient room is allocated, as the Linux man page recommends.
+ However each call to vsnprintf() must be preceded by va_start and
+ followed by va_end. Since calling va_start/va_end is possible only
+ in the function that contains the `...' declaration, we cannot call
+ vsnprintf more than once. Therefore this function saves its state
+ to logvprintf_state and signals the parent to call it again.
-static int
-logvprintf (struct logvprintf_state *state, const char *fmt, va_list args)
+ (An alternative approach would be to use va_copy, but that's not
+ portable.) */
+
+static bool
+log_vprintf_internal (struct logvprintf_state *state, const char *fmt,
+ va_list args)
{
char smallmsg[128];
char *write_ptr = smallmsg;
numwritten = vsnprintf (write_ptr, available_size, fmt, args);
/* vsnprintf() will not step over the limit given by available_size.
- If it fails, it will return either -1 (POSIX?) or the number of
- characters that *would have* been written, if there had been
- enough room (C99). In the former case, we double the
- available_size and malloc to get a larger buffer, and try again.
- In the latter case, we use the returned information to build a
- buffer of the correct size. */
+ If it fails, it returns either -1 (older implementations) or the
+ number of characters (not counting the terminating \0) that
+ *would have* been written if there had been enough room (C99).
+ In the former case, we double available_size and malloc to get a
+ larger buffer, and try again. In the latter case, we use the
+ returned information to build a buffer of the correct size. */
if (numwritten == -1)
{
int newsize = available_size << 1;
state->bigmsg = xrealloc (state->bigmsg, newsize);
state->allocated = newsize;
- return 0;
+ return false;
}
else if (numwritten >= available_size)
{
int newsize = numwritten + 1;
state->bigmsg = xrealloc (state->bigmsg, newsize);
state->allocated = newsize;
- return 0;
+ return false;
}
/* Writing succeeded. */
if (flush_log_p)
logflush ();
else
- needs_flushing = 1;
+ needs_flushing = true;
- return 1;
+ return true;
}
/* Flush LOGFP. Useful while flushing is disabled. */
FILE *fp = get_log_fp ();
if (fp)
fflush (fp);
- needs_flushing = 0;
+ needs_flushing = false;
}
/* Enable or disable log flushing. */
void
-log_set_flush (int flush)
+log_set_flush (bool flush)
{
if (flush == flush_log_p)
return;
- if (flush == 0)
+ if (flush == false)
{
/* Disable flushing by setting flush_log_p to 0. */
- flush_log_p = 0;
+ flush_log_p = false;
}
else
{
flush the log now. */
if (needs_flushing)
logflush ();
- flush_log_p = 1;
+ flush_log_p = true;
}
}
status of storing, with which this function can be called again to
reestablish storing. */
-int
-log_set_save_context (int savep)
+bool
+log_set_save_context (bool savep)
{
- int old = save_context_p;
+ bool old = save_context_p;
save_context_p = savep;
return old;
}
-/* Handle difference in va_start between pre-ANSI and ANSI C. Note
- that we always use `...' in function definitions and let ansi2knr
- convert it for us. */
-
-#ifdef WGET_USE_STDARG
-# define VA_START(args, arg1) va_start (args, arg1)
-#else
-# define VA_START(args, ignored) va_start (args)
-#endif
-
/* Print a message to the screen or to the log. The first argument
defines the verbosity of the message, and the rest are as in
printf(3). */
{
va_list args;
struct logvprintf_state lpstate;
- int done;
+ bool done;
check_redirect_output ();
if (inhibit_logging)
return;
CHECK_VERBOSE (o);
- memset (&lpstate, '\0', sizeof (lpstate));
+ xzero (lpstate);
do
{
- VA_START (args, fmt);
- done = logvprintf (&lpstate, fmt, args);
+ va_start (args, fmt);
+ done = log_vprintf_internal (&lpstate, fmt, args);
va_end (args);
}
while (!done);
}
-#ifdef DEBUG
+#ifdef ENABLE_DEBUG
/* The same as logprintf(), but does anything only if opt.debug is
- non-zero. */
+ true. */
void
debug_logprintf (const char *fmt, ...)
{
{
va_list args;
struct logvprintf_state lpstate;
- int done;
+ bool done;
check_redirect_output ();
if (inhibit_logging)
return;
- memset (&lpstate, '\0', sizeof (lpstate));
+ xzero (lpstate);
do
{
- VA_START (args, fmt);
- done = logvprintf (&lpstate, fmt, args);
+ va_start (args, fmt);
+ done = log_vprintf_internal (&lpstate, fmt, args);
va_end (args);
}
while (!done);
}
}
-#endif /* DEBUG */
+#endif /* ENABLE_DEBUG */
\f
/* Open FILE and set up a logging stream. If FILE cannot be opened,
exit with status of 1. */
void
-log_init (const char *file, int appendp)
+log_init (const char *file, bool appendp)
{
if (file)
{
logfp = fopen (file, appendp ? "a" : "w");
if (!logfp)
{
- perror (opt.lfilename);
+ fprintf (stderr, "%s: %s: %s\n", exec_name, file, strerror (errno));
exit (1);
}
}
easier on the user. */
logfp = stderr;
- /* If the output is a TTY, enable storing, which will make Wget
- remember the last several printed messages, to be able to
- dump them to a log file in case SIGHUP or SIGUSR1 is received
- (or Ctrl+Break is pressed under Windows). */
if (1
#ifdef HAVE_ISATTY
&& isatty (fileno (logfp))
#endif
)
{
- save_context_p = 1;
+ /* If the output is a TTY, enable save context, i.e. store
+ the most recent several messages ("context") and dump
+ them to a log file in case SIGHUP or SIGUSR1 is received
+ (or Ctrl+Break is pressed under Windows). */
+ save_context_p = true;
}
}
}
if (logfp)
fclose (logfp);
logfp = NULL;
- inhibit_logging = 1;
- save_context_p = 0;
+ inhibit_logging = true;
+ save_context_p = false;
for (i = 0; i < SAVED_LOG_LINES; i++)
free_log_line (i);
log_line_current = -1;
- trailing_line = 0;
+ trailing_line = false;
}
/* Dump saved lines to logfp. */
fflush (fp);
}
\f
+/* String escape functions. */
+
+/* Return the number of non-printable characters in SOURCE.
+ Non-printable characters are determined as per safe-ctype.c. */
+
+static int
+count_nonprint (const char *source)
+{
+ const char *p;
+ int cnt;
+ for (p = source, cnt = 0; *p; p++)
+ if (!ISPRINT (*p))
+ ++cnt;
+ return cnt;
+}
+
+/* Copy SOURCE to DEST, escaping non-printable characters.
+
+ Non-printable refers to anything outside the non-control ASCII
+ range (32-126) which means that, for example, CR, LF, and TAB are
+ considered non-printable along with ESC, BS, and other control
+ chars. This is by design: it makes sure that messages from remote
+ servers cannot be easily used to deceive the users by mimicking
+ Wget's output. Disallowing non-ASCII characters is another
+ necessary security measure, which makes sure that remote servers
+ cannot garble the screen or guess the local charset and perform
+ homographic attacks.
+
+ Of course, the above mandates that escnonprint only be used in
+ contexts expected to be ASCII, such as when printing host names,
+ URL components, HTTP headers, FTP server messages, and the like.
+
+ ESCAPE is the leading character of the escape sequence. BASE
+ should be the base of the escape sequence, and must be either 8 for
+ octal or 16 for hex.
+
+ DEST must point to a location with sufficient room to store an
+ encoded version of SOURCE. */
+
+static void
+copy_and_escape (const char *source, char *dest, char escape, int base)
+{
+ const char *from = source;
+ char *to = dest;
+ unsigned char c;
+
+ /* Copy chars from SOURCE to DEST, escaping non-printable ones. */
+ switch (base)
+ {
+ case 8:
+ while ((c = *from++) != '\0')
+ if (ISPRINT (c))
+ *to++ = c;
+ else
+ {
+ *to++ = escape;
+ *to++ = '0' + (c >> 6);
+ *to++ = '0' + ((c >> 3) & 7);
+ *to++ = '0' + (c & 7);
+ }
+ break;
+ case 16:
+ while ((c = *from++) != '\0')
+ if (ISPRINT (c))
+ *to++ = c;
+ else
+ {
+ *to++ = escape;
+ *to++ = XNUM_TO_DIGIT (c >> 4);
+ *to++ = XNUM_TO_DIGIT (c & 0xf);
+ }
+ break;
+ default:
+ abort ();
+ }
+ *to = '\0';
+}
+
+#define RING_SIZE 3
+struct ringel {
+ char *buffer;
+ int size;
+};
+static struct ringel ring[RING_SIZE]; /* ring data */
+
+static const char *
+escnonprint_internal (const char *str, char escape, int base)
+{
+ static int ringpos; /* current ring position */
+ int nprcnt;
+
+ assert (base == 8 || base == 16);
+
+ nprcnt = count_nonprint (str);
+ if (nprcnt == 0)
+ /* If there are no non-printable chars in STR, don't bother
+ copying anything, just return STR. */
+ return str;
+
+ {
+ /* Set up a pointer to the current ring position, so we can write
+ simply r->X instead of ring[ringpos].X. */
+ struct ringel *r = ring + ringpos;
+
+ /* Every non-printable character is replaced with the escape char
+ and three (or two, depending on BASE) *additional* chars. Size
+ must also include the length of the original string and one
+ additional char for the terminating \0. */
+ int needed_size = strlen (str) + 1 + (base == 8 ? 3 * nprcnt : 2 * nprcnt);
+
+ /* If the current buffer is uninitialized or too small,
+ (re)allocate it. */
+ if (r->buffer == NULL || r->size < needed_size)
+ {
+ r->buffer = xrealloc (r->buffer, needed_size);
+ r->size = needed_size;
+ }
+
+ copy_and_escape (str, r->buffer, escape, base);
+ ringpos = (ringpos + 1) % RING_SIZE;
+ return r->buffer;
+ }
+}
+
+/* Return a pointer to a static copy of STR with the non-printable
+ characters escaped as \ooo. If there are no non-printable
+ characters in STR, STR is returned. See copy_and_escape for more
+ information on which characters are considered non-printable.
+
+ DON'T call this function on translated strings because escaping
+ will break them. Don't call it on literal strings from the source,
+ which are by definition trusted. If newlines are allowed in the
+ string, escape and print it line by line because escaping the whole
+ string will convert newlines to \012. (This is so that expectedly
+ single-line messages cannot use embedded newlines to mimic Wget's
+ output and deceive the user.)
+
+ escnonprint doesn't quote its escape character because it is notf
+ meant as a general and reversible quoting mechanism, but as a quick
+ way to defang binary junk sent by malicious or buggy servers.
+
+ NOTE: since this function can return a pointer to static data, be
+ careful to copy its result before calling it again. However, to be
+ more useful with printf, it maintains an internal ring of static
+ buffers to return. Currently the ring size is 3, which means you
+ can print up to three values in the same printf; if more is needed,
+ bump RING_SIZE. */
+
+const char *
+escnonprint (const char *str)
+{
+ return escnonprint_internal (str, '\\', 8);
+}
+
+/* Return a pointer to a static copy of STR with the non-printable
+ characters escaped as %XX. If there are no non-printable
+ characters in STR, STR is returned.
+
+ See escnonprint for usage details. */
+
+const char *
+escnonprint_uri (const char *str)
+{
+ return escnonprint_internal (str, '%', 16);
+}
+
+void
+log_cleanup (void)
+{
+ int i;
+ for (i = 0; i < countof (ring); i++)
+ xfree_null (ring[i].buffer);
+}
+\f
/* When SIGHUP or SIGUSR1 are received, the output is redirected
elsewhere. Such redirection is only allowed once. */
-enum { RR_NONE, RR_REQUESTED, RR_DONE } redirect_request = RR_NONE;
+static enum { RR_NONE, RR_REQUESTED, RR_DONE } redirect_request = RR_NONE;
static const char *redirect_request_signal_name;
/* Redirect output to `wget-log'. */
static void
redirect_output (void)
{
- char *logfile = unique_name (DEFAULT_LOGFILE, 0);
- fprintf (stderr, _("\n%s received, redirecting output to `%s'.\n"),
- redirect_request_signal_name, logfile);
- logfp = fopen (logfile, "w");
- if (!logfp)
+ char *logfile;
+ logfp = unique_create (DEFAULT_LOGFILE, false, &logfile);
+ if (logfp)
+ {
+ fprintf (stderr, _("\n%s received, redirecting output to `%s'.\n"),
+ redirect_request_signal_name, logfile);
+ xfree (logfile);
+ /* Dump the context output to the newly opened log. */
+ log_dump_context ();
+ }
+ else
{
/* Eek! Opening the alternate log file has failed. Nothing we
can do but disable printing completely. */
+ fprintf (stderr, _("\n%s received.\n"), redirect_request_signal_name);
fprintf (stderr, _("%s: %s; disabling logging.\n"),
logfile, strerror (errno));
- inhibit_logging = 1;
- }
- else
- {
- /* Dump the context output to the newly opened log. */
- log_dump_context ();
+ inhibit_logging = true;
}
- xfree (logfile);
- save_context_p = 0;
+ save_context_p = false;
}
/* Check whether a signal handler requested the output to be