Coverage Report

Created: 2026-01-17 06:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl/lib/file.c
Line
Count
Source
1
/***************************************************************************
2
 *                                  _   _ ____  _
3
 *  Project                     ___| | | |  _ \| |
4
 *                             / __| | | | |_) | |
5
 *                            | (__| |_| |  _ <| |___
6
 *                             \___|\___/|_| \_\_____|
7
 *
8
 * Copyright (C) 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 https://curl.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
 * SPDX-License-Identifier: curl
22
 *
23
 ***************************************************************************/
24
#include "curl_setup.h"
25
26
#ifndef CURL_DISABLE_FILE
27
28
#ifdef HAVE_NETINET_IN_H
29
#include <netinet/in.h>
30
#endif
31
#ifdef HAVE_NETDB_H
32
#include <netdb.h>
33
#endif
34
#ifdef HAVE_ARPA_INET_H
35
#include <arpa/inet.h>
36
#endif
37
#ifdef HAVE_NET_IF_H
38
#include <net/if.h>
39
#endif
40
#ifdef HAVE_SYS_IOCTL_H
41
#include <sys/ioctl.h>
42
#endif
43
44
#ifdef HAVE_SYS_PARAM_H
45
#include <sys/param.h>
46
#endif
47
48
#ifdef HAVE_SYS_TYPES_H
49
#include <sys/types.h>
50
#endif
51
52
#ifdef HAVE_DIRENT_H
53
#include <dirent.h>
54
#endif
55
56
#include "urldata.h"
57
#include "progress.h"
58
#include "sendf.h"
59
#include "curl_trc.h"
60
#include "escape.h"
61
#include "file.h"
62
#include "multiif.h"
63
#include "transfer.h"
64
#include "url.h"
65
#include "parsedate.h" /* for the week day and month names */
66
#include "curlx/fopen.h"
67
#include "curl_range.h"
68
69
#if defined(_WIN32) || defined(MSDOS)
70
#define DOS_FILESYSTEM 1
71
#elif defined(__amigaos4__)
72
#define AMIGA_FILESYSTEM 1
73
#endif
74
75
/* meta key for storing protocol meta at easy handle */
76
7.97k
#define CURL_META_FILE_EASY   "meta:proto:file:easy"
77
78
struct FILEPROTO {
79
  char *path; /* the path we operate on */
80
  char *freepath; /* pointer to the allocated block we must free, this might
81
                     differ from the 'path' pointer */
82
  int fd;     /* open file descriptor to read from! */
83
};
84
85
static void file_cleanup(struct FILEPROTO *file)
86
2.80k
{
87
2.80k
  Curl_safefree(file->freepath);
88
2.80k
  file->path = NULL;
89
2.80k
  if(file->fd != -1) {
90
1.15k
    close(file->fd);
91
1.15k
    file->fd = -1;
92
1.15k
  }
93
2.80k
}
94
95
static void file_easy_dtor(void *key, size_t klen, void *entry)
96
1.40k
{
97
1.40k
  struct FILEPROTO *file = entry;
98
1.40k
  (void)key;
99
1.40k
  (void)klen;
100
1.40k
  file_cleanup(file);
101
1.40k
  curlx_free(file);
102
1.40k
}
103
104
static CURLcode file_setup_connection(struct Curl_easy *data,
105
                                      struct connectdata *conn)
106
1.40k
{
107
1.40k
  struct FILEPROTO *filep;
108
1.40k
  (void)conn;
109
  /* allocate the FILE specific struct */
110
1.40k
  filep = curlx_calloc(1, sizeof(*filep));
111
1.40k
  if(!filep ||
112
1.40k
     Curl_meta_set(data, CURL_META_FILE_EASY, filep, file_easy_dtor))
113
0
    return CURLE_OUT_OF_MEMORY;
114
115
1.40k
  return CURLE_OK;
116
1.40k
}
117
118
static CURLcode file_done(struct Curl_easy *data,
119
                          CURLcode status, bool premature)
