smtp: use the upload buffer size for scratch buffer malloc
[platform/upstream/curl.git] / lib / file.c
index 9aae1e4..db04cc2 100644 (file)
@@ -5,11 +5,11 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2009, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2018, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
- * are also available at http://curl.haxx.se/docs/copyright.html.
+ * are also available at https://curl.haxx.se/docs/copyright.html.
  *
  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
  * copies of the Software, and permit persons to whom the Software is
  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
  * KIND, either express or implied.
  *
- * $Id$
  ***************************************************************************/
 
-#include "setup.h"
+#include "curl_setup.h"
 
 #ifndef CURL_DISABLE_FILE
-/* -- WIN32 approved -- */
-#include <stdio.h>
-#include <string.h>
-#include <stdarg.h>
-#include <stdlib.h>
-#include <ctype.h>
-
-#ifdef WIN32
-#include <time.h>
-#include <io.h>
-#include <fcntl.h>
-#else
-#ifdef HAVE_SYS_SOCKET_H
-#include <sys/socket.h>
-#endif
+
 #ifdef HAVE_NETINET_IN_H
 #include <netinet/in.h>
 #endif
-#ifdef HAVE_SYS_TIME_H
-#include <sys/time.h>
-#endif
-#ifdef HAVE_UNISTD_H
-#include <unistd.h>
-#endif
 #ifdef HAVE_NETDB_H
 #include <netdb.h>
 #endif
@@ -69,8 +48,6 @@
 #include <fcntl.h>
 #endif
 
-#endif /* WIN32 */
-
 #include "strtoofft.h"
 #include "urldata.h"
 #include <curl/curl.h>
 #include "getinfo.h"
 #include "transfer.h"
 #include "url.h"
-#include "memory.h"
 #include "parsedate.h" /* for the week day and month names */
-
-#define _MPRINTF_REPLACE /* use our functions only */
-#include <curl/mprintf.h>
-
-/* The last #include file should be: */
+#include "warnless.h"
+#include "curl_range.h"
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
 #include "memdebug.h"
 
-#if defined(WIN32) || defined(MSDOS) || defined(__EMX__) || defined(__SYMBIAN32__)
+#if defined(WIN32) || defined(MSDOS) || defined(__EMX__) || \
+  defined(__SYMBIAN32__)
 #define DOS_FILESYSTEM 1
 #endif
 
+#ifdef OPEN_NEEDS_ARG3
+#  define open_readonly(p,f) open((p),(f),(0))
+#else
+#  define open_readonly(p,f) open((p),(f))
+#endif
+
 /*
  * Forward declarations.
  */
@@ -103,6 +86,9 @@ static CURLcode file_do(struct connectdata *, bool *done);
 static CURLcode file_done(struct connectdata *conn,
                           CURLcode status, bool premature);
 static CURLcode file_connect(struct connectdata *conn, bool *done);
+static CURLcode file_disconnect(struct connectdata *conn,
+                                bool dead_connection);
+static CURLcode file_setup_connection(struct connectdata *conn);
 
 /*
  * FILE scheme handler.
@@ -110,7 +96,7 @@ static CURLcode file_connect(struct connectdata *conn, bool *done);
 
 const struct Curl_handler Curl_handler_file = {
   "FILE",                               /* scheme */
-  ZERO_NULL,                            /* setup_connection */
+  file_setup_connection,                /* setup_connection */
   file_do,                              /* do_it */
   file_done,                            /* done */
   ZERO_NULL,                            /* do_more */
@@ -119,64 +105,24 @@ const struct Curl_handler Curl_handler_file = {
   ZERO_NULL,                            /* doing */
   ZERO_NULL,                            /* proto_getsock */
   ZERO_NULL,                            /* doing_getsock */
+  ZERO_NULL,                            /* domore_getsock */
   ZERO_NULL,                            /* perform_getsock */
-  ZERO_NULL,                            /* disconnect */
+  file_disconnect,                      /* disconnect */
+  ZERO_NULL,                            /* readwrite */
+  ZERO_NULL,                            /* connection_check */
   0,                                    /* defport */
-  PROT_FILE                             /* protocol */
+  CURLPROTO_FILE,                       /* protocol */
+  PROTOPT_NONETWORK | PROTOPT_NOURLQUERY /* flags */
 };
 
 
