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