Home | History | Annotate | Download | only in lib
      1 /***************************************************************************
      2  *                                  _   _ ____  _
      3  *  Project                     ___| | | |  _ \| |
      4  *                             / __| | | | |_) | |
      5  *                            | (__| |_| |  _ <| |___
      6  *                             \___|\___/|_| \_\_____|
      7  *
      8  * Copyright (C) 1998 - 2017, Daniel Stenberg, <daniel (at) haxx.se>, et al.
      9  *
     10  * This software is licensed as described in the file COPYING, which
     11  * you should have received as part of this distribution. The terms
     12  * are also available at https://curl.haxx.se/docs/copyright.html.
     13  *
     14  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
     15  * copies of the Software, and permit persons to whom the Software is
     16  * furnished to do so, under the terms of the COPYING file.
     17  *
     18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
     19  * KIND, either express or implied.
     20  *
     21  ***************************************************************************/
     22 
     23 #include "curl_setup.h"
     24 
     25 #ifndef CURL_DISABLE_FILE
     26 
     27 #ifdef HAVE_NETINET_IN_H
     28 #include <netinet/in.h>
     29 #endif
     30 #ifdef HAVE_NETDB_H
     31 #include <netdb.h>
     32 #endif
     33 #ifdef HAVE_ARPA_INET_H
     34 #include <arpa/inet.h>
     35 #endif
     36 #ifdef HAVE_NET_IF_H
     37 #include <net/if.h>
     38 #endif
     39 #ifdef HAVE_SYS_IOCTL_H
     40 #include <sys/ioctl.h>
     41 #endif
     42 
     43 #ifdef HAVE_SYS_PARAM_H
     44 #include <sys/param.h>
     45 #endif
     46 
     47 #ifdef HAVE_FCNTL_H
     48 #include <fcntl.h>
     49 #endif
     50 
     51 #include "strtoofft.h"
     52 #include "urldata.h"
     53 #include <curl/curl.h>
     54 #include "progress.h"
     55 #include "sendf.h"
     56 #include "escape.h"
     57 #include "file.h"
     58 #include "speedcheck.h"
     59 #include "getinfo.h"
     60 #include "transfer.h"
     61 #include "url.h"
     62 #include "parsedate.h" /* for the week day and month names */
     63 #include "warnless.h"
     64 /* The last 3 #include files should be in this order */
     65 #include "curl_printf.h"
     66 #include "curl_memory.h"
     67 #include "memdebug.h"
     68 
     69 #if defined(WIN32) || defined(MSDOS) || defined(__EMX__) || \
     70   defined(__SYMBIAN32__)
     71 #define DOS_FILESYSTEM 1
     72 #endif
     73 
     74 #ifdef OPEN_NEEDS_ARG3
     75 #  define open_readonly(p,f) open((p),(f),(0))
     76 #else
     77 #  define open_readonly(p,f) open((p),(f))
     78 #endif
     79 
     80 /*
     81  * Forward declarations.
     82  */
     83 
     84 static CURLcode file_do(struct connectdata *, bool *done);
     85 static CURLcode file_done(struct connectdata *conn,
     86                           CURLcode status, bool premature);
     87 static CURLcode file_connect(struct connectdata *conn, bool *done);
     88 static CURLcode file_disconnect(struct connectdata *conn,
     89                                 bool dead_connection);
     90 static CURLcode file_setup_connection(struct connectdata *conn);
     91 
     92 /*
     93  * FILE scheme handler.
     94  */
     95 
     96 const struct Curl_handler Curl_handler_file = {
     97   "FILE",                               /* scheme */
     98   file_setup_connection,                /* setup_connection */
     99   file_do,                              /* do_it */
    100   file_done,                            /* done */
    101   ZERO_NULL,                            /* do_more */
    102   file_connect,                         /* connect_it */
    103   ZERO_NULL,                            /* connecting */
    104   ZERO_NULL,                            /* doing */
    105   ZERO_NULL,                            /* proto_getsock */
    106   ZERO_NULL,                            /* doing_getsock */
    107   ZERO_NULL,                            /* domore_getsock */
    108   ZERO_NULL,                            /* perform_getsock */
    109   file_disconnect,                      /* disconnect */
    110   ZERO_NULL,                            /* readwrite */
    111   ZERO_NULL,                            /* connection_check */
    112   0,                                    /* defport */
    113   CURLPROTO_FILE,                       /* protocol */
    114   PROTOPT_NONETWORK | PROTOPT_NOURLQUERY /* flags */
    115 };
    116 
    117 
    118 static CURLcode file_setup_connection(struct connectdata *conn)
    119 {
    120   /* allocate the FILE specific struct */
    121   conn->data->req.protop = calloc(1, sizeof(struct FILEPROTO));
    122   if(!conn->data->req.protop)
    123     return CURLE_OUT_OF_MEMORY;
    124 
    125   return CURLE_OK;
    126 }
    127 
    128  /*
    129   Check if this is a range download, and if so, set the internal variables
    130   properly. This code is copied from the FTP implementation and might as
    131   well be factored out.
    132  */
    133 static CURLcode file_range(struct connectdata *conn)
    134 {
    135   curl_off_t from, to;
    136   curl_off_t totalsize = -1;
    137   char *ptr;
    138   char *ptr2;
    139   struct Curl_easy *data = conn->data;
    140 
    141   if(data->state.use_range && data->state.range) {
    142     CURLofft from_t;
    143     CURLofft to_t;
    144     from_t = curlx_strtoofft(data->state.range, &ptr, 0, &from);
    145     if(from_t == CURL_OFFT_FLOW)
    146       return CURLE_RANGE_ERROR;
    147     while(*ptr && (ISSPACE(*ptr) || (*ptr == '-')))
    148       ptr++;
    149     to_t = curlx_strtoofft(ptr, &ptr2, 0, &to);
    150     if(to_t == CURL_OFFT_FLOW)
    151       return CURLE_RANGE_ERROR;
    152     if((to_t == CURL_OFFT_INVAL) && !from_t) {
    153       /* X - */
    154       data->state.resume_from = from;
    155       DEBUGF(infof(data, "RANGE %" CURL_FORMAT_CURL_OFF_T " to end of file\n",
    156                    from));
    157     }
    158     else if((from_t == CURL_OFFT_INVAL) && !to_t) {
    159       /* -Y */
    160       data->req.maxdownload = to;
    161       data->state.resume_from = -to;
    162       DEBUGF(infof(data, "RANGE the last %" CURL_FORMAT_CURL_OFF_T " bytes\n",
    163                    to));
    164     }
    165     else {
    166       /* X-Y */
    167       totalsize = to-from;
    168       if(totalsize == CURL_OFF_T_MAX)
    169         /* this is too big to increase, so bail out */
    170         return CURLE_RANGE_ERROR;
    171       data->req.maxdownload = totalsize + 1; /* include last byte */
    172       data->state.resume_from = from;
    173       DEBUGF(infof(data, "RANGE from %" CURL_FORMAT_CURL_OFF_T
    174                    " getting %" CURL_FORMAT_CURL_OFF_T " bytes\n",
    175                    from, data->req.maxdownload));
    176     }
    177     DEBUGF(infof(data, "range-download from %" CURL_FORMAT_CURL_OFF_T
    178                  " to %" CURL_FORMAT_CURL_OFF_T ", totally %"
    179                  CURL_FORMAT_CURL_OFF_T " bytes\n",
    180                  from, to, data->req.maxdownload));
    181   }
    182   else
    183     data->req.maxdownload = -1;
    184   return CURLE_OK;
    185 }
    186 
    187 /*
    188  * file_connect() gets called from Curl_protocol_connect() to allow us to
    189  * do protocol-specific actions at connect-time.  We emulate a
    190  * connect-then-transfer protocol and "connect" to the file here
    191  */
    192 static CURLcode file_connect(struct connectdata *conn, bool *done)
    193 {
    194   struct Curl_easy *data = conn->data;
    195   char *real_path;
    196   struct FILEPROTO *file = data->req.protop;
    197   int fd;
    198 #ifdef DOS_FILESYSTEM
    199   size_t i;
    200   char *actual_path;
    201 #endif
    202   size_t real_path_len;
    203 
    204   CURLcode result = Curl_urldecode(data, data->state.path, 0, &real_path,
    205                                    &real_path_len, FALSE);
    206   if(result)
    207     return result;
    208 
    209 #ifdef DOS_FILESYSTEM
    210   /* If the first character is a slash, and there's
    211      something that looks like a drive at the beginning of
    212      the path, skip the slash.  If we remove the initial
    213      slash in all cases, paths without drive letters end up
    214      relative to the current directory which isn't how
    215      browsers work.
    216 
    217      Some browsers accept | instead of : as the drive letter
    218      separator, so we do too.
    219 
    220      On other platforms, we need the slash to indicate an
    221      absolute pathname.  On Windows, absolute paths start
    222      with a drive letter.
    223   */
    224   actual_path = real_path;
    225   if((actual_path[0] == '/') &&
    226       actual_path[1] &&
    227      (actual_path[2] == ':' || actual_path[2] == '|')) {
    228     actual_path[2] = ':';
    229     actual_path++;
    230     real_path_len--;
    231   }
    232 
    233   /* change path separators from '/' to '\\' for DOS, Windows and OS/2 */
    234   for(i = 0; i < real_path_len; ++i)
    235     if(actual_path[i] == '/')
    236       actual_path[i] = '\\';
    237     else if(!actual_path[i]) { /* binary zero */
    238       Curl_safefree(real_path);
    239       return CURLE_URL_MALFORMAT;
    240     }
    241 
    242   fd = open_readonly(actual_path, O_RDONLY|O_BINARY);
    243   file->path = actual_path;
    244 #else
    245   if(memchr(real_path, 0, real_path_len)) {
    246     /* binary zeroes indicate foul play */
    247     Curl_safefree(real_path);
    248     return CURLE_URL_MALFORMAT;
    249   }
    250 
    251   fd = open_readonly(real_path, O_RDONLY);
    252   file->path = real_path;
    253 #endif
    254   file->freepath = real_path; /* free this when done */
    255 
    256   file->fd = fd;
    257   if(!data->set.upload && (fd == -1)) {
    258     failf(data, "Couldn't open file %s", data->state.path);
    259     file_done(conn, CURLE_FILE_COULDNT_READ_FILE, FALSE);
    260     return CURLE_FILE_COULDNT_READ_FILE;
    261   }
    262   *done = TRUE;
    263 
    264   return CURLE_OK;
    265 }
    266 
    267 static CURLcode file_done(struct connectdata *conn,
    268                                CURLcode status, bool premature)
    269 {
    270   struct FILEPROTO *file = conn->data->req.protop;
    271   (void)status; /* not used */
    272   (void)premature; /* not used */
    273 
    274   if(file) {
    275     Curl_safefree(file->freepath);
    276     file->path = NULL;
    277     if(file->fd != -1)
    278       close(file->fd);
    279     file->fd = -1;
    280   }
    281 
    282   return CURLE_OK;
    283 }
    284 
    285 static CURLcode file_disconnect(struct connectdata *conn,
    286                                 bool dead_connection)
    287 {
    288   struct FILEPROTO *file = conn->data->req.protop;
    289   (void)dead_connection; /* not used */
    290 
    291   if(file) {
    292     Curl_safefree(file->freepath);
    293     file->path = NULL;
    294     if(file->fd != -1)
    295       close(file->fd);
    296     file->fd = -1;
    297   }
    298 
    299   return CURLE_OK;
    300 }
    301 
    302 #ifdef DOS_FILESYSTEM
    303 #define DIRSEP '\\'
    304 #else
    305 #define DIRSEP '/'
    306 #endif
    307 
    308 static CURLcode file_upload(struct connectdata *conn)
    309 {
    310   struct FILEPROTO *file = conn->data->req.protop;
    311   const char *dir = strchr(file->path, DIRSEP);
    312   int fd;
    313   int mode;
    314   CURLcode result = CURLE_OK;
    315   struct Curl_easy *data = conn->data;
    316   char *buf = data->state.buffer;
    317   size_t nread;
    318   size_t nwrite;
    319   curl_off_t bytecount = 0;
    320   struct_stat file_stat;
    321   const char *buf2;
    322 
    323   /*
    324    * Since FILE: doesn't do the full init, we need to provide some extra
    325    * assignments here.
    326    */
    327   conn->data->req.upload_fromhere = buf;
    328 
    329   if(!dir)
    330     return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
    331 
    332   if(!dir[1])
    333     return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
    334 
    335 #ifdef O_BINARY
    336 #define MODE_DEFAULT O_WRONLY|O_CREAT|O_BINARY
    337 #else
    338 #define MODE_DEFAULT O_WRONLY|O_CREAT
    339 #endif
    340 
    341   if(data->state.resume_from)
    342     mode = MODE_DEFAULT|O_APPEND;
    343   else
    344     mode = MODE_DEFAULT|O_TRUNC;
    345 
    346   fd = open(file->path, mode, conn->data->set.new_file_perms);
    347   if(fd < 0) {
    348     failf(data, "Can't open %s for writing", file->path);
    349     return CURLE_WRITE_ERROR;
    350   }
    351 
    352   if(-1 != data->state.infilesize)
    353     /* known size of data to "upload" */
    354     Curl_pgrsSetUploadSize(data, data->state.infilesize);
    355 
    356   /* treat the negative resume offset value as the case of "-" */
    357   if(data->state.resume_from < 0) {
    358     if(fstat(fd, &file_stat)) {
    359       close(fd);
    360       failf(data, "Can't get the size of %s", file->path);
    361       return CURLE_WRITE_ERROR;
    362     }
    363     data->state.resume_from = (curl_off_t)file_stat.st_size;
    364   }
    365 
    366   while(!result) {
    367     int readcount;
    368     result = Curl_fillreadbuffer(conn, (int)data->set.buffer_size, &readcount);
    369     if(result)
    370       break;
    371 
    372     if(readcount <= 0)  /* fix questionable compare error. curlvms */
    373       break;
    374 
    375     nread = (size_t)readcount;
    376 
    377     /*skip bytes before resume point*/
    378     if(data->state.resume_from) {
    379       if((curl_off_t)nread <= data->state.resume_from) {
    380         data->state.resume_from -= nread;
    381         nread = 0;
    382         buf2 = buf;
    383       }
    384       else {
    385         buf2 = buf + data->state.resume_from;
    386         nread -= (size_t)data->state.resume_from;
    387         data->state.resume_from = 0;
    388       }
    389     }
    390     else
    391       buf2 = buf;
    392 
    393     /* write the data to the target */
    394     nwrite = write(fd, buf2, nread);
    395     if(nwrite != nread) {
    396       result = CURLE_SEND_ERROR;
    397       break;
    398     }
    399 
    400     bytecount += nread;
    401 
    402     Curl_pgrsSetUploadCounter(data, bytecount);
    403 
    404     if(Curl_pgrsUpdate(conn))
    405       result = CURLE_ABORTED_BY_CALLBACK;
    406     else
    407       result = Curl_speedcheck(data, Curl_now());
    408   }
    409   if(!result && Curl_pgrsUpdate(conn))
    410     result = CURLE_ABORTED_BY_CALLBACK;
    411 
    412   close(fd);
    413 
    414   return result;
    415 }
    416 
    417 /*
    418  * file_do() is the protocol-specific function for the do-phase, separated
    419  * from the connect-phase above. Other protocols merely setup the transfer in
    420  * the do-phase, to have it done in the main transfer loop but since some
    421  * platforms we support don't allow select()ing etc on file handles (as
    422  * opposed to sockets) we instead perform the whole do-operation in this
    423  * function.
    424  */
    425 static CURLcode file_do(struct connectdata *conn, bool *done)
    426 {
    427   /* This implementation ignores the host name in conformance with
    428      RFC 1738. Only local files (reachable via the standard file system)
    429      are supported. This means that files on remotely mounted directories
    430      (via NFS, Samba, NT sharing) can be accessed through a file:// URL
    431   */
    432   CURLcode result = CURLE_OK;
    433   struct_stat statbuf; /* struct_stat instead of struct stat just to allow the
    434                           Windows version to have a different struct without
    435                           having to redefine the simple word 'stat' */
    436   curl_off_t expected_size = 0;
    437   bool size_known;
    438   bool fstated = FALSE;
    439   ssize_t nread;
    440   struct Curl_easy *data = conn->data;
    441   char *buf = data->state.buffer;
    442   curl_off_t bytecount = 0;
    443   int fd;
    444   struct FILEPROTO *file;
    445 
    446   *done = TRUE; /* unconditionally */
    447 
    448   Curl_initinfo(data);
    449   Curl_pgrsStartNow(data);
    450 
    451   if(data->set.upload)
    452     return file_upload(conn);
    453 
    454   file = conn->data->req.protop;
    455 
    456   /* get the fd from the connection phase */
    457   fd = file->fd;
    458 
    459   /* VMS: This only works reliable for STREAMLF files */
    460   if(-1 != fstat(fd, &statbuf)) {
    461     /* we could stat it, then read out the size */
    462     expected_size = statbuf.st_size;
    463     /* and store the modification time */
    464     data->info.filetime = (long)statbuf.st_mtime;
    465     fstated = TRUE;
    466   }
    467 
    468   if(fstated && !data->state.range && data->set.timecondition) {
    469     if(!Curl_meets_timecondition(data, (time_t)data->info.filetime)) {
    470       *done = TRUE;
    471       return CURLE_OK;
    472     }
    473   }
    474 
    475   /* If we have selected NOBODY and HEADER, it means that we only want file
    476      information. Which for FILE can't be much more than the file size and
    477      date. */
    478   if(data->set.opt_no_body && data->set.include_header && fstated) {
    479     time_t filetime;
    480     struct tm buffer;
    481     const struct tm *tm = &buffer;
    482     char header[80];
    483     snprintf(header, sizeof(header),
    484              "Content-Length: %" CURL_FORMAT_CURL_OFF_T "\r\n", expected_size);
    485     result = Curl_client_write(conn, CLIENTWRITE_BOTH, header, 0);
    486     if(result)
    487       return result;
    488 
    489     result = Curl_client_write(conn, CLIENTWRITE_BOTH,
    490                                (char *)"Accept-ranges: bytes\r\n", 0);
    491     if(result)
    492       return result;
    493 
    494     filetime = (time_t)statbuf.st_mtime;
    495     result = Curl_gmtime(filetime, &buffer);
    496     if(result)
    497       return result;
    498 
    499     /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */
    500     snprintf(header, sizeof(header),
    501              "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n",
    502              Curl_wkday[tm->tm_wday?tm->tm_wday-1:6],
    503              tm->tm_mday,
    504              Curl_month[tm->tm_mon],
    505              tm->tm_year + 1900,
    506              tm->tm_hour,
    507              tm->tm_min,
    508              tm->tm_sec);
    509     result = Curl_client_write(conn, CLIENTWRITE_BOTH, header, 0);
    510     if(!result)
    511       /* set the file size to make it available post transfer */
    512       Curl_pgrsSetDownloadSize(data, expected_size);
    513     return result;
    514   }
    515 
    516   /* Check whether file range has been specified */
    517   file_range(conn);
    518 
    519   /* Adjust the start offset in case we want to get the N last bytes
    520    * of the stream iff the filesize could be determined */
    521   if(data->state.resume_from < 0) {
    522     if(!fstated) {
    523       failf(data, "Can't get the size of file.");
    524       return CURLE_READ_ERROR;
    525     }
    526     data->state.resume_from += (curl_off_t)statbuf.st_size;
    527   }
    528 
    529   if(data->state.resume_from <= expected_size)
    530     expected_size -= data->state.resume_from;
    531   else {
    532     failf(data, "failed to resume file:// transfer");
    533     return CURLE_BAD_DOWNLOAD_RESUME;
    534   }
    535 
    536   /* A high water mark has been specified so we obey... */
    537   if(data->req.maxdownload > 0)
    538     expected_size = data->req.maxdownload;
    539 
    540   if(!fstated || (expected_size == 0))
    541     size_known = FALSE;
    542   else
    543     size_known = TRUE;
    544 
    545   /* The following is a shortcut implementation of file reading
    546      this is both more efficient than the former call to download() and
    547      it avoids problems with select() and recv() on file descriptors
    548      in Winsock */
    549   if(fstated)
    550     Curl_pgrsSetDownloadSize(data, expected_size);
    551 
    552   if(data->state.resume_from) {
    553     if(data->state.resume_from !=
    554        lseek(fd, data->state.resume_from, SEEK_SET))
    555       return CURLE_BAD_DOWNLOAD_RESUME;
    556   }
    557 
    558   Curl_pgrsTime(data, TIMER_STARTTRANSFER);
    559 
    560   while(!result) {
    561     /* Don't fill a whole buffer if we want less than all data */
    562     size_t bytestoread;
    563 
    564     if(size_known) {
    565       bytestoread = (expected_size < data->set.buffer_size) ?
    566         curlx_sotouz(expected_size) : (size_t)data->set.buffer_size;
    567     }
    568     else
    569       bytestoread = data->set.buffer_size-1;
    570 
    571     nread = read(fd, buf, bytestoread);
    572 
    573     if(nread > 0)
    574       buf[nread] = 0;
    575 
    576     if(nread <= 0 || (size_known && (expected_size == 0)))
    577       break;
    578 
    579     bytecount += nread;
    580     if(size_known)
    581       expected_size -= nread;
    582 
    583     result = Curl_client_write(conn, CLIENTWRITE_BODY, buf, nread);
    584     if(result)
    585       return result;
    586 
    587     Curl_pgrsSetDownloadCounter(data, bytecount);
    588 
    589     if(Curl_pgrsUpdate(conn))
    590       result = CURLE_ABORTED_BY_CALLBACK;
    591     else
    592       result = Curl_speedcheck(data, Curl_now());
    593   }
    594   if(Curl_pgrsUpdate(conn))
    595     result = CURLE_ABORTED_BY_CALLBACK;
    596 
    597   return result;
    598 }
    599 
    600 #endif
    601