Annotation of embedaddon/php/ext/standard/ftp_fopen_wrapper.c, revision 1.1.1.4
1.1 misho 1: /*
2: +----------------------------------------------------------------------+
3: | PHP Version 5 |
4: +----------------------------------------------------------------------+
1.1.1.4 ! misho 5: | Copyright (c) 1997-2014 The PHP Group |
1.1 misho 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: */
1.1.1.2 misho 21: /* $Id$ */
1.1 misho 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 */
1.1.1.2 misho 272: if (FG(from_address)) {
273: php_stream_printf(stream TSRMLS_CC, "PASS %s\r\n", FG(from_address));
1.1 misho 274: } else {
275: php_stream_write_string(stream, "PASS anonymous\r\n");
276: }
277: }
278:
279: /* read the response */
280: result = GET_FTP_RESULT(stream);
281:
282: if (result > 299 || result < 200) {
283: php_stream_notify_error(context, PHP_STREAM_NOTIFY_AUTH_RESULT, tmp_line, result);
284: } else {
285: php_stream_notify_info(context, PHP_STREAM_NOTIFY_AUTH_RESULT, tmp_line, result);
286: }
287: }
288: if (result > 299 || result < 200) {
289: goto connect_errexit;
290: }
291:
292: if (puse_ssl) {
293: *puse_ssl = use_ssl;
294: }
295: if (puse_ssl_on_data) {
296: *puse_ssl_on_data = use_ssl_on_data;
297: }
298: if (preuseid) {
299: *preuseid = reuseid;
300: }
301: if (presource) {
302: *presource = resource;
303: }
304:
305: return stream;
306:
307: connect_errexit:
308: if (resource) {
309: php_url_free(resource);
310: }
311:
312: if (stream) {
313: php_stream_close(stream);
314: }
315:
316: return NULL;
317: }
318: /* }}} */
319:
320: /* {{{ php_fopen_do_pasv
321: */
322: static unsigned short php_fopen_do_pasv(php_stream *stream, char *ip, size_t ip_size, char **phoststart TSRMLS_DC)
323: {
324: char tmp_line[512];
325: int result, i;
326: unsigned short portno;
327: char *tpath, *ttpath, *hoststart=NULL;
328:
329: #ifdef HAVE_IPV6
330: /* We try EPSV first, needed for IPv6 and works on some IPv4 servers */
331: php_stream_write_string(stream, "EPSV\r\n");
332: result = GET_FTP_RESULT(stream);
333:
334: /* check if we got a 229 response */
335: if (result != 229) {
336: #endif
337: /* EPSV failed, let's try PASV */
338: php_stream_write_string(stream, "PASV\r\n");
339: result = GET_FTP_RESULT(stream);
340:
341: /* make sure we got a 227 response */
342: if (result != 227) {
343: return 0;
344: }
345:
346: /* parse pasv command (129, 80, 95, 25, 13, 221) */
347: tpath = tmp_line;
348: /* skip over the "227 Some message " part */
349: for (tpath += 4; *tpath && !isdigit((int) *tpath); tpath++);
350: if (!*tpath) {
351: return 0;
352: }
353: /* skip over the host ip, to get the port */
354: hoststart = tpath;
355: for (i = 0; i < 4; i++) {
356: for (; isdigit((int) *tpath); tpath++);
357: if (*tpath != ',') {
358: return 0;
359: }
360: *tpath='.';
361: tpath++;
362: }
363: tpath[-1] = '\0';
364: memcpy(ip, hoststart, ip_size);
365: ip[ip_size-1] = '\0';
366: hoststart = ip;
367:
368: /* pull out the MSB of the port */
369: portno = (unsigned short) strtoul(tpath, &ttpath, 10) * 256;
370: if (ttpath == NULL) {
371: /* didn't get correct response from PASV */
372: return 0;
373: }
374: tpath = ttpath;
375: if (*tpath != ',') {
376: return 0;
377: }
378: tpath++;
379: /* pull out the LSB of the port */
380: portno += (unsigned short) strtoul(tpath, &ttpath, 10);
381: #ifdef HAVE_IPV6
382: } else {
383: /* parse epsv command (|||6446|) */
384: for (i = 0, tpath = tmp_line + 4; *tpath; tpath++) {
385: if (*tpath == '|') {
386: i++;
387: if (i == 3)
388: break;
389: }
390: }
391: if (i < 3) {
392: return 0;
393: }
394: /* pull out the port */
395: portno = (unsigned short) strtoul(tpath + 1, &ttpath, 10);
396: }
397: #endif
398: if (ttpath == NULL) {
399: /* didn't get correct response from EPSV/PASV */
400: return 0;
401: }
402:
403: if (phoststart) {
404: *phoststart = hoststart;
405: }
406:
407: return portno;
408: }
409: /* }}} */
410:
411: /* {{{ php_fopen_url_wrap_ftp
412: */
413: 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)
414: {
415: php_stream *stream = NULL, *datastream = NULL;
416: php_url *resource = NULL;
417: char tmp_line[512];
418: char ip[sizeof("123.123.123.123")];
419: unsigned short portno;
420: char *hoststart = NULL;
421: int result = 0, use_ssl, use_ssl_on_data=0;
422: php_stream *reuseid=NULL;
423: size_t file_size = 0;
424: zval **tmpzval;
425: int allow_overwrite = 0;
426: int read_write = 0;
427: char *transport;
428: int transport_len;
429:
430: tmp_line[0] = '\0';
431:
432: if (strpbrk(mode, "r+")) {
433: read_write = 1; /* Open for reading */
434: }
435: if (strpbrk(mode, "wa+")) {
436: if (read_write) {
437: php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "FTP does not support simultaneous read/write connections");
438: return NULL;
439: }
440: if (strchr(mode, 'a')) {
441: read_write = 3; /* Open for Appending */
442: } else {
1.1.1.3 misho 443: read_write = 2; /* Open for writing */
1.1 misho 444: }
445: }
446: if (!read_write) {
447: /* No mode specified? */
448: php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unknown file open mode");
449: return NULL;
450: }
451:
452: if (context &&
453: php_stream_context_get_option(context, "ftp", "proxy", &tmpzval) == SUCCESS) {
454: if (read_write == 1) {
455: /* Use http wrapper to proxy ftp request */
456: return php_stream_url_wrap_http(wrapper, path, mode, options, opened_path, context STREAMS_CC TSRMLS_CC);
457: } else {
458: /* ftp proxy is read-only */
459: php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "FTP proxy may only be used in read mode");
460: return NULL;
461: }
462: }
463:
464: stream = php_ftp_fopen_connect(wrapper, path, mode, options, opened_path, context, &reuseid, &resource, &use_ssl, &use_ssl_on_data TSRMLS_CC);
465: if (!stream) {
466: goto errexit;
467: }
468:
469: /* set the connection to be binary */
470: php_stream_write_string(stream, "TYPE I\r\n");
471: result = GET_FTP_RESULT(stream);
472: if (result > 299 || result < 200)
473: goto errexit;
474:
475: /* find out the size of the file (verifying it exists) */
476: php_stream_printf(stream TSRMLS_CC, "SIZE %s\r\n", resource->path);
477:
478: /* read the response */
479: result = GET_FTP_RESULT(stream);
480: if (read_write == 1) {
481: /* Read Mode */
482: char *sizestr;
483:
484: /* when reading file, it must exist */
485: if (result > 299 || result < 200) {
486: errno = ENOENT;
487: goto errexit;
488: }
489:
490: sizestr = strchr(tmp_line, ' ');
491: if (sizestr) {
492: sizestr++;
493: file_size = atoi(sizestr);
494: php_stream_notify_file_size(context, file_size, tmp_line, result);
495: }
496: } else if (read_write == 2) {
497: /* when writing file (but not appending), it must NOT exist, unless a context option exists which allows it */
498: if (context && php_stream_context_get_option(context, "ftp", "overwrite", &tmpzval) == SUCCESS) {
499: allow_overwrite = Z_LVAL_PP(tmpzval);
500: }
501: if (result <= 299 && result >= 200) {
502: if (allow_overwrite) {
1.1.1.3 misho 503: /* Context permits overwriting file,
1.1 misho 504: so we just delete whatever's there in preparation */
505: php_stream_printf(stream TSRMLS_CC, "DELE %s\r\n", resource->path);
506: result = GET_FTP_RESULT(stream);
507: if (result >= 300 || result <= 199) {
508: goto errexit;
509: }
510: } else {
511: php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Remote file already exists and overwrite context option not specified");
512: errno = EEXIST;
513: goto errexit;
514: }
515: }
516: }
517:
518: /* set up the passive connection */
519: portno = php_fopen_do_pasv(stream, ip, sizeof(ip), &hoststart TSRMLS_CC);
520:
521: if (!portno) {
522: goto errexit;
523: }
524:
525: /* Send RETR/STOR command */
526: if (read_write == 1) {
527: /* set resume position if applicable */
528: if (context &&
529: php_stream_context_get_option(context, "ftp", "resume_pos", &tmpzval) == SUCCESS &&
530: Z_TYPE_PP(tmpzval) == IS_LONG &&
531: Z_LVAL_PP(tmpzval) > 0) {
532: php_stream_printf(stream TSRMLS_CC, "REST %ld\r\n", Z_LVAL_PP(tmpzval));
533: result = GET_FTP_RESULT(stream);
534: if (result < 300 || result > 399) {
535: php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unable to resume from offset %ld", Z_LVAL_PP(tmpzval));
536: goto errexit;
537: }
538: }
539:
540: /* retrieve file */
541: memcpy(tmp_line, "RETR", sizeof("RETR"));
542: } else if (read_write == 2) {
543: /* Write new file */
544: memcpy(tmp_line, "STOR", sizeof("STOR"));
545: } else {
546: /* Append */
547: memcpy(tmp_line, "APPE", sizeof("APPE"));
548: }
549: php_stream_printf(stream TSRMLS_CC, "%s %s\r\n", tmp_line, (resource->path != NULL ? resource->path : "/"));
550:
551: /* open the data channel */
552: if (hoststart == NULL) {
553: hoststart = resource->host;
554: }
555: transport_len = spprintf(&transport, 0, "tcp://%s:%d", hoststart, portno);
556: datastream = php_stream_xport_create(transport, transport_len, REPORT_ERRORS, STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT, NULL, NULL, context, NULL, NULL);
557: efree(transport);
558: if (datastream == NULL) {
559: goto errexit;
560: }
561:
562: result = GET_FTP_RESULT(stream);
563: if (result != 150 && result != 125) {
564: /* Could not retrieve or send the file
565: * this data will only be sent to us after connection on the data port was initiated.
566: */
567: php_stream_close(datastream);
568: datastream = NULL;
569: goto errexit;
570: }
571:
572: php_stream_context_set(datastream, context);
573: php_stream_notify_progress_init(context, 0, file_size);
574:
575: if (use_ssl_on_data && (php_stream_xport_crypto_setup(datastream,
576: STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL TSRMLS_CC) < 0 ||
577: php_stream_xport_crypto_enable(datastream, 1 TSRMLS_CC) < 0)) {
578:
579: php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unable to activate SSL mode");
580: php_stream_close(datastream);
581: datastream = NULL;
582: goto errexit;
583: }
584:
585: /* remember control stream */
586: datastream->wrapperthis = stream;
587:
588: php_url_free(resource);
589: return datastream;
590:
591: errexit:
592: if (resource) {
593: php_url_free(resource);
594: }
595: if (stream) {
596: php_stream_notify_error(context, PHP_STREAM_NOTIFY_FAILURE, tmp_line, result);
597: php_stream_close(stream);
598: }
599: if (tmp_line[0] != '\0')
600: php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "FTP server reports %s", tmp_line);
601: return NULL;
602: }
603: /* }}} */
604:
605: /* {{{ php_ftp_dirsteam_read
606: */
607: static size_t php_ftp_dirstream_read(php_stream *stream, char *buf, size_t count TSRMLS_DC)
608: {
609: php_stream_dirent *ent = (php_stream_dirent *)buf;
610: php_stream *innerstream;
611: size_t tmp_len;
612: char *basename;
613: size_t basename_len;
614:
615: innerstream = ((php_ftp_dirstream_data *)stream->abstract)->datastream;
616:
617: if (count != sizeof(php_stream_dirent)) {
618: return 0;
619: }
620:
621: if (php_stream_eof(innerstream)) {
622: return 0;
623: }
624:
625: if (!php_stream_get_line(innerstream, ent->d_name, sizeof(ent->d_name), &tmp_len)) {
626: return 0;
627: }
628:
629: php_basename(ent->d_name, tmp_len, NULL, 0, &basename, &basename_len TSRMLS_CC);
630: if (!basename) {
631: return 0;
632: }
633:
634: if (!basename_len) {
635: efree(basename);
636: return 0;
637: }
638:
639: tmp_len = MIN(sizeof(ent->d_name), basename_len - 1);
640: memcpy(ent->d_name, basename, tmp_len);
641: ent->d_name[tmp_len - 1] = '\0';
642: efree(basename);
643:
644: /* Trim off trailing whitespace characters */
645: tmp_len--;
646: while (tmp_len >= 0 &&
647: (ent->d_name[tmp_len] == '\n' || ent->d_name[tmp_len] == '\r' ||
648: ent->d_name[tmp_len] == '\t' || ent->d_name[tmp_len] == ' ')) {
649: ent->d_name[tmp_len--] = '\0';
650: }
651:
652: return sizeof(php_stream_dirent);
653: }
654: /* }}} */
655:
656: /* {{{ php_ftp_dirstream_close
657: */
658: static int php_ftp_dirstream_close(php_stream *stream, int close_handle TSRMLS_DC)
659: {
660: php_ftp_dirstream_data *data = stream->abstract;
661:
662: /* close control connection */
663: if (data->controlstream) {
664: php_stream_close(data->controlstream);
665: data->controlstream = NULL;
666: }
667: /* close data connection */
668: php_stream_close(data->datastream);
669: data->datastream = NULL;
670:
671: efree(data);
672: stream->abstract = NULL;
673:
674: return 0;
675: }
676: /* }}} */
677:
678: /* ftp dirstreams only need to support read and close operations,
679: They can't be rewound because the underlying ftp stream can't be rewound. */
680: static php_stream_ops php_ftp_dirstream_ops = {
681: NULL, /* write */
682: php_ftp_dirstream_read, /* read */
683: php_ftp_dirstream_close, /* close */
684: NULL, /* flush */
685: "ftpdir",
686: NULL, /* rewind */
687: NULL, /* cast */
688: NULL, /* stat */
689: NULL /* set option */
690: };
691:
692: /* {{{ php_stream_ftp_opendir
693: */
694: 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)
695: {
696: php_stream *stream, *reuseid, *datastream = NULL;
697: php_ftp_dirstream_data *dirsdata;
698: php_url *resource = NULL;
699: int result = 0, use_ssl, use_ssl_on_data = 0;
700: char *hoststart = NULL, tmp_line[512];
701: char ip[sizeof("123.123.123.123")];
702: unsigned short portno;
703:
704: tmp_line[0] = '\0';
705:
706: stream = php_ftp_fopen_connect(wrapper, path, mode, options, opened_path, context, &reuseid, &resource, &use_ssl, &use_ssl_on_data TSRMLS_CC);
707: if (!stream) {
708: goto opendir_errexit;
709: }
710:
711: /* set the connection to be ascii */
712: php_stream_write_string(stream, "TYPE A\r\n");
713: result = GET_FTP_RESULT(stream);
714: if (result > 299 || result < 200)
715: goto opendir_errexit;
716:
717: /* set up the passive connection */
718: portno = php_fopen_do_pasv(stream, ip, sizeof(ip), &hoststart TSRMLS_CC);
719:
720: if (!portno) {
721: goto opendir_errexit;
722: }
723:
724: php_stream_printf(stream TSRMLS_CC, "NLST %s\r\n", (resource->path != NULL ? resource->path : "/"));
725:
726: /* open the data channel */
727: if (hoststart == NULL) {
728: hoststart = resource->host;
729: }
730: datastream = php_stream_sock_open_host(hoststart, portno, SOCK_STREAM, 0, 0);
731: if (datastream == NULL) {
732: goto opendir_errexit;
733: }
734:
735: result = GET_FTP_RESULT(stream);
736: if (result != 150 && result != 125) {
737: /* Could not retrieve or send the file
738: * this data will only be sent to us after connection on the data port was initiated.
739: */
740: php_stream_close(datastream);
741: datastream = NULL;
742: goto opendir_errexit;
743: }
744:
745: php_stream_context_set(datastream, context);
746:
747: if (use_ssl_on_data && (php_stream_xport_crypto_setup(stream,
748: STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL TSRMLS_CC) < 0 ||
749: php_stream_xport_crypto_enable(stream, 1 TSRMLS_CC) < 0)) {
750:
751: php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unable to activate SSL mode");
752: php_stream_close(datastream);
753: datastream = NULL;
754: goto opendir_errexit;
755: }
756:
757: php_url_free(resource);
758:
759: dirsdata = emalloc(sizeof *dirsdata);
760: dirsdata->datastream = datastream;
761: dirsdata->controlstream = stream;
762: dirsdata->dirstream = php_stream_alloc(&php_ftp_dirstream_ops, dirsdata, 0, mode);
763:
764: return dirsdata->dirstream;
765:
766: opendir_errexit:
767: if (resource) {
768: php_url_free(resource);
769: }
770: if (stream) {
771: php_stream_notify_error(context, PHP_STREAM_NOTIFY_FAILURE, tmp_line, result);
772: php_stream_close(stream);
773: }
774: if (tmp_line[0] != '\0') {
775: php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "FTP server reports %s", tmp_line);
776: }
777: return NULL;
778: }
779: /* }}} */
780:
781: /* {{{ php_stream_ftp_url_stat
782: */
783: 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)
784: {
785: php_stream *stream = NULL;
786: php_url *resource = NULL;
787: int result;
788: char tmp_line[512];
789:
790: /* If ssb is NULL then someone is misbehaving */
791: if (!ssb) return -1;
792:
793: stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, context, NULL, &resource, NULL, NULL TSRMLS_CC);
794: if (!stream) {
795: goto stat_errexit;
796: }
797:
798: ssb->sb.st_mode = 0644; /* FTP won't give us a valid mode, so aproximate one based on being readable */
799: 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) */
800: result = GET_FTP_RESULT(stream);
801: if (result < 200 || result > 299) {
802: ssb->sb.st_mode |= S_IFREG;
803: } else {
804: ssb->sb.st_mode |= S_IFDIR;
805: }
806:
807: php_stream_write_string(stream, "TYPE I\r\n"); /* we need this since some servers refuse to accept SIZE command in ASCII mode */
808:
809: result = GET_FTP_RESULT(stream);
810:
811: if(result < 200 || result > 299) {
812: goto stat_errexit;
813: }
814:
815: php_stream_printf(stream TSRMLS_CC, "SIZE %s\r\n", (resource->path != NULL ? resource->path : "/"));
816: result = GET_FTP_RESULT(stream);
817: if (result < 200 || result > 299) {
818: /* Failure either means it doesn't exist
819: or it's a directory and this server
820: fails on listing directory sizes */
821: if (ssb->sb.st_mode & S_IFDIR) {
822: ssb->sb.st_size = 0;
823: } else {
824: goto stat_errexit;
825: }
826: } else {
827: ssb->sb.st_size = atoi(tmp_line + 4);
828: }
829:
830: php_stream_printf(stream TSRMLS_CC, "MDTM %s\r\n", (resource->path != NULL ? resource->path : "/"));
831: result = GET_FTP_RESULT(stream);
832: if (result == 213) {
833: char *p = tmp_line + 4;
834: int n;
835: struct tm tm, tmbuf, *gmt;
836: time_t stamp;
837:
838: while (p - tmp_line < sizeof(tmp_line) && !isdigit(*p)) {
839: p++;
840: }
841:
842: if (p - tmp_line > sizeof(tmp_line)) {
843: goto mdtm_error;
844: }
845:
846: 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);
847: if (n != 6) {
848: goto mdtm_error;
849: }
850:
851: tm.tm_year -= 1900;
852: tm.tm_mon--;
853: tm.tm_isdst = -1;
854:
855: /* figure out the GMT offset */
856: stamp = time(NULL);
857: gmt = php_gmtime_r(&stamp, &tmbuf);
858: if (!gmt) {
859: goto mdtm_error;
860: }
861: gmt->tm_isdst = -1;
862:
863: /* apply the GMT offset */
864: tm.tm_sec += stamp - mktime(gmt);
865: tm.tm_isdst = gmt->tm_isdst;
866:
867: ssb->sb.st_mtime = mktime(&tm);
868: } else {
869: /* error or unsupported command */
870: mdtm_error:
871: ssb->sb.st_mtime = -1;
872: }
873:
874: ssb->sb.st_ino = 0; /* Unknown values */
875: ssb->sb.st_dev = 0;
876: ssb->sb.st_uid = 0;
877: ssb->sb.st_gid = 0;
878: ssb->sb.st_atime = -1;
879: ssb->sb.st_ctime = -1;
880:
881: ssb->sb.st_nlink = 1;
882: ssb->sb.st_rdev = -1;
883: #ifdef HAVE_ST_BLKSIZE
884: ssb->sb.st_blksize = 4096; /* Guess since FTP won't expose this information */
885: #ifdef HAVE_ST_BLOCKS
886: ssb->sb.st_blocks = (int)((4095 + ssb->sb.st_size) / ssb->sb.st_blksize); /* emulate ceil */
887: #endif
888: #endif
889: php_stream_close(stream);
890: php_url_free(resource);
891: return 0;
892:
893: stat_errexit:
894: if (resource) {
895: php_url_free(resource);
896: }
897: if (stream) {
898: php_stream_close(stream);
899: }
900: return -1;
901: }
902: /* }}} */
903:
904: /* {{{ php_stream_ftp_unlink
905: */
906: static int php_stream_ftp_unlink(php_stream_wrapper *wrapper, char *url, int options, php_stream_context *context TSRMLS_DC)
907: {
908: php_stream *stream = NULL;
909: php_url *resource = NULL;
910: int result;
911: char tmp_line[512];
912:
913: stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, NULL, NULL, &resource, NULL, NULL TSRMLS_CC);
914: if (!stream) {
915: if (options & REPORT_ERRORS) {
916: php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to connect to %s", url);
917: }
918: goto unlink_errexit;
919: }
920:
921: if (resource->path == NULL) {
922: if (options & REPORT_ERRORS) {
923: php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid path provided in %s", url);
924: }
925: goto unlink_errexit;
926: }
927:
928: /* Attempt to delete the file */
929: php_stream_printf(stream TSRMLS_CC, "DELE %s\r\n", (resource->path != NULL ? resource->path : "/"));
930:
931: result = GET_FTP_RESULT(stream);
932: if (result < 200 || result > 299) {
933: if (options & REPORT_ERRORS) {
934: php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error Deleting file: %s", tmp_line);
935: }
936: goto unlink_errexit;
937: }
938:
939: php_url_free(resource);
940: php_stream_close(stream);
941: return 1;
942:
943: unlink_errexit:
944: if (resource) {
945: php_url_free(resource);
946: }
947: if (stream) {
948: php_stream_close(stream);
949: }
950: return 0;
951: }
952: /* }}} */
953:
954: /* {{{ php_stream_ftp_rename
955: */
956: static int php_stream_ftp_rename(php_stream_wrapper *wrapper, char *url_from, char *url_to, int options, php_stream_context *context TSRMLS_DC)
957: {
958: php_stream *stream = NULL;
959: php_url *resource_from = NULL, *resource_to = NULL;
960: int result;
961: char tmp_line[512];
962:
963: resource_from = php_url_parse(url_from);
964: resource_to = php_url_parse(url_to);
965: /* Must be same scheme (ftp/ftp or ftps/ftps), same host, and same port
966: (or a 21/0 0/21 combination which is also "same")
967: Also require paths to/from */
968: if (!resource_from ||
969: !resource_to ||
970: !resource_from->scheme ||
971: !resource_to->scheme ||
972: strcmp(resource_from->scheme, resource_to->scheme) ||
973: !resource_from->host ||
974: !resource_to->host ||
975: strcmp(resource_from->host, resource_to->host) ||
976: (resource_from->port != resource_to->port &&
977: resource_from->port * resource_to->port != 0 &&
978: resource_from->port + resource_to->port != 21) ||
979: !resource_from->path ||
980: !resource_to->path) {
981: goto rename_errexit;
982: }
983:
984: stream = php_ftp_fopen_connect(wrapper, url_from, "r", 0, NULL, NULL, NULL, NULL, NULL, NULL TSRMLS_CC);
985: if (!stream) {
986: if (options & REPORT_ERRORS) {
987: php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to connect to %s", resource_from->host);
988: }
989: goto rename_errexit;
990: }
991:
992: /* Rename FROM */
993: php_stream_printf(stream TSRMLS_CC, "RNFR %s\r\n", (resource_from->path != NULL ? resource_from->path : "/"));
994:
995: result = GET_FTP_RESULT(stream);
996: if (result < 300 || result > 399) {
997: if (options & REPORT_ERRORS) {
998: php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error Renaming file: %s", tmp_line);
999: }
1000: goto rename_errexit;
1001: }
1002:
1003: /* Rename TO */
1004: php_stream_printf(stream TSRMLS_CC, "RNTO %s\r\n", (resource_to->path != NULL ? resource_to->path : "/"));
1005:
1006: result = GET_FTP_RESULT(stream);
1007: if (result < 200 || result > 299) {
1008: if (options & REPORT_ERRORS) {
1009: php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error Renaming file: %s", tmp_line);
1010: }
1011: goto rename_errexit;
1012: }
1013:
1014: php_url_free(resource_from);
1015: php_url_free(resource_to);
1016: php_stream_close(stream);
1017: return 1;
1018:
1019: rename_errexit:
1020: if (resource_from) {
1021: php_url_free(resource_from);
1022: }
1023: if (resource_to) {
1024: php_url_free(resource_to);
1025: }
1026: if (stream) {
1027: php_stream_close(stream);
1028: }
1029: return 0;
1030: }
1031: /* }}} */
1032:
1033: /* {{{ php_stream_ftp_mkdir
1034: */
1035: static int php_stream_ftp_mkdir(php_stream_wrapper *wrapper, char *url, int mode, int options, php_stream_context *context TSRMLS_DC)
1036: {
1037: php_stream *stream = NULL;
1038: php_url *resource = NULL;
1039: int result, recursive = options & PHP_STREAM_MKDIR_RECURSIVE;
1040: char tmp_line[512];
1041:
1042: stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, NULL, NULL, &resource, NULL, NULL TSRMLS_CC);
1043: if (!stream) {
1044: if (options & REPORT_ERRORS) {
1045: php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to connect to %s", url);
1046: }
1047: goto mkdir_errexit;
1048: }
1049:
1050: if (resource->path == NULL) {
1051: if (options & REPORT_ERRORS) {
1052: php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid path provided in %s", url);
1053: }
1054: goto mkdir_errexit;
1055: }
1056:
1057: if (!recursive) {
1058: php_stream_printf(stream TSRMLS_CC, "MKD %s\r\n", resource->path);
1059: result = GET_FTP_RESULT(stream);
1060: } else {
1061: /* we look for directory separator from the end of string, thus hopefuly reducing our work load */
1062: char *p, *e, *buf;
1063:
1064: buf = estrdup(resource->path);
1065: e = buf + strlen(buf);
1066:
1067: /* find a top level directory we need to create */
1068: while ((p = strrchr(buf, '/'))) {
1069: *p = '\0';
1070: php_stream_printf(stream TSRMLS_CC, "CWD %s\r\n", buf);
1071: result = GET_FTP_RESULT(stream);
1072: if (result >= 200 && result <= 299) {
1073: *p = '/';
1074: break;
1075: }
1076: }
1077: if (p == buf) {
1078: php_stream_printf(stream TSRMLS_CC, "MKD %s\r\n", resource->path);
1079: result = GET_FTP_RESULT(stream);
1080: } else {
1081: php_stream_printf(stream TSRMLS_CC, "MKD %s\r\n", buf);
1082: result = GET_FTP_RESULT(stream);
1083: if (result >= 200 && result <= 299) {
1084: if (!p) {
1085: p = buf;
1086: }
1087: /* create any needed directories if the creation of the 1st directory worked */
1088: while (++p != e) {
1089: if (*p == '\0' && *(p + 1) != '\0') {
1090: *p = '/';
1091: php_stream_printf(stream TSRMLS_CC, "MKD %s\r\n", buf);
1092: result = GET_FTP_RESULT(stream);
1093: if (result < 200 || result > 299) {
1094: if (options & REPORT_ERRORS) {
1095: php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", tmp_line);
1096: }
1097: break;
1098: }
1099: }
1100: }
1101: }
1102: }
1103: efree(buf);
1104: }
1105:
1106: php_url_free(resource);
1107: php_stream_close(stream);
1108:
1109: if (result < 200 || result > 299) {
1110: /* Failure */
1111: return 0;
1112: }
1113:
1114: return 1;
1115:
1116: mkdir_errexit:
1117: if (resource) {
1118: php_url_free(resource);
1119: }
1120: if (stream) {
1121: php_stream_close(stream);
1122: }
1123: return 0;
1124: }
1125: /* }}} */
1126:
1127: /* {{{ php_stream_ftp_rmdir
1128: */
1129: static int php_stream_ftp_rmdir(php_stream_wrapper *wrapper, char *url, int options, php_stream_context *context TSRMLS_DC)
1130: {
1131: php_stream *stream = NULL;
1132: php_url *resource = NULL;
1133: int result;
1134: char tmp_line[512];
1135:
1136: stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, NULL, NULL, &resource, NULL, NULL TSRMLS_CC);
1137: if (!stream) {
1138: if (options & REPORT_ERRORS) {
1139: php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to connect to %s", url);
1140: }
1141: goto rmdir_errexit;
1142: }
1143:
1144: if (resource->path == NULL) {
1145: if (options & REPORT_ERRORS) {
1146: php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid path provided in %s", url);
1147: }
1148: goto rmdir_errexit;
1149: }
1150:
1151: php_stream_printf(stream TSRMLS_CC, "RMD %s\r\n", resource->path);
1152: result = GET_FTP_RESULT(stream);
1153:
1154: if (result < 200 || result > 299) {
1155: if (options & REPORT_ERRORS) {
1156: php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", tmp_line);
1157: }
1158: goto rmdir_errexit;
1159: }
1160:
1161: php_url_free(resource);
1162: php_stream_close(stream);
1163:
1164: return 1;
1165:
1166: rmdir_errexit:
1167: if (resource) {
1168: php_url_free(resource);
1169: }
1170: if (stream) {
1171: php_stream_close(stream);
1172: }
1173: return 0;
1174: }
1175: /* }}} */
1176:
1177: static php_stream_wrapper_ops ftp_stream_wops = {
1178: php_stream_url_wrap_ftp,
1179: php_stream_ftp_stream_close, /* stream_close */
1180: php_stream_ftp_stream_stat,
1181: php_stream_ftp_url_stat, /* stat_url */
1182: php_stream_ftp_opendir, /* opendir */
1183: "ftp",
1184: php_stream_ftp_unlink, /* unlink */
1185: php_stream_ftp_rename, /* rename */
1186: php_stream_ftp_mkdir, /* mkdir */
1187: php_stream_ftp_rmdir /* rmdir */
1188: };
1189:
1190: PHPAPI php_stream_wrapper php_stream_ftp_wrapper = {
1191: &ftp_stream_wops,
1192: NULL,
1193: 1 /* is_url */
1194: };
1195:
1196:
1197: /*
1198: * Local variables:
1199: * tab-width: 4
1200: * c-basic-offset: 4
1201: * End:
1202: * vim600: sw=4 ts=4 fdm=marker
1203: * vim<600: sw=4 ts=4
1204: */
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>