120
2.80k
{
121
2.80k
  struct FILEPROTO *file = Curl_meta_get(data, CURL_META_FILE_EASY);
122
2.80k
  (void)status;
123
2.80k
  (void)premature;
124
125
2.80k
  if(file)
126
1.40k
    file_cleanup(file);
127
128
2.80k
  return CURLE_OK;
129
2.80k
}
130
131
/*
132
 * file_connect() gets called from Curl_protocol_connect() to allow us to
133
 * do protocol-specific actions at connect-time. We emulate a
134
 * connect-then-transfer protocol and "connect" to the file here
135
 */
136
static CURLcode file_connect(struct Curl_easy *data, bool *done)
137
2.58k
{
138
2.58k
  char *real_path;
139
2.58k
  struct FILEPROTO *file = Curl_meta_get(data, CURL_META_FILE_EASY);
140
2.58k
  int fd;
141
#ifdef DOS_FILESYSTEM
142
  size_t i;
143
  char *actual_path;
144
#endif
145
2.58k
  size_t real_path_len;
146
2.58k
  CURLcode result;
147
148
2.58k
  if(!file)
149
0
    return CURLE_FAILED_INIT;
150
151
2.58k
  if(file->path) {
152
    /* already connected.
153
     * the handler->connect_it() is normally only called once, but
154
     * FILE does a special check on setting up the connection which
155
     * calls this explicitly. */
156
1.17k
    *done = TRUE;
157
1.17k
    return CURLE_OK;
158
1.17k
  }
159
160
1.40k
  result = Curl_urldecode(data->state.up.path, 0, &real_path,
161
1.40k
                          &real_path_len, REJECT_ZERO);
162
1.40k
  if(result)
163
1
    return result;
164
165
#ifdef DOS_FILESYSTEM
166
  /* If the first character is a slash, and there is
167
     something that looks like a drive at the beginning of
168
     the path, skip the slash. If we remove the initial
169
     slash in all cases, paths without drive letters end up
170
     relative to the current directory which is not how
171
     browsers work.
172
173
     Some browsers accept | instead of : as the drive letter
174
     separator, so we do too.
175
176
     On other platforms, we need the slash to indicate an
177
     absolute pathname. On Windows, absolute paths start
178
     with a drive letter.
179
  */
180
  actual_path = real_path;
181
  if((actual_path[0] == '/') &&
182
      actual_path[1] &&
183
     (actual_path[2] == ':' || actual_path[2] == '|')) {
184
    actual_path[2] = ':';
185
    actual_path++;
186
    real_path_len--;
187
  }
188
189
  /* change path separators from '/' to '\\' for DOS, Windows and OS/2 */
190
  for(i = 0; i < real_path_len; ++i)
191
    if(actual_path[i] == '/')
192
      actual_path[i] = '\\';
193
    else if(!actual_path[i]) { /* binary zero */
194
      Curl_safefree(real_path);
195
      return CURLE_URL_MALFORMAT;
196
    }
197
198
  fd = curlx_open(actual_path, O_RDONLY | CURL_O_BINARY);
199
  file->path = actual_path;
200
#else
201
1.40k
  if(memchr(real_path, 0, real_path_len)) {
202
    /* binary zeroes indicate foul play */
203
0
    Curl_safefree(real_path);
204
0
    return CURLE_URL_MALFORMAT;
205
0
  }
206
207
#ifdef AMIGA_FILESYSTEM
208
  /*
209
   * A leading slash in an AmigaDOS path denotes the parent
210
   * directory, and hence we block this as it is relative.
211
   * Absolute paths start with 'volumename:', so we check for
212
   * this first. Failing that, we treat the path as a real Unix
213
   * path, but only if the application was compiled with -lunix.
214
   */
215
  fd = -1;
216
  file->path = real_path;
217
218
  if(real_path[0] == '/') {
219
    extern int __unix_path_semantics;
220
    if(strchr(real_path + 1, ':')) {
221
      /* Amiga absolute path */
222
      fd = curlx_open(real_path + 1, O_RDONLY);
223
      file->path++;
224
    }
225
    else if(__unix_path_semantics) {
226
      /* -lunix fallback */
227
      fd = curlx_open(real_path, O_RDONLY);
228
    }
229
  }
230
#else
231
1.40k
  fd = curlx_open(real_path, O_RDONLY);
232
1.40k
  file->path = real_path;
233
1.40k
#endif
234
1.40k
#endif
235
1.40k
  curlx_free(file->freepath);
236
1.40k
  file->freepath = real_path; /* free this when done */
237
238
1.40k
  file->fd = fd;
239
1.40k
  if(!data->state.upload && (fd == -1)) {
240
221
    failf(data, "Could not open file %s", data->state.up.path);
241
221
    file_done(data, CURLE_FILE_COULDNT_READ_FILE, FALSE);
242
221
    return CURLE_FILE_COULDNT_READ_FILE;
243
221
  }
244
1.18k
  *done = TRUE;
245
246
1.18k
  return CURLE_OK;
247
1.40k
}
248
249
static CURLcode file_disconnect(struct Curl_easy *data,
250
                                struct connectdata *conn,
251
                                bool dead_connection)
