quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

file.c (19162B)


      1 /***************************************************************************
      2  *                                  _   _ ____  _
      3  *  Project                     ___| | | |  _ \| |
      4  *                             / __| | | | |_) | |
      5  *                            | (__| |_| |  _ <| |___
      6  *                             \___|\___/|_| \_\_____|
      7  *
      8  * Copyright (C) Daniel Stenberg, <daniel@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.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  * SPDX-License-Identifier: curl
     22  *
     23  ***************************************************************************/
     24 
     25 #include "curl_setup.h"
     26 
     27 #ifndef CURL_DISABLE_FILE
     28 
     29 #ifdef HAVE_NETINET_IN_H
     30 #include <netinet/in.h>
     31 #endif
     32 #ifdef HAVE_NETDB_H
     33 #include <netdb.h>
     34 #endif
     35 #ifdef HAVE_ARPA_INET_H
     36 #include <arpa/inet.h>
     37 #endif
     38 #ifdef HAVE_NET_IF_H
     39 #include <net/if.h>
     40 #endif
     41 #ifdef HAVE_SYS_IOCTL_H
     42 #include <sys/ioctl.h>
     43 #endif
     44 
     45 #ifdef HAVE_SYS_PARAM_H
     46 #include <sys/param.h>
     47 #endif
     48 
     49 #ifdef HAVE_FCNTL_H
     50 #include <fcntl.h>
     51 #endif
     52 
     53 #ifdef HAVE_SYS_TYPES_H
     54 #include <sys/types.h>
     55 #endif
     56 
     57 #ifdef HAVE_DIRENT_H
     58 #include <dirent.h>
     59 #endif
     60 
     61 #include "urldata.h"
     62 #include <curl/curl.h>
     63 #include "progress.h"
     64 #include "sendf.h"
     65 #include "escape.h"
     66 #include "file.h"
     67 #include "speedcheck.h"
     68 #include "getinfo.h"
     69 #include "multiif.h"
     70 #include "transfer.h"
     71 #include "url.h"
     72 #include "parsedate.h" /* for the week day and month names */
     73 #include "curlx/warnless.h"
     74 #include "curl_range.h"
     75 /* The last 3 #include files should be in this order */
     76 #include "curl_printf.h"
     77 #include "curl_memory.h"
     78 #include "memdebug.h"
     79 
     80 #if defined(_WIN32) || defined(MSDOS)
     81 #define DOS_FILESYSTEM 1
     82 #elif defined(__amigaos4__)
     83 #define AMIGA_FILESYSTEM 1
     84 #endif
     85 
     86 /* meta key for storing protocol meta at easy handle */
     87 #define CURL_META_FILE_EASY   "meta:proto:file:easy"
     88 
     89 struct FILEPROTO {
     90   char *path; /* the path we operate on */
     91   char *freepath; /* pointer to the allocated block we must free, this might
     92                      differ from the 'path' pointer */
     93   int fd;     /* open file descriptor to read from! */
     94 };
     95 
     96 /*
     97  * Forward declarations.
     98  */
     99 
    100 static CURLcode file_do(struct Curl_easy *data, bool *done);
    101 static CURLcode file_done(struct Curl_easy *data,
    102                           CURLcode status, bool premature);
    103 static CURLcode file_connect(struct Curl_easy *data, bool *done);
    104 static CURLcode file_disconnect(struct Curl_easy *data,
    105                                 struct connectdata *conn,
    106                                 bool dead_connection);
    107 static CURLcode file_setup_connection(struct Curl_easy *data,
    108                                       struct connectdata *conn);
    109 
    110 /*
    111  * FILE scheme handler.
    112  */
    113 
    114 const struct Curl_handler Curl_handler_file = {
    115   "file",                               /* scheme */
    116   file_setup_connection,                /* setup_connection */
    117   file_do,                              /* do_it */
    118   file_done,                            /* done */
    119   ZERO_NULL,                            /* do_more */
    120   file_connect,                         /* connect_it */
    121   ZERO_NULL,                            /* connecting */
    122   ZERO_NULL,                            /* doing */
    123   ZERO_NULL,                            /* proto_getsock */
    124   ZERO_NULL,                            /* doing_getsock */
    125   ZERO_NULL,                            /* domore_getsock */
    126   ZERO_NULL,                            /* perform_getsock */
    127   file_disconnect,                      /* disconnect */
    128   ZERO_NULL,                            /* write_resp */
    129   ZERO_NULL,                            /* write_resp_hd */
    130   ZERO_NULL,                            /* connection_check */
    131   ZERO_NULL,                            /* attach connection */
    132   ZERO_NULL,                            /* follow */
    133   0,                                    /* defport */
    134   CURLPROTO_FILE,                       /* protocol */
    135   CURLPROTO_FILE,                       /* family */
    136   PROTOPT_NONETWORK | PROTOPT_NOURLQUERY /* flags */
    137 };
    138 
    139 
    140 static void file_cleanup(struct FILEPROTO *file)
    141 {
    142   Curl_safefree(file->freepath);
    143   file->path = NULL;
    144   if(file->fd != -1) {
    145     close(file->fd);
    146     file->fd = -1;
    147   }
    148 }
    149 
    150 static void file_easy_dtor(void *key, size_t klen, void *entry)
    151 {
    152   struct FILEPROTO *file = entry;
    153   (void)key;
    154   (void)klen;
    155   file_cleanup(file);
    156   free(file);
    157 }
    158 
    159 static CURLcode file_setup_connection(struct Curl_easy *data,
    160                                       struct connectdata *conn)
    161 {
    162   struct FILEPROTO *filep;
    163   (void)conn;
    164   /* allocate the FILE specific struct */
    165   filep = calloc(1, sizeof(*filep));
    166   if(!filep ||
    167      Curl_meta_set(data, CURL_META_FILE_EASY, filep, file_easy_dtor))
    168     return CURLE_OUT_OF_MEMORY;
    169 
    170   return CURLE_OK;
    171 }
    172 
    173 /*
    174  * file_connect() gets called from Curl_protocol_connect() to allow us to
    175  * do protocol-specific actions at connect-time. We emulate a
    176  * connect-then-transfer protocol and "connect" to the file here
    177  */
    178 static CURLcode file_connect(struct Curl_easy *data, bool *done)
    179 {
    180   char *real_path;
    181   struct FILEPROTO *file = Curl_meta_get(data, CURL_META_FILE_EASY);
    182   int fd;
    183 #ifdef DOS_FILESYSTEM
    184   size_t i;
    185   char *actual_path;
    186 #endif
    187   size_t real_path_len;
    188   CURLcode result;
    189 
    190   if(!file)
    191     return CURLE_FAILED_INIT;
    192 
    193   if(file->path) {
    194     /* already connected.
    195      * the handler->connect_it() is normally only called once, but
    196      * FILE does a special check on setting up the connection which
    197      * calls this explicitly. */
    198     *done = TRUE;
    199     return CURLE_OK;
    200   }
    201 
    202   result = Curl_urldecode(data->state.up.path, 0, &real_path,
    203                           &real_path_len, REJECT_ZERO);
    204   if(result)
    205     return result;
    206 
    207 #ifdef DOS_FILESYSTEM
    208   /* If the first character is a slash, and there is
    209      something that looks like a drive at the beginning of
    210      the path, skip the slash. If we remove the initial
    211      slash in all cases, paths without drive letters end up
    212      relative to the current directory which is not how
    213      browsers work.
    214 
    215      Some browsers accept | instead of : as the drive letter
    216      separator, so we do too.
    217 
    218      On other platforms, we need the slash to indicate an
    219      absolute pathname. On Windows, absolute paths start
    220      with a drive letter.
    221   */
    222   actual_path = real_path;
    223   if((actual_path[0] == '/') &&
    224       actual_path[1] &&
    225      (actual_path[2] == ':' || actual_path[2] == '|')) {
    226     actual_path[2] = ':';
    227     actual_path++;
    228     real_path_len--;
    229   }
    230 
    231   /* change path separators from '/' to '\\' for DOS, Windows and OS/2 */
    232   for(i = 0; i < real_path_len; ++i)
    233     if(actual_path[i] == '/')
    234       actual_path[i] = '\\';
    235     else if(!actual_path[i]) { /* binary zero */
    236       Curl_safefree(real_path);
    237       return CURLE_URL_MALFORMAT;
    238     }
    239 
    240   fd = open(actual_path, O_RDONLY|CURL_O_BINARY);
    241   file->path = actual_path;
    242 #else
    243   if(memchr(real_path, 0, real_path_len)) {
    244     /* binary zeroes indicate foul play */
    245     Curl_safefree(real_path);
    246     return CURLE_URL_MALFORMAT;
    247   }
    248 
    249   #ifdef AMIGA_FILESYSTEM
    250   /*
    251    * A leading slash in an AmigaDOS path denotes the parent
    252    * directory, and hence we block this as it is relative.
    253    * Absolute paths start with 'volumename:', so we check for
    254    * this first. Failing that, we treat the path as a real Unix
    255    * path, but only if the application was compiled with -lunix.
    256    */
    257   fd = -1;
    258   file->path = real_path;
    259 
    260   if(real_path[0] == '/') {
    261     extern int __unix_path_semantics;
    262     if(strchr(real_path + 1, ':')) {
    263       /* Amiga absolute path */
    264       fd = open(real_path + 1, O_RDONLY);
    265       file->path++;
    266     }
    267     else if(__unix_path_semantics) {
    268       /* -lunix fallback */
    269       fd = open(real_path, O_RDONLY);
    270     }
    271   }
    272   #else
    273   fd = open(real_path, O_RDONLY);
    274   file->path = real_path;
    275   #endif
    276 #endif
    277   free(file->freepath);
    278   file->freepath = real_path; /* free this when done */
    279 
    280   file->fd = fd;
    281   if(!data->state.upload && (fd == -1)) {
    282     failf(data, "Couldn't open file %s", data->state.up.path);
    283     file_done(data, CURLE_FILE_COULDNT_READ_FILE, FALSE);
    284     return CURLE_FILE_COULDNT_READ_FILE;
    285   }
    286   *done = TRUE;
    287 
    288   return CURLE_OK;
    289 }
    290 
    291 static CURLcode file_done(struct Curl_easy *data,
    292                           CURLcode status, bool premature)
    293 {
    294   struct FILEPROTO *file = Curl_meta_get(data, CURL_META_FILE_EASY);
    295   (void)status; /* not used */
    296   (void)premature; /* not used */
    297 
    298   if(file)
    299     file_cleanup(file);
    300 
    301   return CURLE_OK;
    302 }
    303 
    304 static CURLcode file_disconnect(struct Curl_easy *data,
    305                                 struct connectdata *conn,
    306                                 bool dead_connection)
    307 {
    308   (void)dead_connection; /* not used */
    309   (void)conn;
    310   return file_done(data, CURLE_OK, FALSE);
    311 }
    312 
    313 #ifdef DOS_FILESYSTEM
    314 #define DIRSEP '\\'
    315 #else
    316 #define DIRSEP '/'
    317 #endif
    318 
    319 static CURLcode file_upload(struct Curl_easy *data,
    320                             struct FILEPROTO *file)
    321 {
    322   const char *dir = strchr(file->path, DIRSEP);
    323   int fd;
    324   int mode;
    325   CURLcode result = CURLE_OK;
    326   char *xfer_ulbuf;
    327   size_t xfer_ulblen;
    328   curl_off_t bytecount = 0;
    329   struct_stat file_stat;
    330   const char *sendbuf;
    331   bool eos = FALSE;
    332 
    333   /*
    334    * Since FILE: does not do the full init, we need to provide some extra
    335    * assignments here.
    336    */
    337 
    338   if(!dir)
    339     return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
    340 
    341   if(!dir[1])
    342     return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
    343 
    344   mode = O_WRONLY|O_CREAT|CURL_O_BINARY;
    345   if(data->state.resume_from)
    346     mode |= O_APPEND;
    347   else
    348     mode |= O_TRUNC;
    349 
    350 #if (defined(ANDROID) || defined(__ANDROID__)) && \
    351     (defined(__i386__) || defined(__arm__))
    352   fd = open(file->path, mode, (mode_t)data->set.new_file_perms);
    353 #else
    354   fd = open(file->path, mode, data->set.new_file_perms);
    355 #endif
    356   if(fd < 0) {
    357     failf(data, "cannot open %s for writing", file->path);
    358     return CURLE_WRITE_ERROR;
    359   }
    360 
    361   if(-1 != data->state.infilesize)
    362     /* known size of data to "upload" */
    363     Curl_pgrsSetUploadSize(data, data->state.infilesize);
    364 
    365   /* treat the negative resume offset value as the case of "-" */
    366   if(data->state.resume_from < 0) {
    367     if(fstat(fd, &file_stat)) {
    368       close(fd);
    369       failf(data, "cannot get the size of %s", file->path);
    370       return CURLE_WRITE_ERROR;
    371     }
    372     data->state.resume_from = (curl_off_t)file_stat.st_size;
    373   }
    374 
    375   result = Curl_multi_xfer_ulbuf_borrow(data, &xfer_ulbuf, &xfer_ulblen);
    376   if(result)
    377     goto out;
    378 
    379   while(!result && !eos) {
    380     size_t nread;
    381     ssize_t nwrite;
    382     size_t readcount;
    383 
    384     result = Curl_client_read(data, xfer_ulbuf, xfer_ulblen, &readcount, &eos);
    385     if(result)
    386       break;
    387 
    388     if(!readcount)
    389       break;
    390 
    391     nread = readcount;
    392 
    393     /* skip bytes before resume point */
    394     if(data->state.resume_from) {
    395       if((curl_off_t)nread <= data->state.resume_from) {
    396         data->state.resume_from -= nread;
    397         nread = 0;
    398         sendbuf = xfer_ulbuf;
    399       }
    400       else {
    401         sendbuf = xfer_ulbuf + data->state.resume_from;
    402         nread -= (size_t)data->state.resume_from;
    403         data->state.resume_from = 0;
    404       }
    405     }
    406     else
    407       sendbuf = xfer_ulbuf;
    408 
    409     /* write the data to the target */
    410     nwrite = write(fd, sendbuf, nread);
    411     if((size_t)nwrite != nread) {
    412       result = CURLE_SEND_ERROR;
    413       break;
    414     }
    415 
    416     bytecount += nread;
    417 
    418     Curl_pgrsSetUploadCounter(data, bytecount);
    419 
    420     if(Curl_pgrsUpdate(data))
    421       result = CURLE_ABORTED_BY_CALLBACK;
    422     else
    423       result = Curl_speedcheck(data, curlx_now());
    424   }
    425   if(!result && Curl_pgrsUpdate(data))
    426     result = CURLE_ABORTED_BY_CALLBACK;
    427 
    428 out:
    429   close(fd);
    430   Curl_multi_xfer_ulbuf_release(data, xfer_ulbuf);
    431 
    432   return result;
    433 }
    434 
    435 /*
    436  * file_do() is the protocol-specific function for the do-phase, separated
    437  * from the connect-phase above. Other protocols merely setup the transfer in
    438  * the do-phase, to have it done in the main transfer loop but since some
    439  * platforms we support do not allow select()ing etc on file handles (as
    440  * opposed to sockets) we instead perform the whole do-operation in this
    441  * function.
    442  */
    443 static CURLcode file_do(struct Curl_easy *data, bool *done)
    444 {
    445   /* This implementation ignores the hostname in conformance with
    446      RFC 1738. Only local files (reachable via the standard file system)
    447      are supported. This means that files on remotely mounted directories
    448      (via NFS, Samba, NT sharing) can be accessed through a file:// URL
    449   */
    450   struct FILEPROTO *file = Curl_meta_get(data, CURL_META_FILE_EASY);
    451   CURLcode result = CURLE_OK;
    452   struct_stat statbuf; /* struct_stat instead of struct stat just to allow the
    453                           Windows version to have a different struct without
    454                           having to redefine the simple word 'stat' */
    455   curl_off_t expected_size = -1;
    456   bool size_known;
    457   bool fstated = FALSE;
    458   int fd;
    459   char *xfer_buf;
    460   size_t xfer_blen;
    461 
    462   *done = TRUE; /* unconditionally */
    463   if(!file)
    464     return CURLE_FAILED_INIT;
    465 
    466   if(data->state.upload)
    467     return file_upload(data, file);
    468 
    469   /* get the fd from the connection phase */
    470   fd = file->fd;
    471 
    472   /* VMS: This only works reliable for STREAMLF files */
    473   if(-1 != fstat(fd, &statbuf)) {
    474     if(!S_ISDIR(statbuf.st_mode))
    475       expected_size = statbuf.st_size;
    476     /* and store the modification time */
    477     data->info.filetime = statbuf.st_mtime;
    478     fstated = TRUE;
    479   }
    480 
    481   if(fstated && !data->state.range && data->set.timecondition &&
    482      !Curl_meets_timecondition(data, data->info.filetime))
    483     return CURLE_OK;
    484 
    485   if(fstated) {
    486     time_t filetime;
    487     struct tm buffer;
    488     const struct tm *tm = &buffer;
    489     char header[80];
    490     int headerlen;
    491     static const char accept_ranges[]= { "Accept-ranges: bytes\r\n" };
    492     if(expected_size >= 0) {
    493       headerlen =
    494         msnprintf(header, sizeof(header), "Content-Length: %" FMT_OFF_T "\r\n",
    495                   expected_size);
    496       result = Curl_client_write(data, CLIENTWRITE_HEADER, header, headerlen);
    497       if(result)
    498         return result;
    499 
    500       result = Curl_client_write(data, CLIENTWRITE_HEADER,
    501                                  accept_ranges, sizeof(accept_ranges) - 1);
    502       if(result != CURLE_OK)
    503         return result;
    504     }
    505 
    506     filetime = (time_t)statbuf.st_mtime;
    507     result = Curl_gmtime(filetime, &buffer);
    508     if(result)
    509       return result;
    510 
    511     /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */
    512     headerlen =
    513       msnprintf(header, sizeof(header),
    514                 "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n",
    515                 Curl_wkday[tm->tm_wday ? tm->tm_wday-1 : 6],
    516                 tm->tm_mday,
    517                 Curl_month[tm->tm_mon],
    518                 tm->tm_year + 1900,
    519                 tm->tm_hour,
    520                 tm->tm_min,
    521                 tm->tm_sec);
    522     result = Curl_client_write(data, CLIENTWRITE_HEADER, header, headerlen);
    523     if(!result)
    524       /* end of headers */
    525       result = Curl_client_write(data, CLIENTWRITE_HEADER, "\r\n", 2);
    526     if(result)
    527       return result;
    528     /* set the file size to make it available post transfer */
    529     Curl_pgrsSetDownloadSize(data, expected_size);
    530     if(data->req.no_body)
    531       return CURLE_OK;
    532   }
    533 
    534   /* Check whether file range has been specified */
    535   result = Curl_range(data);
    536   if(result)
    537     return result;
    538 
    539   /* Adjust the start offset in case we want to get the N last bytes
    540    * of the stream if the filesize could be determined */
    541   if(data->state.resume_from < 0) {
    542     if(!fstated) {
    543       failf(data, "cannot get the size of file.");
    544       return CURLE_READ_ERROR;
    545     }
    546     data->state.resume_from += (curl_off_t)statbuf.st_size;
    547   }
    548 
    549   if(data->state.resume_from > 0) {
    550     /* We check explicitly if we have a start offset, because
    551      * expected_size may be -1 if we do not know how large the file is,
    552      * in which case we should not adjust it. */
    553     if(data->state.resume_from <= expected_size)
    554       expected_size -= data->state.resume_from;
    555     else {
    556       failf(data, "failed to resume file:// transfer");
    557       return CURLE_BAD_DOWNLOAD_RESUME;
    558     }
    559   }
    560 
    561   /* A high water mark has been specified so we obey... */
    562   if(data->req.maxdownload > 0)
    563     expected_size = data->req.maxdownload;
    564 
    565   if(!fstated || (expected_size <= 0))
    566     size_known = FALSE;
    567   else
    568     size_known = TRUE;
    569 
    570   /* The following is a shortcut implementation of file reading
    571      this is both more efficient than the former call to download() and
    572      it avoids problems with select() and recv() on file descriptors
    573      in Winsock */
    574   if(size_known)
    575     Curl_pgrsSetDownloadSize(data, expected_size);
    576 
    577   if(data->state.resume_from) {
    578     if(!S_ISDIR(statbuf.st_mode)) {
    579 #if defined(__AMIGA__) || defined(__MINGW32CE__)
    580       if(data->state.resume_from !=
    581           lseek(fd, (off_t)data->state.resume_from, SEEK_SET))
    582 #else
    583       if(data->state.resume_from !=
    584           lseek(fd, data->state.resume_from, SEEK_SET))
    585 #endif
    586         return CURLE_BAD_DOWNLOAD_RESUME;
    587     }
    588     else {
    589       return CURLE_BAD_DOWNLOAD_RESUME;
    590     }
    591   }
    592 
    593   result = Curl_multi_xfer_buf_borrow(data, &xfer_buf, &xfer_blen);
    594   if(result)
    595     goto out;
    596 
    597   if(!S_ISDIR(statbuf.st_mode)) {
    598     while(!result) {
    599       ssize_t nread;
    600       /* Do not fill a whole buffer if we want less than all data */
    601       size_t bytestoread;
    602 
    603       if(size_known) {
    604         bytestoread = (expected_size < (curl_off_t)(xfer_blen-1)) ?
    605           curlx_sotouz(expected_size) : (xfer_blen-1);
    606       }
    607       else
    608         bytestoread = xfer_blen-1;
    609 
    610       nread = read(fd, xfer_buf, bytestoread);
    611 
    612       if(nread > 0)
    613         xfer_buf[nread] = 0;
    614 
    615       if(nread <= 0 || (size_known && (expected_size == 0)))
    616         break;
    617 
    618       if(size_known)
    619         expected_size -= nread;
    620 
    621       result = Curl_client_write(data, CLIENTWRITE_BODY, xfer_buf, nread);
    622       if(result)
    623         goto out;
    624 
    625       if(Curl_pgrsUpdate(data))
    626         result = CURLE_ABORTED_BY_CALLBACK;
    627       else
    628         result = Curl_speedcheck(data, curlx_now());
    629       if(result)
    630         goto out;
    631     }
    632   }
    633   else {
    634 #ifdef HAVE_OPENDIR
    635     DIR *dir = opendir(file->path);
    636     struct dirent *entry;
    637 
    638     if(!dir) {
    639       result = CURLE_READ_ERROR;
    640       goto out;
    641     }
    642     else {
    643       while((entry = readdir(dir))) {
    644         if(entry->d_name[0] != '.') {
    645           result = Curl_client_write(data, CLIENTWRITE_BODY,
    646                    entry->d_name, strlen(entry->d_name));
    647           if(result)
    648             break;
    649           result = Curl_client_write(data, CLIENTWRITE_BODY, "\n", 1);
    650           if(result)
    651             break;
    652         }
    653       }
    654       closedir(dir);
    655     }
    656 #else
    657     failf(data, "Directory listing not yet implemented on this platform.");
    658     result = CURLE_READ_ERROR;
    659 #endif
    660   }
    661 
    662   if(Curl_pgrsUpdate(data))
    663     result = CURLE_ABORTED_BY_CALLBACK;
    664 
    665 out:
    666   Curl_multi_xfer_buf_release(data, xfer_buf);
    667   return result;
    668 }
    669 
    670 #endif