]> sjero.net Git - wget/blobdiff - src/ftp.c
replaced read_whole_file() by getline()
[wget] / src / ftp.c
index 9bc92a8945428c1da5721a1c1e08e24375e8d3a9..0ad4b326b820e8aea3e7c8f6742ce881ce8b23fb 100644 (file)
--- a/src/ftp.c
+++ b/src/ftp.c
@@ -1,6 +1,7 @@
 /* File Transfer Protocol support.
-   Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
-   2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
+   Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
+   2005, 2006, 2007, 2008, 2009, 2010, 2011 Free Software Foundation,
+   Inc.
 
 This file is part of GNU Wget.
 
@@ -33,9 +34,8 @@ as that of the covered work.  */
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#ifdef HAVE_UNISTD_H
-# include <unistd.h>
-#endif
+#include <strings.h>
+#include <unistd.h>
 #include <assert.h>
 #include <errno.h>
 #include <time.h>
@@ -49,6 +49,7 @@ as that of the covered work.  */
 #include "netrc.h"
 #include "convert.h"            /* for downloaded_file */
 #include "recur.h"              /* for INFINITE_RECURSION */
+#include "warc.h"
 
 #ifdef __VMS
 # include "vms.h"
@@ -68,12 +69,13 @@ typedef struct
   int cmd;                      /* command code */
   int csock;                    /* control connection socket */
   double dltime;                /* time of the download in msecs */
-  enum stype rs;                /* remote system reported by ftp server */ 
+  enum stype rs;                /* remote system reported by ftp server */
   char *id;                     /* initial directory */
   char *target;                 /* target file name */
   struct url *proxy;            /* FTWK-style proxy */
 } ccon;
 
+extern int numurls;
 
 /* Look for regexp "( *[0-9]+ *byte" (literal parenthesis) anywhere in
    the string S, and return the number converted to wgint, if found, 0
@@ -108,7 +110,7 @@ ftp_expected_bytes (const char *s)
 }
 
 #ifdef ENABLE_IPV6
-/* 
+/*
  * This function sets up a passive data connection with the FTP server.
  * It is merely a wrapper around ftp_epsv, ftp_lpsv and ftp_pasv.
  */
@@ -123,8 +125,8 @@ ftp_do_pasv (int csock, ip_address *addr, int *port)
   if (!socket_ip_address (csock, addr, ENDPOINT_PEER))
     abort ();
 
-  /* If our control connection is over IPv6, then we first try EPSV and then 
-   * LPSV if the former is not supported. If the control connection is over 
+  /* If our control connection is over IPv6, then we first try EPSV and then
+   * LPSV if the former is not supported. If the control connection is over
    * IPv4, we simply issue the good old PASV request. */
   switch (addr->family)
     {
@@ -153,7 +155,7 @@ ftp_do_pasv (int csock, ip_address *addr, int *port)
   return err;
 }
 
-/* 
+/*
  * This function sets up an active data connection with the FTP server.
  * It is merely a wrapper around ftp_eprt, ftp_lprt and ftp_port.
  */
@@ -166,8 +168,8 @@ ftp_do_port (int csock, int *local_sock)
   if (!socket_ip_address (csock, &cip, ENDPOINT_PEER))
     abort ();
 
