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