- /*
-  Check if this is a range download, and if so, set the internal variables
-  properly. This code is copied from the FTP implementation and might as
-  well be factored out.
- */
-static CURLcode file_range(struct connectdata *conn)
+static CURLcode file_setup_connection(struct connectdata *conn)
 {
-  curl_off_t from, to;
-  curl_off_t totalsize=-1;
-  char *ptr;
-  char *ptr2;
-  struct SessionHandle *data = conn->data;
-
-  if(data->state.use_range && data->state.range) {
-    from=curlx_strtoofft(data->state.range, &ptr, 0);
-    while(ptr && *ptr && (isspace((int)*ptr) || (*ptr=='-')))
-      ptr++;
-    to=curlx_strtoofft(ptr, &ptr2, 0);
-    if(ptr == ptr2) {
-      /* we didn't get any digit */
-      to=-1;
-    }
-    if((-1 == to) && (from>=0)) {
-      /* X - */
-      data->state.resume_from = from;
-      DEBUGF(infof(data, "RANGE %" FORMAT_OFF_T " to end of file\n",
-                   from));
-    }
-    else if(from < 0) {
-      /* -Y */
-      totalsize = -from;
-      data->req.maxdownload = -from;
-      data->state.resume_from = from;
-      DEBUGF(infof(data, "RANGE the last %" FORMAT_OFF_T " bytes\n",
-                   totalsize));
-    }
-    else {
-      /* X-Y */
-      totalsize = to-from;
-      data->req.maxdownload = totalsize+1; /* include last byte */
-      data->state.resume_from = from;
-      DEBUGF(infof(data, "RANGE from %" FORMAT_OFF_T
-                   " getting %" FORMAT_OFF_T " bytes\n",
-                   from, data->req.maxdownload));
-    }
-    DEBUGF(infof(data, "range-download from %" FORMAT_OFF_T
-                 " to %" FORMAT_OFF_T ", totally %" FORMAT_OFF_T " bytes\n",
-                 from, to, data->req.maxdownload));
-  }
-  else
-    data->req.maxdownload = -1;
+  /* allocate the FILE specific struct */
+  conn->data->req.protop = calloc(1, sizeof(struct FILEPROTO));
+  if(!conn->data->req.protop)
+    return CURLE_OUT_OF_MEMORY;
+
   return CURLE_OK;
 }
 
