Coverage Report

Created: 2026-06-02 06:36

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/ext/standard/ftp_fopen_wrapper.c
Line
Count
Source
1
/*
2
   +----------------------------------------------------------------------+
3
   | Copyright © The PHP Group and Contributors.                          |
4
   +----------------------------------------------------------------------+
5
   | This source file is subject to the Modified BSD License that is      |
6
   | bundled with this package in the file LICENSE, and is available      |
7
   | through the World Wide Web at <https://www.php.net/license/>.        |
8
   |                                                                      |
9
   | SPDX-License-Identifier: BSD-3-Clause                                |
10
   +----------------------------------------------------------------------+
11
   | Authors: Rasmus Lerdorf <rasmus@php.net>                             |
12
   |          Jim Winstead <jimw@php.net>                                 |
13
   |          Hartmut Holzgraefe <hholzgra@php.net>                       |
14
   |          Sara Golemon <pollita@php.net>                              |
15
   +----------------------------------------------------------------------+
16
 */
17
18
#include "php.h"
19
#include "php_globals.h"
20
#include "php_network.h"
21
#include "php_ini.h"
22
#include "zend_exceptions.h"
23
24
#include <stdio.h>
25
#include <stdlib.h>
26
#include <errno.h>
27
#include <sys/types.h>
28
#include <sys/stat.h>
29
#include <fcntl.h>
30
31
#ifdef PHP_WIN32
32
#include <winsock2.h>
33
#define O_RDONLY _O_RDONLY
34
#include "win32/param.h"
35
#else
36
#include <sys/param.h>
37
#endif
38
39
#include "php_standard.h"
40
#include "ext/uri/php_uri.h"
41
42
#ifdef HAVE_SYS_SOCKET_H
43
#include <sys/socket.h>
44
#endif
45
46
#ifdef PHP_WIN32
47
#include <winsock2.h>
48
#else
49
#include <netinet/in.h>
50
#include <netdb.h>
51
#ifdef HAVE_ARPA_INET_H
52
#include <arpa/inet.h>
53
#endif
54
#endif
55
56
#if defined(PHP_WIN32) || defined(__riscos__)
57
#undef AF_UNIX
58
#endif
59
60
#if defined(AF_UNIX)
61
#include <sys/un.h>
62
#endif
63
64
#include "php_fopen_wrappers.h"
65
66
#define FTPS_ENCRYPT_DATA 1
67
0
#define GET_FTP_RESULT(stream)  get_ftp_result((stream), tmp_line, sizeof(tmp_line))
68
69
typedef struct _php_ftp_dirstream_data {
70
  php_stream *datastream;
71
  php_stream *controlstream;
72
  php_stream *dirstream;
73
} php_ftp_dirstream_data;
74
75
/* {{{ get_ftp_result */
76
static inline int get_ftp_result(php_stream *stream, char *buffer, size_t buffer_size)
77
0
{
78
0
  buffer[0] = '\0'; /* in case read fails to read anything */
79
0
  while (php_stream_gets(stream, buffer, buffer_size-1) &&
80
0
       !(isdigit((unsigned char)buffer[0]) && isdigit((unsigned char)buffer[1]) &&
81
0
       isdigit((unsigned char)buffer[2]) && buffer[3] == ' '));
82
0
  return strtol(buffer, NULL, 10);
83
0
}
84
/* }}} */
85
86
/* {{{ php_stream_ftp_stream_stat */
87
static int php_stream_ftp_stream_stat(php_stream_wrapper *wrapper, php_stream *stream, php_stream_statbuf *ssb)
88
0
{
89
  /* For now, we return with a failure code to prevent the underlying
90
   * file's details from being used instead. */
91
0
  return -1;
92
0
}
93
/* }}} */
94
95
/* {{{ php_stream_ftp_stream_close */
96
static int php_stream_ftp_stream_close(php_stream_wrapper *wrapper, php_stream *stream)
97
0
{
98
0
  php_stream *controlstream = stream->wrapperthis;
99
0
  int ret = 0;
100
101
0
  if (controlstream) {
102
0
    if (strpbrk(stream->mode, "wa+")) {
103
0
      char tmp_line[512];
104
0
      int result;
105
106
      /* For write modes close data stream first to signal EOF to server */
107
0
      result = GET_FTP_RESULT(controlstream);
108
0
      if (result != 226 && result != 250) {
109
0
        php_stream_wrapper_warn(wrapper, PHP_STREAM_CONTEXT(stream), REPORT_ERRORS,
110
0
          ProtocolError,
111
0
          "FTP server error %d:%s", result, tmp_line);
112
0
        ret = EOF;
113
0
      }
114
0
    }
115
116
0
    php_stream_write_string(controlstream, "QUIT\r\n");
117
0
    php_stream_close(controlstream);
118
0
    stream->wrapperthis = NULL;
119
0
  }
120
121
0
  return ret;
122
0
}
123
/* }}} */
124
125
/* {{{ php_ftp_fopen_connect */
126
static php_stream *php_ftp_fopen_connect(php_stream_wrapper *wrapper, const char *path, const char *mode, int options,
127
                     zend_string **opened_path, php_stream_context *context, php_stream **preuseid,
128
                     php_uri **presource, int *puse_ssl, int *puse_ssl_on_data)
