1/***************************************************************************
2 *                                  _   _ ____  _
3 *  Project                     ___| | | |  _ \| |
4 *                             / __| | | | |_) | |
5 *                            | (__| |_| |  _ <| |___
6 *                             \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 1998 - 2012, 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 "curl_memory.h"
63#include "parsedate.h" /* for the week day and month names */
64#include "warnless.h"
65
66#define _MPRINTF_REPLACE /* use our functions only */
67#include <curl/mprintf.h>
68
69/* The last #include file should be: */
70#include "memdebug.h"
71
72#if defined(WIN32) || defined(MSDOS) || defined(__EMX__) || \
73  defined(__SYMBIAN32__)
74#define DOS_FILESYSTEM 1
75#endif
76
77#ifdef OPEN_NEEDS_ARG3
78#  define open_readonly(p,f) open((p),(f),(0))
79#else
80#  define open_readonly(p,f) open((p),(f))
81#endif
82
83/*
84 * Forward declarations.
85 */
86
87static CURLcode file_do(struct connectdata *, bool *done);
88static CURLcode file_done(struct connectdata *conn,
89                          CURLcode status, bool premature);
90static CURLcode file_connect(struct connectdata *conn, bool *done);
91static CURLcode file_disconnect(struct connectdata *conn,
92                                bool dead_connection);
93
94
95/*
96 * FILE scheme handler.
97 */
98
99const struct Curl_handler Curl_handler_file = {
100  "FILE",                               /* scheme */
101  ZERO_NULL,                            /* setup_connection */
102  file_do,                              /* do_it */
103  file_done,                            /* done */
104  ZERO_NULL,                            /* do_more */
105  file_connect,                         /* connect_it */
106  ZERO_NULL,                            /* connecting */
107  ZERO_NULL,                            /* doing */
108  ZERO_NULL,                            /* proto_getsock */
109  ZERO_NULL,                            /* doing_getsock */
110  ZERO_NULL,                            /* domore_getsock */
111  ZERO_NULL,                            /* perform_getsock */
112  file_disconnect,                      /* disconnect */
113  ZERO_NULL,                            /* readwrite */
114  0,                                    /* defport */
115  CURLPROTO_FILE,                       /* protocol */
116  PROTOPT_NONETWORK | PROTOPT_NOURLQUERY /* flags */
117};
118
119
120 /*
121  Check if this is a range download, and if so, set the internal variables
122  properly. This code is copied from the FTP implementation and might as
123  well be factored out.
124 */
125static CURLcode file_range(struct connectdata *conn)
126{
127  curl_off_t from, to;
128  curl_off_t totalsize=-1;
129  char *ptr;
130  char *ptr2;
131  struct SessionHandle *data = conn->data;
132
133  if(data->state.use_range && data->state.range) {
134    from=curlx_strtoofft(data->state.range, &ptr, 0);
135    while(*ptr && (ISSPACE(*ptr) || (*ptr=='-')))
136      ptr++;
137    to=curlx_strtoofft(ptr, &ptr2, 0);
138    if(ptr == ptr2) {
139      /* we didn't get any digit */
140      to=-1;
141    }
142    if((-1 == to) && (from>=0)) {
143      /* X - */
144      data->state.resume_from = from;
145      DEBUGF(infof(data, "RANGE %" FORMAT_OFF_T " to end of file\n",
146                   from));
147    }
148    else if(from < 0) {
149      /* -Y */
150      data->req.maxdownload = -from;
151      data->state.resume_from = from;
152      DEBUGF(infof(data, "RANGE the last %" FORMAT_OFF_T " bytes\n",
153                   -from));
154    }
155    else {
156      /* X-Y */
157      totalsize = to-from;
158      data->req.maxdownload = totalsize+1; /* include last byte */
159      data->state.resume_from = from;
160      DEBUGF(infof(data, "RANGE from %" FORMAT_OFF_T
161                   " getting %" FORMAT_OFF_T " bytes\n",
162                   from, data->req.maxdownload));
163    }
164    DEBUGF(infof(data, "range-download from %" FORMAT_OFF_T
165                 " to %" FORMAT_OFF_T ", totally %" FORMAT_OFF_T " bytes\n",
166                 from, to, data->req.maxdownload));
167  }
168  else
169    data->req.maxdownload = -1;
170  return CURLE_OK;
171}
172
173/*
174 * file_connect() gets called from Curl_protocol_connect() to allow us to
175 * do protocol-specific actions at connect-time.  We emulate a
176 * connect-then-transfer protocol and "connect" to the file here
177 */
178static CURLcode file_connect(struct connectdata *conn, bool *done)
179{
180  struct SessionHandle *data = conn->data;
181  char *real_path;
182  struct FILEPROTO *file;
183  int fd;
184#ifdef DOS_FILESYSTEM
185  int i;
186  char *actual_path;
187#endif
188
189  /* If there already is a protocol-specific struct allocated for this
190     sessionhandle, deal with it */
191  Curl_reset_reqproto(conn);
192
193  real_path = curl_easy_unescape(data, data->state.path, 0, NULL);
194  if(!real_path)
195    return CURLE_OUT_OF_MEMORY;
196
197  if(!data->state.proto.file) {
198    file = calloc(1, sizeof(struct FILEPROTO));
199    if(!file) {
200      free(real_path);
201      return CURLE_OUT_OF_MEMORY;
202    }
203    data->state.proto.file = file;
204  }
205  else {
206    /* file is not a protocol that can deal with "persistancy" */
207    file = data->state.proto.file;
208    Curl_safefree(file->freepath);
209    file->path = NULL;
210    if(file->fd != -1)
211      close(file->fd);
212    file->fd = -1;
213  }
214
215#ifdef DOS_FILESYSTEM
216  /* If the first character is a slash, and there's
217     something that looks like a drive at the beginning of
218     the path, skip the slash.  If we remove the initial
219     slash in all cases, paths without drive letters end up
220     relative to the current directory which isn't how
221     browsers work.
222
223     Some browsers accept | instead of : as the drive letter
224     separator, so we do too.
225
226     On other platforms, we need the slash to indicate an
227     absolute pathname.  On Windows, absolute paths start
228     with a drive letter.
229  */
230  actual_path = real_path;
231  if((actual_path[0] == '/') &&
232      actual_path[1] &&
233     (actual_path[2] == ':' || actual_path[2] == '|')) {
234    actual_path[2] = ':';
235    actual_path++;
236  }
237
238  /* change path separators from '/' to '\\' for DOS, Windows and OS/2 */
239  for(i=0; actual_path[i] != '\0'; ++i)
240    if(actual_path[i] == '/')
241      actual_path[i] = '\\';
242
243  fd = open_readonly(actual_path, O_RDONLY|O_BINARY);
244  file->path = actual_path;
245#else
246  fd = open_readonly(real_path, O_RDONLY);
247  file->path = real_path;
248#endif
249  file->freepath = real_path; /* free this when done */
250
251  file->fd = fd;
252  if(!data->set.upload && (fd == -1)) {
253    failf(data, "Couldn't open file %s", data->state.path);
254    file_done(conn, CURLE_FILE_COULDNT_READ_FILE, FALSE);
255    return CURLE_FILE_COULDNT_READ_FILE;
256  }
257  *done = TRUE;
258
259  return CURLE_OK;
260}
261
262static CURLcode file_done(struct connectdata *conn,
263                               CURLcode status, bool premature)
264{
265  struct FILEPROTO *file = conn->data->state.proto.file;
266  (void)status; /* not used */
267  (void)premature; /* not used */
268
269  if(file) {
270    Curl_safefree(file->freepath);
271    file->path = NULL;
272    if(file->fd != -1)
273      close(file->fd);
274    file->fd = -1;
275  }
276
277  return CURLE_OK;
278}
279
280static CURLcode file_disconnect(struct connectdata *conn,
281                                bool dead_connection)
282{
283  struct FILEPROTO *file = conn->data->state.proto.file;
284  (void)dead_connection; /* not used */
285
286  if(file) {
287    Curl_safefree(file->freepath);
288    file->path = NULL;
289    if(file->fd != -1)
290      close(file->fd);
291    file->fd = -1;
292  }
293
294  return CURLE_OK;
295}
296
297#ifdef DOS_FILESYSTEM
298#define DIRSEP '\\'
299#else
300#define DIRSEP '/'
301#endif
302
303static CURLcode file_upload(struct connectdata *conn)
304{
305  struct FILEPROTO *file = conn->data->state.proto.file;
306  const char *dir = strchr(file->path, DIRSEP);
307  int fd;
308  int mode;
309  CURLcode res=CURLE_OK;
310  struct SessionHandle *data = conn->data;
311  char *buf = data->state.buffer;
312  size_t nread;
313  size_t nwrite;
314  curl_off_t bytecount = 0;
315  struct timeval now = Curl_tvnow();
316  struct_stat file_stat;
317  const char* buf2;
318
319  /*
320   * Since FILE: doesn't do the full init, we need to provide some extra
321   * assignments here.
322   */
323  conn->fread_func = data->set.fread_func;
324  conn->fread_in = data->set.in;
325  conn->data->req.upload_fromhere = buf;
326
327  if(!dir)
328    return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
329
330  if(!dir[1])
331    return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
332
333#ifdef O_BINARY
334#define MODE_DEFAULT O_WRONLY|O_CREAT|O_BINARY
335#else
336#define MODE_DEFAULT O_WRONLY|O_CREAT
337#endif
338
339  if(data->state.resume_from)
340    mode = MODE_DEFAULT|O_APPEND;
341  else
342    mode = MODE_DEFAULT|O_TRUNC;
343
344  fd = open(file->path, mode, conn->data->set.new_file_perms);
345  if(fd < 0) {
346    failf(data, "Can't open %s for writing", file->path);
347    return CURLE_WRITE_ERROR;
348  }
349
350  if(-1 != data->set.infilesize)
351    /* known size of data to "upload" */
352    Curl_pgrsSetUploadSize(data, data->set.infilesize);
353
354  /* treat the negative resume offset value as the case of "-" */
355  if(data->state.resume_from < 0) {
356    if(fstat(fd, &file_stat)) {
357      close(fd);
358      failf(data, "Can't get the size of %s", file->path);
359      return CURLE_WRITE_ERROR;
360    }
361    else
362      data->state.resume_from = (curl_off_t)file_stat.st_size;
363  }
364
365  while(res == CURLE_OK) {
366    int readcount;
367    res = Curl_fillreadbuffer(conn, BUFSIZE, &readcount);
368    if(res)
369      break;
370
371    if(readcount <= 0)  /* fix questionable compare error. curlvms */
372      break;
373
374    nread = (size_t)readcount;
375
376    /*skip bytes before resume point*/
377    if(data->state.resume_from) {
378      if((curl_off_t)nread <= data->state.resume_from ) {
379        data->state.resume_from -= nread;
380        nread = 0;
381        buf2 = buf;
382      }
383      else {
384        buf2 = buf + data->state.resume_from;
385        nread -= (size_t)data->state.resume_from;
386        data->state.resume_from = 0;
387      }
388    }
389    else
390      buf2 = buf;
391
392    /* write the data to the target */
393    nwrite = write(fd, buf2, nread);
394    if(nwrite != nread) {
395      res = CURLE_SEND_ERROR;
396      break;
397    }
398
399    bytecount += nread;
400
401    Curl_pgrsSetUploadCounter(data, bytecount);
402
403    if(Curl_pgrsUpdate(conn))
404      res = CURLE_ABORTED_BY_CALLBACK;
405    else
406      res = Curl_speedcheck(data, now);
407  }
408  if(!res && Curl_pgrsUpdate(conn))
409    res = CURLE_ABORTED_BY_CALLBACK;
410
411  close(fd);
412
413  return res;
414}
415
416/*
417 * file_do() is the protocol-specific function for the do-phase, separated
418 * from the connect-phase above. Other protocols merely setup the transfer in
419 * the do-phase, to have it done in the main transfer loop but since some
420 * platforms we support don't allow select()ing etc on file handles (as
421 * opposed to sockets) we instead perform the whole do-operation in this
422 * function.
423 */
424static CURLcode file_do(struct connectdata *conn, bool *done)
425{
426  /* This implementation ignores the host name in conformance with
427     RFC 1738. Only local files (reachable via the standard file system)
428     are supported. This means that files on remotely mounted directories
429     (via NFS, Samba, NT sharing) can be accessed through a file:// URL
430  */
431  CURLcode res = CURLE_OK;
432  struct_stat statbuf; /* struct_stat instead of struct stat just to allow the
433                          Windows version to have a different struct without
434                          having to redefine the simple word 'stat' */
435  curl_off_t expected_size=0;
436  bool fstated=FALSE;
437  ssize_t nread;
438  struct SessionHandle *data = conn->data;
439  char *buf = data->state.buffer;
440  curl_off_t bytecount = 0;
441  int fd;
442  struct timeval now = Curl_tvnow();
443
444  *done = TRUE; /* unconditionally */
445
446  Curl_initinfo(data);
447  Curl_pgrsStartNow(data);
448
449  if(data->set.upload)
450    return file_upload(conn);
451
452  /* get the fd from the connection phase */
453  fd = conn->data->state.proto.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    CURLcode result;
476    snprintf(buf, sizeof(data->state.buffer),
477             "Content-Length: %" FORMAT_OFF_T "\r\n", expected_size);
478    result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0);
479    if(result)
480      return result;
481
482    result = Curl_client_write(conn, CLIENTWRITE_BOTH,
483                               (char *)"Accept-ranges: bytes\r\n", 0);
484    if(result)
485      return result;
486
487    if(fstated) {
488      time_t filetime = (time_t)statbuf.st_mtime;
489      struct tm buffer;
490      const struct tm *tm = &buffer;
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    }
507    /* if we fstat()ed the file, set the file size to make it available post-
508       transfer */
509    if(fstated)
510      Curl_pgrsSetDownloadSize(data, expected_size);
511    return result;
512  }
513
514  /* Check whether file range has been specified */
515  file_range(conn);
516
517  /* Adjust the start offset in case we want to get the N last bytes
518   * of the stream iff the filesize could be determined */
519  if(data->state.resume_from < 0) {
520    if(!fstated) {
521      failf(data, "Can't get the size of file.");
522      return CURLE_READ_ERROR;
523    }
524    else
525      data->state.resume_from += (curl_off_t)statbuf.st_size;
526  }
527
528  if(data->state.resume_from <= expected_size)
529    expected_size -= data->state.resume_from;
530  else {
531    failf(data, "failed to resume file:// transfer");
532    return CURLE_BAD_DOWNLOAD_RESUME;
533  }
534
535  /* A high water mark has been specified so we obey... */
536  if(data->req.maxdownload > 0)
537    expected_size = data->req.maxdownload;
538
539  if(fstated && (expected_size == 0))
540    return CURLE_OK;
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(res == CURLE_OK) {
558    /* Don't fill a whole buffer if we want less than all data */
559    size_t bytestoread =
560      (expected_size < CURL_OFF_T_C(BUFSIZE) - CURL_OFF_T_C(1)) ?
561      curlx_sotouz(expected_size) : BUFSIZE - 1;
562
563    nread = read(fd, buf, bytestoread);
564
565    if(nread > 0)
566      buf[nread] = 0;
567
568    if(nread <= 0 || expected_size == 0)
569      break;
570
571    bytecount += nread;
572    expected_size -= nread;
573
574    res = Curl_client_write(conn, CLIENTWRITE_BODY, buf, nread);
575    if(res)
576      return res;
577
578    Curl_pgrsSetDownloadCounter(data, bytecount);
579
580    if(Curl_pgrsUpdate(conn))
581      res = CURLE_ABORTED_BY_CALLBACK;
582    else
583      res = Curl_speedcheck(data, now);
584  }
585  if(Curl_pgrsUpdate(conn))
586    res = CURLE_ABORTED_BY_CALLBACK;
587
588  return res;
589}
590
591#endif
592