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