• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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