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