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