root/ext/standard/ftp_fopen_wrapper.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. get_ftp_result
  2. php_stream_ftp_stream_stat
  3. php_stream_ftp_stream_close
  4. php_ftp_fopen_connect
  5. php_fopen_do_pasv
  6. php_stream_url_wrap_ftp
  7. php_ftp_dirstream_read
  8. php_ftp_dirstream_close
  9. php_stream_ftp_opendir
  10. php_stream_ftp_url_stat
  11. php_stream_ftp_unlink
  12. php_stream_ftp_rename
  13. php_stream_ftp_mkdir
  14. php_stream_ftp_rmdir

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

/* [<][>][^][v][top][bottom][index][help] */