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