129
0
{
130
0
  php_stream *stream = NULL, *reuseid = NULL;
131
0
  php_uri *resource = NULL;
132
0
  int result, use_ssl, use_ssl_on_data = 0;
133
0
  char tmp_line[512];
134
0
  char *transport;
135
0
  int transport_len;
136
137
0
  const php_uri_parser *uri_parser = php_stream_context_get_uri_parser("ftp", context);
138
0
  if (uri_parser == NULL) {
139
0
    zend_value_error("%s(): Provided stream context has invalid value for the \"uri_parser_class\" option", get_active_function_name());
140
0
    return NULL;
141
0
  }
142
143
0
  resource = php_uri_parse_to_struct(uri_parser, path, strlen(path), PHP_URI_COMPONENT_READ_MODE_RAW, true);
144
0
  if (resource == NULL || resource->path == NULL) {
145
0
    if (resource && presource) {
146
0
      *presource = resource;
147
0
    }
148
0
    return NULL;
149
0
  }
150
151
0
  use_ssl = resource->scheme && (ZSTR_LEN(resource->scheme) > 3) && ZSTR_VAL(resource->scheme)[3] == 's';
152
153
  /* use port 21 if one wasn't specified */
154
0
  if (resource->port == 0)
155
0
    resource->port = 21;
156
157
0
  transport_len = (int)spprintf(&transport, 0, "tcp://%s:" ZEND_LONG_FMT, ZSTR_VAL(resource->host), resource->port);
158
0
  stream = php_stream_xport_create(transport, transport_len, REPORT_ERRORS, STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT, NULL, NULL, context, NULL, NULL);
159
0
  efree(transport);
160
0
  if (stream == NULL) {
161
0
    result = 0; /* silence */
162
0
    goto connect_errexit;
163
0
  }
164
165
0
  php_stream_context_set(stream, context);
166
0
  php_stream_notify_info(context, PHP_STREAM_NOTIFY_CONNECT, NULL, 0);
167
168
  /* Start talking to ftp server */
169
0
  result = GET_FTP_RESULT(stream);
170
0
  if (result > 299 || result < 200) {
171
0
    php_stream_notify_error(context, PHP_STREAM_NOTIFY_FAILURE, tmp_line, result);
172
0
    goto connect_errexit;
173
0
  }
174
175
0
  if (use_ssl) {
176
177
    /* send the AUTH TLS request name */
178
0
    php_stream_write_string(stream, "AUTH TLS\r\n");
179
180
    /* get the response */
181
0
    result = GET_FTP_RESULT(stream);
182
0
    if (result != 234) {
183
      /* AUTH TLS not supported try AUTH SSL */
184
0
      php_stream_write_string(stream, "AUTH SSL\r\n");
185
186
      /* get the response */
187
0
      result = GET_FTP_RESULT(stream);
188
0
      if (result != 334) {
189
0
        php_stream_wrapper_log_warn(wrapper, context, options, SslNotSupported,
190
0
          "Server doesn't support FTPS.");
191
0
        goto connect_errexit;
192
0
      } else {
193
        /* we must reuse the old SSL session id */
194
        /* if we talk to an old ftpd-ssl */
195
0
        reuseid = stream;
196
0
      }
197
0
    } else {
198
      /* encrypt data etc */
199
200
201
0
    }
202
203
0
  }
204
205
0
  if (use_ssl) {
206
0
    if (php_stream_xport_crypto_setup(stream,
207
0
        STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL) < 0
208
0
        || php_stream_xport_crypto_enable(stream, 1) < 0) {
209
0
      php_stream_wrapper_log_warn(wrapper, context, options, SslNotSupported,
210
0
        "Unable to activate SSL mode");
211
0
      php_stream_close(stream);
212
0
      stream = NULL;
213
0
      goto connect_errexit;
214
0
    }
215
216
    /* set PBSZ to 0 */
217
0
    php_stream_write_string(stream, "PBSZ 0\r\n");
218
219
    /* ignore the response */
220
0
    result = GET_FTP_RESULT(stream);
221
222
    /* set data connection protection level */
223
0
#if FTPS_ENCRYPT_DATA
224
0
    php_stream_write_string(stream, "PROT P\r\n");
225
226
    /* get the response */
227
0
    result = GET_FTP_RESULT(stream);
228
0
    use_ssl_on_data = (result >= 200 && result<=299) || reuseid;
229
#else
230
    php_stream_write_string(stream, "PROT C\r\n");
231
232
    /* get the response */
233
    result = GET_FTP_RESULT(stream);
234
#endif
235
0
  }
236
237
0
#define PHP_FTP_CNTRL_CHK(val, val_len, err_msg) { \
238
0
  unsigned char *s = (unsigned char *) val, *e = (unsigned char *) s + val_len; \
239
0
  while (s < e) { \
240
0
    if (iscntrl((unsigned char)*s)) { \
241
0
      php_stream_wrapper_log_warn(wrapper, context, options, AuthFailed, err_msg, val);  \
242
0
      goto connect_errexit; \
243
0
    } \
244
0
    s++;  \
245
0
  }  \
246
0
}
247
248
  /* send the user name */
249
0
  if (resource->user != NULL) {
250
0
    ZSTR_LEN(resource->user) = php_raw_url_decode(ZSTR_VAL(resource->user), ZSTR_LEN(resource->user));
251
252
0
    PHP_FTP_CNTRL_CHK(ZSTR_VAL(resource->user), ZSTR_LEN(resource->user), "Invalid login %s")
253
254
0
    php_stream_printf(stream, "USER %s\r\n", ZSTR_VAL(resource->user));
255
0
  } else {
256
0
    php_stream_write_string(stream, "USER anonymous\r\n");
257
0
  }
258
259
  /* get the response */
260
0
  result = GET_FTP_RESULT(stream);
261
262
  /* if a password is required, send it */
263
0
  if (result >= 300 && result <= 399) {
264
0
    php_stream_notify_info(context, PHP_STREAM_NOTIFY_AUTH_REQUIRED, tmp_line, 0);
265
266
0
    if (resource->password != NULL) {
267
0
      ZSTR_LEN(resource->password) = php_raw_url_decode(ZSTR_VAL(resource->password), ZSTR_LEN(resource->password));
268
269
0
      PHP_FTP_CNTRL_CHK(ZSTR_VAL(resource->password), ZSTR_LEN(resource->password), "Invalid password %s")
270
271
0
      php_stream_printf(stream, "PASS %s\r\n", ZSTR_VAL(resource->password));
272
0
    } else {
273
      /* if the user has configured who they are,
274
         send that as the password */
275
0
      if (FG(from_address)) {
276
0
        php_stream_printf(stream, "PASS %s\r\n", FG(from_address));
277
0
      } else {
278
0
        php_stream_write_string(stream, "PASS anonymous\r\n");
279
0
      }
280
0
    }
281
282
    /* read the response */
283
0
    result = GET_FTP_RESULT(stream);
284
285
0
    if (result > 299 || result < 200) {
286
0
      php_stream_notify_error(context, PHP_STREAM_NOTIFY_AUTH_RESULT, tmp_line, result);
287
0
    } else {
288
0
      php_stream_notify_info(context, PHP_STREAM_NOTIFY_AUTH_RESULT, tmp_line, result);
289
0
    }
290
0
  }
291
0
  if (result > 299 || result < 200) {
292
0
    goto connect_errexit;
293
0
  }
294
295
0
  if (puse_ssl) {
296
0
    *puse_ssl = use_ssl;
297
0
  }
298
0
  if (puse_ssl_on_data) {
299
0
    *puse_ssl_on_data = use_ssl_on_data;
300
0
  }
