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