-  /* If our control connection is over IPv6, then we first try EPRT and then 
-   * LPRT if the former is not supported. If the control connection is over 
+  /* If our control connection is over IPv6, then we first try EPRT and then
+   * LPRT if the former is not supported. If the control connection is over
    * IPv4, we simply issue the good old PORT request. */
   switch (cip.family)
     {
@@ -221,7 +223,7 @@ print_length (wgint size, wgint start, bool authoritative)
     logprintf (LOG_VERBOSE, " (%s)", human_readable (size));
   if (start > 0)
     {
-      if (start >= 1024)
+      if (size - start >= 1024)
         logprintf (LOG_VERBOSE, _(", %s (%s) remaining"),
                    number_to_static_string (size - start),
                    human_readable (size - start));
@@ -232,21 +234,25 @@ print_length (wgint size, wgint start, bool authoritative)
   logputs (LOG_VERBOSE, !authoritative ? _(" (unauthoritative)\n") : "\n");
 }
 
+static uerr_t ftp_get_listing (struct url *, ccon *, struct fileinfo **);
+
 /* Retrieves a file with denoted parameters through opening an FTP
    connection to the server.  It always closes the data connection,
-   and closes the control connection in case of error.  */
+   and closes the control connection in case of error.  If warc_tmp
+   is non-NULL, the downloaded data will be written there as well.  */
 static uerr_t
-getftp (struct url *u, wgint *len, wgint restval, ccon *con)
+getftp (struct url *u, wgint passed_expected_bytes, wgint *qtyread,
+        wgint restval, ccon *con, int count, FILE *warc_tmp)
 {
   int csock, dtsock, local_sock, res;
   uerr_t err = RETROK;          /* appease the compiler */
   FILE *fp;
-  char *user, *passwd, *respline;
-  char *tms;
-  const char *tmrate;
+  char *respline, *tms;
+  const char *user, *passwd, *tmrate;
   int cmd = con->cmd;
   bool pasv_mode_open = false;
   wgint expected_bytes = 0;
+  bool got_expected_bytes = false;
   bool rest_failed = false;
   int flags;
   wgint rd_size;
@@ -262,6 +268,8 @@ getftp (struct url *u, wgint *len, wgint restval, ccon *con)
   /* Make sure that at least *something* is requested.  */
   assert ((cmd & (DO_LIST | DO_CWD | DO_RETR | DO_LOGIN)) != 0);
 
+  *qtyread = restval;
+
   user = u->user;
   passwd = u->passwd;
   search_netrc (u->host, (const char **)&user, (const char **)&passwd, 1);
@@ -280,13 +288,6 @@ getftp (struct url *u, wgint *len, wgint restval, ccon *con)
     {
       char    *host = con->proxy ? con->proxy->host : u->host;
       int      port = con->proxy ? con->proxy->port : u->port;
-      char *logname = user;
-
-      if (con->proxy)
-        {
-          /* If proxy is in use, log in as username@target-site. */
-          logname = concat_strings (user, "@", u->host, (char *) 0);
-        }
 
       /* Login to the server: */
 
@@ -294,10 +295,10 @@ getftp (struct url *u, wgint *len, wgint restval, ccon *con)
 
       csock = connect_to_host (host, port);
       if (csock == E_HOST)
-        return HOSTERR;
+          return HOSTERR;
       else if (csock < 0)
-        return (retryable_socket_connect_error (errno)
-                ? CONERROR : CONIMPOSSIBLE);
+          return (retryable_socket_connect_error (errno)
+                  ? CONERROR : CONIMPOSSIBLE);
 
       if (cmd & LEAVE_PENDING)
         con->csock = csock;
@@ -305,13 +306,19 @@ getftp (struct url *u, wgint *len, wgint restval, ccon *con)
         con->csock = -1;
 
       /* Second: Login with proper USER/PASS sequence.  */
-      logprintf (LOG_VERBOSE, _("Logging in as %s ... "), escnonprint (user));
+      logprintf (LOG_VERBOSE, _("Logging in as %s ... "),
+                 quotearg_style (escape_quoting_style, user));
       if (opt.server_response)
         logputs (LOG_ALWAYS, "\n");
-      err = ftp_login (csock, logname, passwd);
-
       if (con->proxy)
-        xfree (logname);
+        {
+          /* If proxy is in use, log in as username@target-site. */
+          char *logname = concat_strings (user, "@", u->host, (char *) 0);
+          err = ftp_login (csock, logname, passwd);
+          xfree (logname);
+        }
+      else
+        err = ftp_login (csock, user, passwd);
 
       /* FTPRERR, FTPSRVERR, WRITEFAILED, FTPLOGREFUSED, FTPLOGINC */
       switch (err)
@@ -421,7 +428,7 @@ Error in server response, closing control connection.\n"));
       */
 
       /* VMS will report something like "PUB$DEVICE:[INITIAL.FOLDER]".
-         Convert it to "/INITIAL/FOLDER" */ 
+         Convert it to "/INITIAL/FOLDER" */
       if (con->rs == ST_VMS)
         {
           char *path = strchr (con->id, '[');
@@ -494,10 +501,10 @@ Error in server response, closing control connection.\n"));
         logputs (LOG_VERBOSE, _("==> CWD not needed.\n"));
       else
         {
-          char *targ;
-         int cwd_count;
-         int cwd_end;
-         int cwd_start;
+          const char *targ = NULL;
+          int cwd_count;
+          int cwd_end;
+          int cwd_start;
 
           char *target = u->dir;
 
@@ -521,7 +528,7 @@ Error in server response, closing control connection.\n"));
                Why is this wise even on UNIX?  It certainly fouls VMS.
                See below for a more reliable, more universal method.
             */
+
             /* 2008-04-22 MJC.
                I'm not crazy about it either. I'm informed it's useful
                for misconfigured servers that have some dirs in the path
@@ -595,7 +602,7 @@ Error in server response, closing control connection.\n"));
 #endif /* 0 */
 
           /* 2004-09-20 SMS.
-             A relative directory is relative to the initial directory. 
+             A relative directory is relative to the initial directory.
              Thus, what _is_ useful on VMS (and probably elsewhere) is
              to CWD to the initial directory (ideally, whatever the
              server reports, _exactly_, NOT badly UNIX-ixed), and then
@@ -609,16 +616,16 @@ Error in server response, closing control connection.\n"));
              The VMS restriction may be relaxed when the squirrely code
              above is reformed.
           */
-         if ((con->rs == ST_VMS) && (target[0] != '/'))
-           {
-             cwd_start = 0;
-             DEBUGP (("Using two-step CWD for relative path.\n"));
-           }
-         else
-           {
+          if ((con->rs == ST_VMS) && (target[0] != '/'))
+            {
+              cwd_start = 0;
+              DEBUGP (("Using two-step CWD for relative path.\n"));
+            }
+          else
+            {
               /* Go straight to the target. */
-             cwd_start = 1;
-           }
+              cwd_start = 1;
+            }
 
           /* At least one VMS FTP server (TCPware V5.6-2) can switch to
              a UNIX emulation mode when given a UNIX-like directory
@@ -636,10 +643,10 @@ Error in server response, closing control connection.\n"));
              Unlike the rest of this block, this particular behavior
              _is_ VMS-specific, so it gets its own VMS test.
           */
-         if ((con->rs == ST_VMS) && (strchr( target, '/') != NULL))
+          if ((con->rs == ST_VMS) && (strchr( target, '/') != NULL))
             {
               cwd_end = 3;
-             DEBUGP (("Using extra \"CWD []\" step for VMS server.\n"));
+              DEBUGP (("Using extra \"CWD []\" step for VMS server.\n"));
             }
           else
             {
@@ -649,22 +656,22 @@ Error in server response, closing control connection.\n"));
           /* 2004-09-20 SMS. */
           /* Sorry about the deviant indenting.  Laziness. */
 
-         for (cwd_count = cwd_start; cwd_count < cwd_end; cwd_count++)
-       {
+          for (cwd_count = cwd_start; cwd_count < cwd_end; cwd_count++)
+            {
           switch (cwd_count)
             {
               case 0:
-               /* Step one (optional): Go to the initial directory,
-                  exactly as reported by the server.
-               */
-               targ = con->id;
+                /* Step one (optional): Go to the initial directory,
+                   exactly as reported by the server.
+                */
+                targ = con->id;
                 break;
 
               case 1:
-               /* Step two: Go to the target directory.  (Absolute or
-                  relative will work now.)
-               */
-               targ = target;
+                /* Step two: Go to the target directory.  (Absolute or
+                   relative will work now.)
+                */
+                targ = target;
                 break;
 
               case 2:
@@ -677,11 +684,11 @@ Error in server response, closing control connection.\n"));
               default:
                 /* Can't happen. */
                 assert (1);
-           }
+            }
 
           if (!opt.server_response)
-            logprintf (LOG_VERBOSE, "==> CWD (%d) %s ... ",
-             cwd_count, escnonprint (target));
+            logprintf (LOG_VERBOSE, "==> CWD (%d) %s ... ", cwd_count,
+                       quotearg_style (escape_quoting_style, target));
           err = ftp_cwd (csock, targ);
           /* FTPRERR, WRITEFAILED, FTPNSFOD */
           switch (err)
@@ -702,8 +709,8 @@ Error in server response, closing control connection.\n"));
               return err;
             case FTPNSFOD:
               logputs (LOG_VERBOSE, "\n");
-              logprintf (LOG_NOTQUIET, _("No such directory `%s'.\n\n"),
-                         escnonprint (u->dir));
+              logprintf (LOG_NOTQUIET, _("No such directory %s.\n\n"),
+                         quote (u->dir));
               fd_close (csock);
               con->csock = -1;
               return err;
@@ -725,15 +732,16 @@ Error in server response, closing control connection.\n"));
   else /* do not CWD */
     logputs (LOG_VERBOSE, _("==> CWD not required.\n"));
 
-  if ((cmd & DO_RETR) && *len == 0)
+  if ((cmd & DO_RETR) && passed_expected_bytes == 0)
     {
       if (opt.verbose)
         {
           if (!opt.server_response)
-            logprintf (LOG_VERBOSE, "==> SIZE %s ... ", escnonprint (u->file));
+            logprintf (LOG_VERBOSE, "==> SIZE %s ... ",
+                       quotearg_style (escape_quoting_style, u->file));
         }
 
-      err = ftp_size (csock, u->file, len);
+      err = ftp_size (csock, u->file, &expected_bytes);
       /* FTPRERR */
       switch (err)
         {
@@ -746,14 +754,25 @@ Error in server response, closing control connection.\n"));
           con->csock = -1;
           return err;
         case FTPOK:
+          got_expected_bytes = true;
           /* Everything is OK.  */
           break;
         default:
           abort ();
         }
         if (!opt.server_response)
-          logprintf (LOG_VERBOSE, *len ? "%s\n" : _("done.\n"),
-                     number_to_static_string (*len));
+          logprintf (LOG_VERBOSE, expected_bytes ? "%s\n" : _("done.\n"),
+                     number_to_static_string (expected_bytes));
+    }
+
+  if (cmd & DO_RETR && restval > 0 && restval == expected_bytes)
+    {
+      /* Server confirms that file has length restval. We should stop now.
+         Some servers (f.e. NcFTPd) return error when receive REST 0 */
+      logputs (LOG_VERBOSE, _("File has already been retrieved.\n"));
+      fd_close (csock);
+      con->csock = -1;
+      return RETRFINISHED;
     }
 
   /* If anything is to be retrieved, PORT (or PASV) must be sent.  */
@@ -796,7 +815,7 @@ Error in server response, closing control connection.\n"));
             }   /* switch (err) */
           if (err==FTPOK)
             {
-              DEBUGP (("trying to connect to %s port %d\n", 
+              DEBUGP (("trying to connect to %s port %d\n",
                       print_address (&passive_addr), passive_port));
               dtsock = connect_to_ip (&passive_addr, passive_port, NULL);
               if (dtsock < 0)
@@ -918,12 +937,43 @@ Error in server response, closing control connection.\n"));
 
   if (cmd & DO_RETR)
     {
-      /* If we're in spider mode, don't really retrieve anything.  The
-         fact that we got to this point should be proof enough that
-         the file exists, vaguely akin to HTTP's concept of a "HEAD"
-         request.  */
+      /* If we're in spider mode, don't really retrieve anything except
+         the directory listing and verify whether the given "file" exists.  */
       if (opt.spider)
         {
+          bool exists = false;
+          uerr_t res;
+          struct fileinfo *f;
+          res = ftp_get_listing (u, con, &f);
+          /* Set the DO_RETR command flag again, because it gets unset when
+             calling ftp_get_listing() and would otherwise cause an assertion
+             failure earlier on when this function gets repeatedly called
+             (e.g., when recursing).  */
+          con->cmd |= DO_RETR;
+          if (res == RETROK)
+            {
+              while (f)
+                {
+                  if (!strcmp (f->name, u->file))
+                    {
+                      exists = true;
+                      break;
+                    }
+                  f = f->next;
+                }
+              if (exists)
+                {
+                  logputs (LOG_VERBOSE, "\n");
+                  logprintf (LOG_NOTQUIET, _("File %s exists.\n"),
+                             quote (u->file));
+                }
+              else
+                {
+                  logputs (LOG_VERBOSE, "\n");
+                  logprintf (LOG_NOTQUIET, _("No such file %s.\n"),
+                             quote (u->file));
+                }
+            }
           fd_close (csock);
           con->csock = -1;
           fd_close (dtsock);
@@ -937,7 +987,8 @@ Error in server response, closing control connection.\n"));
             {
               if (restval)
                 logputs (LOG_VERBOSE, "\n");
-              logprintf (LOG_VERBOSE, "==> RETR %s ... ", escnonprint (u->file));
+              logprintf (LOG_VERBOSE, "==> RETR %s ... ",
+                         quotearg_style (escape_quoting_style, u->file));
             }
         }
 
@@ -965,16 +1016,12 @@ Error in server response, closing control connection.\n"));
           return err;
         case FTPNSFOD:
           logputs (LOG_VERBOSE, "\n");
-          logprintf (LOG_NOTQUIET, _("No such file `%s'.\n\n"),
-                     escnonprint (u->file));
+          logprintf (LOG_NOTQUIET, _("No such file %s.\n\n"),
+                     quote (u->file));
           fd_close (dtsock);
           fd_close (local_sock);
           return err;
         case FTPOK:
-          if (getenv( "FTP_DELETE") != NULL)
-          {
-            err = ftp_dele (csock, u->file);
-          }
           break;
         default:
           abort ();
@@ -982,7 +1029,9 @@ Error in server response, closing control connection.\n"));
 
       if (!opt.server_response)
         logputs (LOG_VERBOSE, _("done.\n"));
-      expected_bytes = ftp_expected_bytes (ftp_last_respline);
+
+      if (! got_expected_bytes)
+        expected_bytes = ftp_expected_bytes (ftp_last_respline);
     } /* do retrieve */
 
   if (cmd & DO_LIST)
@@ -1016,8 +1065,8 @@ Error in server response, closing control connection.\n"));
           return err;
         case FTPNSFOD:
           logputs (LOG_VERBOSE, "\n");
-          logprintf (LOG_NOTQUIET, _("No such file or directory `%s'.\n\n"),
-                     ".");
+          logprintf (LOG_NOTQUIET, _("No such file or directory %s.\n\n"),
+                     quote ("."));
           fd_close (dtsock);
           fd_close (local_sock);
           return err;
@@ -1028,7 +1077,9 @@ Error in server response, closing control connection.\n"));
         }
       if (!opt.server_response)
         logputs (LOG_VERBOSE, _("done.\n"));
-      expected_bytes = ftp_expected_bytes (ftp_last_respline);
+
+      if (! got_expected_bytes)
+        expected_bytes = ftp_expected_bytes (ftp_last_respline);
     } /* cmd & DO_LIST */
 
   if (!(cmd & (DO_LIST | DO_RETR)) || (opt.spider && !(cmd & DO_LIST)))
@@ -1036,11 +1087,11 @@ Error in server response, closing control connection.\n"));
 
   /* Some FTP servers return the total length of file after REST
      command, others just return the remaining size. */
-  if (*len && restval && expected_bytes
-      && (expected_bytes == *len - restval))
+  if (passed_expected_bytes && restval && expected_bytes
+      && (expected_bytes == passed_expected_bytes - restval))
     {
       DEBUGP (("Lying FTP server found, adjusting.\n"));
-      expected_bytes = *len;
+      expected_bytes = passed_expected_bytes;
     }
 
   /* If no transmission was required, then everything is OK.  */
@@ -1053,12 +1104,12 @@ Error in server response, closing control connection.\n"));
       if (dtsock < 0)
         {
           logprintf (LOG_NOTQUIET, "accept: %s\n", strerror (errno));
-          return err;
+          return CONERROR;
         }
     }
 
   /* Open the file -- if output_stream is set, use it instead.  */
-  
+
   /* 2005-04-17 SMS.
      Note that having the output_stream ("-O") file opened in main()
      (main.c) rather limits the ability in VMS to open the file
@@ -1072,14 +1123,14 @@ Error in server response, closing control connection.\n"));
 #ifdef __VMS
       char *targ;
 
-      targ = ods_conformcon->target);
+      targ = ods_conform (con->target);
       if (targ != con->target)
         {
-          xfreecon->target);
+          xfree (con->target);
           con->target = targ;
         }
 #endif /* def __VMS */
+
       mkalldirs (con->target);
       if (opt.backups)
         rotate_backups (con->target);
@@ -1090,15 +1141,27 @@ Error in server response, closing control connection.\n"));
    Elsewhere, define a constant "binary" flag.
    Isn't it nice to have distinct text and binary file types?
 */
-# define BIN_TYPE_TRANSFER (type_char != 'A')
+/* 2011-09-30 SMS.
+   Added listing files to the set of non-"binary" (text, Stream_LF)
+   files.  (Wget works either way, but other programs, like, say, text
+   editors, work better on listing files which have text attributes.)
+   Now we use "binary" attributes for a binary ("IMAGE") transfer,
+   unless "--ftp-stmlf" was specified, and we always use non-"binary"
+   (text, Stream_LF) attributes for a listing file, or for an ASCII
+   transfer.
+   Tidied the VMS-specific BIN_TYPE_xxx macros, and changed the call to
+   fopen_excl() (restored?) to use BIN_TYPE_FILE instead of "true".
+*/
 #ifdef __VMS
+# define BIN_TYPE_TRANSFER (type_char != 'A')
+# define BIN_TYPE_FILE \
+   ((!(cmd & DO_LIST)) && BIN_TYPE_TRANSFER && (opt.ftp_stmlf == 0))
 # define FOPEN_OPT_ARGS "fop=sqo", "acc", acc_cb, &open_id
 # define FOPEN_OPT_ARGS_BIN "ctx=bin,stm", "rfm=fix", "mrs=512" FOPEN_OPT_ARGS
-# define BIN_TYPE_FILE (BIN_TYPE_TRANSFER && (opt.ftp_stmlf == 0))
 #else /* def __VMS */
-# define BIN_TYPE_FILE 1
+# define BIN_TYPE_FILE true
 #endif /* def __VMS [else] */
+
       if (restval && !(con->cmd & DO_LIST))
         {
 #ifdef __VMS
@@ -1119,8 +1182,23 @@ Error in server response, closing control connection.\n"));
 #endif /* def __VMS [else] */
         }
       else if (opt.noclobber || opt.always_rest || opt.timestamping || opt.dirstruct
-               || opt.output_document)
+               || opt.output_document || count > 0)
         {
+          if (opt.unlink && file_exists_p (con->target))
+            {
+              int res = unlink (con->target);
+              if (res < 0)
+                {
+                  logprintf (LOG_NOTQUIET, "%s: %s\n", con->target,
+                    strerror (errno));
+                    fd_close (csock);
+                    con->csock = -1;
+                    fd_close (dtsock);
+                    fd_close (local_sock);
+                    return UNLINKERR;
+                }
+            }
+
 #ifdef __VMS
           int open_id;
 
@@ -1140,7 +1218,7 @@ Error in server response, closing control connection.\n"));
         }
       else
         {
-          fp = fopen_excl (con->target, true);
+          fp = fopen_excl (con->target, BIN_TYPE_FILE);
           if (!fp && errno == EEXIST)
             {
               /* We cannot just invent a new name and use it (which is
@@ -1169,10 +1247,11 @@ Error in server response, closing control connection.\n"));
   else
     fp = output_stream;
 
-  if (*len)
+  if (passed_expected_bytes)
     {
-      print_length (*len, restval, true);
-      expected_bytes = *len;    /* for fd_read_body's progress bar */
+      print_length (passed_expected_bytes, restval, true);
+      expected_bytes = passed_expected_bytes;
+        /* for fd_read_body's progress bar */
     }
   else if (expected_bytes)
     print_length (expected_bytes, restval, false);
@@ -1181,11 +1260,10 @@ Error in server response, closing control connection.\n"));
   flags = 0;
   if (restval && rest_failed)
     flags |= rb_skip_startpos;
-  *len = restval;
   rd_size = 0;
   res = fd_read_body (dtsock, fp,
                       expected_bytes ? expected_bytes - restval : 0,
-                      restval, &rd_size, len, &con->dltime, flags);
+                      restval, &rd_size, qtyread, &con->dltime, flags, warc_tmp);
 
   tms = datetime_str (time (NULL));
   tmrate = retr_rate (rd_size, con->dltime);
@@ -1196,15 +1274,18 @@ Error in server response, closing control connection.\n"));
   if (!output_stream || con->cmd & DO_LIST)
     fclose (fp);
 
-  /* If fd_read_body couldn't write to fp, bail out.  */
-  if (res == -2)
+  /* If fd_read_body couldn't write to fp or warc_tmp, bail out.  */
+  if (res == -2 || (warc_tmp != NULL && res == -3))
     {
       logprintf (LOG_NOTQUIET, _("%s: %s, closing control connection.\n"),
                  con->target, strerror (errno));
       fd_close (csock);
       con->csock = -1;
       fd_close (dtsock);
-      return FWRITEERR;
+      if (res == -2)
+        return FWRITEERR;
+      else if (res == -3)
+        return WARC_TMP_FWRITEERR;
     }
   else if (res == -1)
     {
@@ -1286,17 +1367,20 @@ Error in server response, closing control connection.\n"));
         logprintf (LOG_ALWAYS, "%s: %s\n", con->target, strerror (errno));
       else
         {
-          char *line;
-          /* The lines are being read with read_whole_line because of
+          char *line = NULL;
+          size_t bufsize = 0;
+          ssize_t len;
+
+          /* The lines are being read with getline because of
              no-buffering on opt.lfile.  */
-          while ((line = read_whole_line (fp)) != NULL)
+          while ((len = getline (&line, &bufsize, fp)) > 0)
             {
-              char *p = strchr (line, '\0');
-              while (p > line && (p[-1] == '\n' || p[-1] == '\r'))
-                *--p = '\0';
-              logprintf (LOG_ALWAYS, "%s\n", escnonprint (line));
-              xfree (line);
+              while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r'))
+                line[--len] = '\0';
+              logprintf (LOG_ALWAYS, "%s\n",
+                         quotearg_style (escape_quoting_style, line));
             }
+          xfree (line);
           fclose (fp);
         }
     } /* con->cmd & DO_LIST && server_response */
@@ -1310,15 +1394,20 @@ Error in server response, closing control connection.\n"));
    This loop either gets commands from con, or (if ON_YOUR_OWN is
    set), makes them up to retrieve the file given by the URL.  */
 static uerr_t
-ftp_loop_internal (struct url *u, struct fileinfo *f, ccon *con)
+ftp_loop_internal (struct url *u, struct fileinfo *f, ccon *con, char **local_file)
 {
   int count, orig_lp;
-  wgint restval, len = 0;
+  wgint restval, len = 0, qtyread = 0;
   char *tms, *locf;
   const char *tmrate = NULL;
   uerr_t err;
   struct_stat st;
 
+  /* Declare WARC variables. */
+  bool warc_enabled = (opt.warc_filename != NULL);
+  FILE *warc_tmp = NULL;
+  ip_address *warc_ip = NULL;
+
   /* Get the target, and set the name for the message accordingly. */
   if ((f == NULL) && (con->target))
     {
@@ -1328,17 +1417,24 @@ ftp_loop_internal (struct url *u, struct fileinfo *f, ccon *con)
   else
     {
       /* URL-derived file.  Consider "-O file" name. */
-      con->target = url_file_name (u);
+      con->target = url_file_name (u, NULL);
       if (!opt.output_document)
         locf = con->target;
       else
         locf = opt.output_document;
     }
 
-  if (opt.noclobber && file_exists_p (con->target))
+  /* If the output_document was given, then this check was already done and
+     the file didn't exist. Hence the !opt.output_document */
+
+  /* If we receive .listing file it is necessary to determine system type of the ftp
+     server even if opn.noclobber is given. Thus we must ignore opt.noclobber in
+     order to establish connection with the server and get system type. */
+  if (opt.noclobber && !opt.output_document && file_exists_p (con->target)
+      && !((con->cmd & DO_LIST) && !(con->cmd & DO_RETR)))
     {
       logprintf (LOG_VERBOSE,
-                 _("File `%s' already there; not retrieving.\n"), con->target);
+                 _("File %s already there; not retrieving.\n"), quote (con->target));
       /* If the file is there, we suppose it's retrieved OK.  */
       return RETROK;
     }
@@ -1353,6 +1449,21 @@ ftp_loop_internal (struct url *u, struct fileinfo *f, ccon *con)
 
   orig_lp = con->cmd & LEAVE_PENDING ? 1 : 0;
 
+  /* For file RETR requests, we can write a WARC record.
+     We record the file contents to a temporary file. */
+  if (warc_enabled && (con->cmd & DO_RETR))
+    {
+      warc_tmp = warc_tempfile ();
+      if (warc_tmp == NULL)
+        return WARC_TMP_FOPENERR;
+
+      if (!con->proxy && con->csock != -1)
+        {
+          warc_ip = (ip_address *) alloca (sizeof (ip_address));
+          socket_ip_address (con->csock, warc_ip, ENDPOINT_PEER);
+        }
+    }
+
   /* THE loop.  */
   do
     {
@@ -1391,7 +1502,7 @@ ftp_loop_internal (struct url *u, struct fileinfo *f, ccon *con)
            first attempt to clobber existing data.)  */
         restval = st.st_size;
       else if (count > 1)
-        restval = len;          /* start where the previous run left off */
+        restval = qtyread;          /* start where the previous run left off */
       else
         restval = 0;
 
@@ -1405,19 +1516,22 @@ ftp_loop_internal (struct url *u, struct fileinfo *f, ccon *con)
           strcpy (tmp, "        ");
           if (count > 1)
             sprintf (tmp, _("(try:%2d)"), count);
-          logprintf (LOG_VERBOSE, "--%s--  %s\n  %s => `%s'\n",
-                     tms, hurl, tmp, locf);
+          logprintf (LOG_VERBOSE, "--%s--  %s\n  %s => %s\n",
+                     tms, hurl, tmp, quote (locf));
 #ifdef WINDOWS
           ws_changetitle (hurl);
 #endif
           xfree (hurl);
         }
       /* Send getftp the proper length, if fileinfo was provided.  */
-      if (f)
+      if (f && f->type != FT_SYMLINK)
         len = f->size;
       else
         len = 0;
-      err = getftp (u, &len, restval, con);
+
+      /* If we are working on a WARC record, getftp should also write
+         to the warc_tmp file. */
+      err = getftp (u, len, &qtyread, restval, con, count, warc_tmp);
 
       if (con->csock == -1)
         con->st &= ~DONE_CWD;
@@ -1428,7 +1542,10 @@ ftp_loop_internal (struct url *u, struct fileinfo *f, ccon *con)
         {
         case HOSTERR: case CONIMPOSSIBLE: case FWRITEERR: case FOPENERR:
         case FTPNSFOD: case FTPLOGINC: case FTPNOPASV: case CONTNOTSUPPORTED:
+        case UNLINKERR: case WARC_TMP_FWRITEERR:
           /* Fatal errors, give up.  */
+          if (warc_tmp != NULL)
+            fclose (warc_tmp);
           return err;
         case CONSOCKERR: case CONERROR: case FTPSRVERR: case FTPRERR:
         case WRITEFAILED: case FTPUNKNOWNTYPE: case FTPSYSERR:
@@ -1440,14 +1557,14 @@ ftp_loop_internal (struct url *u, struct fileinfo *f, ccon *con)
             {
               /* Re-determine the file name. */
               xfree_null (con->target);
-              con->target = url_file_name (u);
+              con->target = url_file_name (u, NULL);
               locf = con->target;
             }
           continue;
         case FTPRETRINT:
           /* If the control connection was closed, the retrieval
              will be considered OK if f->size == len.  */
-          if (!f || len != f->size)
+          if (!f || qtyread != f->size)
             {
               printwhat (count, opt.ntry);
               continue;
@@ -1462,7 +1579,7 @@ ftp_loop_internal (struct url *u, struct fileinfo *f, ccon *con)
         }
       tms = datetime_str (time (NULL));
       if (!opt.spider)
-        tmrate = retr_rate (len - restval, con->dltime);
+        tmrate = retr_rate (qtyread - restval, con->dltime);
 
       /* If we get out of the switch above without continue'ing, we've
          successfully downloaded a file.  Remember this fact. */
@@ -1474,8 +1591,17 @@ ftp_loop_internal (struct url *u, struct fileinfo *f, ccon *con)
           con->csock = -1;
         }
       if (!opt.spider)
-        logprintf (LOG_VERBOSE, _("%s (%s) - `%s' saved [%s]\n\n"),
-                   tms, tmrate, locf, number_to_static_string (len));
+        {
+          bool write_to_stdout = (opt.output_document && HYPHENP (opt.output_document));
+
+          logprintf (LOG_VERBOSE,
+                     write_to_stdout
+                     ? _("%s (%s) - written to stdout %s[%s]\n\n")
+                     : _("%s (%s) - %s saved [%s]\n\n"),
+                     tms, tmrate,
+                     write_to_stdout ? "" : quote (locf),
+                     number_to_static_string (qtyread));
+        }
       if (!opt.verbose && !opt.quiet)
         {
           /* Need to hide the password from the URL.  The `if' is here
@@ -1483,10 +1609,23 @@ ftp_loop_internal (struct url *u, struct fileinfo *f, ccon *con)
              time. */
           char *hurl = url_string (u, URL_AUTH_HIDE_PASSWD);
           logprintf (LOG_NONVERBOSE, "%s URL: %s [%s] -> \"%s\" [%d]\n",
-                     tms, hurl, number_to_static_string (len), locf, count);
+                     tms, hurl, number_to_static_string (qtyread), locf, count);
           xfree (hurl);
         }
 
+      if (warc_enabled && (con->cmd & DO_RETR))
+        {
+          /* Create and store a WARC resource record for the retrieved file. */
+          bool warc_res;
+
+          warc_res = warc_write_resource_record (NULL, u->url, NULL, NULL,
+                                                  warc_ip, NULL, warc_tmp, -1);
+          if (! warc_res)
+            return WARC_ERR;
+
+          /* warc_write_resource_record has also closed warc_tmp. */
+        }
+
       if ((con->cmd & DO_LIST))
         /* This is a directory listing file. */
         {
@@ -1494,8 +1633,8 @@ ftp_loop_internal (struct url *u, struct fileinfo *f, ccon *con)
             /* --dont-remove-listing was specified, so do count this towards the
                number of bytes and files downloaded. */
             {
-              total_downloaded_bytes += len;
-              opt.numurls++;
+              total_downloaded_bytes += qtyread;
+              numurls++;
             }
 
           /* Deletion of listing files is not controlled by --delete-after, but
@@ -1509,10 +1648,10 @@ ftp_loop_internal (struct url *u, struct fileinfo *f, ccon *con)
              downloaded if they're going to be deleted.  People seeding proxies,
              for instance, may want to know how many bytes and files they've
              downloaded through it. */
-          total_downloaded_bytes += len;
-          opt.numurls++;
+          total_downloaded_bytes += qtyread;
+          numurls++;
 
-          if (opt.delete_after)
+          if (opt.delete_after && !input_file_url (opt.input_filename))
             {
               DEBUGP (("\
 Removing file due to --delete-after in ftp_loop_internal():\n"));
@@ -1527,6 +1666,10 @@ Removing file due to --delete-after in ftp_loop_internal():\n"));
         con->cmd |= LEAVE_PENDING;
       else
         con->cmd &= ~LEAVE_PENDING;
+
+      if (local_file)
+        *local_file = xstrdup (locf);
+
       return RETROK;
     } while (!opt.ntry || (count < opt.ntry));
 
@@ -1555,26 +1698,31 @@ ftp_get_listing (struct url *u, ccon *con, struct fileinfo **f)
   /* Find the listing file name.  We do it by taking the file name of
      the URL and replacing the last component with the listing file
      name.  */
-  uf = url_file_name (u);
+  uf = url_file_name (u, NULL);
   lf = file_merge (uf, LIST_FILENAME);
   xfree (uf);
-  DEBUGP ((_("Using `%s' as listing tmp file.\n"), lf));
+  DEBUGP ((_("Using %s as listing tmp file.\n"), quote (lf)));
 
-  con->target = lf;
-  err = ftp_loop_internal (u, NULL, con);
+  con->target = xstrdup (lf);
+  xfree (lf);
+  err = ftp_loop_internal (u, NULL, con, NULL);
+  lf = xstrdup (con->target);
+  xfree (con->target);
   con->target = old_target;
 
   if (err == RETROK)
-    *f = ftp_parse_ls (lf, con->rs);
-  else
-    *f = NULL;
-  if (opt.remove_listing)
     {
-      if (unlink (lf))
-        logprintf (LOG_NOTQUIET, "unlink: %s\n", strerror (errno));
-      else
-        logprintf (LOG_VERBOSE, _("Removed `%s'.\n"), lf);
+      *f = ftp_parse_ls (lf, con->rs);
+      if (opt.remove_listing)
+        {
+          if (unlink (lf))
+            logprintf (LOG_NOTQUIET, "unlink: %s\n", strerror (errno));
+          else
+            logprintf (LOG_VERBOSE, _("Removed %s.\n"), quote (lf));
+        }
     }
+  else
+    *f = NULL;
   xfree (lf);
   con->cmd &= ~DO_LIST;
   return err;
@@ -1644,7 +1792,7 @@ ftp_retrieve_list (struct url *u, struct fileinfo *f, ccon *con)
       ofile = xstrdup (u->file);
       url_set_file (u, f->name);
 
-      con->target = url_file_name (u);
+      con->target = url_file_name (u, NULL);
       err = RETROK;
 
       dlthis = true;
@@ -1678,15 +1826,15 @@ ftp_retrieve_list (struct url *u, struct fileinfo *f, ccon *con)
                   /* Remote file is older, file sizes can be compared and
                      are both equal. */
                   logprintf (LOG_VERBOSE, _("\
-Remote file no newer than local file `%s' -- not retrieving.\n"), con->target);
+Remote file no newer than local file %s -- not retrieving.\n"), quote (con->target));
                   dlthis = false;
                 }
               else if (eq_size)
                 {
                   /* Remote file is newer or sizes cannot be matched */
                   logprintf (LOG_VERBOSE, _("\
-Remote file is newer than local file `%s' -- retrieving.\n\n"),
-                             con->target);
+Remote file is newer than local file %s -- retrieving.\n\n"),
+                             quote (con->target));
                 }
               else
                 {
@@ -1728,14 +1876,15 @@ The sizes do not match (local %s) -- retrieving.\n\n"),
                             {
                               logprintf (LOG_VERBOSE, _("\
 Already have correct symlink %s -> %s\n\n"),
-                                         con->target, escnonprint (f->linkto));
+                                         quote (con->target),
+                                         quote (f->linkto));
                               dlthis = false;
                               break;
                             }
                         }
                     }
                   logprintf (LOG_VERBOSE, _("Creating symlink %s -> %s\n"),
-                             con->target, escnonprint (f->linkto));
+                             quote (con->target), quote (f->linkto));
                   /* Unlink before creating symlink!  */
                   unlink (con->target);
                   if (symlink (f->linkto, con->target) == -1)
