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