252
1.40k
{
253
1.40k
  (void)dead_connection;
254
1.40k
  (void)conn;
255
1.40k
  return file_done(data, CURLE_OK, FALSE);
256
1.40k
}
257
258
#ifdef DOS_FILESYSTEM
259
#define DIRSEP '\\'
260
#else
261
27
#define DIRSEP '/'
262
#endif
263
264
static CURLcode file_upload(struct Curl_easy *data,
265
                            struct FILEPROTO *file)
266
27
{
267
27
  const char *dir = strchr(file->path, DIRSEP);
268
27
  int fd;
269
27
  int mode;
270
27
  CURLcode result = CURLE_OK;
271
27
  char *xfer_ulbuf;
272
27
  size_t xfer_ulblen;
273
27
  struct_stat file_stat;
274
27
  const char *sendbuf;
275
27
  bool eos = FALSE;
276
277
  /*
278
   * Since FILE: does not do the full init, we need to provide some extra
279
   * assignments here.
280
   */
281
282
27
  if(!dir)
283
0
    return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
284
285
27
  if(!dir[1])
286
3
    return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
287
288
24
  mode = O_WRONLY | O_CREAT | CURL_O_BINARY;
289
24
  if(data->state.resume_from)
290
1
    mode |= O_APPEND;
291
23
  else
292
23
    mode |= O_TRUNC;
293
294
#ifdef _WIN32
295
  fd = curlx_open(file->path, mode,
296
                  data->set.new_file_perms & (_S_IREAD | _S_IWRITE));
297
#elif (defined(ANDROID) || defined(__ANDROID__)) && \
298
  (defined(__i386__) || defined(__arm__))
299
  fd = curlx_open(file->path, mode, (mode_t)data->set.new_file_perms);
300
#else
301
24
  fd = curlx_open(file->path, mode, data->set.new_file_perms);
302
24
#endif
303
24
  if(fd < 0) {
304
21
    failf(data, "cannot open %s for writing", file->path);
305
21
    return CURLE_WRITE_ERROR;
306
21
  }
307
308
3
  if(data->state.infilesize != -1)
309
    /* known size of data to "upload" */
310
3
    Curl_pgrsSetUploadSize(data, data->state.infilesize);
311
312
  /* treat the negative resume offset value as the case of "-" */
313
3
  if(data->state.resume_from < 0) {
314
0
    if(fstat(fd, &file_stat)) {
315
0
      close(fd);
316
0
      failf(data, "cannot get the size of %s", file->path);
317
0
      return CURLE_WRITE_ERROR;
318
0
    }
319
0
    data->state.resume_from = (curl_off_t)file_stat.st_size;
320
0
  }
321
322
3
  result = Curl_multi_xfer_ulbuf_borrow(data, &xfer_ulbuf, &xfer_ulblen);
323
3
  if(result)
324
0
    goto out;
325
326
3
  while(!result && !eos) {
327
3
    size_t nread, nwritten;
328
3
    ssize_t rv;
329
3
    size_t readcount;
330
331
3
    result = Curl_client_read(data, xfer_ulbuf, xfer_ulblen, &readcount, &eos);
332
3
    if(result)
333
0
      break;
334
335
3
    if(!readcount)
336
3
      break;
337
338
0
    nread = readcount;
339
340
    /* skip bytes before resume point */
341
0
    if(data->state.resume_from) {
342
0
      if((curl_off_t)nread <= data->state.resume_from) {
343
0
        data->state.resume_from -= nread;
344
0
        nread = 0;
345
0
        sendbuf = xfer_ulbuf;
346
0
      }
347
0
      else {
348
0
        sendbuf = xfer_ulbuf + data->state.resume_from;
349
0
        nread -= (size_t)data->state.resume_from;
350
0
        data->state.resume_from = 0;
351
0
      }
352
0
    }
353
0
    else
354
0
      sendbuf = xfer_ulbuf;
355
356
    /* write the data to the target */
357
0
    rv = write(fd, sendbuf, nread);
358
0
    if(!curlx_sztouz(rv, &nwritten) || (nwritten != nread)) {
359
0
      result = CURLE_SEND_ERROR;
360
0
      break;
361
0
    }
362
0
    Curl_pgrs_upload_inc(data, nwritten);
363
364
0
    result = Curl_pgrsCheck(data);
365
0
  }
366
3
  if(!result)
367
3
    result = Curl_pgrsUpdate(data);
368
369
3
out:
370
3
  close(fd);
371
3
  Curl_multi_xfer_ulbuf_release(data, xfer_ulbuf);
372
373
3
  return result;
374
3
}
375
376
/*
377
 * file_do() is the protocol-specific function for the do-phase, separated
378
 * from the connect-phase above. Other protocols merely setup the transfer in
379
 * the do-phase, to have it done in the main transfer loop but since some
380
 * platforms we support do not allow select()ing etc on file handles (as
381
 * opposed to sockets) we instead perform the whole do-operation in this
382
 * function.
383
 */
