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