Andres Garcia pointed out that we searched for a slash badly since it is
[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 #if defined(WIN32) || defined(__EMX__)
184 #define DIRSEP '\\'
185 #else
186 #define DIRSEP '/'
187 #endif
188
189 static CURLcode file_upload(struct connectdata *conn)
190 {
191   struct FILEPROTO *file = conn->proto.file;
192   char *dir = strchr(file->path, DIRSEP);
193   FILE *fp;
194   CURLcode res=CURLE_OK;
195   struct SessionHandle *data = conn->data;
196   char *buf = data->state.buffer;
197   size_t nread;
198   size_t nwrite;
199   curl_off_t bytecount = 0;
200   struct timeval now = Curl_tvnow();
201
202   /*
203    * Since FILE: doesn't do the full init, we need to provide some extra
204    * assignments here.
205    */
206   conn->fread = data->set.fread;
207   conn->fread_in = data->set.in;
208   conn->upload_fromhere = buf;
209
210   if(!dir)
211     return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
212
213   if(!dir[1])
214      return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
215
216   fp = fopen(file->path, "wb");
217   if(!fp) {
218     failf(data, "Can't open %s for writing", file->path);
219     return CURLE_WRITE_ERROR;
220   }
221
222   if(-1 != data->set.infilesize)
223     /* known size of data to "upload" */
224     Curl_pgrsSetUploadSize(data, data->set.infilesize);
225
226   while (res == CURLE_OK) {
227     int readcount;
228     res = Curl_fillreadbuffer(conn, BUFSIZE, &readcount);
229     if(res)
230       return res;
231
232     nread = (size_t)readcount;
233
234     if (nread <= 0)
235       break;
236
237     /* write the data to the target */
238     nwrite = fwrite(buf, 1, nread, fp);
239     if(nwrite != nread) {
240       res = CURLE_SEND_ERROR;
241       break;
242     }
243
244     bytecount += nread;
245
246     Curl_pgrsSetUploadCounter(data, bytecount);
247
248     if(Curl_pgrsUpdate(conn))
249       res = CURLE_ABORTED_BY_CALLBACK;
250     else
251       res = Curl_speedcheck(data, now);
252   }
253   if(!res && Curl_pgrsUpdate(conn))
254     res = CURLE_ABORTED_BY_CALLBACK;
255
256   fclose(fp);
257
258   return res;
259 }
260
261 /*
262  * Curl_file() is the protocol-specific function for the do-phase, separated
263  * from the connect-phase above. Other protocols merely setup the transfer in
264  * the do-phase, to have it done in the main transfer loop but since some
265  * platforms we support don't allow select()ing etc on file handles (as
266  * opposed to sockets) we instead perform the whole do-operation in this
267  * function.
268  */
269 CURLcode Curl_file(struct connectdata *conn)
270 {
271   /* This implementation ignores the host name in conformance with
272      RFC 1738. Only local files (reachable via the standard file system)
273      are supported. This means that files on remotely mounted directories
274      (via NFS, Samba, NT sharing) can be accessed through a file:// URL
275   */
276   CURLcode res = CURLE_OK;
277   struct stat statbuf;
278   curl_off_t expected_size=0;
279   bool fstated=FALSE;
280   ssize_t nread;
281   struct SessionHandle *data = conn->data;
282   char *buf = data->state.buffer;
283   curl_off_t bytecount = 0;
284   int fd;
285   struct timeval now = Curl_tvnow();
286
287   Curl_readwrite_init(conn);
288   Curl_initinfo(data);
289   Curl_pgrsStartNow(data);
290
291   if(data->set.upload)
292     return file_upload(conn);
293
294   /* get the fd from the connection phase */
295   fd = conn->proto.file->fd;
296
297   /* VMS: This only works reliable for STREAMLF files */
298   if( -1 != fstat(fd, &statbuf)) {
299     /* we could stat it, then read out the size */
300     expected_size = statbuf.st_size;
301     fstated = TRUE;
302   }
303
304   /* If we have selected NOBODY and HEADER, it means that we only want file
305      information. Which for FILE can't be much more than the file size and
306      date. */
307   if(conn->bits.no_body && data->set.include_header && fstated) {
308     CURLcode result;
309     snprintf(buf, sizeof(data->state.buffer),
310              "Content-Length: %" FORMAT_OFF_T "\r\n", expected_size);
311     result = Curl_client_write(data, CLIENTWRITE_BOTH, buf, 0);
312     if(result)
313       return result;
314
315     result = Curl_client_write(data, CLIENTWRITE_BOTH,
316                                (char *)"Accept-ranges: bytes\r\n", 0);
317     if(result)
318       return result;
319
320 #ifdef HAVE_STRFTIME
321     if(fstated) {
322       struct tm *tm;
323       time_t clock = (time_t)statbuf.st_mtime;
324 #ifdef HAVE_GMTIME_R
325       struct tm buffer;
326       tm = (struct tm *)gmtime_r(&clock, &buffer);
327 #else
328       tm = gmtime(&clock);
329 #endif
330       /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */
331       strftime(buf, BUFSIZE-1, "Last-Modified: %a, %d %b %Y %H:%M:%S GMT\r\n",
332                tm);
333       result = Curl_client_write(data, CLIENTWRITE_BOTH, buf, 0);
334     }
335 #endif
336     return result;
337   }
338
339   /* Added by Dolbneff A.V & Spiridonoff A.V */
340   if (conn->resume_from <= expected_size)
341     expected_size -= conn->resume_from;
342   else
343     /* Is this error code suitable in such situation? */
344     return CURLE_FTP_BAD_DOWNLOAD_RESUME;
345
346   if (fstated && (expected_size == 0))
347     return CURLE_OK;
348
349   /* The following is a shortcut implementation of file reading
350      this is both more efficient than the former call to download() and
351      it avoids problems with select() and recv() on file descriptors
352      in Winsock */
353   if(fstated)
354     Curl_pgrsSetDownloadSize(data, expected_size);
355
356   if(conn->resume_from)
357     lseek(fd, conn->resume_from, SEEK_SET);
358
359   Curl_pgrsTime(data, TIMER_STARTTRANSFER);
360
361   while (res == CURLE_OK) {
362     nread = read(fd, buf, BUFSIZE-1);
363
364     if ( nread > 0)
365       buf[nread] = 0;
366
367     if (nread <= 0)
368       break;
369
370     bytecount += nread;
371
372     res = Curl_client_write(data, CLIENTWRITE_BODY, buf, nread);
373     if(res)
374       return res;
375
376     Curl_pgrsSetDownloadCounter(data, bytecount);
377
378     if(Curl_pgrsUpdate(conn))
379       res = CURLE_ABORTED_BY_CALLBACK;
380     else
381       res = Curl_speedcheck(data, now);
382   }
383   if(Curl_pgrsUpdate(conn))
384     res = CURLE_ABORTED_BY_CALLBACK;
385
386   close(fd);
387
388   return res;
389 }
390 #endif