384
static CURLcode file_do(struct Curl_easy *data, bool *done)
385
1.17k
{
386
  /* This implementation ignores the hostname in conformance with
387
     RFC 1738. Only local files (reachable via the standard file system)
388
     are supported. This means that files on remotely mounted directories
389
     (via NFS, Samba, NT sharing) can be accessed through a file:// URL
390
  */
391
1.17k
  struct FILEPROTO *file = Curl_meta_get(data, CURL_META_FILE_EASY);
392
1.17k
  CURLcode result = CURLE_OK;
393
1.17k
  struct_stat statbuf; /* struct_stat instead of struct stat just to allow the
394
                          Windows version to have a different struct without
395
                          having to redefine the simple word 'stat' */
396
1.17k
  curl_off_t expected_size = -1;
397
1.17k
  bool size_known;
398
1.17k
  bool fstated = FALSE;
399
1.17k
  int fd;
400
1.17k
  char *xfer_buf;
401
1.17k
  size_t xfer_blen;
402
403
1.17k
  *done = TRUE; /* unconditionally */
404
1.17k
  if(!file)
405
0
    return CURLE_FAILED_INIT;
406
407
1.17k
  if(data->state.upload)
408
27
    return file_upload(data, file);
409
410
  /* get the fd from the connection phase */
411
1.14k
  fd = file->fd;
412
413
  /* VMS: This only works reliable for STREAMLF files */
414
1.14k
  if(fstat(fd, &statbuf) != -1) {
415
1.14k
    if(!S_ISDIR(statbuf.st_mode))
416
338
      expected_size = statbuf.st_size;
417
    /* and store the modification time */
418
1.14k
    data->info.filetime = statbuf.st_mtime;
419
1.14k
    fstated = TRUE;
420
1.14k
  }
421
422
1.14k
  if(fstated && !data->state.range && data->set.timecondition &&
423
49
     !Curl_meets_timecondition(data, data->info.filetime))
424
17
    return CURLE_OK;
425
426
1.13k
  if(fstated) {
427
1.13k
    time_t filetime;
428
1.13k
    struct tm buffer;
429
1.13k
    const struct tm *tm = &buffer;
430
1.13k
    char header[80];
431
1.13k
    int headerlen;
432
1.13k
    static const char accept_ranges[] = { "Accept-ranges: bytes\r\n" };
433
1.13k
    if(expected_size >= 0) {
434
338
      headerlen =
435
338
        curl_msnprintf(header, sizeof(header),
436
338
                       "Content-Length: %" FMT_OFF_T "\r\n", expected_size);
437
338
      result = Curl_client_write(data, CLIENTWRITE_HEADER, header, headerlen);
438
338
      if(result)
439
0
        return result;
440
441
338
      result = Curl_client_write(data, CLIENTWRITE_HEADER,
442
338
                                 accept_ranges, sizeof(accept_ranges) - 1);
443
338
      if(result != CURLE_OK)
444
0
        return result;
445
338
    }
446
447
1.13k
    filetime = (time_t)statbuf.st_mtime;
448
1.13k
    result = curlx_gmtime(filetime, &buffer);
449
1.13k
    if(result)
450
0
      return result;
451
452
    /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */
453
1.13k
    headerlen =
454
1.13k
      curl_msnprintf(header, sizeof(header),
455
1.13k
                     "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n",
456
1.13k
                     Curl_wkday[tm->tm_wday ? tm->tm_wday - 1 : 6],
457
1.13k
                     tm->tm_mday,
458
1.13k
                     Curl_month[tm->tm_mon],
459
1.13k
                     tm->tm_year + 1900,
460
1.13k
                     tm->tm_hour,
461
1.13k
                     tm->tm_min,
462
1.13k
                     tm->tm_sec);
463
1.13k
    result = Curl_client_write(data, CLIENTWRITE_HEADER, header, headerlen);
464
1.13k
    if(!result)
465
      /* end of headers */
466
1.13k
      result = Curl_client_write(data, CLIENTWRITE_HEADER, "\r\n", 2);
467
1.13k
    if(result)
468
0
      return result;
469
    /* set the file size to make it available post transfer */
470
1.13k
    Curl_pgrsSetDownloadSize(data, expected_size);
471
1.13k
    if(data->req.no_body)
472
1
      return CURLE_OK;
473
1.13k
  }
474
475
  /* Check whether file range has been specified */
476
1.12k
  result = Curl_range(data);
477
1.12k
  if(result)
478
70
    return result;
479
480
  /* Adjust the start offset in case we want to get the N last bytes
481
   * of the stream if the filesize could be determined */
482
1.05k
  if(data->state.resume_from < 0) {
483
178
    if(!fstated) {
484
0
      failf(data, "cannot get the size of file.");
485
0
      return CURLE_READ_ERROR;
486
0
    }
487
178
    data->state.resume_from += (curl_off_t)statbuf.st_size;
488
178
  }
489
490
1.05k
  if(data->state.resume_from > 0) {
491
    /* We check explicitly if we have a start offset, because
492
     * expected_size may be -1 if we do not know how large the file is,
493
     * in which case we should not adjust it. */
494
225
    if(data->state.resume_from <= expected_size)
495
65
      expected_size -= data->state.resume_from;
496
160
    else {
497
160
      failf(data, "failed to resume file:// transfer");
498
160
      return CURLE_BAD_DOWNLOAD_RESUME;
499
160
    }
500
225
  }
501
502
  /* A high water mark has been specified so we obey... */
503
899
  if(data->req.maxdownload > 0)
504
372
    expected_size = data->req.maxdownload;
505
506
899
  if(!fstated || (expected_size <= 0))
507
350
    size_known = FALSE;
508
549
  else
509
549
    size_known = TRUE;
510
511
  /* The following is a shortcut implementation of file reading
512
     this is both more efficient than the former call to download() and
513
     it avoids problems with select() and recv() on file descriptors
514
     in Winsock */
515
899
  if(size_known)
516
549
    Curl_pgrsSetDownloadSize(data, expected_size);
517
518
899
  if(data->state.resume_from) {
519
221
    if(!S_ISDIR(statbuf.st_mode)) {
520
#ifdef __AMIGA__
521
      if(data->state.resume_from !=
522
         lseek(fd, (off_t)data->state.resume_from, SEEK_SET))
523
#else
524
120
      if(data->state.resume_from !=
525
120
         lseek(fd, data->state.resume_from, SEEK_SET))
526
55
#endif
527
55
        return CURLE_BAD_DOWNLOAD_RESUME;
528
120
    }
529
101
    else {
530
101
      return CURLE_BAD_DOWNLOAD_RESUME;
531
101
    }
532
221
  }
533
534
743
  result = Curl_multi_xfer_buf_borrow(data, &xfer_buf, &xfer_blen);
535
743
  if(result)
536
0
    goto out;
537
538
743
  if(!S_ISDIR(statbuf.st_mode)) {
539
14.3k
    while(!result) {
540
14.3k
      ssize_t nread;
541
      /* Do not fill a whole buffer if we want less than all data */
542
14.3k
      size_t bytestoread;
543
544
14.3k
      if(size_known) {
545
14.2k
        bytestoread = (expected_size < (curl_off_t)(xfer_blen - 1)) ?
546
13.8k
          curlx_sotouz(expected_size) : (xfer_blen - 1);
547
14.2k
      }
548
73
      else
549
73
        bytestoread = xfer_blen - 1;
550
551
14.3k
      nread = read(fd, xfer_buf, bytestoread);
552
553
14.3k
      if(nread > 0)
554
14.0k
        xfer_buf[nread] = 0;
555
556
14.3k
      if(nread <= 0 || (size_known && (expected_size == 0)))
557
242
        break;
558
559
14.0k
      if(size_known)
560
14.0k
        expected_size -= nread;
561
562
14.0k
      result = Curl_client_write(data, CLIENTWRITE_BODY, xfer_buf, nread);
563
14.0k
      if(result)
564
22
        goto out;
565
566
14.0k
      result = Curl_pgrsCheck(data);
567
14.0k
      if(result)
568
0
        goto out;
569
14.0k
    }
570
264
  }
571
479
  else {
572
479
#ifdef HAVE_OPENDIR
573
479
    DIR *dir = opendir(file->path);
574
479
    struct dirent *entry;
575
576
479
    if(!dir) {
577
0
      result = CURLE_READ_ERROR;
578
0
      goto out;
579
0
    }
580
479
    else {
581
23.2k
      while((entry = readdir(dir))) {
582
22.7k
        if(entry->d_name[0] != '.') {
583
21.4k
          result = Curl_client_write(data, CLIENTWRITE_BODY,
584
21.4k
                   entry->d_name, strlen(entry->d_name));
585
21.4k
          if(result)
586
12
            break;
587
21.3k
          result = Curl_client_write(data, CLIENTWRITE_BODY, "\n", 1);
588
21.3k
          if(result)
589
5
            break;
590
21.3k
        }
591
22.7k
      }
592
479
      closedir(dir);
593
479
    }
594
#else
595
    failf(data, "Directory listing not yet implemented on this platform.");
596
    result = CURLE_READ_ERROR;
597
#endif
598
479
  }
599
600
721
  if(!result)
601
704
    result = Curl_pgrsUpdate(data);
602
603
743
out:
604
743
  Curl_multi_xfer_buf_release(data, xfer_buf);
605
743
  return result;
606
721
}
607
608
/*
609
 * FILE scheme handler.
610
 */
611
const struct Curl_handler Curl_handler_file = {
612
  "file",                               /* scheme */
613
  file_setup_connection,                /* setup_connection */
614
  file_do,                              /* do_it */
615
  file_done,                            /* done */
616
  ZERO_NULL,                            /* do_more */
617
  file_connect,                         /* connect_it */
618
  ZERO_NULL,                            /* connecting */
619
  ZERO_NULL,                            /* doing */
620
  ZERO_NULL,                            /* proto_pollset */
621
  ZERO_NULL,                            /* doing_pollset */
622
  ZERO_NULL,                            /* domore_pollset */
623
  ZERO_NULL,                            /* perform_pollset */
624
  file_disconnect,                      /* disconnect */
625
  ZERO_NULL,                            /* write_resp */
626
  ZERO_NULL,                            /* write_resp_hd */
627
  ZERO_NULL,                            /* connection_check */
628
  ZERO_NULL,                            /* attach connection */
629
  ZERO_NULL,                            /* follow */
630
  0,                                    /* defport */
631
  CURLPROTO_FILE,                       /* protocol */
632
  CURLPROTO_FILE,                       /* family */
633
  PROTOPT_NONETWORK | PROTOPT_NOURLQUERY /* flags */
634
};
635
636
#endif