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