301
0
  if (preuseid) {
302
0
    *preuseid = reuseid;
303
0
  }
304
0
  if (presource) {
305
0
    *presource = resource;
306
0
  }
307
308
0
  return stream;
309
310
0
connect_errexit:
311
0
  php_uri_struct_free(resource);
312
313
0
  if (stream) {
314
0
    php_stream_close(stream);
315
0
  }
316
317
0
  return NULL;
318
0
}
319
/* }}} */
320
321
/* {{{ php_fopen_do_pasv */
322
static unsigned short php_fopen_do_pasv(php_stream *stream, char *ip, size_t ip_size, char **phoststart)
323
0
{
324
0
  char tmp_line[512];
325
0
  int result, i;
326
0
  unsigned short portno;
327
0
  char *tpath, *ttpath, *hoststart=NULL;
328
329
0
#ifdef HAVE_IPV6
330
  /* We try EPSV first, needed for IPv6 and works on some IPv4 servers */
331
0
  php_stream_write_string(stream, "EPSV\r\n");
332
0
  result = GET_FTP_RESULT(stream);
333
334
  /* check if we got a 229 response */
335
0
  if (result != 229) {
336
0
#endif
337
    /* EPSV failed, let's try PASV */
338
0
    php_stream_write_string(stream, "PASV\r\n");
339
0
    result = GET_FTP_RESULT(stream);
340
341
    /* make sure we got a 227 response */
342
0
    if (result != 227) {
343
0
      return 0;
344
0
    }
345
346
    /* parse pasv command (129, 80, 95, 25, 13, 221) */
347
0
    tpath = tmp_line;
348
    /* skip over the "227 Some message " part */
349
0
    for (tpath += 4; *tpath && !isdigit((unsigned char)*tpath); tpath++);
350
0
    if (!*tpath) {
351
0
      return 0;
352
0
    }
353
    /* skip over the host ip, to get the port */
354
0
    hoststart = tpath;
355
0
    for (i = 0; i < 4; i++) {
356
0
      for (; isdigit((unsigned char)*tpath); tpath++);
357
0
      if (*tpath != ',') {
358
0
        return 0;
359
0
      }
360
0
      *tpath='.';
361
0
      tpath++;
362
0
    }
363
0
    tpath[-1] = '\0';
364
0
    memcpy(ip, hoststart, ip_size);
365
0
    ip[ip_size-1] = '\0';
366
0
    hoststart = ip;
367
368
    /* pull out the MSB of the port */
369
0
    portno = (unsigned short) strtoul(tpath, &ttpath, 10) * 256;
370
0
    if (ttpath == NULL) {
371
      /* didn't get correct response from PASV */
372
0
      return 0;
373
0
    }
374
0
    tpath = ttpath;
375
0
    if (*tpath != ',') {
376
0
      return 0;
377
0
    }
378
0
    tpath++;
379
    /* pull out the LSB of the port */
380
0
    portno += (unsigned short) strtoul(tpath, &ttpath, 10);
381
0
#ifdef HAVE_IPV6
382
0
  } else {
383
    /* parse epsv command (|||6446|) */
384
0
    for (i = 0, tpath = tmp_line + 4; *tpath; tpath++) {
385
0
      if (*tpath == '|') {
386
0
        i++;
387
0
        if (i == 3)
388
0
          break;
389
0
      }
390
0
    }
391
0
    if (i < 3) {
392
0
      return 0;
393
0
    }
394
    /* pull out the port */
395
0
    portno = (unsigned short) strtoul(tpath + 1, &ttpath, 10);
396
0
  }
397
0
#endif
398
0
  if (ttpath == NULL) {
399
    /* didn't get correct response from EPSV/PASV */
400
0
    return 0;
401
0
  }
402
403
0
  if (phoststart) {
404
0
    *phoststart = hoststart;
405
0
  }
406
407
0
  return portno;
408
0
}
409
/* }}} */
410
411
/* {{{ php_fopen_url_wrap_ftp */
412
php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, const char *path, const char *mode,
413
                   int options, zend_string **opened_path, php_stream_context *context STREAMS_DC)
