Coverage Report

Created: 2025-07-11 06:33

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