Coverage Report

Created: 2026-03-07 07:03

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
#include "urldata.h"
26
#include "file.h"
27
28
#ifndef CURL_DISABLE_FILE
29
30
#ifdef HAVE_NETINET_IN_H
31
#include <netinet/in.h>
32
#endif
33
#ifdef HAVE_NETDB_H
34
#include <netdb.h>
35
#endif
36
#ifdef HAVE_ARPA_INET_H
37
#include <arpa/inet.h>
38
#endif
39
#ifdef HAVE_NET_IF_H
40
#include <net/if.h>
41
#endif
42
#ifdef HAVE_SYS_IOCTL_H
43
#include <sys/ioctl.h>
44
#endif
45
46
#ifdef HAVE_SYS_PARAM_H
47
#include <sys/param.h>
48
#endif
49
50
#ifdef HAVE_SYS_TYPES_H
51
#include <sys/types.h>
52
#endif
53
54
#ifdef HAVE_DIRENT_H
55
#include <dirent.h>
56
#endif
57
58
#include "progress.h"
59
#include "sendf.h"
60
#include "curl_trc.h"
61
#include "escape.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.84k
#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
3.04k
{
87
3.04k
  Curl_safefree(file->freepath);
88
3.04k
  file->path = NULL;
89
3.04k
  if(file->fd != -1) {
90
1.11k
    curlx_close(file->fd);
91
1.11k
    file->fd = -1;
92
1.11k
  }
93
3.04k
}
94
95
static void file_easy_dtor(void *key, size_t klen, void *entry)
96
1.39k
{
97
1.39k
  struct FILEPROTO *file = entry;
98
1.39k
  (void)key;
99
1.39k
  (void)klen;
100
1.39k
  file_cleanup(file);
101
1.39k
  curlx_free(file);
102
1.39k
}
103
104
static CURLcode file_setup_connection(struct Curl_easy *data,
105
                                      struct connectdata *conn)
106
1.39k
{
107
1.39k
  struct FILEPROTO *filep;
108
1.39k
  (void)conn;
109
  /* allocate the FILE specific struct */
110
1.39k
  filep = curlx_calloc(1, sizeof(*filep));
111
1.39k
  if(!filep ||
112
1.39k
     Curl_meta_set(data, CURL_META_FILE_EASY, filep, file_easy_dtor))
113
0
    return CURLE_OUT_OF_MEMORY;
114
115
1.39k
  return CURLE_OK;
116
1.39k
}
117
118
static CURLcode file_done(struct Curl_easy *data,
119
                          CURLcode status, bool premature)
120
2.78k
{
121
2.78k
  struct FILEPROTO *file = Curl_meta_get(data, CURL_META_FILE_EASY);
122
2.78k
  (void)status;
123
2.78k
  (void)premature;
124
125
2.78k
  if(file)
126
1.64k
    file_cleanup(file);
127
128
2.78k
  return CURLE_OK;
129
2.78k
}
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.53k
{
138
2.53k
  char *real_path;
139
2.53k
  struct FILEPROTO *file = Curl_meta_get(data, CURL_META_FILE_EASY);
140
2.53k
  int fd;
141
#ifdef DOS_FILESYSTEM
142
  size_t i;
143
  char *actual_path;
144
#endif
145
2.53k
  size_t real_path_len;
146
2.53k
  CURLcode result;
147
148
2.53k
  if(!file)
149
0
    return CURLE_FAILED_INIT;
150
151
2.53k
  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.13k
    *done = TRUE;
157
1.13k
    return CURLE_OK;
158
1.13k
  }
159
160
1.39k
  result = Curl_urldecode(data->state.up.path, 0, &real_path,
161
1.39k
                          &real_path_len, REJECT_ZERO);
162
1.39k
  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.39k
  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.39k
  fd = curlx_open(real_path, O_RDONLY);
232
1.39k
  file->path = real_path;
233
1.39k
#endif
234
1.39k
#endif
235
1.39k
  curlx_free(file->freepath);
236
1.39k
  file->freepath = real_path; /* free this when done */
237
238
1.39k
  file->fd = fd;
239
1.39k
  if(!data->state.upload && (fd == -1)) {
240
250
    failf(data, "Could not open file %s", data->state.up.path);
241
250
    file_done(data, CURLE_FILE_COULDNT_READ_FILE, FALSE);
242
250
    return CURLE_FILE_COULDNT_READ_FILE;
243
250
  }