@@ -1744,29 +1893,29 @@ Already have correct symlink %s -> %s\n\n"),
                 } /* have f->linkto */
 #else  /* not HAVE_SYMLINK */
               logprintf (LOG_NOTQUIET,
-                         _("Symlinks not supported, skipping symlink `%s'.\n"),
-                         con->target);
+                         _("Symlinks not supported, skipping symlink %s.\n"),
+                         quote (con->target));
 #endif /* not HAVE_SYMLINK */
             }
           else                /* opt.retr_symlinks */
             {
               if (dlthis)
-                err = ftp_loop_internal (u, f, con);
+                err = ftp_loop_internal (u, f, con, NULL);
             } /* opt.retr_symlinks */
           break;
         case FT_DIRECTORY:
           if (!opt.recursive)
-            logprintf (LOG_NOTQUIET, _("Skipping directory `%s'.\n"),
-                       escnonprint (f->name));
+            logprintf (LOG_NOTQUIET, _("Skipping directory %s.\n"),
+                       quote (f->name));
           break;
         case FT_PLAINFILE:
           /* Call the retrieve loop.  */
           if (dlthis)
-            err = ftp_loop_internal (u, f, con);
+            err = ftp_loop_internal (u, f, con, NULL);
           break;
         case FT_UNKNOWN:
           logprintf (LOG_NOTQUIET, _("%s: unknown/unsupported file type.\n"),
-                     escnonprint (f->name));
+                     quote (f->name));
           break;
         }       /* switch */
 
