blob: 91176aa1168d998f23d9a601fb5c054ae8ab3875 [file] [log] [blame]
Kristian Monsen5ab50182010-05-14 18:53:44 +01001/***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
Lucas Eckels9bd90e62012-08-06 15:07:02 -07008 * Copyright (C) 1998 - 2010, Daniel Stenberg, <daniel@haxx.se>, et al.
Kristian Monsen5ab50182010-05-14 18:53:44 +01009 *
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);
Lucas Eckels9bd90e62012-08-06 15:07:02 -0700149 while(*ptr && (isspace((int)*ptr) || (*ptr=='-')))
Kristian Monsen5ab50182010-05-14 18:53:44 +0100150 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 */
Kristian Monsen5ab50182010-05-14 18:53:44 +0100164 data->req.maxdownload = -from;
165 data->state.resume_from = from;
166 DEBUGF(infof(data, "RANGE the last %" FORMAT_OFF_T " bytes\n",
Lucas Eckels9bd90e62012-08-06 15:07:02 -0700167 -from));
Kristian Monsen5ab50182010-05-14 18:53:44 +0100168 }
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 */
192static 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
277static 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
297static 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 */
425static 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