244
1.14k
  *done = TRUE;
245
246
1.14k
  return CURLE_OK;
247
1.39k
}
248
249
static CURLcode file_disconnect(struct Curl_easy *data,
250
                                struct connectdata *conn,
251
                                bool dead_connection)
252
1.39k
{
253
1.39k
  (void)dead_connection;
254
1.39k
  (void)conn;
255
1.39k
  return file_done(data, CURLE_OK, FALSE);
256
1.39k
}
257
258
#ifdef DOS_FILESYSTEM
259
#define DIRSEP '\\'
260
#else
261
46
#define DIRSEP '/'
262
#endif
263
264
static CURLcode file_upload(struct Curl_easy *data,
265
                            struct FILEPROTO *file)
266
46
{
267
46
  const char *dir = strchr(file->path, DIRSEP);
268
46
  int fd;
269
46
  int mode;
270
46
  CURLcode result = CURLE_OK;
271
46
  char *xfer_ulbuf;
272
46
  size_t xfer_ulblen;
273
46
  curlx_struct_stat file_stat;
274
46
  const char *sendbuf;
275
46
  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
46
  if(!dir)
283
0
    return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
284
285
46
  if(!dir[1])
286
3
    return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
287
288
43
  mode = O_WRONLY | O_CREAT | CURL_O_BINARY;
289
43
  if(data->state.resume_from)
290
26
    mode |= O_APPEND;
291
17
  else
292
17
    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
43
  fd = curlx_open(file->path, mode, data->set.new_file_perms);
302
43
#endif
303
43
  if(fd < 0) {
304
15
    failf(data, "cannot open %s for writing", file->path);
305
15
    return CURLE_WRITE_ERROR;
306
15
  }
307
308
28
  if(data->state.infilesize != -1)
309
    /* known size of data to "upload" */
310
28
    Curl_pgrsSetUploadSize(data, data->state.infilesize);
311
312
  /* treat the negative resume offset value as the case of "-" */
313
28
  if(data->state.resume_from < 0) {
314
0
    if(curlx_fstat(fd, &file_stat)) {
315
0
      curlx_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
28
  result = Curl_multi_xfer_ulbuf_borrow(data, &xfer_ulbuf, &xfer_ulblen);
323
28
  if(result)
324
0
    goto out;
325
326
28
  while(!result && !eos) {
327
28
    size_t nread, nwritten;
328
28
    ssize_t rv;
329
28
    size_t readcount;
330
331
28
    result = Curl_client_read(data, xfer_ulbuf, xfer_ulblen, &readcount, &eos);
332
28
    if(result)
333
0
      break;
334
335
28
    if(!readcount)
336
28
      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
28
  if(!result)
367
28
    result = Curl_pgrsUpdate(data);
368
369
28
out:
370
28
  curlx_close(fd);
371
28
  Curl_multi_xfer_ulbuf_release(data, xfer_ulbuf);
372
373
28
  return result;
374
28
}
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.13k
{
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.13k
  struct FILEPROTO *file = Curl_meta_get(data, CURL_META_FILE_EASY);
392
1.13k
  CURLcode result = CURLE_OK;
393
1.13k
  curlx_struct_stat statbuf;
394
1.13k
  curl_off_t expected_size = -1;
395
1.13k
  bool size_known;
396
1.13k
  bool fstated = FALSE;
397
1.13k
  int fd;
398
1.13k
  char *xfer_buf;
399
1.13k
  size_t xfer_blen;
400
401
1.13k
  *done = TRUE; /* unconditionally */
402
1.13k
  if(!file)
403
0
    return CURLE_FAILED_INIT;
404
405
1.13k
  if(data->state.upload)
406
46
    return file_upload(data, file);
407
408
  /* get the fd from the connection phase */
409
1.08k
  fd = file->fd;
410
411
  /* VMS: This only works reliable for STREAMLF files */
412
1.08k
  if(curlx_fstat(fd, &statbuf) != -1) {
413
1.08k
    if(!S_ISDIR(statbuf.st_mode))
414
327
      expected_size = statbuf.st_size;
415
    /* and store the modification time */
416
1.08k
    data->info.filetime = statbuf.st_mtime;
417
1.08k
    fstated = TRUE;
418
1.08k
  }
419
420
1.08k
  if(fstated && !data->state.range && data->set.timecondition &&
421
72
     !Curl_meets_timecondition(data, data->info.filetime))
422
32
    return CURLE_OK;
423
424
1.05k
  if(fstated) {
425
1.05k
    time_t filetime;
426
1.05k
    struct tm buffer;
427
1.05k
    const struct tm *tm = &buffer;
428
1.05k
    char header[80];
429
1.05k
    int headerlen;
430
1.05k
    static const char accept_ranges[] = { "Accept-ranges: bytes\r\n" };
431
1.05k
    if(expected_size >= 0) {
432
327
      headerlen =
433
327
        curl_msnprintf(header, sizeof(header),
434
327
                       "Content-Length: %" FMT_OFF_T "\r\n", expected_size);
435
327
      result = Curl_client_write(data, CLIENTWRITE_HEADER, header, headerlen);
436
327
      if(result)
437
0
        return result;
438
439
327
      result = Curl_client_write(data, CLIENTWRITE_HEADER,
440
327
                                 accept_ranges, sizeof(accept_ranges) - 1);
441
327
      if(result != CURLE_OK)
442
0
        return result;
443
327
    }
444
445
1.05k
    filetime = (time_t)statbuf.st_mtime;
446
1.05k
    result = curlx_gmtime(filetime, &buffer);
447
1.05k
    if(result)
448
0
      return result;
449
450
    /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */
451
1.05k
    headerlen =
452
1.05k
      curl_msnprintf(header, sizeof(header),
453
1.05k
                     "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n",
454
1.05k
                     Curl_wkday[tm->tm_wday ? tm->tm_wday - 1 : 6],
455
1.05k
                     tm->tm_mday,
456
1.05k
                     Curl_month[tm->tm_mon],
457
1.05k
                     tm->tm_year + 1900,
458
1.05k
                     tm->tm_hour,
459
1.05k
                     tm->tm_min,
460
1.05k
                     tm->tm_sec);
461
1.05k
    result = Curl_client_write(data, CLIENTWRITE_HEADER, header, headerlen);
462
1.05k
    if(!result)
463
      /* end of headers */
464
1.05k
      result = Curl_client_write(data, CLIENTWRITE_HEADER, "\r\n", 2);
465
1.05k
    if(result)
466
0
      return result;
467
    /* set the file size to make it available post transfer */
468
1.05k
    Curl_pgrsSetDownloadSize(data, expected_size);
469
1.05k
    if(data->req.no_body)
470
1
      return CURLE_OK;
471
1.05k
  }
472
473
  /* Check whether file range has been specified */
474
1.05k
  result = Curl_range(data);
475
1.05k
  if(result)
476
57
    return result;
477
478
  /* Adjust the start offset in case we want to get the N last bytes
479
   * of the stream if the filesize could be determined */
480
996
  if(data->state.resume_from < 0) {
481
195
    if(!fstated) {
482
0
      failf(data, "cannot get the size of file.");
483
0
      return CURLE_READ_ERROR;
484
0
    }
485
195
    data->state.resume_from += (curl_off_t)statbuf.st_size;
486
195
  }
487
488
996
  if(data->state.resume_from > 0) {
489
    /* We check explicitly if we have a start offset, because
490
     * expected_size may be -1 if we do not know how large the file is,
491
     * in which case we should not adjust it. */
492
191
    if(data->state.resume_from <= expected_size)
493
58
      expected_size -= data->state.resume_from;
494
133
    else {
495
133
      failf(data, "failed to resume file:// transfer");
496
133
      return CURLE_BAD_DOWNLOAD_RESUME;
497
133
    }
498
191
  }
499
500
  /* A high water mark has been specified so we obey... */
501
863
  if(data->req.maxdownload > 0)
502
376
    expected_size = data->req.maxdownload;
503
504
863
  if(!fstated || (expected_size <= 0))
505
316
    size_known = FALSE;
506
547
  else
507
547
    size_known = TRUE;
508
509
  /* The following is a shortcut implementation of file reading
510
     this is both more efficient than the former call to download() and
511
     it avoids problems with select() and recv() on file descriptors
512
     in Winsock */
513
863
  if(size_known)
514
547
    Curl_pgrsSetDownloadSize(data, expected_size);
515
516
863
  if(data->state.resume_from) {
517
224
    if(!S_ISDIR(statbuf.st_mode)) {
518
113
      if(data->state.resume_from !=
519
113
         curl_lseek(fd, data->state.resume_from, SEEK_SET))
520
55
        return CURLE_BAD_DOWNLOAD_RESUME;
521
113
    }
522
111
    else {
523
111
      return CURLE_BAD_DOWNLOAD_RESUME;
524
111
    }
525
224
  }
526
527
697
  result = Curl_multi_xfer_buf_borrow(data, &xfer_buf, &xfer_blen);
528
697
  if(result)
529
0
    goto out;
530
531
697
  if(!S_ISDIR(statbuf.st_mode)) {
532
19.3k
    while(!result) {
533
19.3k
      ssize_t nread;
534
      /* Do not fill a whole buffer if we want less than all data */
535
19.3k
      size_t bytestoread;
536
537
19.3k
      if(size_known) {
538
19.2k
        bytestoread = (expected_size < (curl_off_t)(xfer_blen - 1)) ?
539
18.9k
          curlx_sotouz(expected_size) : (xfer_blen - 1);
540
19.2k
      }
541
62
      else
542
62
        bytestoread = xfer_blen - 1;
543
544
19.3k
      nread = read(fd, xfer_buf, bytestoread);
545
546
19.3k
      if(nread > 0)
547
19.1k
        xfer_buf[nread] = 0;
548
549
19.3k
      if(nread <= 0 || (size_known && (expected_size == 0)))
550
233
        break;
551
552
19.1k
      if(size_known)
553
19.0k
        expected_size -= nread;
554
555
19.1k
      result = Curl_client_write(data, CLIENTWRITE_BODY, xfer_buf, nread);
556
19.1k
      if(result)
557
25
        goto out;
558
559
19.0k
      result = Curl_pgrsCheck(data);
560
19.0k
      if(result)
561
0
        goto out;
562
19.0k
    }
563
258
  }
564
439
  else {
565
439
#ifdef HAVE_OPENDIR
566
439
    DIR *dir = opendir(file->path);
567
439
    struct dirent *entry;
568
569
439
    if(!dir) {
570
0
      result = CURLE_READ_ERROR;
571
0
      goto out;
572
0
    }
573
439
    else {
574
26.8k
      while((entry = readdir(dir))) {
575
26.4k
        if(entry->d_name[0] != '.') {
576
25.1k
          result = Curl_client_write(data, CLIENTWRITE_BODY,
577
25.1k
                   entry->d_name, strlen(entry->d_name));
578
25.1k
          if(result)
579
13
            break;
580
25.1k
          result = Curl_client_write(data, CLIENTWRITE_BODY, "\n", 1);
581
25.1k
          if(result)
582
2
            break;
583
25.1k
        }
584
26.4k
      }
585
439
      closedir(dir);
586
439
    }
587
#else
588
    failf(data, "Directory listing not yet implemented on this platform.");
589
    result = CURLE_READ_ERROR;
590
#endif
591
439
  }
592
593
672
  if(!result)
594
657
    result = Curl_pgrsUpdate(data);
595
596
697
out:
597
697
  Curl_multi_xfer_buf_release(data, xfer_buf);
598
697
  return result;
599
672
}
600
601
static const struct Curl_protocol Curl_protocol_file = {
602
  file_setup_connection,                /* setup_connection */
603
  file_do,                              /* do_it */
604
  file_done,                            /* done */
605
  ZERO_NULL,                            /* do_more */
606
  file_connect,                         /* connect_it */
607
  ZERO_NULL,                            /* connecting */
608
  ZERO_NULL,                            /* doing */
609
  ZERO_NULL,                            /* proto_pollset */
610
  ZERO_NULL,                            /* doing_pollset */
611
  ZERO_NULL,                            /* domore_pollset */
612
  ZERO_NULL,                            /* perform_pollset */
613
  file_disconnect,                      /* disconnect */
614
  ZERO_NULL,                            /* write_resp */
615
  ZERO_NULL,                            /* write_resp_hd */
616
  ZERO_NULL,                            /* connection_check */
617
  ZERO_NULL,                            /* attach connection */
618
  ZERO_NULL,                            /* follow */
619
};
620
621
#endif
622
623
/*
624
 * FILE scheme handler.
625
 */
626
const struct Curl_scheme Curl_scheme_file = {
627
  "file",                               /* scheme */
628
#ifdef CURL_DISABLE_FILE
629
  ZERO_NULL,
630
#else
631
  &Curl_protocol_file,
632
#endif
633
  CURLPROTO_FILE,                       /* protocol */
634
  CURLPROTO_FILE,                       /* family */
635
  PROTOPT_NONETWORK | PROTOPT_NOURLQUERY, /* flags */
636
  0                                     /* defport */
637
};