@@ -1778,19 +1927,12 @@ Already have correct symlink %s -> %s\n\n"),
        * appropriate.  (Do the test once, and save the result.)
        */
 
-      /* #### This code repeats in http.c and ftp.c.  Move it to a
-         function!  */
-      actual_target = NULL;
-      if (opt.output_document)
-        {
-          if (output_stream_regular)
-            actual_target = opt.output_document;
-        }
-      else
-        actual_target = con->target;
+      set_local_file (&actual_target, con->target);
 
-      /* If downloading a plain file, set valid (non-zero) permissions. */
-      if (dlthis && (actual_target != NULL) && (f->type == FT_PLAINFILE))
+      /* If downloading a plain file, and the user requested it, then
+         set valid (non-zero) permissions. */
+      if (dlthis && (actual_target != NULL) &&
+       (f->type == FT_PLAINFILE) && opt.preserve_perm)
         {
           if (f->perms)
             chmod (actual_target, f->perms);
@@ -1801,10 +1943,10 @@ Already have correct symlink %s -> %s\n\n"),
       /* Set the time-stamp information to the local file.  Symlinks
          are not to be stamped because it sets the stamp on the
          original.  :( */
-
       if (actual_target != NULL)
         {
-          if (!(f->type == FT_SYMLINK && !opt.retr_symlinks)
+          if (opt.useservertimestamps
+              && !(f->type == FT_SYMLINK && !opt.retr_symlinks)
               && f->tstamp != -1
               && dlthis
               && file_exists_p (con->target))
@@ -1813,7 +1955,7 @@ Already have correct symlink %s -> %s\n\n"),
             }
           else if (f->tstamp == -1)
             logprintf (LOG_NOTQUIET, _("%s: corrupt time-stamp.\n"),
-            actual_target);
+                       actual_target);
         }
 
       xfree (con->target);
