Base code merged to SPIN 2.4
[platform/upstream/curl.git] / lib / file.c
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2014, 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
87 static CURLcode file_do(struct connectdata *, bool *done);
88 static CURLcode file_done(struct connectdata *conn,
89                           CURLcode status, bool premature);
90 static CURLcode file_connect(struct connectdata *conn, bool *done);
91 static CURLcode file_disconnect(struct connectdata *conn,
92                                 bool dead_connection);
93 static CURLcode file_setup_connection(struct connectdata *conn);
94
95 /*
96  * FILE scheme handler.
97  */
98
99 const 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
120 static 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  */
135 static 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  */
189 static 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   int real_path_len;
200
201   real_path = curl_easy_unescape(data, data->state.path, 0, &real_path_len);
202   if(!real_path)
203     return CURLE_OUT_OF_MEMORY;
204
205 #ifdef DOS_FILESYSTEM
206   /* If the first character is a slash, and there's
207      something that looks like a drive at the beginning of
208      the path, skip the slash.  If we remove the initial
209      slash in all cases, paths without drive letters end up
210      relative to the current directory which isn't how
211      browsers work.
212
213      Some browsers accept | instead of : as the drive letter
214      separator, so we do too.
215
216      On other platforms, we need the slash to indicate an
217      absolute pathname.  On Windows, absolute paths start
218      with a drive letter.
219   */
220   actual_path = real_path;
221   if((actual_path[0] == '/') &&
222       actual_path[1] &&
223      (actual_path[2] == ':' || actual_path[2] == '|')) {
224     actual_path[2] = ':';
225     actual_path++;
226     real_path_len--;
227   }
228
229   /* change path separators from '/' to '\\' for DOS, Windows and OS/2 */
230   for(i=0; i < real_path_len; ++i)
231     if(actual_path[i] == '/')
232       actual_path[i] = '\\';
233     else if(!actual_path[i]) /* binary zero */
234       return CURLE_URL_MALFORMAT;
235
236   fd = open_readonly(actual_path, O_RDONLY|O_BINARY);
237   file->path = actual_path;
238 #else
239   if(memchr(real_path, 0, real_path_len))
240     /* binary zeroes indicate foul play */
241     return CURLE_URL_MALFORMAT;
242
243   fd = open_readonly(real_path, O_RDONLY);
244   file->path = real_path;
245 #endif
246   file->freepath = real_path; /* free this when done */
247
248   file->fd = fd;
249   if(!data->set.upload && (fd == -1)) {
250     failf(data, "Couldn't open file %s", data->state.path);
251     file_done(conn, CURLE_FILE_COULDNT_READ_FILE, FALSE);
252     return CURLE_FILE_COULDNT_READ_FILE;
253   }
254   *done = TRUE;
255
256   return CURLE_OK;
257 }
258
259 static CURLcode file_done(struct connectdata *conn,
260                                CURLcode status, bool premature)
261 {
262   struct FILEPROTO *file = conn->data->req.protop;
263   (void)status; /* not used */
264   (void)premature; /* not used */
265
266   if(file) {
267     Curl_safefree(file->freepath);
268     file->path = NULL;
269     if(file->fd != -1)
270       close(file->fd);
271     file->fd = -1;
272   }
273
274   return CURLE_OK;
275 }
276
277 static CURLcode file_disconnect(struct connectdata *conn,
278                                 bool dead_connection)
279 {
280   struct FILEPROTO *file = conn->data->req.protop;
281   (void)dead_connection; /* not used */
282
283   if(file) {
284     Curl_safefree(file->freepath);
285     file->path = NULL;
286     if(file->fd != -1)
287       close(file->fd);
288     file->fd = -1;
289   }
290
291   return CURLE_OK;
292 }
293
294 #ifdef DOS_FILESYSTEM
295 #define DIRSEP '\\'
296 #else
297 #define DIRSEP '/'
298 #endif
299
300 static CURLcode file_upload(struct connectdata *conn)
301 {
302   struct FILEPROTO *file = conn->data->req.protop;
303   const char *dir = strchr(file->path, DIRSEP);
304   int fd;
305   int mode;
306   CURLcode result = CURLE_OK;
307   struct SessionHandle *data = conn->data;
308   char *buf = data->state.buffer;
309   size_t nread;
310   size_t nwrite;
311   curl_off_t bytecount = 0;
312   struct timeval now = Curl_tvnow();
313   struct_stat file_stat;
314   const char* buf2;
315
316   /*
317    * Since FILE: doesn't do the full init, we need to provide some extra
318    * assignments here.
319    */
320   conn->fread_func = data->set.fread_func;
321   conn->fread_in = data->set.in;
322   conn->data->req.upload_fromhere = buf;
323
324   if(!dir)
325     return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
326
327   if(!dir[1])
328     return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
329
330 #ifdef O_BINARY
331 #define MODE_DEFAULT O_WRONLY|O_CREAT|O_BINARY
332 #else
333 #define MODE_DEFAULT O_WRONLY|O_CREAT
334 #endif
335
336   if(data->state.resume_from)
337     mode = MODE_DEFAULT|O_APPEND;
338   else
339     mode = MODE_DEFAULT|O_TRUNC;
340
341   fd = open(file->path, mode, conn->data->set.new_file_perms);
342   if(fd < 0) {
343     failf(data, "Can't open %s for writing", file->path);
344     return CURLE_WRITE_ERROR;
345   }
346
347   if(-1 != data->state.infilesize)
348     /* known size of data to "upload" */
349     Curl_pgrsSetUploadSize(data, data->state.infilesize);
350
351   /* treat the negative resume offset value as the case of "-" */
352   if(data->state.resume_from < 0) {
353     if(fstat(fd, &file_stat)) {
354       close(fd);
355       failf(data, "Can't get the size of %s", file->path);
356       return CURLE_WRITE_ERROR;
357     }
358     else
359       data->state.resume_from = (curl_off_t)file_stat.st_size;
360   }
361
362   while(!result) {
363     int readcount;
364     result = Curl_fillreadbuffer(conn, BUFSIZE, &readcount);
365     if(result)
366       break;
367
368     if(readcount <= 0)  /* fix questionable compare error. curlvms */
369       break;
370
371     nread = (size_t)readcount;
372
373     /*skip bytes before resume point*/
374     if(data->state.resume_from) {
375       if((curl_off_t)nread <= data->state.resume_from ) {
376         data->state.resume_from -= nread;
377         nread = 0;
378         buf2 = buf;
379       }
380       else {
381         buf2 = buf + data->state.resume_from;
382         nread -= (size_t)data->state.resume_from;
383         data->state.resume_from = 0;
384       }
385     }
386     else
387       buf2 = buf;
388
389     /* write the data to the target */
390     nwrite = write(fd, buf2, nread);
391     if(nwrite != nread) {
392       result = CURLE_SEND_ERROR;
393       break;
394     }
395
396     bytecount += nread;
397
398     Curl_pgrsSetUploadCounter(data, bytecount);
399
400     if(Curl_pgrsUpdate(conn))
401       result = CURLE_ABORTED_BY_CALLBACK;
402     else
403       result = Curl_speedcheck(data, now);
404   }
405   if(!result && Curl_pgrsUpdate(conn))
406     result = CURLE_ABORTED_BY_CALLBACK;
407
408   close(fd);
409
410   return result;
411 }
412
413 /*
414  * file_do() is the protocol-specific function for the do-phase, separated
415  * from the connect-phase above. Other protocols merely setup the transfer in
416  * the do-phase, to have it done in the main transfer loop but since some
417  * platforms we support don't allow select()ing etc on file handles (as
418  * opposed to sockets) we instead perform the whole do-operation in this
419  * function.
420  */
421 static CURLcode file_do(struct connectdata *conn, bool *done)
422 {
423   /* This implementation ignores the host name in conformance with
424      RFC 1738. Only local files (reachable via the standard file system)
425      are supported. This means that files on remotely mounted directories
426      (via NFS, Samba, NT sharing) can be accessed through a file:// URL
427   */
428   CURLcode result = CURLE_OK;
429   struct_stat statbuf; /* struct_stat instead of struct stat just to allow the
430                           Windows version to have a different struct without
431                           having to redefine the simple word 'stat' */
432   curl_off_t expected_size=0;
433   bool fstated=FALSE;
434   ssize_t nread;
435   struct SessionHandle *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     snprintf(buf, sizeof(data->state.buffer),
476              "Content-Length: %" CURL_FORMAT_CURL_OFF_T "\r\n", expected_size);
477     result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0);
478     if(result)
479       return result;
480
481     result = Curl_client_write(conn, CLIENTWRITE_BOTH,
482                                (char *)"Accept-ranges: bytes\r\n", 0);
483     if(result)
484       return result;
485
486     if(fstated) {
487       time_t filetime = (time_t)statbuf.st_mtime;
488       struct tm buffer;
489       const struct tm *tm = &buffer;
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     }
506     /* if we fstat()ed the file, set the file size to make it available post-
507        transfer */
508     if(fstated)
509       Curl_pgrsSetDownloadSize(data, expected_size);
510     return result;
511   }
512
513   /* Check whether file range has been specified */
514   file_range(conn);
515
516   /* Adjust the start offset in case we want to get the N last bytes
517    * of the stream iff the filesize could be determined */
518   if(data->state.resume_from < 0) {
519     if(!fstated) {
520       failf(data, "Can't get the size of file.");
521       return CURLE_READ_ERROR;
522     }
523     else
524       data->state.resume_from += (curl_off_t)statbuf.st_size;
525   }
526
527   if(data->state.resume_from <= expected_size)
528     expected_size -= data->state.resume_from;
529   else {
530     failf(data, "failed to resume file:// transfer");
531     return CURLE_BAD_DOWNLOAD_RESUME;
532   }
533
534   /* A high water mark has been specified so we obey... */
535   if(data->req.maxdownload > 0)
536     expected_size = data->req.maxdownload;
537
538   if(fstated && (expected_size == 0))
539     return CURLE_OK;
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       (expected_size < CURL_OFF_T_C(BUFSIZE) - CURL_OFF_T_C(1)) ?
560       curlx_sotouz(expected_size) : BUFSIZE - 1;
561
562     nread = read(fd, buf, bytestoread);
563
564     if(nread > 0)
565       buf[nread] = 0;
566
567     if(nread <= 0 || expected_size == 0)
568       break;
569
570     bytecount += nread;
571     expected_size -= nread;
572
573     result = Curl_client_write(conn, CLIENTWRITE_BODY, buf, nread);
574     if(result)
575       return result;
576
577     Curl_pgrsSetDownloadCounter(data, bytecount);
578
579     if(Curl_pgrsUpdate(conn))
580       result = CURLE_ABORTED_BY_CALLBACK;
581     else
582       result = Curl_speedcheck(data, now);
583   }
584   if(Curl_pgrsUpdate(conn))
585     result = CURLE_ABORTED_BY_CALLBACK;
586
587   return result;
588 }
589
590 #endif