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