VirtualBox

source: vbox/trunk/src/libs/curl-8.7.1/lib/file.c@ 106655

Last change on this file since 106655 was 104083, checked in by vboxsync, 11 months ago

curl-8.7.1: Applied and adjusted our curl changes to 8.4.0. bugref:10639

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

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette