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