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