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