@@ -187,40 +133,20 @@ static CURLcode file_range(struct connectdata *conn)
  */
 static CURLcode file_connect(struct connectdata *conn, bool *done)
 {
-  struct SessionHandle *data = conn->data;
-  char *real_path = curl_easy_unescape(data, data->state.path, 0, NULL);
-  struct FILEPROTO *file;
+  struct Curl_easy *data = conn->data;
+  char *real_path;
+  struct FILEPROTO *file = data->req.protop;
   int fd;
 #ifdef DOS_FILESYSTEM
-  int i;
+  size_t i;
   char *actual_path;
 #endif
+  size_t real_path_len;
 
-  if(!real_path)
-    return CURLE_OUT_OF_MEMORY;
-
-  /* If there already is a protocol-specific struct allocated for this
-     sessionhandle, deal with it */
-  Curl_reset_reqproto(conn);
-
-  if(!data->state.proto.file) {
-    file = calloc(sizeof(struct FILEPROTO), 1);
-    if(!file) {
-      free(real_path);
-      return CURLE_OUT_OF_MEMORY;
-    }
-    data->state.proto.file = file;
-  }
-  else {
-    /* file is not a protocol that can deal with "persistancy" */
-    file = data->state.proto.file;
-    Curl_safefree(file->freepath);
-    if(file->fd != -1)
-      close(file->fd);
-    file->path = NULL;
-    file->freepath = NULL;
-    file->fd = -1;
-  }
+  CURLcode result = Curl_urldecode(data, data->state.path, 0, &real_path,
+                                   &real_path_len, FALSE);
+  if(result)
+    return result;
 
 #ifdef DOS_FILESYSTEM
   /* If the first character is a slash, and there's
@@ -240,21 +166,31 @@ static CURLcode file_connect(struct connectdata *conn, bool *done)
   actual_path = real_path;
   if((actual_path[0] == '/') &&
       actual_path[1] &&
-      (actual_path[2] == ':' || actual_path[2] == '|'))
-  {
+     (actual_path[2] == ':' || actual_path[2] == '|')) {
     actual_path[2] = ':';
     actual_path++;
+    real_path_len--;
   }
 
   /* change path separators from '/' to '\\' for DOS, Windows and OS/2 */
-  for (i=0; actual_path[i] != '\0'; ++i)
+  for(i = 0; i < real_path_len; ++i)
     if(actual_path[i] == '/')
       actual_path[i] = '\\';
+    else if(!actual_path[i]) { /* binary zero */
+      Curl_safefree(real_path);
+      return CURLE_URL_MALFORMAT;
+    }
 
-  fd = open(actual_path, O_RDONLY | O_BINARY);  /* no CR/LF translation! */
+  fd = open_readonly(actual_path, O_RDONLY|O_BINARY);
   file->path = actual_path;
 #else
-  fd = open(real_path, O_RDONLY);
+  if(memchr(real_path, 0, real_path_len)) {
+    /* binary zeroes indicate foul play */
+    Curl_safefree(real_path);
+    return CURLE_URL_MALFORMAT;
+  }
+
+  fd = open_readonly(real_path, O_RDONLY);
   file->path = real_path;
 #endif
   file->freepath = real_path; /* free this when done */
@@ -273,13 +209,34 @@ static CURLcode file_connect(struct connectdata *conn, bool *done)
 static CURLcode file_done(struct connectdata *conn,
                                CURLcode status, bool premature)
 {
-  struct FILEPROTO *file = conn->data->state.proto.file;
+  struct FILEPROTO *file = conn->data->req.protop;
   (void)status; /* not used */
   (void)premature; /* not used */
-  Curl_safefree(file->freepath);
 
-  if(file->fd != -1)
-    close(file->fd);
+  if(file) {
+    Curl_safefree(file->freepath);
+    file->path = NULL;
+    if(file->fd != -1)
+      close(file->fd);
+    file->fd = -1;
+  }
+
+  return CURLE_OK;
+}
+
+static CURLcode file_disconnect(struct connectdata *conn,
+                                bool dead_connection)
+{
+  struct FILEPROTO *file = conn->data->req.protop;
+  (void)dead_connection; /* not used */
+
+  if(file) {
+    Curl_safefree(file->freepath);
+    file->path = NULL;
+    if(file->fd != -1)
+      close(file->fd);
+    file->fd = -1;
+  }
 
   return CURLE_OK;
 }
@@ -292,77 +249,66 @@ static CURLcode file_done(struct connectdata *conn,
 
 static CURLcode file_upload(struct connectdata *conn)
 {
-  struct FILEPROTO *file = conn->data->state.proto.file;
+  struct FILEPROTO *file = conn->data->req.protop;
   const char *dir = strchr(file->path, DIRSEP);
-  FILE *fp;
-  CURLcode res=CURLE_OK;
-  struct SessionHandle *data = conn->data;
+  int fd;
+  int mode;
+  CURLcode result = CURLE_OK;
+  struct Curl_easy *data = conn->data;
   char *buf = data->state.buffer;
   size_t nread;
   size_t nwrite;
   curl_off_t bytecount = 0;
-  struct timeval now = Curl_tvnow();
   struct_stat file_stat;
-  const charbuf2;
+  const char *buf2;
 
   /*
    * Since FILE: doesn't do the full init, we need to provide some extra
    * assignments here.
    */
-  conn->fread_func = data->set.fread_func;
-  conn->fread_in = data->set.in;
   conn->data->req.upload_fromhere = buf;
 
   if(!dir)
     return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
 
   if(!dir[1])
-     return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
-
-  if(data->state.resume_from)
-    fp = fopen( file->path, "ab" );
-  else {
-    int fd;
+    return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
 
-#ifdef DOS_FILESYSTEM
-    fd = open(file->path, O_WRONLY|O_CREAT|O_TRUNC|O_BINARY,
-              conn->data->set.new_file_perms);
+#ifdef O_BINARY
+#define MODE_DEFAULT O_WRONLY|O_CREAT|O_BINARY
 #else
-    fd = open(file->path, O_WRONLY|O_CREAT|O_TRUNC,
-              conn->data->set.new_file_perms);
+#define MODE_DEFAULT O_WRONLY|O_CREAT
 #endif
-    if(fd < 0) {
-      failf(data, "Can't open %s for writing", file->path);
-      return CURLE_WRITE_ERROR;
-    }
-    close(fd);
-    fp = fopen(file->path, "wb");
-  }
 
-  if(!fp) {
+  if(data->state.resume_from)
+    mode = MODE_DEFAULT|O_APPEND;
+  else
+    mode = MODE_DEFAULT|O_TRUNC;
+
+  fd = open(file->path, mode, conn->data->set.new_file_perms);
+  if(fd < 0) {
     failf(data, "Can't open %s for writing", file->path);
     return CURLE_WRITE_ERROR;
   }
 
-  if(-1 != data->set.infilesize)
+  if(-1 != data->state.infilesize)
     /* known size of data to "upload" */
-    Curl_pgrsSetUploadSize(data, data->set.infilesize);
+    Curl_pgrsSetUploadSize(data, data->state.infilesize);
 
   /* treat the negative resume offset value as the case of "-" */
   if(data->state.resume_from < 0) {
-    if(fstat(fileno(fp), &file_stat)) {
-      fclose(fp);
+    if(fstat(fd, &file_stat)) {
+      close(fd);
       failf(data, "Can't get the size of %s", file->path);
       return CURLE_WRITE_ERROR;
     }
-    else
-      data->state.resume_from = (curl_off_t)file_stat.st_size;
+    data->state.resume_from = (curl_off_t)file_stat.st_size;
   }
 
-  while(res == CURLE_OK) {
+  while(!result) {
     int readcount;
-    res = Curl_fillreadbuffer(conn, BUFSIZE, &readcount);
-    if(res)
+    result = Curl_fillreadbuffer(conn, (int)data->set.buffer_size, &readcount);
+    if(result)
       break;
 
     if(readcount <= 0)  /* fix questionable compare error. curlvms */
@@ -372,7 +318,7 @@ static CURLcode file_upload(struct connectdata *conn)
 
     /*skip bytes before resume point*/
     if(data->state.resume_from) {
-      if( (curl_off_t)nread <= data->state.resume_from ) {
+      if((curl_off_t)nread <= data->state.resume_from) {
         data->state.resume_from -= nread;
         nread = 0;
         buf2 = buf;
@@ -387,9 +333,9 @@ static CURLcode file_upload(struct connectdata *conn)
       buf2 = buf;
 
     /* write the data to the target */
-    nwrite = fwrite(buf2, 1, nread, fp);
+    nwrite = write(fd, buf2, nread);
     if(nwrite != nread) {
-      res = CURLE_SEND_ERROR;
+      result = CURLE_SEND_ERROR;
       break;
     }
 
@@ -398,16 +344,16 @@ static CURLcode file_upload(struct connectdata *conn)
     Curl_pgrsSetUploadCounter(data, bytecount);
 
     if(Curl_pgrsUpdate(conn))
-      res = CURLE_ABORTED_BY_CALLBACK;
+      result = CURLE_ABORTED_BY_CALLBACK;
     else
-      res = Curl_speedcheck(data, now);
+      result = Curl_speedcheck(data, Curl_now());
   }
-  if(!res && Curl_pgrsUpdate(conn))
-    res = CURLE_ABORTED_BY_CALLBACK;
+  if(!result && Curl_pgrsUpdate(conn))
+    result = CURLE_ABORTED_BY_CALLBACK;
 
-  fclose(fp);
+  close(fd);
 
-  return res;
+  return result;
 }
 
 /*
@@ -425,19 +371,19 @@ static CURLcode file_do(struct connectdata *conn, bool *done)
      are supported. This means that files on remotely mounted directories
      (via NFS, Samba, NT sharing) can be accessed through a file:// URL
   */
-  CURLcode res = CURLE_OK;
+  CURLcode result = CURLE_OK;
   struct_stat statbuf; /* struct_stat instead of struct stat just to allow the
                           Windows version to have a different struct without
                           having to redefine the simple word 'stat' */
-  curl_off_t expected_size=0;
-  bool fstated=FALSE;
+  curl_off_t expected_size = 0;
+  bool size_known;
+  bool fstated = FALSE;
   ssize_t nread;
-  size_t bytestoread;
-  struct SessionHandle *data = conn->data;
+  struct Curl_easy *data = conn->data;
   char *buf = data->state.buffer;
   curl_off_t bytecount = 0;
   int fd;
-  struct timeval now = Curl_tvnow();
+  struct FILEPROTO *file;
 
   *done = TRUE; /* unconditionally */
 
@@ -447,26 +393,38 @@ static CURLcode file_do(struct connectdata *conn, bool *done)
   if(data->set.upload)
     return file_upload(conn);
 
+  file = conn->data->req.protop;
+
   /* get the fd from the connection phase */
-  fd = conn->data->state.proto.file->fd;
+  fd = file->fd;
 
   /* VMS: This only works reliable for STREAMLF files */
-  if( -1 != fstat(fd, &statbuf)) {
+  if(-1 != fstat(fd, &statbuf)) {
     /* we could stat it, then read out the size */
     expected_size = statbuf.st_size;
     /* and store the modification time */
-    data->info.filetime = (long)statbuf.st_mtime;
+    data->info.filetime = statbuf.st_mtime;
     fstated = TRUE;
   }
 
+  if(fstated && !data->state.range && data->set.timecondition) {
+    if(!Curl_meets_timecondition(data, data->info.filetime)) {
+      *done = TRUE;
+      return CURLE_OK;
+    }
+  }
+
   /* If we have selected NOBODY and HEADER, it means that we only want file
      information. Which for FILE can't be much more than the file size and
      date. */
   if(data->set.opt_no_body && data->set.include_header && fstated) {
-    CURLcode result;
-    snprintf(buf, sizeof(data->state.buffer),
-             "Content-Length: %" FORMAT_OFF_T "\r\n", expected_size);
-    result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0);
+    time_t filetime;
+    struct tm buffer;
+    const struct tm *tm = &buffer;
+    char header[80];
+    snprintf(header, sizeof(header),
+             "Content-Length: %" CURL_FORMAT_CURL_OFF_T "\r\n", expected_size);
+    result = Curl_client_write(conn, CLIENTWRITE_BOTH, header, 0);
     if(result)
       return result;
 
@@ -475,36 +433,32 @@ static CURLcode file_do(struct connectdata *conn, bool *done)
     if(result)
       return result;
 
-    if(fstated) {
-      const struct tm *tm;
-      time_t filetime = (time_t)statbuf.st_mtime;
-#ifdef HAVE_GMTIME_R
-      struct tm buffer;
-      tm = (const struct tm *)gmtime_r(&filetime, &buffer);
-#else
-      tm = gmtime(&filetime);
-#endif
-      /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */
-      snprintf(buf, BUFSIZE-1,
-               "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n",
-               Curl_wkday[tm->tm_wday?tm->tm_wday-1:6],
-               tm->tm_mday,
-               Curl_month[tm->tm_mon],
-               tm->tm_year + 1900,
-               tm->tm_hour,
-               tm->tm_min,
-               tm->tm_sec);
-      result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0);
-    }
-    /* if we fstat()ed the file, set the file size to make it available post-
-       transfer */
-    if(fstated)
+    filetime = (time_t)statbuf.st_mtime;
+    result = Curl_gmtime(filetime, &buffer);
+    if(result)
+      return result;
+
+    /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */
+    snprintf(header, sizeof(header),
+             "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n",
+             Curl_wkday[tm->tm_wday?tm->tm_wday-1:6],
+             tm->tm_mday,
+             Curl_month[tm->tm_mon],
+             tm->tm_year + 1900,
+             tm->tm_hour,
+             tm->tm_min,
+             tm->tm_sec);
+    result = Curl_client_write(conn, CLIENTWRITE_BOTH, header, 0);
+    if(!result)
+      /* set the file size to make it available post transfer */
       Curl_pgrsSetDownloadSize(data, expected_size);
     return result;
   }
 
   /* Check whether file range has been specified */
-  file_range(conn);
+  result = Curl_range(conn);
+  if(result)
+    return result;
 
   /* Adjust the start offset in case we want to get the N last bytes
    * of the stream iff the filesize could be determined */
@@ -513,8 +467,7 @@ static CURLcode file_do(struct connectdata *conn, bool *done)
       failf(data, "Can't get the size of file.");
       return CURLE_READ_ERROR;
     }
-    else
-      data->state.resume_from += (curl_off_t)statbuf.st_size;
+    data->state.resume_from += (curl_off_t)statbuf.st_size;
   }
 
   if(data->state.resume_from <= expected_size)
@@ -525,11 +478,13 @@ static CURLcode file_do(struct connectdata *conn, bool *done)
   }
 
   /* A high water mark has been specified so we obey... */
-  if (data->req.maxdownload > 0)
+  if(data->req.maxdownload > 0)
     expected_size = data->req.maxdownload;
 
-  if(fstated && (expected_size == 0))
-    return CURLE_OK;
+  if(!fstated || (expected_size == 0))
+    size_known = FALSE;
+  else
+    size_known = TRUE;
 
   /* The following is a shortcut implementation of file reading
      this is both more efficient than the former call to download() and
@@ -546,35 +501,44 @@ static CURLcode file_do(struct connectdata *conn, bool *done)
 
   Curl_pgrsTime(data, TIMER_STARTTRANSFER);
 
-  while(res == CURLE_OK) {
+  while(!result) {
     /* Don't fill a whole buffer if we want less than all data */
-    bytestoread = (expected_size < BUFSIZE-1)?(size_t)expected_size:BUFSIZE-1;
+    size_t bytestoread;
+
+    if(size_known) {
+      bytestoread = (expected_size < data->set.buffer_size) ?
+        curlx_sotouz(expected_size) : (size_t)data->set.buffer_size;
+    }
+    else
+      bytestoread = data->set.buffer_size-1;
+
     nread = read(fd, buf, bytestoread);
 
-    if( nread > 0)
+    if(nread > 0)
       buf[nread] = 0;
 
-    if (nread <= 0 || expected_size == 0)
+    if(nread <= 0 || (size_known && (expected_size == 0)))
       break;
 
     bytecount += nread;
-    expected_size -= nread;
+    if(size_known)
+      expected_size -= nread;
 
-    res = Curl_client_write(conn, CLIENTWRITE_BODY, buf, nread);
-    if(res)
-      return res;
+    result = Curl_client_write(conn, CLIENTWRITE_BODY, buf, nread);
+    if(result)
+      return result;
 
     Curl_pgrsSetDownloadCounter(data, bytecount);
 
     if(Curl_pgrsUpdate(conn))
-      res = CURLE_ABORTED_BY_CALLBACK;
+      result = CURLE_ABORTED_BY_CALLBACK;
     else
-      res = Curl_speedcheck(data, now);
+      result = Curl_speedcheck(data, Curl_now());
   }
   if(Curl_pgrsUpdate(conn))
-    res = CURLE_ABORTED_BY_CALLBACK;
+    result = CURLE_ABORTED_BY_CALLBACK;
 
-  return res;
+  return result;
 }
 
 #endif