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