414
0
{
415
0
  php_stream *stream = NULL, *datastream = NULL;
416
0
  php_uri *resource = NULL;
417
0
  char tmp_line[512];
418
0
  char ip[sizeof("123.123.123.123")];
419
0
  unsigned short portno;
420
0
  char *hoststart = NULL;
421
0
  int result = 0, use_ssl, use_ssl_on_data=0;
422
0
  php_stream *reuseid=NULL;
423
0
  size_t file_size = 0;
424
0
  zval *tmpzval;
425
0
  bool allow_overwrite = false;
426
0
  int8_t read_write = 0;
427
0
  char *transport;
428
0
  int transport_len;
429
0
  zend_string *error_message = NULL;
430
431
0
  tmp_line[0] = '\0';
432
433
0
  if (strpbrk(mode, "r+")) {
434
0
    read_write = 1; /* Open for reading */
435
0
  }
436
0
  if (strpbrk(mode, "wa+")) {
437
0
    if (read_write) {
438
0
      php_stream_wrapper_log_warn(wrapper, context, options, ModeNotSupported,
439
0
        "FTP does not support simultaneous read/write connections");
440
0
      return NULL;
441
0
    }
442
0
    if (strchr(mode, 'a')) {
443
0
      read_write = 3; /* Open for Appending */
444
0
    } else {
445
0
      read_write = 2; /* Open for writing */
446
0
    }
447
0
  }
448
0
  if (!read_write) {
449
    /* No mode specified? */
450
0
    php_stream_wrapper_log_warn(wrapper, context, options, InvalidMode,
451
0
      "Unknown file open mode");
452
0
    return NULL;
453
0
  }
454
455
0
  if (context &&
456
0
    (tmpzval = php_stream_context_get_option(context, "ftp", "proxy")) != NULL) {
457
0
    if (read_write == 1) {
458
      /* Use http wrapper to proxy ftp request */
459
0
      return php_stream_url_wrap_http(wrapper, path, mode, options, opened_path, context STREAMS_CC);
460
0
    } else {
461
      /* ftp proxy is read-only */
462
0
      php_stream_wrapper_log_warn(wrapper, context, options, ModeNotSupported,
463
0
        "FTP proxy may only be used in read mode");
464
0
      return NULL;
465
0
    }
466
0
  }
467
468
0
  stream = php_ftp_fopen_connect(wrapper, path, mode, options, opened_path, context, &reuseid, &resource, &use_ssl, &use_ssl_on_data);
469
0
  if (!stream) {
470
0
    goto errexit;
471
0
  }
472
473
  /* set the connection to be binary */
474
0
  php_stream_write_string(stream, "TYPE I\r\n");
475
0
  result = GET_FTP_RESULT(stream);
476
0
  if (result > 299 || result < 200)
477
0
    goto errexit;
478
479
  /* find out the size of the file (verifying it exists) */
480
0
  php_stream_printf(stream, "SIZE %s\r\n", ZSTR_VAL(resource->path));
481
482
  /* read the response */
483
0
  result = GET_FTP_RESULT(stream);
484
0
  if (read_write == 1) {
485
    /* Read Mode */
486
0
    char *sizestr;
487
488
    /* when reading file, it must exist */
489
0
    if (result > 299 || result < 200) {
490
0
      errno = ENOENT;
491
0
      goto errexit;
492
0
    }
493
494
0
    sizestr = strchr(tmp_line, ' ');
495
0
    if (sizestr) {
496
0
      sizestr++;
497
0
      file_size = atoi(sizestr);
498
0
      php_stream_notify_file_size(context, file_size, tmp_line, result);
499
0
    }
500
0
  } else if (read_write == 2) {
501
    /* when writing file (but not appending), it must NOT exist, unless a context option exists which allows it */
502
0
    if (context && (tmpzval = php_stream_context_get_option(context, "ftp", "overwrite")) != NULL) {
503
0
      allow_overwrite = zend_is_true(tmpzval);
504
0
    }
505
0
    if (result <= 299 && result >= 200) {
506
0
      if (allow_overwrite) {
507
        /* Context permits overwriting file,
508
           so we just delete whatever's there in preparation */
509
0
        php_stream_printf(stream, "DELE %s\r\n", ZSTR_VAL(resource->path));
510
0
        result = GET_FTP_RESULT(stream);
511
0
        if (result >= 300 || result <= 199) {
512
0
          goto errexit;
513
0
        }
514
0
      } else {
515
0
        php_stream_wrapper_log_warn(wrapper, context, options, AlreadyExists,
516
0
          "Remote file already exists and overwrite context option not specified");
517
0
        errno = EEXIST;
518
0
        goto errexit;
519
0
      }
520
0
    }
521
0
  }
522
523
  /* set up the passive connection */
524
0
  portno = php_fopen_do_pasv(stream, ip, sizeof(ip), &hoststart);
525
526
0
  if (!portno) {
527
0
    goto errexit;
528
0
  }
529
530
  /* Send RETR/STOR command */
531
0
  if (read_write == 1) {
532
    /* set resume position if applicable */
533
0
    if (context &&
534
0
      (tmpzval = php_stream_context_get_option(context, "ftp", "resume_pos")) != NULL &&
535
0
      Z_TYPE_P(tmpzval) == IS_LONG &&
536
0
      Z_LVAL_P(tmpzval) > 0) {
537
0
      php_stream_printf(stream, "REST " ZEND_LONG_FMT "\r\n", Z_LVAL_P(tmpzval));
538
0
      result = GET_FTP_RESULT(stream);
539
0
      if (result < 300 || result > 399) {
540
0
        php_stream_wrapper_log_warn(wrapper, context, options, ResumptionFailed,
541
0
          "Unable to resume from offset " ZEND_LONG_FMT, Z_LVAL_P(tmpzval));
542
0
        goto errexit;
543
0
      }
544
0
    }
545
546
    /* retrieve file */
547
0
    memcpy(tmp_line, "RETR", sizeof("RETR"));
548
0
  } else if (read_write == 2) {
549
    /* Write new file */
550
0
    memcpy(tmp_line, "STOR", sizeof("STOR"));
551
0
  } else {
552
    /* Append */
553
0
    memcpy(tmp_line, "APPE", sizeof("APPE"));
554
0
  }
555
0
  php_stream_printf(stream, "%s %s\r\n", tmp_line, (resource->path != NULL ? ZSTR_VAL(resource->path) : "/"));
556
557
  /* open the data channel */
558
0
  if (hoststart == NULL) {
559
0
    hoststart = ZSTR_VAL(resource->host);
560
0
  }
561
0
  transport_len = (int)spprintf(&transport, 0, "tcp://%s:%d", hoststart, portno);
562
0
  datastream = php_stream_xport_create(transport, transport_len, REPORT_ERRORS, STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT, NULL, NULL, context, &error_message, NULL);
563
0
  efree(transport);
564
0
  if (datastream == NULL) {
565
0
    tmp_line[0]='\0';
566
0
    goto errexit;
567
0
  }
568
569
0
  result = GET_FTP_RESULT(stream);
570
0
  if (result != 150 && result != 125) {
571
    /* Could not retrieve or send the file
572
     * this data will only be sent to us after connection on the data port was initiated.
573
     */
574
0
    php_stream_close(datastream);
575
0
    datastream = NULL;
576
0
    goto errexit;
577
0
  }
578
579
0
  php_stream_context_set(datastream, context);
580
0
  php_stream_notify_progress_init(context, 0, file_size);
581
582
0
  if (use_ssl_on_data && (php_stream_xport_crypto_setup(datastream,
583
0
      STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL) < 0 ||
584
0
      php_stream_xport_crypto_enable(datastream, 1) < 0)) {
585
586
0
    php_stream_wrapper_log_warn(wrapper, context, options, SslNotSupported,
587
0
      "Unable to activate SSL mode");
588
0
    php_stream_close(datastream);
589
0
    datastream = NULL;
590
0
    tmp_line[0]='\0';
591
0
    goto errexit;
592
0
  }
593
594
  /* remember control stream */
595
0
  datastream->wrapperthis = stream;
596
597
0
  php_uri_struct_free(resource);
598
0
  return datastream;
599
600
0
errexit:
601
0
  if (resource) {
602
0
    php_uri_struct_free(resource);
603
0
  }
604
0
  if (stream) {
605
0
    php_stream_notify_error(context, PHP_STREAM_NOTIFY_FAILURE, tmp_line, result);
606
0
    php_stream_close(stream);
607
0
  }
608
0
  if (tmp_line[0] != '\0')
609
0
    php_stream_wrapper_log_warn(wrapper, context, options, ProtocolError,
610
0
      "FTP server reports %s", tmp_line);
611
612
0
  if (error_message) {
613
0
    php_stream_wrapper_log_warn(wrapper, context, options, NetworkSendFailed,
614
0
      "Failed to set up data channel: %s", ZSTR_VAL(error_message));
615
0
    zend_string_release(error_message);
616
0
  }
617
0
  return NULL;
618
0
}
619
/* }}} */
620
621
/* {{{ php_ftp_dirsteam_read */
622
static ssize_t php_ftp_dirstream_read(php_stream *stream, char *buf, size_t count)
623
0
{
624
0
  php_stream_dirent *ent = (php_stream_dirent *)buf;
625
0
  php_stream *innerstream;
626
0
  size_t tmp_len;
627
0
  zend_string *basename;
628
629
0
  innerstream =  ((php_ftp_dirstream_data *)stream->abstract)->datastream;
630
631
0
  if (count != sizeof(php_stream_dirent)) {
632
0
    return -1;
633
0
  }
634
635
0
  if (php_stream_eof(innerstream)) {
636
0
    return 0;
637
0
  }
638
639
0
  if (!php_stream_get_line(innerstream, ent->d_name, sizeof(ent->d_name), &tmp_len)) {
640
0
    return -1;
641
0
  }
642
643
0
  basename = php_basename(ent->d_name, tmp_len, NULL, 0);
644
645
0
  tmp_len = MIN(sizeof(ent->d_name), ZSTR_LEN(basename) - 1);
646
0
  memcpy(ent->d_name, ZSTR_VAL(basename), tmp_len);
647
0
  ent->d_name[tmp_len - 1] = '\0';
648
0
  zend_string_release_ex(basename, 0);
649
0
  ent->d_type = DT_UNKNOWN;
650
651
  /* Trim off trailing whitespace characters */
652
0
  while (tmp_len > 0 &&
653
0
      (ent->d_name[tmp_len - 1] == '\n' || ent->d_name[tmp_len - 1] == '\r' ||
654
0
       ent->d_name[tmp_len - 1] == '\t' || ent->d_name[tmp_len - 1] == ' ')) {
655
0
    ent->d_name[--tmp_len] = '\0';
656
0
  }
657
658
0
  return sizeof(php_stream_dirent);
659
0
}
660
/* }}} */
661
662
/* {{{ php_ftp_dirstream_close */
663
static int php_ftp_dirstream_close(php_stream *stream, int close_handle)
664
0
{
665
0
  php_ftp_dirstream_data *data = stream->abstract;
666
667
  /* close control connection */
668
0
  if (data->controlstream) {
669
0
    php_stream_close(data->controlstream);
670
0
    data->controlstream = NULL;
671
0
  }
672
  /* close data connection */
673
0
  php_stream_close(data->datastream);
674
0
  data->datastream = NULL;
675
676
0
  efree(data);
677
0
  stream->abstract = NULL;
678
679
0
  return 0;
680
0
}
681
/* }}} */
682
683
/* ftp dirstreams only need to support read and close operations,
684
   They can't be rewound because the underlying ftp stream can't be rewound. */
