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