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