685
static const php_stream_ops php_ftp_dirstream_ops = {
686
  NULL, /* write */
687
  php_ftp_dirstream_read, /* read */
688
  php_ftp_dirstream_close, /* close */
689
  NULL, /* flush */
690
  "ftpdir",
691
  NULL, /* rewind */
692
  NULL, /* cast */
693
  NULL, /* stat */
694
  NULL  /* set option */
695
};
696
697
/* {{{ php_stream_ftp_opendir */
698
static php_stream * php_stream_ftp_opendir(php_stream_wrapper *wrapper, const char *path, const char *mode, int options,
699
                  zend_string **opened_path, php_stream_context *context STREAMS_DC)
700
0
{
701
0
  php_stream *stream, *reuseid, *datastream = NULL;
702
0
  php_ftp_dirstream_data *dirsdata;
703
0
  php_uri *resource = NULL;
704
0
  int result = 0, use_ssl, use_ssl_on_data = 0;
705
0
  char *hoststart = NULL, tmp_line[512];
706
0
  char ip[sizeof("123.123.123.123")];
707
0
  unsigned short portno;
708
709
0
  tmp_line[0] = '\0';
710
711
0
  stream = php_ftp_fopen_connect(wrapper, path, mode, options, opened_path, context, &reuseid, &resource, &use_ssl, &use_ssl_on_data);
712
0
  if (!stream) {
713
0
    goto opendir_errexit;
714
0
  }
715
716
  /* set the connection to be ascii */
717
0
  php_stream_write_string(stream, "TYPE A\r\n");
718
0
  result = GET_FTP_RESULT(stream);
719
0
  if (result > 299 || result < 200)
720
0
    goto opendir_errexit;
721
722
  // tmp_line isn't relevant after the php_fopen_do_pasv().
723
0
  tmp_line[0] = '\0';
724
725
  /* set up the passive connection */
726
0
  portno = php_fopen_do_pasv(stream, ip, sizeof(ip), &hoststart);
727
728
0
  if (!portno) {
729
0
    goto opendir_errexit;
730
0
  }
731
732
  /* open the data channel */
733
0
  if (hoststart == NULL) {
734
0
    hoststart = ZSTR_VAL(resource->host);
735
0
  }
736
737
0
  datastream = php_stream_sock_open_host(hoststart, portno, SOCK_STREAM, 0, 0);
738
0
  if (datastream == NULL) {
739
0
    goto opendir_errexit;
740
0
  }
741
742
0
  php_stream_printf(stream, "NLST %s\r\n", (resource->path != NULL ? ZSTR_VAL(resource->path) : "/"));
743
744
0
  result = GET_FTP_RESULT(stream);
745
0
  if (result != 150 && result != 125) {
746
    /* Could not retrieve or send the file
747
     * this data will only be sent to us after connection on the data port was initiated.
748
     */
749
0
    php_stream_close(datastream);
750
0
    datastream = NULL;
751
0
    goto opendir_errexit;
752
0
  }
753
754
0
  php_stream_context_set(datastream, context);
755
0
  if (use_ssl_on_data && (php_stream_xport_crypto_setup(datastream,
756
0
      STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL) < 0 ||
757
0
      php_stream_xport_crypto_enable(datastream, 1) < 0)) {
758
759
0
    php_stream_wrapper_log_warn(wrapper, context, options, SslNotSupported,
760
0
      "Unable to activate SSL mode");
761
0
    php_stream_close(datastream);
762
0
    datastream = NULL;
763
0
    goto opendir_errexit;
764
0
  }
765
766
0
  php_uri_struct_free(resource);
767
768
0
  dirsdata = emalloc(sizeof *dirsdata);
769
0
  dirsdata->datastream = datastream;
770
0
  dirsdata->controlstream = stream;
771
0
  dirsdata->dirstream = php_stream_alloc(&php_ftp_dirstream_ops, dirsdata, 0, mode);
772
773
0
  return dirsdata->dirstream;
774
775
0
opendir_errexit:
776
0
  if (resource) {
777
0
    php_uri_struct_free(resource);
778
0
  }
779
0
  if (stream) {
780
0
    php_stream_notify_error(context, PHP_STREAM_NOTIFY_FAILURE, tmp_line, result);
781
0
    php_stream_close(stream);
782
0
  }
783
0
  if (tmp_line[0] != '\0') {
784
0
    php_stream_wrapper_log_warn(wrapper, context, options, ProtocolError,
785
0
      "FTP server reports %s", tmp_line);
786
0
  }
787
0
  return NULL;
788
0
}
789
/* }}} */
790
791
/* {{{ php_stream_ftp_url_stat */
792
static int php_stream_ftp_url_stat(php_stream_wrapper *wrapper, const char *url, int flags, php_stream_statbuf *ssb, php_stream_context *context)
793
0
{
794
0
  php_stream *stream = NULL;
795
0
  php_uri *resource = NULL;
796
0
  int result;
797
0
  char tmp_line[512];
798
799
  /* If ssb is NULL then someone is misbehaving */
800
0
  if (!ssb) return -1;
801
802
0
  stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, context, NULL, &resource, NULL, NULL);
