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