@@ -1823,7 +1965,9 @@ Already have correct symlink %s -> %s\n\n"),
       xfree (ofile);
 
       /* Break on fatals.  */
-      if (err == QUOTEXC || err == HOSTERR || err == FWRITEERR)
+      if (err == QUOTEXC || err == HOSTERR || err == FWRITEERR
+          || err == WARC_ERR || err == WARC_TMP_FOPENERR
+          || err == WARC_TMP_FWRITEERR)
         break;
       con->cmd &= ~ (DO_CWD | DO_LOGIN);
       f = f->next;
@@ -1885,8 +2029,8 @@ ftp_retrieve_dirs (struct url *u, struct fileinfo *f, ccon *con)
       if (!accdir (newdir))
         {
           logprintf (LOG_VERBOSE, _("\
-Not descending to `%s' as it is excluded/not-included.\n"),
-                     escnonprint (newdir));
+Not descending to %s as it is excluded/not-included.\n"),
+                     quote (newdir));
           continue;
         }
 
@@ -1950,8 +2094,8 @@ ftp_retrieve_glob (struct url *u, ccon *con, int action)
         {
           if (f->type != FT_DIRECTORY && !acceptable (f->name))
             {
-              logprintf (LOG_VERBOSE, _("Rejecting `%s'.\n"),
-                         escnonprint (f->name));
+              logprintf (LOG_VERBOSE, _("Rejecting %s.\n"),
+                         quote (f->name));
               f = delelement (f, &start);
             }
           else
@@ -1964,8 +2108,8 @@ ftp_retrieve_glob (struct url *u, ccon *con, int action)
     {
       if (has_insecure_name_p (f->name))
         {
-          logprintf (LOG_VERBOSE, _("Rejecting `%s'.\n"),
-                     escnonprint (f->name));
+          logprintf (LOG_VERBOSE, _("Rejecting %s.\n"),
+                     quote (f->name));
           f = delelement (f, &start);
         }
       else
@@ -1988,7 +2132,8 @@ ftp_retrieve_glob (struct url *u, ccon *con, int action)
               if (matchres == -1)
                 {
                   logprintf (LOG_NOTQUIET, _("Error matching %s against %s: %s\n"),
-                             u->file, escnonprint (f->name), strerror (errno));
+                             u->file, quotearg_style (escape_quoting_style, f->name),
+                             strerror (errno));
                   break;
                 }
               if (matchres == FNM_NOMATCH)
@@ -2004,8 +2149,22 @@ ftp_retrieve_glob (struct url *u, ccon *con, int action)
         }
       else if (action == GLOB_GETONE)
         {
+#ifdef __VMS
+          /* 2009-09-09 SMS.
+           * Odd-ball compiler ("HP C V7.3-009 on OpenVMS Alpha V7.3-2")
+           * bug causes spurious %CC-E-BADCONDIT complaint with this
+           * "?:" statement.  (Different linkage attributes for strcmp()
+           * and strcasecmp().)  Converting to "if" changes the
+           * complaint to %CC-W-PTRMISMATCH on "cmp = strcmp;".  Adding
+           * the senseless type cast clears the complaint, and looks
+           * harmless.
+           */
+          int (*cmp) (const char *, const char *)
+            = opt.ignore_case ? strcasecmp : (int (*)())strcmp;
+#else /* def __VMS */
           int (*cmp) (const char *, const char *)
             = opt.ignore_case ? strcasecmp : strcmp;
+#endif /* def __VMS [else] */
           f = start;
           while (f)
             {
@@ -2019,7 +2178,7 @@ ftp_retrieve_glob (struct url *u, ccon *con, int action)
   if (start)
     {
       /* Just get everything.  */
-      ftp_retrieve_list (u, start, con);
+      res = ftp_retrieve_list (u, start, con);
     }
   else
     {
@@ -2028,14 +2187,14 @@ ftp_retrieve_glob (struct url *u, ccon *con, int action)
           /* No luck.  */
           /* #### This message SUCKS.  We should see what was the
              reason that nothing was retrieved.  */
-          logprintf (LOG_VERBOSE, _("No matches on pattern `%s'.\n"),
-                     escnonprint (u->file));
+          logprintf (LOG_VERBOSE, _("No matches on pattern %s.\n"),
+                     quote (u->file));
         }
       else if (action == GLOB_GETONE) /* GLOB_GETONE or GLOB_GETALL */
         {
           /* Let's try retrieving it anyway.  */
           con->st |= ON_YOUR_OWN;
-          res = ftp_loop_internal (u, NULL, con);
+          res = ftp_loop_internal (u, NULL, con, NULL);
           return res;
         }
 
@@ -2048,15 +2207,15 @@ ftp_retrieve_glob (struct url *u, ccon *con, int action)
   if (opt.quota && total_downloaded_bytes > opt.quota)
     return QUOTEXC;
   else
-    /* #### Should we return `res' here?  */
-    return RETROK;
+    return res;
 }
 
 /* The wrapper that calls an appropriate routine according to contents
    of URL.  Inherently, its capabilities are limited on what can be
    encoded into a URL.  */
 uerr_t
-ftp_loop (struct url *u, int *dt, struct url *proxy, bool recursive, bool glob)
+ftp_loop (struct url *u, char **local_file, int *dt, struct url *proxy,
+          bool recursive, bool glob)
 {
   ccon con;                     /* FTP connection */
   uerr_t res;
@@ -2086,7 +2245,7 @@ ftp_loop (struct url *u, int *dt, struct url *proxy, bool recursive, bool glob)
               char *filename = (opt.output_document
                                 ? xstrdup (opt.output_document)
                                 : (con.target ? xstrdup (con.target)
-                                   : url_file_name (u)));
+                                   : url_file_name (u, NULL)));
               res = ftp_index (filename, u, f);
               if (res == FTPOK && opt.verbose)
                 {
@@ -2099,13 +2258,13 @@ ftp_loop (struct url *u, int *dt, struct url *proxy, bool recursive, bool glob)
                       else
                         sz = -1;
                       logprintf (LOG_NOTQUIET,
-                                 _("Wrote HTML-ized index to `%s' [%s].\n"),
-                                 filename, number_to_static_string (sz));
+                                 _("Wrote HTML-ized index to %s [%s].\n"),
+                                 quote (filename), number_to_static_string (sz));
                     }
                   else
                     logprintf (LOG_NOTQUIET,
-                               _("Wrote HTML-ized index to `%s'.\n"),
-                               filename);
+                               _("Wrote HTML-ized index to %s.\n"),
+                               quote (filename));
                 }
               xfree (filename);
             }
@@ -2135,7 +2294,7 @@ ftp_loop (struct url *u, int *dt, struct url *proxy, bool recursive, bool glob)
                                    ispattern ? GLOB_GLOBALL : GLOB_GETONE);
         }
       else
-        res = ftp_loop_internal (u, NULL, &con);
+        res = ftp_loop_internal (u, NULL, &con, local_file);
     }
   if (res == FTPOK)
     res = RETROK;