803
0
  if (!stream) {
804
0
    goto stat_errexit;
805
0
  }
806
807
0
  ssb->sb.st_mode = 0644;                 /* FTP won't give us a valid mode, so approximate one based on being readable */
808
0
  php_stream_printf(stream, "CWD %s\r\n", (resource->path != NULL ? ZSTR_VAL(resource->path) : "/")); /* If we can CWD to it, it's a directory (maybe a link, but we can't tell) */
809
0
  result = GET_FTP_RESULT(stream);
810
0
  if (result < 200 || result > 299) {
811
0
    ssb->sb.st_mode |= S_IFREG;
812
0
  } else {
813
0
    ssb->sb.st_mode |= S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH;
814
0
  }
815
816
0
  php_stream_write_string(stream, "TYPE I\r\n"); /* we need this since some servers refuse to accept SIZE command in ASCII mode */
817
818
0
  result = GET_FTP_RESULT(stream);
819
820
0
  if(result < 200 || result > 299) {
821
0
    goto stat_errexit;
822
0
  }
823
824
0
  php_stream_printf(stream, "SIZE %s\r\n", (resource->path != NULL ? ZSTR_VAL(resource->path) : "/"));
825
0
  result = GET_FTP_RESULT(stream);
826
0
  if (result < 200 || result > 299) {
827
    /* Failure either means it doesn't exist
828
       or it's a directory and this server
829
       fails on listing directory sizes */
830
0
    if (ssb->sb.st_mode & S_IFDIR) {
831
0
      ssb->sb.st_size = 0;
832
0
    } else {
833
0
      goto stat_errexit;
834
0
    }
835
0
  } else {
836
0
    ssb->sb.st_size = atoi(tmp_line + 4);
837
0
  }
838
839
0
  php_stream_printf(stream, "MDTM %s\r\n", (resource->path != NULL ? ZSTR_VAL(resource->path) : "/"));
840
0
  result = GET_FTP_RESULT(stream);
841
0
  if (result == 213) {
842
0
    char *p = tmp_line + 4;
843
0
    int n;
844
0
    struct tm tm, tmbuf, *gmt;
845
0
    time_t stamp;
846
847
0
    while ((size_t)(p - tmp_line) < sizeof(tmp_line) && !isdigit((unsigned char)*p)) {
848
0
      p++;
849
0
    }
850
851
0
    if ((size_t)(p - tmp_line) > sizeof(tmp_line)) {
852
0
      goto mdtm_error;
853
0
    }
854
855
0
    n = sscanf(p, "%4d%2d%2d%2d%2d%2d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
856
0
    if (n != 6) {
857
0
      goto mdtm_error;
858
0
    }
859
860
0
    tm.tm_year -= 1900;
861
0
    tm.tm_mon--;
862
0
    tm.tm_isdst = -1;
863
864
    /* figure out the GMT offset */
865
0
    stamp = time(NULL);
866
0
    gmt = php_gmtime_r(&stamp, &tmbuf);
867
0
    if (!gmt) {
868
0
      goto mdtm_error;
869
0
    }
870
0
    gmt->tm_isdst = -1;
871
872
    /* apply the GMT offset */
873
0
    tm.tm_sec += (long)(stamp - mktime(gmt));
874
0
    tm.tm_isdst = gmt->tm_isdst;
875
876
0
    ssb->sb.st_mtime = mktime(&tm);
877
0
  } else {
878
    /* error or unsupported command */
879
0
mdtm_error:
880
0
    ssb->sb.st_mtime = -1;
881
0
  }
882
883
0
  ssb->sb.st_ino = 0;           /* Unknown values */
884
0
  ssb->sb.st_dev = 0;
885
0
  ssb->sb.st_uid = 0;
886
0
  ssb->sb.st_gid = 0;
887
0
  ssb->sb.st_atime = -1;
888
0
  ssb->sb.st_ctime = -1;
889
890
0
  ssb->sb.st_nlink = 1;
891
0
  ssb->sb.st_rdev = -1;
892
0
#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
893
0
  ssb->sb.st_blksize = 4096;        /* Guess since FTP won't expose this information */
894
0
#ifdef HAVE_STRUCT_STAT_ST_BLOCKS
895
0
  ssb->sb.st_blocks = (int)((4095 + ssb->sb.st_size) / ssb->sb.st_blksize); /* emulate ceil */
896
0
#endif
897
0
#endif
898
0
  php_stream_close(stream);
899
0
  php_uri_struct_free(resource);
900
0
  return 0;
901
902
0
stat_errexit:
903
0
  if (resource) {
904
0
    php_uri_struct_free(resource);
905
0
  }
906
0
  if (stream) {
907
0
    php_stream_close(stream);
908
0
  }
909
0
  return -1;
910
0
}
911
/* }}} */
912
913
/* {{{ php_stream_ftp_unlink */
914
static int php_stream_ftp_unlink(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context)
915
0
{
916
0
  php_stream *stream = NULL;
917
0
  php_uri *resource = NULL;
918
0
  int result;
919
0
  char tmp_line[512];
920
921
0
  stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, context, NULL, &resource, NULL, NULL);
