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 5                                                        |
   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) TSRMLS_CC)
  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 TSRMLS_DC)
  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 TSRMLS_DC)
  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 TSRMLS_DC)
 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 TSRMLS_CC, 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                                                                                  char **opened_path, php_stream_context *context, php_stream **preuseid,
 135                                                                                  php_url **presource, int *puse_ssl, int *puse_ssl_on_data TSRMLS_DC)
 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 = 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 TSRMLS_CC) < 0
 207                                 || php_stream_xport_crypto_enable(stream, 1 TSRMLS_CC) < 0) {
 208                         php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "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 = val, *e = s + val_len;       \
 237         while (s < e) { \
 238                 if (iscntrl(*s)) {      \
 239                         php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, 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 = php_raw_url_decode(resource->user, strlen(resource->user));
 249 
 250                 PHP_FTP_CNTRL_CHK(resource->user, tmp_len, "Invalid login %s")
 251 
 252                 php_stream_printf(stream TSRMLS_CC, "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 = php_raw_url_decode(resource->pass, strlen(resource->pass));
 266 
 267                         PHP_FTP_CNTRL_CHK(resource->pass, tmp_len, "Invalid password %s")
 268 
 269                         php_stream_printf(stream TSRMLS_CC, "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 TSRMLS_CC, "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 TSRMLS_DC)
 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, char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_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         int allow_overwrite = 0;
 428         int 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 TSRMLS_CC, "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 TSRMLS_CC, "Unknown file open mode");
 451                 return NULL;
 452         }
 453 
 454         if (context &&
 455                 php_stream_context_get_option(context, "ftp", "proxy", &tmpzval) == SUCCESS) {
 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 TSRMLS_CC);
 459                 } else {
 460                         /* ftp proxy is read-only */
 461                         php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "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 TSRMLS_CC);
 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 TSRMLS_CC, "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 && php_stream_context_get_option(context, "ftp", "overwrite", &tmpzval) == SUCCESS) {
 501                         allow_overwrite = Z_LVAL_PP(tmpzval);
 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 TSRMLS_CC, "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 TSRMLS_CC, "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 TSRMLS_CC);
 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                         php_stream_context_get_option(context, "ftp", "resume_pos", &tmpzval) == SUCCESS &&
 532                         Z_TYPE_PP(tmpzval) == IS_LONG &&
 533                         Z_LVAL_PP(tmpzval) > 0) {
 534                         php_stream_printf(stream TSRMLS_CC, "REST %ld\r\n", Z_LVAL_PP(tmpzval));
 535                         result = GET_FTP_RESULT(stream);
 536                         if (result < 300 || result > 399) {                     
 537                                 php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unable to resume from offset %ld", Z_LVAL_PP(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 TSRMLS_CC, "%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 = 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 TSRMLS_CC) < 0 ||
 579                         php_stream_xport_crypto_enable(datastream, 1 TSRMLS_CC) < 0)) {
 580 
 581                 php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "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 TSRMLS_CC, "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 TSRMLS_DC)
 610 {
 611         php_stream_dirent *ent = (php_stream_dirent *)buf;
 612         php_stream *innerstream;
 613         size_t tmp_len;
 614         char *basename;
 615         size_t basename_len;
 616 
 617         innerstream =  ((php_ftp_dirstream_data *)stream->abstract)->datastream;
 618 
 619         if (count != sizeof(php_stream_dirent)) {
 620                 return 0;
 621         }
 622 
 623         if (php_stream_eof(innerstream)) {
 624                 return 0;
 625         }
 626 
 627         if (!php_stream_get_line(innerstream, ent->d_name, sizeof(ent->d_name), &tmp_len)) {
 628                 return 0;
 629         }
 630 
 631         php_basename(ent->d_name, tmp_len, NULL, 0, &basename, &basename_len TSRMLS_CC);
 632         if (!basename) {
 633                 return 0;
 634         }
 635 
 636         if (!basename_len) {
 637                 efree(basename);
 638                 return 0;
 639         }
 640 
 641         tmp_len = MIN(sizeof(ent->d_name), basename_len - 1);
 642         memcpy(ent->d_name, basename, tmp_len);
 643         ent->d_name[tmp_len - 1] = '\0';
 644         efree(basename);
 645 
 646         /* Trim off trailing whitespace characters */
 647         while (tmp_len > 0 &&
 648                         (ent->d_name[tmp_len - 1] == '\n' || ent->d_name[tmp_len - 1] == '\r' ||
 649                          ent->d_name[tmp_len - 1] == '\t' || ent->d_name[tmp_len - 1] == ' ')) {
 650                 ent->d_name[--tmp_len] = '\0';
 651         }
 652 
 653         return sizeof(php_stream_dirent);
 654 }
 655 /* }}} */
 656 
 657 /* {{{ php_ftp_dirstream_close
 658  */
 659 static int php_ftp_dirstream_close(php_stream *stream, int close_handle TSRMLS_DC)
 660 {
 661         php_ftp_dirstream_data *data = stream->abstract;
 662 
 663         /* close control connection */
 664         if (data->controlstream) {
 665                 php_stream_close(data->controlstream);
 666                 data->controlstream = NULL;
 667         }
 668         /* close data connection */
 669         php_stream_close(data->datastream);
 670         data->datastream = NULL;
 671         
 672         efree(data);
 673         stream->abstract = NULL;
 674 
 675         return 0;
 676 }
 677 /* }}} */
 678 
 679 /* ftp dirstreams only need to support read and close operations,
 680    They can't be rewound because the underlying ftp stream can't be rewound. */
 681 static php_stream_ops php_ftp_dirstream_ops = {
 682         NULL, /* write */
 683         php_ftp_dirstream_read, /* read */
 684         php_ftp_dirstream_close, /* close */
 685         NULL, /* flush */
 686         "ftpdir",
 687         NULL, /* rewind */
 688         NULL, /* cast */
 689         NULL, /* stat */
 690         NULL  /* set option */
 691 };
 692 
 693 /* {{{ php_stream_ftp_opendir
 694  */
 695 php_stream * php_stream_ftp_opendir(php_stream_wrapper *wrapper, const char *path, const char *mode, int options,
 696                                                                         char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC)
 697 {
 698         php_stream *stream, *reuseid, *datastream = NULL;
 699         php_ftp_dirstream_data *dirsdata;
 700         php_url *resource = NULL;
 701         int result = 0, use_ssl, use_ssl_on_data = 0;
 702         char *hoststart = NULL, tmp_line[512];
 703         char ip[sizeof("123.123.123.123")];
 704         unsigned short portno;
 705 
 706         tmp_line[0] = '\0';
 707 
 708         stream = php_ftp_fopen_connect(wrapper, path, mode, options, opened_path, context, &reuseid, &resource, &use_ssl, &use_ssl_on_data TSRMLS_CC);
 709         if (!stream) {
 710                 goto opendir_errexit;   
 711         }
 712 
 713         /* set the connection to be ascii */
 714         php_stream_write_string(stream, "TYPE A\r\n");
 715         result = GET_FTP_RESULT(stream);
 716         if (result > 299 || result < 200)
 717                 goto opendir_errexit;
 718 
 719         /* set up the passive connection */
 720         portno = php_fopen_do_pasv(stream, ip, sizeof(ip), &hoststart TSRMLS_CC);
 721 
 722         if (!portno) {
 723                 goto opendir_errexit;
 724         }
 725 
 726         php_stream_printf(stream TSRMLS_CC, "NLST %s\r\n", (resource->path != NULL ? resource->path : "/"));
 727         
 728         /* open the data channel */
 729         if (hoststart == NULL) {
 730                 hoststart = resource->host;
 731         }
 732         datastream = php_stream_sock_open_host(hoststart, portno, SOCK_STREAM, 0, 0);
 733         if (datastream == NULL) {
 734                 goto opendir_errexit;
 735         }
 736 
 737         result = GET_FTP_RESULT(stream);
 738         if (result != 150 && result != 125) {
 739                 /* Could not retrieve or send the file 
 740                  * this data will only be sent to us after connection on the data port was initiated.
 741                  */
 742                 php_stream_close(datastream);
 743                 datastream = NULL;
 744                 goto opendir_errexit;   
 745         }
 746         
 747         php_stream_context_set(datastream, context);
 748 
 749         if (use_ssl_on_data && (php_stream_xport_crypto_setup(stream,
 750                         STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL TSRMLS_CC) < 0 ||
 751                         php_stream_xport_crypto_enable(stream, 1 TSRMLS_CC) < 0)) {
 752 
 753                 php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unable to activate SSL mode");
 754                 php_stream_close(datastream);
 755                 datastream = NULL;
 756                 goto opendir_errexit;
 757         }
 758 
 759         php_url_free(resource);
 760 
 761         dirsdata = emalloc(sizeof *dirsdata);
 762         dirsdata->datastream = datastream;
 763         dirsdata->controlstream = stream;
 764         dirsdata->dirstream = php_stream_alloc(&php_ftp_dirstream_ops, dirsdata, 0, mode);
 765 
 766         return dirsdata->dirstream;
 767 
 768 opendir_errexit:
 769         if (resource) {
 770                 php_url_free(resource);
 771         }
 772         if (stream) {
 773                 php_stream_notify_error(context, PHP_STREAM_NOTIFY_FAILURE, tmp_line, result);
 774                 php_stream_close(stream);
 775         }
 776         if (tmp_line[0] != '\0') {
 777                 php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "FTP server reports %s", tmp_line);
 778         }
 779         return NULL;
 780 }
 781 /* }}} */
 782 
 783 /* {{{ php_stream_ftp_url_stat
 784  */
 785 static int php_stream_ftp_url_stat(php_stream_wrapper *wrapper, const char *url, int flags, php_stream_statbuf *ssb, php_stream_context *context TSRMLS_DC)
 786 {
 787         php_stream *stream = NULL;
 788         php_url *resource = NULL;
 789         int result;
 790         char tmp_line[512];
 791 
 792         /* If ssb is NULL then someone is misbehaving */
 793         if (!ssb) return -1;
 794 
 795         stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, context, NULL, &resource, NULL, NULL TSRMLS_CC);
 796         if (!stream) {
 797                 goto stat_errexit;
 798         }
 799 
 800         ssb->sb.st_mode = 0644;                                                                 /* FTP won't give us a valid mode, so aproximate one based on being readable */
 801         php_stream_printf(stream TSRMLS_CC, "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) */
 802         result = GET_FTP_RESULT(stream);
 803         if (result < 200 || result > 299) {
 804                 ssb->sb.st_mode |= S_IFREG;
 805         } else {
 806                 ssb->sb.st_mode |= S_IFDIR;
 807         }
 808 
 809         php_stream_write_string(stream, "TYPE I\r\n"); /* we need this since some servers refuse to accept SIZE command in ASCII mode */
 810         
 811         result = GET_FTP_RESULT(stream);
 812 
 813         if(result < 200 || result > 299) {
 814                 goto stat_errexit;
 815         }
 816         
 817         php_stream_printf(stream TSRMLS_CC, "SIZE %s\r\n", (resource->path != NULL ? resource->path : "/"));
 818         result = GET_FTP_RESULT(stream);
 819         if (result < 200 || result > 299) {
 820                 /* Failure either means it doesn't exist 
 821                    or it's a directory and this server
 822                    fails on listing directory sizes */
 823                 if (ssb->sb.st_mode & S_IFDIR) {
 824                         ssb->sb.st_size = 0;
 825                 } else {
 826                         goto stat_errexit;
 827                 }
 828         } else {
 829                 ssb->sb.st_size = atoi(tmp_line + 4);
 830         }
 831 
 832         php_stream_printf(stream TSRMLS_CC, "MDTM %s\r\n", (resource->path != NULL ? resource->path : "/"));
 833         result = GET_FTP_RESULT(stream);
 834         if (result == 213) {
 835                 char *p = tmp_line + 4;
 836                 int n;
 837                 struct tm tm, tmbuf, *gmt;
 838                 time_t stamp;
 839 
 840                 while (p - tmp_line < sizeof(tmp_line) && !isdigit(*p)) {
 841                         p++;
 842                 }
 843 
 844                 if (p - tmp_line > sizeof(tmp_line)) {
 845                         goto mdtm_error;
 846                 }
 847 
 848                 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);
 849                 if (n != 6) {
 850                         goto mdtm_error;
 851                 }
 852 
 853                 tm.tm_year -= 1900;
 854                 tm.tm_mon--;
 855                 tm.tm_isdst = -1;
 856 
 857                 /* figure out the GMT offset */
 858                 stamp = time(NULL);
 859                 gmt = php_gmtime_r(&stamp, &tmbuf);
 860                 if (!gmt) {
 861                         goto mdtm_error;
 862                 }
 863                 gmt->tm_isdst = -1;
 864 
 865                 /* apply the GMT offset */
 866                 tm.tm_sec += stamp - mktime(gmt);
 867                 tm.tm_isdst = gmt->tm_isdst;
 868 
 869                 ssb->sb.st_mtime = mktime(&tm);
 870         } else {
 871                 /* error or unsupported command */
 872 mdtm_error:
 873                 ssb->sb.st_mtime = -1;
 874         }
 875 
 876         ssb->sb.st_ino = 0;                                             /* Unknown values */
 877         ssb->sb.st_dev = 0;
 878         ssb->sb.st_uid = 0;
 879         ssb->sb.st_gid = 0;
 880         ssb->sb.st_atime = -1;
 881         ssb->sb.st_ctime = -1;
 882 
 883         ssb->sb.st_nlink = 1;
 884         ssb->sb.st_rdev = -1;
 885 #ifdef HAVE_ST_BLKSIZE
 886         ssb->sb.st_blksize = 4096;                              /* Guess since FTP won't expose this information */
 887 #ifdef HAVE_ST_BLOCKS
 888         ssb->sb.st_blocks = (int)((4095 + ssb->sb.st_size) / ssb->sb.st_blksize); /* emulate ceil */
 889 #endif
 890 #endif
 891         php_stream_close(stream);
 892         php_url_free(resource);
 893         return 0;
 894 
 895 stat_errexit:
 896         if (resource) {
 897                 php_url_free(resource);
 898         }
 899         if (stream) {
 900                 php_stream_close(stream);
 901         }
 902         return -1;
 903 }
 904 /* }}} */
 905 
 906 /* {{{ php_stream_ftp_unlink
 907  */
 908 static int php_stream_ftp_unlink(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context TSRMLS_DC)
 909 {
 910         php_stream *stream = NULL;
 911         php_url *resource = NULL;
 912         int result;
 913         char tmp_line[512];
 914 
 915         stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, NULL, NULL, &resource, NULL, NULL TSRMLS_CC);
 916         if (!stream) {
 917                 if (options & REPORT_ERRORS) {
 918                         php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to connect to %s", url);
 919                 }
 920                 goto unlink_errexit;
 921         }
 922 
 923         if (resource->path == NULL) {
 924                 if (options & REPORT_ERRORS) {
 925                         php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid path provided in %s", url);
 926                 }
 927                 goto unlink_errexit;
 928         }
 929 
 930         /* Attempt to delete the file */
 931         php_stream_printf(stream TSRMLS_CC, "DELE %s\r\n", (resource->path != NULL ? resource->path : "/"));
 932 
 933         result = GET_FTP_RESULT(stream);
 934         if (result < 200 || result > 299) {
 935                 if (options & REPORT_ERRORS) {
 936                         php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error Deleting file: %s", tmp_line);
 937                 }
 938                 goto unlink_errexit;
 939         }
 940 
 941         php_url_free(resource);
 942         php_stream_close(stream);
 943         return 1;
 944 
 945 unlink_errexit:
 946         if (resource) {
 947                 php_url_free(resource);
 948         }
 949         if (stream) {
 950                 php_stream_close(stream);
 951         }
 952         return 0;
 953 }
 954 /* }}} */
 955 
 956 /* {{{ php_stream_ftp_rename
 957  */
 958 static int php_stream_ftp_rename(php_stream_wrapper *wrapper, const char *url_from, const char *url_to, int options, php_stream_context *context TSRMLS_DC)
 959 {
 960         php_stream *stream = NULL;
 961         php_url *resource_from = NULL, *resource_to = NULL;
 962         int result;
 963         char tmp_line[512];
 964 
 965         resource_from = php_url_parse(url_from);
 966         resource_to = php_url_parse(url_to);
 967         /* Must be same scheme (ftp/ftp or ftps/ftps), same host, and same port 
 968                 (or a 21/0 0/21 combination which is also "same") 
 969            Also require paths to/from */
 970         if (!resource_from ||
 971                 !resource_to ||
 972                 !resource_from->scheme ||
 973                 !resource_to->scheme ||
 974                 strcmp(resource_from->scheme, resource_to->scheme) ||
 975                 !resource_from->host ||
 976                 !resource_to->host ||
 977                 strcmp(resource_from->host, resource_to->host) ||
 978                 (resource_from->port != resource_to->port && 
 979                  resource_from->port * resource_to->port != 0 && 
 980                  resource_from->port + resource_to->port != 21) ||
 981                 !resource_from->path ||
 982                 !resource_to->path) {
 983                 goto rename_errexit;
 984         }
 985 
 986         stream = php_ftp_fopen_connect(wrapper, url_from, "r", 0, NULL, NULL, NULL, NULL, NULL, NULL TSRMLS_CC);
 987         if (!stream) {
 988                 if (options & REPORT_ERRORS) {
 989                         php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to connect to %s", resource_from->host);
 990                 }
 991                 goto rename_errexit;
 992         }
 993 
 994         /* Rename FROM */
 995         php_stream_printf(stream TSRMLS_CC, "RNFR %s\r\n", (resource_from->path != NULL ? resource_from->path : "/"));
 996 
 997         result = GET_FTP_RESULT(stream);
 998         if (result < 300 || result > 399) {
 999                 if (options & REPORT_ERRORS) {
1000                         php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error Renaming file: %s", tmp_line);
1001                 }
1002                 goto rename_errexit;
1003         }
1004 
1005         /* Rename TO */
1006         php_stream_printf(stream TSRMLS_CC, "RNTO %s\r\n", (resource_to->path != NULL ? resource_to->path : "/"));
1007 
1008         result = GET_FTP_RESULT(stream);
1009         if (result < 200 || result > 299) {
1010                 if (options & REPORT_ERRORS) {
1011                         php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error Renaming file: %s", tmp_line);
1012                 }
1013                 goto rename_errexit;
1014         }
1015 
1016         php_url_free(resource_from);
1017         php_url_free(resource_to);
1018         php_stream_close(stream);
1019         return 1;
1020 
1021 rename_errexit:
1022         if (resource_from) {
1023                 php_url_free(resource_from);
1024         }
1025         if (resource_to) {
1026                 php_url_free(resource_to);
1027         }
1028         if (stream) {
1029                 php_stream_close(stream);
1030         }
1031         return 0;
1032 }
1033 /* }}} */
1034 
1035 /* {{{ php_stream_ftp_mkdir
1036  */
1037 static int php_stream_ftp_mkdir(php_stream_wrapper *wrapper, const char *url, int mode, int options, php_stream_context *context TSRMLS_DC)
1038 {
1039         php_stream *stream = NULL;
1040         php_url *resource = NULL;
1041         int result, recursive = options & PHP_STREAM_MKDIR_RECURSIVE;
1042         char tmp_line[512];
1043 
1044         stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, NULL, NULL, &resource, NULL, NULL TSRMLS_CC);
1045         if (!stream) {
1046                 if (options & REPORT_ERRORS) {
1047                         php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to connect to %s", url);
1048                 }
1049                 goto mkdir_errexit;
1050         }
1051 
1052         if (resource->path == NULL) {
1053                 if (options & REPORT_ERRORS) {
1054                         php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid path provided in %s", url);
1055                 }
1056                 goto mkdir_errexit;
1057         }
1058 
1059         if (!recursive) {
1060                 php_stream_printf(stream TSRMLS_CC, "MKD %s\r\n", resource->path);
1061                 result = GET_FTP_RESULT(stream);
1062     } else {
1063         /* we look for directory separator from the end of string, thus hopefuly reducing our work load */
1064         char *p, *e, *buf;
1065 
1066         buf = estrdup(resource->path);
1067         e = buf + strlen(buf);
1068 
1069         /* find a top level directory we need to create */
1070         while ((p = strrchr(buf, '/'))) {
1071             *p = '\0';
1072                         php_stream_printf(stream TSRMLS_CC, "CWD %s\r\n", buf);
1073                         result = GET_FTP_RESULT(stream);
1074                         if (result >= 200 && result <= 299) {
1075                                 *p = '/';
1076                                 break;
1077                         }
1078         }
1079         if (p == buf) {
1080                         php_stream_printf(stream TSRMLS_CC, "MKD %s\r\n", resource->path);
1081                         result = GET_FTP_RESULT(stream);
1082         } else {
1083                         php_stream_printf(stream TSRMLS_CC, "MKD %s\r\n", buf);
1084                         result = GET_FTP_RESULT(stream);
1085                         if (result >= 200 && result <= 299) {
1086                                 if (!p) {
1087                                         p = buf;
1088                                 }
1089                                 /* create any needed directories if the creation of the 1st directory worked */
1090                                 while (++p != e) {
1091                                         if (*p == '\0' && *(p + 1) != '\0') {
1092                                                 *p = '/';
1093                                                 php_stream_printf(stream TSRMLS_CC, "MKD %s\r\n", buf);
1094                                                 result = GET_FTP_RESULT(stream);
1095                                                 if (result < 200 || result > 299) {
1096                                                         if (options & REPORT_ERRORS) {
1097                                                                 php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", tmp_line);
1098                                                         }
1099                                                         break;
1100                                                 }
1101                                         }
1102                                 }
1103                         }
1104                 }
1105         efree(buf);
1106     }
1107 
1108         php_url_free(resource);
1109         php_stream_close(stream);
1110 
1111         if (result < 200 || result > 299) {
1112                 /* Failure */
1113                 return 0;
1114         }
1115 
1116         return 1;
1117 
1118 mkdir_errexit:
1119         if (resource) {
1120                 php_url_free(resource);
1121         }
1122         if (stream) {
1123                 php_stream_close(stream);
1124         }
1125         return 0;
1126 }
1127 /* }}} */
1128 
1129 /* {{{ php_stream_ftp_rmdir
1130  */
1131 static int php_stream_ftp_rmdir(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context TSRMLS_DC)
1132 {
1133         php_stream *stream = NULL;
1134         php_url *resource = NULL;
1135         int result;
1136         char tmp_line[512];
1137 
1138         stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, NULL, NULL, &resource, NULL, NULL TSRMLS_CC);
1139         if (!stream) {
1140                 if (options & REPORT_ERRORS) {
1141                         php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to connect to %s", url);
1142                 }
1143                 goto rmdir_errexit;
1144         }
1145 
1146         if (resource->path == NULL) {
1147                 if (options & REPORT_ERRORS) {
1148                         php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid path provided in %s", url);
1149                 }
1150                 goto rmdir_errexit;
1151         }
1152 
1153         php_stream_printf(stream TSRMLS_CC, "RMD %s\r\n", resource->path);
1154         result = GET_FTP_RESULT(stream);
1155 
1156         if (result < 200 || result > 299) {
1157                 if (options & REPORT_ERRORS) {
1158                         php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", tmp_line);
1159                 }
1160                 goto rmdir_errexit;
1161         }
1162 
1163         php_url_free(resource);
1164         php_stream_close(stream);
1165 
1166         return 1;
1167 
1168 rmdir_errexit:
1169         if (resource) {
1170                 php_url_free(resource);
1171         }
1172         if (stream) {
1173                 php_stream_close(stream);
1174         }
1175         return 0;
1176 }
1177 /* }}} */
1178 
1179 static php_stream_wrapper_ops ftp_stream_wops = {
1180         php_stream_url_wrap_ftp,
1181         php_stream_ftp_stream_close, /* stream_close */
1182         php_stream_ftp_stream_stat,
1183         php_stream_ftp_url_stat, /* stat_url */
1184         php_stream_ftp_opendir, /* opendir */
1185         "ftp",
1186         php_stream_ftp_unlink, /* unlink */
1187         php_stream_ftp_rename, /* rename */
1188         php_stream_ftp_mkdir,  /* mkdir */
1189         php_stream_ftp_rmdir   /* rmdir */
1190 };
1191 
1192 PHPAPI php_stream_wrapper php_stream_ftp_wrapper =      {
1193         &ftp_stream_wops,
1194         NULL,
1195         1 /* is_url */
1196 };
1197 
1198 
1199 /*
1200  * Local variables:
1201  * tab-width: 4
1202  * c-basic-offset: 4
1203  * End:
1204  * vim600: sw=4 ts=4 fdm=marker
1205  * vim<600: sw=4 ts=4
1206  */

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