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