922
0
  if (!stream) {
923
0
    if (options & REPORT_ERRORS) {
924
0
      php_stream_wrapper_warn(wrapper, context, options, AuthFailed,
925
0
        "Unable to connect to %s", url);
926
0
    }
927
0
    goto unlink_errexit;
928
0
  }
929
930
0
  if (resource->path == NULL) {
931
0
    if (options & REPORT_ERRORS) {
932
0
      php_stream_wrapper_warn(wrapper, context, options, InvalidPath,
933
0
        "Invalid path provided in %s", url);
934
0
    }
935
0
    goto unlink_errexit;
936
0
  }
937
938
  /* Attempt to delete the file */
939
0
  php_stream_printf(stream, "DELE %s\r\n", ZSTR_VAL(resource->path));
940
941
0
  result = GET_FTP_RESULT(stream);
942
0
  if (result < 200 || result > 299) {
943
0
    if (options & REPORT_ERRORS) {
944
0
      php_stream_wrapper_warn(wrapper, context, options, UnlinkFailed,
945
0
        "Error Deleting file: %s", tmp_line);
946
0
    }
947
0
    goto unlink_errexit;
948
0
  }
949
950
0
  php_uri_struct_free(resource);
951
0
  php_stream_close(stream);
952
0
  return 1;
953
954
0
unlink_errexit:
955
0
  if (resource) {
956
0
    php_uri_struct_free(resource);
957
0
  }
958
0
  if (stream) {
959
0
    php_stream_close(stream);
960
0
  }
961
0
  return 0;
962
0
}
963
/* }}} */
964
965
/* {{{ php_stream_ftp_rename */
966
static int php_stream_ftp_rename(php_stream_wrapper *wrapper, const char *url_from, const char *url_to, int options, php_stream_context *context)
967
0
{
968
0
  php_stream *stream = NULL;
969
0
  php_uri *resource_from = NULL, *resource_to = NULL;
970
0
  int result;
971
0
  char tmp_line[512];
972
973
0
  const php_uri_parser *uri_parser = php_stream_context_get_uri_parser("ftp", context);
974
0
  if (uri_parser == NULL) {
975
0
    zend_value_error("%s(): Provided stream context has invalid value for the \"uri_parser_class\" option", get_active_function_name());
976
0
    return 0;
977
0
  }
978
979
0
  resource_from = php_uri_parse_to_struct(uri_parser, url_from, strlen(url_from), PHP_URI_COMPONENT_READ_MODE_RAW, true);
980
0
  if (!resource_from) {
981
0
    return 0;
982
0
  }
983
984
0
  resource_to = php_uri_parse_to_struct(uri_parser, url_to, strlen(url_to), PHP_URI_COMPONENT_READ_MODE_RAW, true);
985
0
  if (!resource_to) {
986
0
    goto rename_errexit;
987
0
  }
988
989
  /* Must be same scheme (ftp/ftp or ftps/ftps), same host, and same port
990
    (or a 21/0 0/21 combination which is also "same")
991
     Also require paths to/from */
992
0
  if (!resource_from->scheme ||
993
0
    !resource_to->scheme ||
994
0
    !zend_string_equals(resource_from->scheme, resource_to->scheme) ||
995
0
    !resource_from->host ||
996
0
    !resource_to->host ||
997
0
    !zend_string_equals(resource_from->host, resource_to->host) ||
998
0
    (resource_from->port != resource_to->port &&
999
0
     resource_from->port * resource_to->port != 0 &&
1000
0
     resource_from->port + resource_to->port != 21) ||
1001
0
    !resource_from->path ||
1002
0
    !resource_to->path) {
1003
0
    goto rename_errexit;
1004
0
  }
1005
1006
0
  stream = php_ftp_fopen_connect(wrapper, url_from, "r", 0, NULL, context, NULL, NULL, NULL, NULL);
1007
0
  if (!stream) {
1008
0
    if (options & REPORT_ERRORS) {
1009
0
      php_stream_wrapper_warn(wrapper, context, options, AuthFailed,
1010
0
        "Unable to connect to %s", ZSTR_VAL(resource_from->host));
1011
0
    }
1012
0
    goto rename_errexit;
1013
0
  }
1014
1015
  /* Rename FROM */
1016
0
  php_stream_printf(stream, "RNFR %s\r\n", ZSTR_VAL(resource_from->path));
1017
1018
0
  result = GET_FTP_RESULT(stream);
1019
0
  if (result < 300 || result > 399) {
1020
0
    if (options & REPORT_ERRORS) {
1021
0
      php_stream_wrapper_warn(wrapper, context, options, RenameFailed,
1022
0
        "Error Renaming file: %s", tmp_line);
1023
0
    }
1024
0
    goto rename_errexit;
1025
0
  }
1026
1027
  /* Rename TO */
1028
0
  php_stream_printf(stream, "RNTO %s\r\n", ZSTR_VAL(resource_to->path));
1029
1030
0
  result = GET_FTP_RESULT(stream);
1031
0
  if (result < 200 || result > 299) {
1032
0
    if (options & REPORT_ERRORS) {
1033
0
      php_stream_wrapper_warn(wrapper, context, options, RenameFailed,
1034
0
        "Error Renaming file: %s", tmp_line);
1035
0
    }
1036
0
    goto rename_errexit;
1037
0
  }
1038
1039
0
  php_uri_struct_free(resource_from);
1040
0
  php_uri_struct_free(resource_to);
1041
0
  php_stream_close(stream);
1042
0
  return 1;
1043
1044
0
rename_errexit:
1045
0
  php_uri_struct_free(resource_from);
1046
0
  if (resource_to) {
1047
0
    php_uri_struct_free(resource_to);
1048
0
  }
1049
0
  if (stream) {
1050
0
    php_stream_close(stream);
1051
0
  }
1052
0
  return 0;
1053
0
}
1054
/* }}} */
1055
1056
/* {{{ php_stream_ftp_mkdir */
1057
static int php_stream_ftp_mkdir(php_stream_wrapper *wrapper, const char *url, int mode, int options, php_stream_context *context)
1058
0
{
1059
0
  php_stream *stream = NULL;
1060
0
  php_uri *resource = NULL;
1061
0
  int result, recursive = options & PHP_STREAM_MKDIR_RECURSIVE;
1062
0
  char tmp_line[512];
1063
1064
0
  stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, context, NULL, &resource, NULL, NULL);
