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