Watcom lacks <sys/time.h>.
[platform/upstream/curl.git] / lib / file.c
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2006, 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 #ifdef HAVE_SYS_TYPES_H
34 #include <sys/types.h>
35 #endif
36 #ifdef HAVE_SYS_STAT_H
37 #include <sys/stat.h>
38 #endif
39
40 #if defined(WIN32) && !defined(__GNUC__) || defined(__MINGW32__)
41 #include <time.h>
42 #include <io.h>
43 #include <fcntl.h>
44 #else
45 #ifdef HAVE_SYS_SOCKET_H
46 #include <sys/socket.h>
47 #endif
48 #ifdef HAVE_NETINET_IN_H
49 #include <netinet/in.h>
50 #endif
51 #ifndef __WATCOMC__
52 #include <sys/time.h>
53 #endif
54 #ifdef HAVE_UNISTD_H
55 #include <unistd.h>
56 #endif
57 #ifdef HAVE_NETDB_H
58 #include <netdb.h>
59 #endif
60 #ifdef HAVE_ARPA_INET_H
61 #include <arpa/inet.h>
62 #endif
63 #ifdef HAVE_NET_IF_H
64 #include <net/if.h>
65 #endif
66 #include <sys/ioctl.h>
67 #include <signal.h>
68
69 #ifdef HAVE_SYS_PARAM_H
70 #include <sys/param.h>
71 #endif
72
73 #ifdef HAVE_FCNTL_H
74 #include <fcntl.h>
75 #endif
76
77 #endif
78
79 #include "urldata.h"
80 #include <curl/curl.h>
81 #include "progress.h"
82 #include "sendf.h"
83 #include "escape.h"
84 #include "file.h"
85 #include "speedcheck.h"
86 #include "getinfo.h"
87 #include "transfer.h"
88 #include "url.h"
89 #include "memory.h"
90 #include "parsedate.h" /* for the week day and month names */
91
92 #define _MPRINTF_REPLACE /* use our functions only */
93 #include <curl/mprintf.h>
94
95 /* The last #include file should be: */
96 #include "memdebug.h"
97
98 /*
99  * Curl_file_connect() gets called from Curl_protocol_connect() to allow us to
100  * do protocol-specific actions at connect-time.  We emulate a
101  * connect-then-transfer protocol and "connect" to the file here
102  */
103 CURLcode Curl_file_connect(struct connectdata *conn)
104 {
105   char *real_path = curl_easy_unescape(conn->data, conn->path, 0, NULL);
106   struct FILEPROTO *file;
107   int fd;
108 #if defined(WIN32) || defined(MSDOS) || defined(__EMX__)
109   int i;
110   char *actual_path;
111 #endif
112
113   if(!real_path)
114     return CURLE_OUT_OF_MEMORY;
115
116   file = (struct FILEPROTO *)calloc(sizeof(struct FILEPROTO), 1);
117   if(!file) {
118     free(real_path);
119     return CURLE_OUT_OF_MEMORY;
120   }
121
122   conn->proto.file = file;
123
124 #if defined(WIN32) || defined(MSDOS) || defined(__EMX__)
125   /* If the first character is a slash, and there's
126      something that looks like a drive at the beginning of
127      the path, skip the slash.  If we remove the initial
128      slash in all cases, paths without drive letters end up
129      relative to the current directory which isn't how
130      browsers work.
131
132      Some browsers accept | instead of : as the drive letter
133      separator, so we do too.
134
135      On other platforms, we need the slash to indicate an
136      absolute pathname.  On Windows, absolute paths start
137      with a drive letter.
138   */
139   actual_path = real_path;
140   if ((actual_path[0] == '/') &&
141       actual_path[1] &&
142       (actual_path[2] == ':' || actual_path[2] == '|'))
143   {
144     actual_path[2] = ':';
145     actual_path++;
146   }
147
148   /* change path separators from '/' to '\\' for DOS, Windows and OS/2 */
149   for (i=0; actual_path[i] != '\0'; ++i)
150     if (actual_path[i] == '/')
151       actual_path[i] = '\\';
152
153   fd = open(actual_path, O_RDONLY | O_BINARY);  /* no CR/LF translation! */
154   file->path = actual_path;
155 #else
156   fd = open(real_path, O_RDONLY);
157   file->path = real_path;
158 #endif
159   file->freepath = real_path; /* free this when done */
160
161   file->fd = fd;
162   if(!conn->data->set.upload && (fd == -1)) {
163     failf(conn->data, "Couldn't open file %s", conn->path);
164     Curl_file_done(conn, CURLE_FILE_COULDNT_READ_FILE);
165     return CURLE_FILE_COULDNT_READ_FILE;
166   }
167
168   return CURLE_OK;
169 }
170
171 CURLcode Curl_file_done(struct connectdata *conn,
172                         CURLcode status)
173 {
174   struct FILEPROTO *file = conn->proto.file;
175   (void)status; /* not used */
176   Curl_safefree(file->freepath);
177
178   if(file->fd != -1)
179     close(file->fd);
180
181   return CURLE_OK;
182 }
183
184 #if defined(WIN32) || defined(MSDOS) || defined(__EMX__)
185 #define DIRSEP '\\'
186 #else
187 #define DIRSEP '/'
188 #endif
189
190 static CURLcode file_upload(struct connectdata *conn)
191 {
192   struct FILEPROTO *file = conn->proto.file;
193   char *dir = strchr(file->path, DIRSEP);
194   FILE *fp;
195   CURLcode res=CURLE_OK;
196   struct SessionHandle *data = conn->data;
197   char *buf = data->state.buffer;
198   size_t nread;
199   size_t nwrite;
200   curl_off_t bytecount = 0;
201   struct timeval now = Curl_tvnow();
202
203   /*
204    * Since FILE: doesn't do the full init, we need to provide some extra
205    * assignments here.
206    */
207   conn->fread = data->set.fread;
208   conn->fread_in = data->set.in;
209   conn->upload_fromhere = buf;
210
211   if(!dir)
212     return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
213
214   if(!dir[1])
215      return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
216
217   fp = fopen(file->path, "wb");
218   if(!fp) {
219     failf(data, "Can't open %s for writing", file->path);
220     return CURLE_WRITE_ERROR;
221   }
222
223   if(-1 != data->set.infilesize)
224     /* known size of data to "upload" */
225     Curl_pgrsSetUploadSize(data, data->set.infilesize);
226
227   while (res == CURLE_OK) {
228     int readcount;
229     res = Curl_fillreadbuffer(conn, BUFSIZE, &readcount);
230     if(res)
231       break;
232
233     if (readcount <= 0)  /* fix questionable compare error. curlvms */
234       break;
235
236     nread = (size_t)readcount;
237
238     /* write the data to the target */
239     nwrite = fwrite(buf, 1, nread, fp);
240     if(nwrite != nread) {
241       res = CURLE_SEND_ERROR;
242       break;
243     }
244
245     bytecount += nread;
246
247     Curl_pgrsSetUploadCounter(data, bytecount);
248
249     if(Curl_pgrsUpdate(conn))
250       res = CURLE_ABORTED_BY_CALLBACK;
251     else
252       res = Curl_speedcheck(data, now);
253   }
254   if(!res && Curl_pgrsUpdate(conn))
255     res = CURLE_ABORTED_BY_CALLBACK;
256
257   fclose(fp);
258
259   return res;
260 }
261
262 /*
263  * Curl_file() is the protocol-specific function for the do-phase, separated
264  * from the connect-phase above. Other protocols merely setup the transfer in
265  * the do-phase, to have it done in the main transfer loop but since some
266  * platforms we support don't allow select()ing etc on file handles (as
267  * opposed to sockets) we instead perform the whole do-operation in this
268  * function.
269  */
270 CURLcode Curl_file(struct connectdata *conn, bool *done)
271 {
272   /* This implementation ignores the host name in conformance with
273      RFC 1738. Only local files (reachable via the standard file system)
274      are supported. This means that files on remotely mounted directories
275      (via NFS, Samba, NT sharing) can be accessed through a file:// URL
276   */
277   CURLcode res = CURLE_OK;
278   struct_stat statbuf; /* struct_stat instead of struct stat just to allow the
279                           Windows version to have a different struct without
280                           having to redefine the simple word 'stat' */
281   curl_off_t expected_size=0;
282   bool fstated=FALSE;
283   ssize_t nread;
284   struct SessionHandle *data = conn->data;
285   char *buf = data->state.buffer;
286   curl_off_t bytecount = 0;
287   int fd;
288   struct timeval now = Curl_tvnow();
289
290   *done = TRUE; /* unconditionally */
291
292   Curl_readwrite_init(conn);
293   Curl_initinfo(data);
294   Curl_pgrsStartNow(data);
295
296   if(data->set.upload)
297     return file_upload(conn);
298
299   /* get the fd from the connection phase */
300   fd = conn->proto.file->fd;
301
302   /* VMS: This only works reliable for STREAMLF files */
303   if( -1 != fstat(fd, &statbuf)) {
304     /* we could stat it, then read out the size */
305     expected_size = statbuf.st_size;
306     fstated = TRUE;
307   }
308
309   /* If we have selected NOBODY and HEADER, it means that we only want file
310      information. Which for FILE can't be much more than the file size and
311      date. */
312   if(conn->bits.no_body && data->set.include_header && fstated) {
313     CURLcode result;
314     snprintf(buf, sizeof(data->state.buffer),
315              "Content-Length: %" FORMAT_OFF_T "\r\n", expected_size);
316     result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0);
317     if(result)
318       return result;
319
320     result = Curl_client_write(conn, CLIENTWRITE_BOTH,
321                                (char *)"Accept-ranges: bytes\r\n", 0);
322     if(result)
323       return result;
324
325     if(fstated) {
326       struct tm *tm;
327       time_t clock = (time_t)statbuf.st_mtime;
328 #ifdef HAVE_GMTIME_R
329       struct tm buffer;
330       tm = (struct tm *)gmtime_r(&clock, &buffer);
331 #else
332       tm = gmtime(&clock);
333 #endif
334       /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */
335       snprintf(buf, BUFSIZE-1,
336                "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n",
337                Curl_wkday[tm->tm_wday?tm->tm_wday-1:6],
338                tm->tm_mday,
339                Curl_month[tm->tm_mon],
340                tm->tm_year + 1900,
341                tm->tm_hour,
342                tm->tm_min,
343                tm->tm_sec);
344       result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0);
345     }
346     return result;
347   }
348
349   if (conn->resume_from <= expected_size)
350     expected_size -= conn->resume_from;
351   else {
352     failf(data, "failed to resume file:// transfer");
353     return CURLE_BAD_DOWNLOAD_RESUME;
354   }
355
356   if (fstated && (expected_size == 0))
357     return CURLE_OK;
358
359   /* The following is a shortcut implementation of file reading
360      this is both more efficient than the former call to download() and
361      it avoids problems with select() and recv() on file descriptors
362      in Winsock */
363   if(fstated)
364     Curl_pgrsSetDownloadSize(data, expected_size);
365
366   if(conn->resume_from)
367     lseek(fd, conn->resume_from, SEEK_SET);
368
369   Curl_pgrsTime(data, TIMER_STARTTRANSFER);
370
371   while (res == CURLE_OK) {
372     nread = read(fd, buf, BUFSIZE-1);
373
374     if ( nread > 0)
375       buf[nread] = 0;
376
377     if (nread <= 0)
378       break;
379
380     bytecount += nread;
381
382     res = Curl_client_write(conn, CLIENTWRITE_BODY, buf, nread);
383     if(res)
384       return res;
385
386     Curl_pgrsSetDownloadCounter(data, bytecount);
387
388     if(Curl_pgrsUpdate(conn))
389       res = CURLE_ABORTED_BY_CALLBACK;
390     else
391       res = Curl_speedcheck(data, now);
392   }
393   if(Curl_pgrsUpdate(conn))
394     res = CURLE_ABORTED_BY_CALLBACK;
395
396   return res;
397 }
398
399 #endif