1065
0
  if (!stream) {
1066
0
    if (options & REPORT_ERRORS) {
1067
0
      php_stream_wrapper_warn(wrapper, context, options, AuthFailed,
1068
0
        "Unable to connect to %s", url);
1069
0
    }
1070
0
    goto mkdir_errexit;
1071
0
  }
1072
1073
0
  if (resource->path == NULL) {
1074
0
    if (options & REPORT_ERRORS) {
1075
0
      php_stream_wrapper_warn(wrapper, context, options, InvalidPath,
1076
0
        "Invalid path provided in %s", url);
1077
0
    }
1078
0
    goto mkdir_errexit;
1079
0
  }
1080
1081
0
  if (!recursive) {
1082
0
    php_stream_printf(stream, "MKD %s\r\n", ZSTR_VAL(resource->path));
1083
0
    result = GET_FTP_RESULT(stream);
1084
0
  } else {
1085
    /* we look for directory separator from the end of string, thus hopefully reducing our work load */
1086
0
    char *p, *e, *buf;
1087
1088
0
    buf = estrndup(ZSTR_VAL(resource->path), ZSTR_LEN(resource->path));
1089
0
    e = buf + ZSTR_LEN(resource->path);
1090
1091
    /* find a top level directory we need to create */
1092
0
    while ((p = strrchr(buf, '/'))) {
1093
0
      *p = '\0';
1094
0
      php_stream_printf(stream, "CWD %s\r\n", strlen(buf) ? buf : "/");
1095
0
      result = GET_FTP_RESULT(stream);
1096
0
      if (result >= 200 && result <= 299) {
1097
0
        *p = '/';
1098
0
        break;
1099
0
      }
1100
0
    }
1101
1102
0
    php_stream_printf(stream, "MKD %s\r\n", strlen(buf) ? buf : "/");
1103
0
    result = GET_FTP_RESULT(stream);
1104
1105
0
    if (result >= 200 && result <= 299) {
1106
0
      if (!p) {
1107
0
        p = buf;
1108
0
      }
1109
      /* create any needed directories if the creation of the 1st directory worked */
1110
0
      while (p != e) {
1111
0
        if (*p == '\0' && *(p + 1) != '\0') {
1112
0
          *p = '/';
1113
0
          php_stream_printf(stream, "MKD %s\r\n", buf);
1114
0
          result = GET_FTP_RESULT(stream);
1115
0
          if (result < 200 || result > 299) {
1116
0
            if (options & REPORT_ERRORS) {
1117
0
              php_stream_wrapper_warn(wrapper, context, options, MkdirFailed,
1118
0
                "%s", tmp_line);
1119
0
            }
1120
0
            break;
1121
0
          }
1122
0
        }
1123
0
        ++p;
1124
0
      }
1125
0
    }
1126
1127
0
    efree(buf);
1128
0
  }
1129
1130
0
  php_uri_struct_free(resource);
1131
0
  php_stream_close(stream);
1132
1133
0
  if (result < 200 || result > 299) {
1134
    /* Failure */
1135
0
    return 0;
1136
0
  }
1137
1138
0
  return 1;
1139
1140
0
mkdir_errexit:
1141
0
  if (resource) {
1142
0
    php_uri_struct_free(resource);
1143
0
  }
1144
0
  if (stream) {
1145
0
    php_stream_close(stream);
1146
0
  }
1147
0
  return 0;
1148
0
}
1149
/* }}} */
1150
1151
/* {{{ php_stream_ftp_rmdir */
1152
static int php_stream_ftp_rmdir(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context)
1153
0
{
1154
0
  php_stream *stream = NULL;
1155
0
  php_uri *resource = NULL;
1156
0
  int result;
1157
0
  char tmp_line[512];
1158
1159
0
  stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, context, NULL, &resource, NULL, NULL);
1160
0
  if (!stream) {
1161
0
    if (options & REPORT_ERRORS) {
1162
0
      php_stream_wrapper_warn(wrapper, context, options, AuthFailed,
1163
0
        "Unable to connect to %s", url);
1164
0
    }
1165
0
    goto rmdir_errexit;
1166
0
  }
1167
1168
0
  if (resource->path == NULL) {
1169
0
    if (options & REPORT_ERRORS) {
1170
0
      php_stream_wrapper_warn(wrapper, context, options, InvalidPath,
1171
0
        "Invalid path provided in %s", url);
1172
0
    }
1173
0
    goto rmdir_errexit;
1174
0
  }
1175
1176
0
  php_stream_printf(stream, "RMD %s\r\n", ZSTR_VAL(resource->path));
1177
0
  result = GET_FTP_RESULT(stream);
1178
1179
0
  if (result < 200 || result > 299) {
1180
0
    if (options & REPORT_ERRORS) {
1181
0
      php_stream_wrapper_warn(wrapper, context, options, RmdirFailed,
1182
0
        "%s", tmp_line);
1183
0
    }
1184
0
    goto rmdir_errexit;
1185
0
  }
1186
1187
0
  php_uri_struct_free(resource);
1188
0
  php_stream_close(stream);
1189
1190
0
  return 1;
1191
1192
0
rmdir_errexit:
1193
0
  if (resource) {
1194
0
    php_uri_struct_free(resource);
1195
0
  }
1196
0
  if (stream) {
1197
0
    php_stream_close(stream);
1198
0
  }
1199
0
  return 0;
1200
0
}
1201
/* }}} */
1202
1203
static const php_stream_wrapper_ops ftp_stream_wops = {
1204
  php_stream_url_wrap_ftp,
1205
  php_stream_ftp_stream_close, /* stream_close */
1206
  php_stream_ftp_stream_stat,
1207
  php_stream_ftp_url_stat, /* stat_url */
1208
  php_stream_ftp_opendir, /* opendir */
1209
  "ftp",
1210
  php_stream_ftp_unlink, /* unlink */
1211
  php_stream_ftp_rename, /* rename */
1212
  php_stream_ftp_mkdir,  /* mkdir */
1213
  php_stream_ftp_rmdir,  /* rmdir */
1214
  NULL
1215
};
1216
1217
PHPAPI const php_stream_wrapper php_stream_ftp_wrapper =  {
1218
  &ftp_stream_wops,
1219
  NULL,
1220
  1 /* is_url */
1221
};