Home | History | Annotate | Download | only in lib
      1 /***************************************************************************
      2  *                                  _   _ ____  _
      3  *  Project                     ___| | | |  _ \| |
      4  *                             / __| | | | |_) | |
      5  *                            | (__| |_| |  _ <| |___
      6  *                             \___|\___/|_| \_\_____|
      7  *
      8  * Copyright (C) 1998 - 2016, 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   0,                                    /* defport */
    112   CURLPROTO_FILE,                       /* protocol */
    113   PROTOPT_NONETWORK | PROTOPT_NOURLQUERY /* flags */
    114 };
    115 
    116 
    117 static CURLcode file_setup_connection(struct connectdata *conn)
    118 {
    119   /* allocate the FILE specific struct */
    120   conn->data->req.protop = calloc(1, sizeof(struct FILEPROTO));
    121   if(!conn->data->req.protop)
    122     return CURLE_OUT_OF_MEMORY;
    123 
    124   return CURLE_OK;
    125 }
    126 
    127  /*
    128   Check if this is a range download, and if so, set the internal variables
    129   properly. This code is copied from the FTP implementation and might as
    130   well be factored out.
    131  */
    132 static CURLcode file_range(struct connectdata *conn)
    133 {
    134   curl_off_t from, to;
    135   curl_off_t totalsize=-1;
    136   char *ptr;
    137   char *ptr2;
    138   struct Curl_easy *data = conn->data;
    139 
    140   if(data->state.use_range && data->state.range) {
    141     from=curlx_strtoofft(data->state.range, &ptr, 0);
    142     while(*ptr && (ISSPACE(*ptr) || (*ptr=='-')))
    143       ptr++;
    144     to=curlx_strtoofft(ptr, &ptr2, 0);
    145     if(ptr == ptr2) {
    146       /* we didn't get any digit */
    147       to=-1;
    148     }
    149     if((-1 == to) && (from>=0)) {
    150       /* X - */
    151       data->state.resume_from = from;
    152       DEBUGF(infof(data, "RANGE %" CURL_FORMAT_CURL_OFF_T " to end of file\n",
    153                    from));
    154     }
    155     else if(from < 0) {
    156       /* -Y */
    157       data->req.maxdownload = -from;
    158       data->state.resume_from = from;
    159       DEBUGF(infof(data, "RANGE the last %" CURL_FORMAT_CURL_OFF_T " bytes\n",
    160                    -from));
    161     }
    162     else {
    163       /* X-Y */
    164       totalsize = to-from;
    165       data->req.maxdownload = totalsize+1; /* include last byte */
    166       data->state.resume_from = from;
    167       DEBUGF(infof(data, "RANGE from %" CURL_FORMAT_CURL_OFF_T
    168                    " getting %" CURL_FORMAT_CURL_OFF_T " bytes\n",
    169                    from, data->req.maxdownload));
    170     }
    171     DEBUGF(infof(data, "range-download from %" CURL_FORMAT_CURL_OFF_T
    172                  " to %" CURL_FORMAT_CURL_OFF_T ", totally %"
    173                  CURL_FORMAT_CURL_OFF_T " bytes\n",
    174                  from, to, data->req.maxdownload));
    175   }
    176   else
    177     data->req.maxdownload = -1;
    178   return CURLE_OK;
    179 }
    180 
    181 /*
    182  * file_connect() gets called from Curl_protocol_connect() to allow us to
    183  * do protocol-specific actions at connect-time.  We emulate a
    184  * connect-then-transfer protocol and "connect" to the file here
    185  */
    186 static CURLcode file_connect(struct connectdata *conn, bool *done)
    187 {
    188   struct Curl_easy *data = conn->data;
    189   char *real_path;
    190   struct FILEPROTO *file = data->req.protop;
    191   int fd;
    192 #ifdef DOS_FILESYSTEM
    193   int i;
    194   char *actual_path;
    195 #endif
    196   int real_path_len;
    197 
    198   real_path = curl_easy_unescape(data, data->state.path, 0, &real_path_len);
    199   if(!real_path)
    200     return CURLE_OUT_OF_MEMORY;
    201 
    202 #ifdef DOS_FILESYSTEM
    203   /* If the first character is a slash, and there's
    204      something that looks like a drive at the beginning of
    205      the path, skip the slash.  If we remove the initial
    206      slash in all cases, paths without drive letters end up
    207      relative to the current directory which isn't how
    208      browsers work.
    209 
    210      Some browsers accept | instead of : as the drive letter
    211      separator, so we do too.
    212 
    213      On other platforms, we need the slash to indicate an
    214      absolute pathname.  On Windows, absolute paths start
    215      with a drive letter.
    216   */
    217   actual_path = real_path;
    218   if((actual_path[0] == '/') &&
    219       actual_path[1] &&
    220      (actual_path[2] == ':' || actual_path[2] == '|')) {
    221     actual_path[2] = ':';
    222     actual_path++;
    223     real_path_len--;
    224   }
    225 
    226   /* change path separators from '/' to '\\' for DOS, Windows and OS/2 */
    227   for(i=0; i < real_path_len; ++i)
    228     if(actual_path[i] == '/')
    229       actual_path[i] = '\\';
    230     else if(!actual_path[i]) { /* binary zero */
    231       Curl_safefree(real_path);
    232       return CURLE_URL_MALFORMAT;
    233     }
    234 
    235   fd = open_readonly(actual_path, O_RDONLY|O_BINARY);
    236   file->path = actual_path;
    237 #else
    238   if(memchr(real_path, 0, real_path_len)) {
    239     /* binary zeroes indicate foul play */
    240     Curl_safefree(real_path);
    241     return CURLE_URL_MALFORMAT;
    242   }
    243 
    244   fd = open_readonly(real_path, O_RDONLY);
    245   file->path = real_path;
    246 #endif
    247   file->freepath = real_path; /* free this when done */
    248 
    249   file->fd = fd;
    250   if(!data->set.upload && (fd == -1)) {
    251     failf(data, "Couldn't open file %s", data->state.path);
    252     file_done(conn, CURLE_FILE_COULDNT_READ_FILE, FALSE);
    253     return CURLE_FILE_COULDNT_READ_FILE;
    254   }
    255   *done = TRUE;
    256 
    257   return CURLE_OK;
    258 }
    259 
    260 static CURLcode file_done(struct connectdata *conn,
    261                                CURLcode status, bool premature)
    262 {
    263   struct FILEPROTO *file = conn->data->req.protop;
    264   (void)status; /* not used */
    265   (void)premature; /* not used */
    266 
    267   if(file) {
    268     Curl_safefree(file->freepath);
    269     file->path = NULL;
    270     if(file->fd != -1)
    271       close(file->fd);
    272     file->fd = -1;
    273   }
    274 
    275   return CURLE_OK;
    276 }
    277 
    278 static CURLcode file_disconnect(struct connectdata *conn,
    279                                 bool dead_connection)
    280 {
    281   struct FILEPROTO *file = conn->data->req.protop;
    282   (void)dead_connection; /* not used */
    283 
    284   if(file) {
    285     Curl_safefree(file->freepath);
    286     file->path = NULL;
    287     if(file->fd != -1)
    288       close(file->fd);
    289     file->fd = -1;
    290   }
    291 
    292   return CURLE_OK;
    293 }
    294 
    295 #ifdef DOS_FILESYSTEM
    296 #define DIRSEP '\\'
    297 #else
    298 #define DIRSEP '/'
    299 #endif
    300 
    301 static CURLcode file_upload(struct connectdata *conn)
    302 {
    303   struct FILEPROTO *file = conn->data->req.protop;
    304   const char *dir = strchr(file->path, DIRSEP);
    305   int fd;
    306   int mode;
    307   CURLcode result = CURLE_OK;
    308   struct Curl_easy *data = conn->data;
    309   char *buf = data->state.buffer;
    310   size_t nread;
    311   size_t nwrite;
    312   curl_off_t bytecount = 0;
    313   struct timeval now = Curl_tvnow();
    314   struct_stat file_stat;
    315   const char* buf2;
    316 
    317   /*
    318    * Since FILE: doesn't do the full init, we need to provide some extra
    319    * assignments here.
    320    */
    321   conn->data->req.upload_fromhere = buf;
    322 
    323   if(!dir)
    324     return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
    325 
    326   if(!dir[1])
    327     return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
    328 
    329 #ifdef O_BINARY
    330 #define MODE_DEFAULT O_WRONLY|O_CREAT|O_BINARY
    331 #else
    332 #define MODE_DEFAULT O_WRONLY|O_CREAT
    333 #endif
    334 
    335   if(data->state.resume_from)
    336     mode = MODE_DEFAULT|O_APPEND;
    337   else
    338     mode = MODE_DEFAULT|O_TRUNC;
    339 
    340   fd = open(file->path, mode, conn->data->set.new_file_perms);
    341   if(fd < 0) {
    342     failf(data, "Can't open %s for writing", file->path);
    343     return CURLE_WRITE_ERROR;
    344   }
    345 
    346   if(-1 != data->state.infilesize)
    347     /* known size of data to "upload" */
    348     Curl_pgrsSetUploadSize(data, data->state.infilesize);
    349 
    350   /* treat the negative resume offset value as the case of "-" */
    351   if(data->state.resume_from < 0) {
    352     if(fstat(fd, &file_stat)) {
    353       close(fd);
    354       failf(data, "Can't get the size of %s", file->path);
    355       return CURLE_WRITE_ERROR;
    356     }
    357     else
    358       data->state.resume_from = (curl_off_t)file_stat.st_size;
    359   }
    360 
    361   while(!result) {
    362     int readcount;
    363     result = Curl_fillreadbuffer(conn, BUFSIZE, &readcount);
    364     if(result)
    365       break;
    366 
    367     if(readcount <= 0)  /* fix questionable compare error. curlvms */
    368       break;
    369 
    370     nread = (size_t)readcount;
    371 
    372     /*skip bytes before resume point*/
    373     if(data->state.resume_from) {
    374       if((curl_off_t)nread <= data->state.resume_from) {
    375         data->state.resume_from -= nread;
    376         nread = 0;
    377         buf2 = buf;
    378       }
    379       else {
    380         buf2 = buf + data->state.resume_from;
    381         nread -= (size_t)data->state.resume_from;
    382         data->state.resume_from = 0;
    383       }
    384     }
    385     else
    386       buf2 = buf;
    387 
    388     /* write the data to the target */
    389     nwrite = write(fd, buf2, nread);
    390     if(nwrite != nread) {
    391       result = CURLE_SEND_ERROR;
    392       break;
    393     }
    394 
    395     bytecount += nread;
    396 
    397     Curl_pgrsSetUploadCounter(data, bytecount);
    398 
    399     if(Curl_pgrsUpdate(conn))
    400       result = CURLE_ABORTED_BY_CALLBACK;
    401     else
    402       result = Curl_speedcheck(data, now);
    403   }
    404   if(!result && Curl_pgrsUpdate(conn))
    405     result = CURLE_ABORTED_BY_CALLBACK;
    406 
    407   close(fd);
    408 
    409   return result;
    410 }
    411 
    412 /*
    413  * file_do() is the protocol-specific function for the do-phase, separated
    414  * from the connect-phase above. Other protocols merely setup the transfer in
    415  * the do-phase, to have it done in the main transfer loop but since some
    416  * platforms we support don't allow select()ing etc on file handles (as
    417  * opposed to sockets) we instead perform the whole do-operation in this
    418  * function.
    419  */
    420 static CURLcode file_do(struct connectdata *conn, bool *done)
    421 {
    422   /* This implementation ignores the host name in conformance with
    423      RFC 1738. Only local files (reachable via the standard file system)
    424      are supported. This means that files on remotely mounted directories
    425      (via NFS, Samba, NT sharing) can be accessed through a file:// URL
    426   */
    427   CURLcode result = CURLE_OK;
    428   struct_stat statbuf; /* struct_stat instead of struct stat just to allow the
    429                           Windows version to have a different struct without
    430                           having to redefine the simple word 'stat' */
    431   curl_off_t expected_size=0;
    432   bool size_known;
    433   bool fstated=FALSE;
    434   ssize_t nread;
    435   struct Curl_easy *data = conn->data;
    436   char *buf = data->state.buffer;
    437   curl_off_t bytecount = 0;
    438   int fd;
    439   struct timeval now = Curl_tvnow();
    440   struct FILEPROTO *file;
    441 
    442   *done = TRUE; /* unconditionally */
    443 
    444   Curl_initinfo(data);
    445   Curl_pgrsStartNow(data);
    446 
    447   if(data->set.upload)
    448     return file_upload(conn);
    449 
    450   file = conn->data->req.protop;
    451 
    452   /* get the fd from the connection phase */
    453   fd = file->fd;
    454 
    455   /* VMS: This only works reliable for STREAMLF files */
    456   if(-1 != fstat(fd, &statbuf)) {
    457     /* we could stat it, then read out the size */
    458     expected_size = statbuf.st_size;
    459     /* and store the modification time */
    460     data->info.filetime = (long)statbuf.st_mtime;
    461     fstated = TRUE;
    462   }
    463 
    464   if(fstated && !data->state.range && data->set.timecondition) {
    465     if(!Curl_meets_timecondition(data, (time_t)data->info.filetime)) {
    466       *done = TRUE;
    467       return CURLE_OK;
    468     }
    469   }
    470 
    471   /* If we have selected NOBODY and HEADER, it means that we only want file
    472      information. Which for FILE can't be much more than the file size and
    473      date. */
    474   if(data->set.opt_no_body && data->set.include_header && fstated) {
    475     time_t filetime;
    476     struct tm buffer;
    477     const struct tm *tm = &buffer;
    478     snprintf(buf, sizeof(data->state.buffer),
    479              "Content-Length: %" CURL_FORMAT_CURL_OFF_T "\r\n", expected_size);
    480     result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0);
    481     if(result)
    482       return result;
    483 
    484     result = Curl_client_write(conn, CLIENTWRITE_BOTH,
    485                                (char *)"Accept-ranges: bytes\r\n", 0);
    486     if(result)
    487       return result;
    488 
    489     filetime = (time_t)statbuf.st_mtime;
    490     result = Curl_gmtime(filetime, &buffer);
    491     if(result)
    492       return result;
    493 
    494     /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */
    495     snprintf(buf, BUFSIZE-1,
    496              "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n",
    497              Curl_wkday[tm->tm_wday?tm->tm_wday-1:6],
    498              tm->tm_mday,
    499              Curl_month[tm->tm_mon],
    500              tm->tm_year + 1900,
    501              tm->tm_hour,
    502              tm->tm_min,
    503              tm->tm_sec);
    504     result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0);
    505     if(!result)
    506       /* set the file size to make it available post transfer */
    507       Curl_pgrsSetDownloadSize(data, expected_size);
    508     return result;
    509   }
    510 
    511   /* Check whether file range has been specified */
    512   file_range(conn);
    513 
    514   /* Adjust the start offset in case we want to get the N last bytes
    515    * of the stream iff the filesize could be determined */
    516   if(data->state.resume_from < 0) {
    517     if(!fstated) {
    518       failf(data, "Can't get the size of file.");
    519       return CURLE_READ_ERROR;
    520     }
    521     else
    522       data->state.resume_from += (curl_off_t)statbuf.st_size;
    523   }
    524 
    525   if(data->state.resume_from <= expected_size)
    526     expected_size -= data->state.resume_from;
    527   else {
    528     failf(data, "failed to resume file:// transfer");
    529     return CURLE_BAD_DOWNLOAD_RESUME;
    530   }
    531 
    532   /* A high water mark has been specified so we obey... */
    533   if(data->req.maxdownload > 0)
    534     expected_size = data->req.maxdownload;
    535 
    536   if(!fstated || (expected_size == 0))
    537     size_known = FALSE;
    538   else
    539     size_known = TRUE;
    540 
    541   /* The following is a shortcut implementation of file reading
    542      this is both more efficient than the former call to download() and
    543      it avoids problems with select() and recv() on file descriptors
    544      in Winsock */
    545   if(fstated)
    546     Curl_pgrsSetDownloadSize(data, expected_size);
    547 
    548   if(data->state.resume_from) {
    549     if(data->state.resume_from !=
    550        lseek(fd, data->state.resume_from, SEEK_SET))
    551       return CURLE_BAD_DOWNLOAD_RESUME;
    552   }
    553 
    554   Curl_pgrsTime(data, TIMER_STARTTRANSFER);
    555 
    556   while(!result) {
    557     /* Don't fill a whole buffer if we want less than all data */
    558     size_t bytestoread;
    559 
    560     if(size_known) {
    561       bytestoread =
    562         (expected_size < CURL_OFF_T_C(BUFSIZE) - CURL_OFF_T_C(1)) ?
    563         curlx_sotouz(expected_size) : BUFSIZE - 1;
    564     }
    565     else
    566       bytestoread = BUFSIZE-1;
    567 
    568     nread = read(fd, buf, bytestoread);
    569 
    570     if(nread > 0)
    571       buf[nread] = 0;
    572 
    573     if(nread <= 0 || (size_known && (expected_size == 0)))
    574       break;
    575 
    576     bytecount += nread;
    577     if(size_known)
    578       expected_size -= nread;
    579 
    580     result = Curl_client_write(conn, CLIENTWRITE_BODY, buf, nread);
    581     if(result)
    582       return result;
    583 
    584     Curl_pgrsSetDownloadCounter(data, bytecount);
    585 
    586     if(Curl_pgrsUpdate(conn))
    587       result = CURLE_ABORTED_BY_CALLBACK;
    588     else
    589       result = Curl_speedcheck(data, now);
    590   }
    591   if(Curl_pgrsUpdate(conn))
    592     result = CURLE_ABORTED_BY_CALLBACK;
    593 
    594   return result;
    595 }
    596 
    597 #endif
    598