Annotation of embedaddon/php/ext/standard/ftp_fopen_wrapper.c, revision 1.1
1.1 ! misho 1: /*
! 2: +----------------------------------------------------------------------+
! 3: | PHP Version 5 |
! 4: +----------------------------------------------------------------------+
! 5: | Copyright (c) 1997-2012 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: ftp_fopen_wrapper.c 321634 2012-01-01 13:15:04Z felipe $ */
! 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, char *path, char *mode, int options, char **opened_path, php_stream_context *context,
! 134: php_stream **preuseid, php_url **presource, int *puse_ssl, int *puse_ssl_on_data TSRMLS_DC)
! 135: {
! 136: php_stream *stream = NULL, *reuseid = NULL;
! 137: php_url *resource = NULL;
! 138: int result, use_ssl, use_ssl_on_data = 0, tmp_len;
! 139: char tmp_line[512];
! 140: char *transport;
! 141: int transport_len;
! 142:
! 143: resource = php_url_parse(path);
! 144: if (resource == NULL || resource->path == NULL) {
! 145: if (resource && presource) {
! 146: *presource = resource;
! 147: }
! 148: return NULL;
! 149: }
! 150:
! 151: use_ssl = resource->scheme && (strlen(resource->scheme) > 3) && resource->scheme[3] == 's';
! 152:
! 153: /* use port 21 if one wasn't specified */
! 154: if (resource->port == 0)
! 155: resource->port = 21;
! 156:
! 157: transport_len = spprintf(&transport, 0, "tcp://%s:%d", resource->host, resource->port);
! 158: stream = php_stream_xport_create(transport, transport_len, REPORT_ERRORS, STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT, NULL, NULL, context, NULL, NULL);
! 159: efree(transport);
! 160: if (stream == NULL) {
! 161: result = 0; /* silence */
! 162: goto connect_errexit;
! 163: }
! 164:
! 165: php_stream_context_set(stream, context);
! 166: php_stream_notify_info(context, PHP_STREAM_NOTIFY_CONNECT, NULL, 0);
! 167:
! 168: /* Start talking to ftp server */
! 169: result = GET_FTP_RESULT(stream);
! 170: if (result > 299 || result < 200) {
! 171: php_stream_notify_error(context, PHP_STREAM_NOTIFY_FAILURE, tmp_line, result);
! 172: goto connect_errexit;
! 173: }
! 174:
! 175: if (use_ssl) {
! 176:
! 177: /* send the AUTH TLS request name */
! 178: php_stream_write_string(stream, "AUTH TLS\r\n");
! 179:
! 180: /* get the response */
! 181: result = GET_FTP_RESULT(stream);
! 182: if (result != 234) {
! 183: /* AUTH TLS not supported try AUTH SSL */
! 184: php_stream_write_string(stream, "AUTH SSL\r\n");
! 185:
! 186: /* get the response */
! 187: result = GET_FTP_RESULT(stream);
! 188: if (result != 334) {
! 189: use_ssl = 0;
! 190: } else {
! 191: /* we must reuse the old SSL session id */
! 192: /* if we talk to an old ftpd-ssl */
! 193: reuseid = stream;
! 194: }
! 195: } else {
! 196: /* encrypt data etc */
! 197:
! 198:
! 199: }
! 200:
! 201: }
! 202:
! 203: if (use_ssl) {
! 204: if (php_stream_xport_crypto_setup(stream,
! 205: STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL TSRMLS_CC) < 0
! 206: || php_stream_xport_crypto_enable(stream, 1 TSRMLS_CC) < 0) {
! 207: php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unable to activate SSL mode");
! 208: php_stream_close(stream);
! 209: stream = NULL;
! 210: goto connect_errexit;
! 211: }
! 212:
! 213: /* set PBSZ to 0 */
! 214: php_stream_write_string(stream, "PBSZ 0\r\n");
! 215:
! 216: /* ignore the response */
! 217: result = GET_FTP_RESULT(stream);
! 218:
! 219: /* set data connection protection level */
! 220: #if FTPS_ENCRYPT_DATA
! 221: php_stream_write_string(stream, "PROT P\r\n");
! 222:
! 223: /* get the response */
! 224: result = GET_FTP_RESULT(stream);
! 225: use_ssl_on_data = (result >= 200 && result<=299) || reuseid;
! 226: #else
! 227: php_stream_write_string(stream, "PROT C\r\n");
! 228:
! 229: /* get the response */
! 230: result = GET_FTP_RESULT(stream);
! 231: #endif
! 232: }
! 233:
! 234: #define PHP_FTP_CNTRL_CHK(val, val_len, err_msg) { \
! 235: unsigned char *s = val, *e = s + val_len; \
! 236: while (s < e) { \
! 237: if (iscntrl(*s)) { \
! 238: php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, err_msg, val); \
! 239: goto connect_errexit; \
! 240: } \
! 241: s++; \
! 242: } \
! 243: }
! 244:
! 245: /* send the user name */
! 246: if (resource->user != NULL) {
! 247: tmp_len = php_raw_url_decode(resource->user, strlen(resource->user));
! 248:
! 249: PHP_FTP_CNTRL_CHK(resource->user, tmp_len, "Invalid login %s")
! 250:
! 251: php_stream_printf(stream TSRMLS_CC, "USER %s\r\n", resource->user);
! 252: } else {
! 253: php_stream_write_string(stream, "USER anonymous\r\n");
! 254: }
! 255:
! 256: /* get the response */
! 257: result = GET_FTP_RESULT(stream);
! 258:
! 259: /* if a password is required, send it */
! 260: if (result >= 300 && result <= 399) {
! 261: php_stream_notify_info(context, PHP_STREAM_NOTIFY_AUTH_REQUIRED, tmp_line, 0);
! 262:
! 263: if (resource->pass != NULL) {
! 264: tmp_len = php_raw_url_decode(resource->pass, strlen(resource->pass));
! 265:
! 266: PHP_FTP_CNTRL_CHK(resource->pass, tmp_len, "Invalid password %s")
! 267:
! 268: php_stream_printf(stream TSRMLS_CC, "PASS %s\r\n", resource->pass);
! 269: } else {
! 270: /* if the user has configured who they are,
! 271: send that as the password */
! 272: char *from_address = php_ini_string("from", sizeof("from"), 0);
! 273: if (from_address[0] != '\0') {
! 274: php_stream_printf(stream TSRMLS_CC, "PASS %s\r\n", 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, char *path, char *mode, int options, char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC)
! 415: {
! 416: php_stream *stream = NULL, *datastream = NULL;
! 417: php_url *resource = NULL;
! 418: char tmp_line[512];
! 419: char ip[sizeof("123.123.123.123")];
! 420: unsigned short portno;
! 421: char *hoststart = NULL;
! 422: int result = 0, use_ssl, use_ssl_on_data=0;
! 423: php_stream *reuseid=NULL;
! 424: size_t file_size = 0;
! 425: zval **tmpzval;
! 426: int allow_overwrite = 0;
! 427: int read_write = 0;
! 428: char *transport;
! 429: int transport_len;
! 430:
! 431: tmp_line[0] = '\0';
! 432:
! 433: if (strpbrk(mode, "r+")) {
! 434: read_write = 1; /* Open for reading */
! 435: }
! 436: if (strpbrk(mode, "wa+")) {
! 437: if (read_write) {
! 438: php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "FTP does not support simultaneous read/write connections");
! 439: return NULL;
! 440: }
! 441: if (strchr(mode, 'a')) {
! 442: read_write = 3; /* Open for Appending */
! 443: } else {
! 444: read_write = 2; /* Open for writting */
! 445: }
! 446: }
! 447: if (!read_write) {
! 448: /* No mode specified? */
! 449: php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unknown file open mode");
! 450: return NULL;
! 451: }
! 452:
! 453: if (context &&
! 454: php_stream_context_get_option(context, "ftp", "proxy", &tmpzval) == SUCCESS) {
! 455: if (read_write == 1) {
! 456: /* Use http wrapper to proxy ftp request */
! 457: return php_stream_url_wrap_http(wrapper, path, mode, options, opened_path, context STREAMS_CC TSRMLS_CC);
! 458: } else {
! 459: /* ftp proxy is read-only */
! 460: php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "FTP proxy may only be used in read mode");
! 461: return NULL;
! 462: }
! 463: }
! 464:
! 465: stream = php_ftp_fopen_connect(wrapper, path, mode, options, opened_path, context, &reuseid, &resource, &use_ssl, &use_ssl_on_data TSRMLS_CC);
! 466: if (!stream) {
! 467: goto errexit;
! 468: }
! 469:
! 470: /* set the connection to be binary */
! 471: php_stream_write_string(stream, "TYPE I\r\n");
! 472: result = GET_FTP_RESULT(stream);
! 473: if (result > 299 || result < 200)
! 474: goto errexit;
! 475:
! 476: /* find out the size of the file (verifying it exists) */
! 477: php_stream_printf(stream TSRMLS_CC, "SIZE %s\r\n", resource->path);
! 478:
! 479: /* read the response */
! 480: result = GET_FTP_RESULT(stream);
! 481: if (read_write == 1) {
! 482: /* Read Mode */
! 483: char *sizestr;
! 484:
! 485: /* when reading file, it must exist */
! 486: if (result > 299 || result < 200) {
! 487: errno = ENOENT;
! 488: goto errexit;
! 489: }
! 490:
! 491: sizestr = strchr(tmp_line, ' ');
! 492: if (sizestr) {
! 493: sizestr++;
! 494: file_size = atoi(sizestr);
! 495: php_stream_notify_file_size(context, file_size, tmp_line, result);
! 496: }
! 497: } else if (read_write == 2) {
! 498: /* when writing file (but not appending), it must NOT exist, unless a context option exists which allows it */
! 499: if (context && php_stream_context_get_option(context, "ftp", "overwrite", &tmpzval) == SUCCESS) {
! 500: allow_overwrite = Z_LVAL_PP(tmpzval);
! 501: }
! 502: if (result <= 299 && result >= 200) {
! 503: if (allow_overwrite) {
! 504: /* Context permits overwritting file,
! 505: so we just delete whatever's there in preparation */
! 506: php_stream_printf(stream TSRMLS_CC, "DELE %s\r\n", resource->path);
! 507: result = GET_FTP_RESULT(stream);
! 508: if (result >= 300 || result <= 199) {
! 509: goto errexit;
! 510: }
! 511: } else {
! 512: php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Remote file already exists and overwrite context option not specified");
! 513: errno = EEXIST;
! 514: goto errexit;
! 515: }
! 516: }
! 517: }
! 518:
! 519: /* set up the passive connection */
! 520: portno = php_fopen_do_pasv(stream, ip, sizeof(ip), &hoststart TSRMLS_CC);
! 521:
! 522: if (!portno) {
! 523: goto errexit;
! 524: }
! 525:
! 526: /* Send RETR/STOR command */
! 527: if (read_write == 1) {
! 528: /* set resume position if applicable */
! 529: if (context &&
! 530: php_stream_context_get_option(context, "ftp", "resume_pos", &tmpzval) == SUCCESS &&
! 531: Z_TYPE_PP(tmpzval) == IS_LONG &&
! 532: Z_LVAL_PP(tmpzval) > 0) {
! 533: php_stream_printf(stream TSRMLS_CC, "REST %ld\r\n", Z_LVAL_PP(tmpzval));
! 534: result = GET_FTP_RESULT(stream);
! 535: if (result < 300 || result > 399) {
! 536: php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unable to resume from offset %ld", Z_LVAL_PP(tmpzval));
! 537: goto errexit;
! 538: }
! 539: }
! 540:
! 541: /* retrieve file */
! 542: memcpy(tmp_line, "RETR", sizeof("RETR"));
! 543: } else if (read_write == 2) {
! 544: /* Write new file */
! 545: memcpy(tmp_line, "STOR", sizeof("STOR"));
! 546: } else {
! 547: /* Append */
! 548: memcpy(tmp_line, "APPE", sizeof("APPE"));
! 549: }
! 550: php_stream_printf(stream TSRMLS_CC, "%s %s\r\n", tmp_line, (resource->path != NULL ? resource->path : "/"));
! 551:
! 552: /* open the data channel */
! 553: if (hoststart == NULL) {
! 554: hoststart = resource->host;
! 555: }
! 556: transport_len = spprintf(&transport, 0, "tcp://%s:%d", hoststart, portno);
! 557: datastream = php_stream_xport_create(transport, transport_len, REPORT_ERRORS, STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT, NULL, NULL, context, NULL, NULL);
! 558: efree(transport);
! 559: if (datastream == NULL) {
! 560: goto errexit;
! 561: }
! 562:
! 563: result = GET_FTP_RESULT(stream);
! 564: if (result != 150 && result != 125) {
! 565: /* Could not retrieve or send the file
! 566: * this data will only be sent to us after connection on the data port was initiated.
! 567: */
! 568: php_stream_close(datastream);
! 569: datastream = NULL;
! 570: goto errexit;
! 571: }
! 572:
! 573: php_stream_context_set(datastream, context);
! 574: php_stream_notify_progress_init(context, 0, file_size);
! 575:
! 576: if (use_ssl_on_data && (php_stream_xport_crypto_setup(datastream,
! 577: STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL TSRMLS_CC) < 0 ||
! 578: php_stream_xport_crypto_enable(datastream, 1 TSRMLS_CC) < 0)) {
! 579:
! 580: php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unable to activate SSL mode");
! 581: php_stream_close(datastream);
! 582: datastream = NULL;
! 583: goto errexit;
! 584: }
! 585:
! 586: /* remember control stream */
! 587: datastream->wrapperthis = stream;
! 588:
! 589: php_url_free(resource);
! 590: return datastream;
! 591:
! 592: errexit:
! 593: if (resource) {
! 594: php_url_free(resource);
! 595: }
! 596: if (stream) {
! 597: php_stream_notify_error(context, PHP_STREAM_NOTIFY_FAILURE, tmp_line, result);
! 598: php_stream_close(stream);
! 599: }
! 600: if (tmp_line[0] != '\0')
! 601: php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "FTP server reports %s", tmp_line);
! 602: return NULL;
! 603: }
! 604: /* }}} */
! 605:
! 606: /* {{{ php_ftp_dirsteam_read
! 607: */
! 608: static size_t php_ftp_dirstream_read(php_stream *stream, char *buf, size_t count TSRMLS_DC)
! 609: {
! 610: php_stream_dirent *ent = (php_stream_dirent *)buf;
! 611: php_stream *innerstream;
! 612: size_t tmp_len;
! 613: char *basename;
! 614: size_t basename_len;
! 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: php_basename(ent->d_name, tmp_len, NULL, 0, &basename, &basename_len TSRMLS_CC);
! 631: if (!basename) {
! 632: return 0;
! 633: }
! 634:
! 635: if (!basename_len) {
! 636: efree(basename);
! 637: return 0;
! 638: }
! 639:
! 640: tmp_len = MIN(sizeof(ent->d_name), basename_len - 1);
! 641: memcpy(ent->d_name, basename, tmp_len);
! 642: ent->d_name[tmp_len - 1] = '\0';
! 643: efree(basename);
! 644:
! 645: /* Trim off trailing whitespace characters */
! 646: tmp_len--;
! 647: while (tmp_len >= 0 &&
! 648: (ent->d_name[tmp_len] == '\n' || ent->d_name[tmp_len] == '\r' ||
! 649: ent->d_name[tmp_len] == '\t' || ent->d_name[tmp_len] == ' ')) {
! 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, char *path, char *mode, int options, char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC)
! 696: {
! 697: php_stream *stream, *reuseid, *datastream = NULL;
! 698: php_ftp_dirstream_data *dirsdata;
! 699: php_url *resource = NULL;
! 700: int result = 0, use_ssl, use_ssl_on_data = 0;
! 701: char *hoststart = NULL, tmp_line[512];
! 702: char ip[sizeof("123.123.123.123")];
! 703: unsigned short portno;
! 704:
! 705: tmp_line[0] = '\0';
! 706:
! 707: stream = php_ftp_fopen_connect(wrapper, path, mode, options, opened_path, context, &reuseid, &resource, &use_ssl, &use_ssl_on_data TSRMLS_CC);
! 708: if (!stream) {
! 709: goto opendir_errexit;
! 710: }
! 711:
! 712: /* set the connection to be ascii */
! 713: php_stream_write_string(stream, "TYPE A\r\n");
! 714: result = GET_FTP_RESULT(stream);
! 715: if (result > 299 || result < 200)
! 716: goto opendir_errexit;
! 717:
! 718: /* set up the passive connection */
! 719: portno = php_fopen_do_pasv(stream, ip, sizeof(ip), &hoststart TSRMLS_CC);
! 720:
! 721: if (!portno) {
! 722: goto opendir_errexit;
! 723: }
! 724:
! 725: php_stream_printf(stream TSRMLS_CC, "NLST %s\r\n", (resource->path != NULL ? resource->path : "/"));
! 726:
! 727: /* open the data channel */
! 728: if (hoststart == NULL) {
! 729: hoststart = resource->host;
! 730: }
! 731: datastream = php_stream_sock_open_host(hoststart, portno, SOCK_STREAM, 0, 0);
! 732: if (datastream == NULL) {
! 733: goto opendir_errexit;
! 734: }
! 735:
! 736: result = GET_FTP_RESULT(stream);
! 737: if (result != 150 && result != 125) {
! 738: /* Could not retrieve or send the file
! 739: * this data will only be sent to us after connection on the data port was initiated.
! 740: */
! 741: php_stream_close(datastream);
! 742: datastream = NULL;
! 743: goto opendir_errexit;
! 744: }
! 745:
! 746: php_stream_context_set(datastream, context);
! 747:
! 748: if (use_ssl_on_data && (php_stream_xport_crypto_setup(stream,
! 749: STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL TSRMLS_CC) < 0 ||
! 750: php_stream_xport_crypto_enable(stream, 1 TSRMLS_CC) < 0)) {
! 751:
! 752: php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unable to activate SSL mode");
! 753: php_stream_close(datastream);
! 754: datastream = NULL;
! 755: goto opendir_errexit;
! 756: }
! 757:
! 758: php_url_free(resource);
! 759:
! 760: dirsdata = emalloc(sizeof *dirsdata);
! 761: dirsdata->datastream = datastream;
! 762: dirsdata->controlstream = stream;
! 763: dirsdata->dirstream = php_stream_alloc(&php_ftp_dirstream_ops, dirsdata, 0, mode);
! 764:
! 765: return dirsdata->dirstream;
! 766:
! 767: opendir_errexit:
! 768: if (resource) {
! 769: php_url_free(resource);
! 770: }
! 771: if (stream) {
! 772: php_stream_notify_error(context, PHP_STREAM_NOTIFY_FAILURE, tmp_line, result);
! 773: php_stream_close(stream);
! 774: }
! 775: if (tmp_line[0] != '\0') {
! 776: php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "FTP server reports %s", tmp_line);
! 777: }
! 778: return NULL;
! 779: }
! 780: /* }}} */
! 781:
! 782: /* {{{ php_stream_ftp_url_stat
! 783: */
! 784: static int php_stream_ftp_url_stat(php_stream_wrapper *wrapper, char *url, int flags, php_stream_statbuf *ssb, php_stream_context *context TSRMLS_DC)
! 785: {
! 786: php_stream *stream = NULL;
! 787: php_url *resource = NULL;
! 788: int result;
! 789: char tmp_line[512];
! 790:
! 791: /* If ssb is NULL then someone is misbehaving */
! 792: if (!ssb) return -1;
! 793:
! 794: stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, context, NULL, &resource, NULL, NULL TSRMLS_CC);
! 795: if (!stream) {
! 796: goto stat_errexit;
! 797: }
! 798:
! 799: ssb->sb.st_mode = 0644; /* FTP won't give us a valid mode, so aproximate one based on being readable */
! 800: 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) */
! 801: result = GET_FTP_RESULT(stream);
! 802: if (result < 200 || result > 299) {
! 803: ssb->sb.st_mode |= S_IFREG;
! 804: } else {
! 805: ssb->sb.st_mode |= S_IFDIR;
! 806: }
! 807:
! 808: php_stream_write_string(stream, "TYPE I\r\n"); /* we need this since some servers refuse to accept SIZE command in ASCII mode */
! 809:
! 810: result = GET_FTP_RESULT(stream);
! 811:
! 812: if(result < 200 || result > 299) {
! 813: goto stat_errexit;
! 814: }
! 815:
! 816: php_stream_printf(stream TSRMLS_CC, "SIZE %s\r\n", (resource->path != NULL ? resource->path : "/"));
! 817: result = GET_FTP_RESULT(stream);
! 818: if (result < 200 || result > 299) {
! 819: /* Failure either means it doesn't exist
! 820: or it's a directory and this server
! 821: fails on listing directory sizes */
! 822: if (ssb->sb.st_mode & S_IFDIR) {
! 823: ssb->sb.st_size = 0;
! 824: } else {
! 825: goto stat_errexit;
! 826: }
! 827: } else {
! 828: ssb->sb.st_size = atoi(tmp_line + 4);
! 829: }
! 830:
! 831: php_stream_printf(stream TSRMLS_CC, "MDTM %s\r\n", (resource->path != NULL ? resource->path : "/"));
! 832: result = GET_FTP_RESULT(stream);
! 833: if (result == 213) {
! 834: char *p = tmp_line + 4;
! 835: int n;
! 836: struct tm tm, tmbuf, *gmt;
! 837: time_t stamp;
! 838:
! 839: while (p - tmp_line < sizeof(tmp_line) && !isdigit(*p)) {
! 840: p++;
! 841: }
! 842:
! 843: if (p - tmp_line > sizeof(tmp_line)) {
! 844: goto mdtm_error;
! 845: }
! 846:
! 847: 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);
! 848: if (n != 6) {
! 849: goto mdtm_error;
! 850: }
! 851:
! 852: tm.tm_year -= 1900;
! 853: tm.tm_mon--;
! 854: tm.tm_isdst = -1;
! 855:
! 856: /* figure out the GMT offset */
! 857: stamp = time(NULL);
! 858: gmt = php_gmtime_r(&stamp, &tmbuf);
! 859: if (!gmt) {
! 860: goto mdtm_error;
! 861: }
! 862: gmt->tm_isdst = -1;
! 863:
! 864: /* apply the GMT offset */
! 865: tm.tm_sec += stamp - mktime(gmt);
! 866: tm.tm_isdst = gmt->tm_isdst;
! 867:
! 868: ssb->sb.st_mtime = mktime(&tm);
! 869: } else {
! 870: /* error or unsupported command */
! 871: mdtm_error:
! 872: ssb->sb.st_mtime = -1;
! 873: }
! 874:
! 875: ssb->sb.st_ino = 0; /* Unknown values */
! 876: ssb->sb.st_dev = 0;
! 877: ssb->sb.st_uid = 0;
! 878: ssb->sb.st_gid = 0;
! 879: ssb->sb.st_atime = -1;
! 880: ssb->sb.st_ctime = -1;
! 881:
! 882: ssb->sb.st_nlink = 1;
! 883: ssb->sb.st_rdev = -1;
! 884: #ifdef HAVE_ST_BLKSIZE
! 885: ssb->sb.st_blksize = 4096; /* Guess since FTP won't expose this information */
! 886: #ifdef HAVE_ST_BLOCKS
! 887: ssb->sb.st_blocks = (int)((4095 + ssb->sb.st_size) / ssb->sb.st_blksize); /* emulate ceil */
! 888: #endif
! 889: #endif
! 890: php_stream_close(stream);
! 891: php_url_free(resource);
! 892: return 0;
! 893:
! 894: stat_errexit:
! 895: if (resource) {
! 896: php_url_free(resource);
! 897: }
! 898: if (stream) {
! 899: php_stream_close(stream);
! 900: }
! 901: return -1;
! 902: }
! 903: /* }}} */
! 904:
! 905: /* {{{ php_stream_ftp_unlink
! 906: */
! 907: static int php_stream_ftp_unlink(php_stream_wrapper *wrapper, char *url, int options, php_stream_context *context TSRMLS_DC)
! 908: {
! 909: php_stream *stream = NULL;
! 910: php_url *resource = NULL;
! 911: int result;
! 912: char tmp_line[512];
! 913:
! 914: stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, NULL, NULL, &resource, NULL, NULL TSRMLS_CC);
! 915: if (!stream) {
! 916: if (options & REPORT_ERRORS) {
! 917: php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to connect to %s", url);
! 918: }
! 919: goto unlink_errexit;
! 920: }
! 921:
! 922: if (resource->path == NULL) {
! 923: if (options & REPORT_ERRORS) {
! 924: php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid path provided in %s", url);
! 925: }
! 926: goto unlink_errexit;
! 927: }
! 928:
! 929: /* Attempt to delete the file */
! 930: php_stream_printf(stream TSRMLS_CC, "DELE %s\r\n", (resource->path != NULL ? resource->path : "/"));
! 931:
! 932: result = GET_FTP_RESULT(stream);
! 933: if (result < 200 || result > 299) {
! 934: if (options & REPORT_ERRORS) {
! 935: php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error Deleting file: %s", tmp_line);
! 936: }
! 937: goto unlink_errexit;
! 938: }
! 939:
! 940: php_url_free(resource);
! 941: php_stream_close(stream);
! 942: return 1;
! 943:
! 944: unlink_errexit:
! 945: if (resource) {
! 946: php_url_free(resource);
! 947: }
! 948: if (stream) {
! 949: php_stream_close(stream);
! 950: }
! 951: return 0;
! 952: }
! 953: /* }}} */
! 954:
! 955: /* {{{ php_stream_ftp_rename
! 956: */
! 957: static int php_stream_ftp_rename(php_stream_wrapper *wrapper, char *url_from, char *url_to, int options, php_stream_context *context TSRMLS_DC)
! 958: {
! 959: php_stream *stream = NULL;
! 960: php_url *resource_from = NULL, *resource_to = NULL;
! 961: int result;
! 962: char tmp_line[512];
! 963:
! 964: resource_from = php_url_parse(url_from);
! 965: resource_to = php_url_parse(url_to);
! 966: /* Must be same scheme (ftp/ftp or ftps/ftps), same host, and same port
! 967: (or a 21/0 0/21 combination which is also "same")
! 968: Also require paths to/from */
! 969: if (!resource_from ||
! 970: !resource_to ||
! 971: !resource_from->scheme ||
! 972: !resource_to->scheme ||
! 973: strcmp(resource_from->scheme, resource_to->scheme) ||
! 974: !resource_from->host ||
! 975: !resource_to->host ||
! 976: strcmp(resource_from->host, resource_to->host) ||
! 977: (resource_from->port != resource_to->port &&
! 978: resource_from->port * resource_to->port != 0 &&
! 979: resource_from->port + resource_to->port != 21) ||
! 980: !resource_from->path ||
! 981: !resource_to->path) {
! 982: goto rename_errexit;
! 983: }
! 984:
! 985: stream = php_ftp_fopen_connect(wrapper, url_from, "r", 0, NULL, NULL, NULL, NULL, NULL, NULL TSRMLS_CC);
! 986: if (!stream) {
! 987: if (options & REPORT_ERRORS) {
! 988: php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to connect to %s", resource_from->host);
! 989: }
! 990: goto rename_errexit;
! 991: }
! 992:
! 993: /* Rename FROM */
! 994: php_stream_printf(stream TSRMLS_CC, "RNFR %s\r\n", (resource_from->path != NULL ? resource_from->path : "/"));
! 995:
! 996: result = GET_FTP_RESULT(stream);
! 997: if (result < 300 || result > 399) {
! 998: if (options & REPORT_ERRORS) {
! 999: php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error Renaming file: %s", tmp_line);
! 1000: }
! 1001: goto rename_errexit;
! 1002: }
! 1003:
! 1004: /* Rename TO */
! 1005: php_stream_printf(stream TSRMLS_CC, "RNTO %s\r\n", (resource_to->path != NULL ? resource_to->path : "/"));
! 1006:
! 1007: result = GET_FTP_RESULT(stream);
! 1008: if (result < 200 || result > 299) {
! 1009: if (options & REPORT_ERRORS) {
! 1010: php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error Renaming file: %s", tmp_line);
! 1011: }
! 1012: goto rename_errexit;
! 1013: }
! 1014:
! 1015: php_url_free(resource_from);
! 1016: php_url_free(resource_to);
! 1017: php_stream_close(stream);
! 1018: return 1;
! 1019:
! 1020: rename_errexit:
! 1021: if (resource_from) {
! 1022: php_url_free(resource_from);
! 1023: }
! 1024: if (resource_to) {
! 1025: php_url_free(resource_to);
! 1026: }
! 1027: if (stream) {
! 1028: php_stream_close(stream);
! 1029: }
! 1030: return 0;
! 1031: }
! 1032: /* }}} */
! 1033:
! 1034: /* {{{ php_stream_ftp_mkdir
! 1035: */
! 1036: static int php_stream_ftp_mkdir(php_stream_wrapper *wrapper, char *url, int mode, int options, php_stream_context *context TSRMLS_DC)
! 1037: {
! 1038: php_stream *stream = NULL;
! 1039: php_url *resource = NULL;
! 1040: int result, recursive = options & PHP_STREAM_MKDIR_RECURSIVE;
! 1041: char tmp_line[512];
! 1042:
! 1043: stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, NULL, NULL, &resource, NULL, NULL TSRMLS_CC);
! 1044: if (!stream) {
! 1045: if (options & REPORT_ERRORS) {
! 1046: php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to connect to %s", url);
! 1047: }
! 1048: goto mkdir_errexit;
! 1049: }
! 1050:
! 1051: if (resource->path == NULL) {
! 1052: if (options & REPORT_ERRORS) {
! 1053: php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid path provided in %s", url);
! 1054: }
! 1055: goto mkdir_errexit;
! 1056: }
! 1057:
! 1058: if (!recursive) {
! 1059: php_stream_printf(stream TSRMLS_CC, "MKD %s\r\n", resource->path);
! 1060: result = GET_FTP_RESULT(stream);
! 1061: } else {
! 1062: /* we look for directory separator from the end of string, thus hopefuly reducing our work load */
! 1063: char *p, *e, *buf;
! 1064:
! 1065: buf = estrdup(resource->path);
! 1066: e = buf + strlen(buf);
! 1067:
! 1068: /* find a top level directory we need to create */
! 1069: while ((p = strrchr(buf, '/'))) {
! 1070: *p = '\0';
! 1071: php_stream_printf(stream TSRMLS_CC, "CWD %s\r\n", buf);
! 1072: result = GET_FTP_RESULT(stream);
! 1073: if (result >= 200 && result <= 299) {
! 1074: *p = '/';
! 1075: break;
! 1076: }
! 1077: }
! 1078: if (p == buf) {
! 1079: php_stream_printf(stream TSRMLS_CC, "MKD %s\r\n", resource->path);
! 1080: result = GET_FTP_RESULT(stream);
! 1081: } else {
! 1082: php_stream_printf(stream TSRMLS_CC, "MKD %s\r\n", buf);
! 1083: result = GET_FTP_RESULT(stream);
! 1084: if (result >= 200 && result <= 299) {
! 1085: if (!p) {
! 1086: p = buf;
! 1087: }
! 1088: /* create any needed directories if the creation of the 1st directory worked */
! 1089: while (++p != e) {
! 1090: if (*p == '\0' && *(p + 1) != '\0') {
! 1091: *p = '/';
! 1092: php_stream_printf(stream TSRMLS_CC, "MKD %s\r\n", buf);
! 1093: result = GET_FTP_RESULT(stream);
! 1094: if (result < 200 || result > 299) {
! 1095: if (options & REPORT_ERRORS) {
! 1096: php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", tmp_line);
! 1097: }
! 1098: break;
! 1099: }
! 1100: }
! 1101: }
! 1102: }
! 1103: }
! 1104: efree(buf);
! 1105: }
! 1106:
! 1107: php_url_free(resource);
! 1108: php_stream_close(stream);
! 1109:
! 1110: if (result < 200 || result > 299) {
! 1111: /* Failure */
! 1112: return 0;
! 1113: }
! 1114:
! 1115: return 1;
! 1116:
! 1117: mkdir_errexit:
! 1118: if (resource) {
! 1119: php_url_free(resource);
! 1120: }
! 1121: if (stream) {
! 1122: php_stream_close(stream);
! 1123: }
! 1124: return 0;
! 1125: }
! 1126: /* }}} */
! 1127:
! 1128: /* {{{ php_stream_ftp_rmdir
! 1129: */
! 1130: static int php_stream_ftp_rmdir(php_stream_wrapper *wrapper, char *url, int options, php_stream_context *context TSRMLS_DC)
! 1131: {
! 1132: php_stream *stream = NULL;
! 1133: php_url *resource = NULL;
! 1134: int result;
! 1135: char tmp_line[512];
! 1136:
! 1137: stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, NULL, NULL, &resource, NULL, NULL TSRMLS_CC);
! 1138: if (!stream) {
! 1139: if (options & REPORT_ERRORS) {
! 1140: php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to connect to %s", url);
! 1141: }
! 1142: goto rmdir_errexit;
! 1143: }
! 1144:
! 1145: if (resource->path == NULL) {
! 1146: if (options & REPORT_ERRORS) {
! 1147: php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid path provided in %s", url);
! 1148: }
! 1149: goto rmdir_errexit;
! 1150: }
! 1151:
! 1152: php_stream_printf(stream TSRMLS_CC, "RMD %s\r\n", resource->path);
! 1153: result = GET_FTP_RESULT(stream);
! 1154:
! 1155: if (result < 200 || result > 299) {
! 1156: if (options & REPORT_ERRORS) {
! 1157: php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", tmp_line);
! 1158: }
! 1159: goto rmdir_errexit;
! 1160: }
! 1161:
! 1162: php_url_free(resource);
! 1163: php_stream_close(stream);
! 1164:
! 1165: return 1;
! 1166:
! 1167: rmdir_errexit:
! 1168: if (resource) {
! 1169: php_url_free(resource);
! 1170: }
! 1171: if (stream) {
! 1172: php_stream_close(stream);
! 1173: }
! 1174: return 0;
! 1175: }
! 1176: /* }}} */
! 1177:
! 1178: static php_stream_wrapper_ops ftp_stream_wops = {
! 1179: php_stream_url_wrap_ftp,
! 1180: php_stream_ftp_stream_close, /* stream_close */
! 1181: php_stream_ftp_stream_stat,
! 1182: php_stream_ftp_url_stat, /* stat_url */
! 1183: php_stream_ftp_opendir, /* opendir */
! 1184: "ftp",
! 1185: php_stream_ftp_unlink, /* unlink */
! 1186: php_stream_ftp_rename, /* rename */
! 1187: php_stream_ftp_mkdir, /* mkdir */
! 1188: php_stream_ftp_rmdir /* rmdir */
! 1189: };
! 1190:
! 1191: PHPAPI php_stream_wrapper php_stream_ftp_wrapper = {
! 1192: &ftp_stream_wops,
! 1193: NULL,
! 1194: 1 /* is_url */
! 1195: };
! 1196:
! 1197:
! 1198: /*
! 1199: * Local variables:
! 1200: * tab-width: 4
! 1201: * c-basic-offset: 4
! 1202: * End:
! 1203: * vim600: sw=4 ts=4 fdm=marker
! 1204: * vim<600: sw=4 ts=4
! 1205: */
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>