diff --git a/NEWS b/NEWS index e9abc9353b477..f36baf4dcdf20 100644 --- a/NEWS +++ b/NEWS @@ -917,6 +917,10 @@ PHP NEWS . Fixed invalid handle error with Implicit Result Sets. (Chris Jones) . Fixed bug #72524 (Binding null values triggers ORA-24816 error). (Chris Jones) +- IMAP: + . Fixed bug #77153 (imap_open allows to run arbitrary shell commands via + mailbox parameter). (Stas) + - ODBC: . Fixed bug #73448 (odbc_errormsg returns trash, always 513 bytes). (Anatol) @@ -1090,6 +1094,10 @@ PHP NEWS . Fixed bug #72479 (Use After Free Vulnerability in SNMP with GC and unserialize()). (Stas) +- Phar: + . Fixed bug #77247 (heap buffer overflow in phar_detect_phar_fname_ext). + (Stas) + - Soap: . Fixed bug #73538 (SoapClient::__setSoapHeaders doesn't overwrite SOAP headers). (duncan3dc) @@ -1985,6 +1993,9 @@ PHP NEWS . Fixed bug #71972 (Cyclic references causing session_start(): Failed to decode session object). (Laruence) +- COM: + . Fixed bug #77177 (Serializing or unserializing COM objects crashes). (cmb) + - Sockets: . Added socket_export_stream() function for getting a stream compatible resource from a socket resource. (Chris Wright, Bob) diff --git a/NEWS.orig b/NEWS.orig new file mode 100644 index 0000000000000..bc0c119819ab6 --- /dev/null +++ b/NEWS.orig @@ -0,0 +1,3262 @@ +PHP NEWS +||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| +06 Jul 2017, PHP 7.1.7 + +- Core: + . Fixed bug #74738 (Multiple [PATH=] and [HOST=] sections not properly + parsed). (Manuel Mausz) + . Fixed bug #74658 (Undefined constants in array properties result in broken + properties). (Laruence) + . Fixed misparsing of abstract unix domain socket names. (Sara) + . Fixed bug #74603 (PHP INI Parsing Stack Buffer Overflow Vulnerability). + (Stas) + . Fixed bug #74101, bug #74614 (Unserialize Heap Use-After-Free (READ: 1) in + zval_get_type). (Nikita) + . Fixed bug #74111 (Heap buffer overread (READ: 1) finish_nested_data from + unserialize). (Nikita) + . Fixed bug #74819 (wddx_deserialize() heap out-of-bound read via + php_parse_date()). (Derick) + +- Date: + . Fixed bug #74639 (implement clone for DatePeriod and DateInterval). + (andrewnester) + +- DOM: + . Fixed bug #69373 (References to deleted XPath query results). (ttoohey) + +- GD: + . Fixed bug #74435 (Buffer over-read into uninitialized memory). (cmb) + +- Intl: + . Fixed bug #73473 (Stack Buffer Overflow in msgfmt_parse_message). (libnex) + . Fixed bug #74705 (Wrong reflection on Collator::getSortKey and + collator_get_sort_key). (Tyson Andre, Remi) + +- Mbstring: + . Add oniguruma upstream fix (CVE-2017-9224, CVE-2017-9226, CVE-2017-9227, + CVE-2017-9228, CVE-2017-9229) (Remi, Mamoru TASAKA) + +- OCI8: + . Add TAF callback (PR #2459). (KoenigsKind) + +- Opcache: + . Fixed bug #74663 (Segfault with opcache.memory_protect and + validate_timestamp). (Laruence) + . Revert opcache.enable_cli to default disabled. (Nikita) + +- OpenSSL: + . Fixed bug #74720 (pkcs7_en/decrypt does not work if \x1a is used in + content). (Anatol) + . Fixed bug #74651 (negative-size-param (-1) in memcpy in zif_openssl_seal()). + (Stas) + +- PDO_OCI: + . Support Instant Client 12.2 in --with-pdo-oci configure option. + (Tianfang Yang) + +- Reflection: + . Fixed bug #74673 (Segfault when cast Reflection object to string with + undefined constant). (Laruence) + +- SPL: + . Fixed bug #74478 (null coalescing operator failing with SplFixedArray). + (jhdxr) + +- FTP: + . Fixed bug #74598 (ftp:// wrapper ignores context arg). (Sara) + +- PHAR: + . Fixed bug #74386 (Phar::__construct reflection incorrect). (villfa) + +- SOAP + . Fixed bug #74679 (Incorrect conversion array with WSDL_CACHE_MEMORY). + (Dmitry) + +- Streams: + . Fixed bug #74556 (stream_socket_get_name() returns '\0'). (Sara) + +8 Jun 2017, PHP 7.1.6 + +- Core: + . Fixed bug #74600 (crash (SIGSEGV) in _zend_hash_add_or_update_i). + (Laruence) + . Fixed bug #74546 (SIGILL in ZEND_FETCH_CLASS_CONSTANT_SPEC_CONST_CONST). + (Laruence) + . Fixed bug #74589 (__DIR__ wrong for unicode character). (Anatol) + +- intl: + . Fixed bug #74468 (wrong reflection on Collator::sortWithSortKeys). (villfa) + +- MySQLi: + . Fixed bug #74547 (mysqli::change_user() doesn't accept null as $database + argument w/strict_types). (Anatol) + +- Opcache: + . Fixed bug #74596 (SIGSEGV with opcache.revalidate_path enabled). (Laruence) + +- phar: + . Fixed bug #51918 (Phar::webPhar() does not handle requests sent through PUT + and DELETE method). (Christian Weiske) + +- Readline: + . Fixed bug #74490 (readline() moves the cursor to the beginning of the line). + (Anatol) + +- Standard: + . Fixed bug #74510 (win32/sendmail.c anchors CC header but not BCC). + (Damian Wadley, Anatol) + +- xmlreader: + . Fixed bug #74457 (Wrong reflection on XMLReader::expand). (villfa) + +11 May 2017, PHP 7.1.5 + +- Core: + . Fixed bug #74408 (Endless loop bypassing execution time limit). (Laruence) + . Fixed bug #74353 (Segfault when killing within bash script trap code). + (Laruence) + . Fixed bug #74340 (Magic function __get has different behavior in php 7.1.x). + (Nikita) + . Fixed bug #74188 (Null coalescing operator fails for undeclared static + class properties). (tpunt) + . Fixed bug #74444 (multiple catch freezes in some cases). (David Matějka) + . Fixed bug #74410 (stream_select() is broken on Windows Nanoserver). + (Matt Ficken) + . Fixed bug #74337 (php-cgi.exe crash on facebook callback). + (Anton Serbulov) + . Patch for bug #74216 was reverted. (Anatol) + +- Date: + . Fixed bug #74404 (Wrong reflection on DateTimeZone::getTransitions). + (krakjoe) + . Fixed bug #74080 (add constant for RFC7231 format datetime). (duncan3dc) + +- DOM: + . Fixed bug #74416 (Wrong reflection on DOMNode::cloneNode). + (Remi, Fabien Villepinte) + +- Fileinfo: + . Fixed bug #74379 (syntax error compile error in libmagic/apprentice.c). + (Laruence) + +- GD: + . Fixed bug #74343 (compile fails on solaris 11 with system gd2 library). + (krakjoe) + +- MySQLi: + . Fixed bug #74432 (mysqli_connect adding ":3306" to $host if $port parameter + not given). (Anatol) + +- MySQLnd: + . Fixed bug #74376 (Invalid free of persistent results on error/connection + loss). (Yussuf Khalil) + +- Intl: + . Fixed bug #65683 (Intl does not support DateTimeImmutable). (Ben Scholzen) + . Fixed bug #74298 (IntlDateFormatter->format() doesn't return + microseconds/fractions). (Andrew Nester) + . Fixed bug #74433 (wrong reflection for Normalizer methods). (villfa) + . Fixed bug #74439 (wrong reflection for Locale methods). (villfa) + +- Opcache: + . Fixed bug #74456 (Segmentation error while running a script in CLI mode). + (Laruence) + . Fixed bug #74431 (foreach infinite loop). (Nikita) + . Fixed bug #74442 (Opcached version produces a nested array). (Nikita) + +- OpenSSL: + . Fixed bug #73833 (null character not allowed in openssl_pkey_get_private). + (Jakub Zelenka) + . Fixed bug #73711 (Segfault in openssl_pkey_new when generating DSA or DH + key). (Jakub Zelenka) + . Fixed bug #74341 (openssl_x509_parse fails to parse ASN.1 UTCTime without + seconds). (Moritz Fain) + . Fixed bug #73808 (iv length warning too restrictive for aes-128-ccm). + (Jakub Zelenka) + +- phar: + . Fixed bug #74383 (phar method parameters reflection correction). + (mhagstrand) + +- Readline: + . Fixed bug #74489 (readline() immediately returns false in interactive + console mode). (Anatol) + +- Standard: + . Fixed bug #72071 (setcookie allows max-age to be negative). (Craig Duncan) + . Fixed bug #74361 (Compaction in array_rand() violates COW). (Nikita) + +- Streams: + . Fixed bug #74429 (Remote socket URI with unique persistence identifier + broken). (Sara) + +13 Apr 2017, PHP 7.1.4 + +- Core: + . Fixed bug #74149 (static embed SAPI linkage error). (krakjoe) + . Fixed bug #73370 (falsely exits with "Out of Memory" when using + USE_ZEND_ALLOC=0). (Nikita) + . Fixed bug #73960 (Leak with instance method calling static method with + referenced return). (Nikita) + . Fixed bug #69676 (Resolution of self::FOO in class constants not correct). + (Nikita) + . Fixed bug #74265 (Build problems after 7.0.17 release: undefined reference + to `isfinite'). (Nikita) + . Fixed bug #74302 (yield fromLABEL is over-greedy). (Sara) + +- Apache: + . Reverted patch for bug #61471, fixes bug #74318. (Anatol) + +- Date: + . Fixed bug #72096 (Swatch time value incorrect for dates before 1970). (mcq8) + +- DOM: + . Fixed bug #74004 (LIBXML_NOWARNING flag ingnored on loadHTML*). + (somedaysummer) + +- iconv: + . Fixed bug #74230 (iconv fails to fail on surrogates). (Anatol) + +- OCI8: + . Fixed uninitialized data causing random crash. (Dmitry) + +- Opcache: + . Fixed bug #74250 (OPcache compilation performance regression in PHP 5.6/7 + with huge classes). (Nikita) + +- OpenSSL: + . Fixed bug #72333 (fwrite() on non-blocking SSL sockets doesn't work). + (Jakub Zelenka) + +- PDO MySQL: + . Fixed bug #71003 (Expose MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT to PDO + interface). (Thomas Orozco) + +- SPL: + . Fixed bug #74058 (ArrayObject can not notice changes). (Andrew Nester) + +- Sqlite: + . Implemented FR #74217 (Allow creation of deterministic sqlite functions). + (Andrew Nester) + +- Streams: + . Fixed bug #74216 (Correctly fail on invalid IP address ports). (Sara) + +- Zlib: + . Fixed bug #74240 (deflate_add can allocate too much memory). (Matt Bonneau) + +16 Mar 2017, PHP 7.1.3 + +- Core: + . Fixed bug #74157 (Segfault with nested generators). (Laruence) + . Fixed bug #74164 (PHP hangs when an invalid value is dynamically passed to + typehinted by-ref arg). (Laruence) + . Fixed bug #74093 (Maximum execution time of n+2 seconds exceed not written + in error_log). (Laruence) + . Fixed bug #73989 (PHP 7.1 Segfaults within Symfony test suite). + (Dmitry, Laruence) + . Fixed bug #74084 (Out of bound read - zend_mm_alloc_small). (Laruence) + . Fixed bug #73807 (Performance problem with processing large post request). + (Nikita) + . Fixed bug #73998 (array_key_exists fails on arrays created by + get_object_vars). (mhagstrand) + . Fixed bug #73954 (NAN check fails on Alpine Linux with musl). (Andrea) + . Fixed bug #73677 (Generating phar.phar core dump with gcc ASAN enabled + build). (ondrej) + +- Apache: + . Fixed bug #61471 (Incomplete POST does not timeout but is passed to PHP). + (Zheng Shao) + +- Date: + . Fixed bug #73837 ("new DateTime()" sometimes returns 1 second ago value). + (Derick) + +- FPM: + . Fixed bug #69860 (php-fpm process accounting is broken with keepalive). + (Denis Yeldandi) + +- Hash: + . Fixed bug #73127 (gost-crypto hash incorrect if input data contains long + 0xFF sequence). (Grundik) + +- GD: + . Fixed bug #74031 (ReflectionFunction for imagepng is missing last two + parameters). (finwe) + +- Mysqlnd: + . Fixed bug #74021 (fetch_array broken data. Data more then MEDIUMBLOB). + (Andrew Nester, Nikita) + +- Opcache: + . Fixed bug #74152 (if statement says true to a null variable). (Laruence) + . Fixed bug #74019 (Segfault with list). (Laruence) + +- OpenSSL: + . Fixed bug #74022 (PHP Fast CGI crashes when reading from a pfx file). + (Anatol) + . Fixed bug #74099 (Memory leak with openssl_encrypt()). (Andrew Nester) + . Fixed bug #74159 (Writing a large buffer to a non-blocking encrypted stream + fails with "bad write retry"). (trowski) + +- PDO_OCI: + . Fixed bug #54379 (PDO_OCI: UTF-8 output gets truncated). (gureedo / Oracle) + +- SQLite3: + . Fixed bug #74413 (incorrect reflection for SQLite3::enableExceptions). + (krakjoe) + +- Standard: + . Fixed bug #74005 (mail.add_x_header causes RFC-breaking lone line feed). + (Anatol) + . Fixed bug #74041 (substr_count with length=0 broken). (Nikita) + . Fixed bug #73118 (is_callable callable name reports misleading value for + anonymous classes). (Adam Saponara) + . Fixed bug #74105 (PHP on Linux should use /dev/urandom when getrandom is + not available). (Benjamin Robin) + . Fixed bug #74708 (Invalid Reflection signatures for random_bytes and + random_int). (Tyson Andre, Remi) + +- Streams: + . Fixed bug #73496 (Invalid memory access in zend_inline_hash_func). + (Laruence) + . Fixed bug #74090 (stream_get_contents maxlength>-1 returns empty string). + (Anatol) + +16 Feb 2017, PHP 7.1.2 + +- Core: + . Improved GENERATOR_CREATE opcode handler. (Bob, Dmitry) + . Fixed bug #73877 (readlink() returns garbage for UTF-8 paths). (Anatol) + . Fixed bug #73876 (Crash when exporting **= in expansion of assign op). + (Sara) + . Fixed bug #73962 (bug with symlink related to cyrillic directory). (Anatol) + . Fixed bug #73969 (segfault in debug_print_backtrace). (andrewnester) + . Fixed bug #73994 (arginfo incorrect for unpack). (krakjoe) + . Fixed bug #73973 (assertion error in debug_zval_dump). (andrewnester) + +- DOM: + . Fixed bug #54382 (getAttributeNodeNS doesn't get xmlns* attributes). + (aboks) + +- DTrace: + . Fixed bug #73965 (DTrace reported as enabled when disabled). (Remi) + +- FCGI: + . Fixed bug #73904 (php-cgi fails to load -c specified php.ini file). (Anatol) + . Fixed bug #72898 (PHP_FCGI_CHILDREN is not included in phpinfo()). (Anatol) + +- FPM: + . Fixed bug #69865 (php-fpm does not close stderr when using syslog). + (m6w6) + +- GD: + . Fixed bug #73968 (Premature failing of XBM reading). (cmb) + +- GMP: + . Fixed bug #69993 (test for gmp.h needs to test machine includes). + (Jordan Gigov) + +- Hash: + . Added hash_hkdf() function. (Andrey Andreev) + . Fixed bug #73961 (environmental build dependency in hash sha3 source). + (krakjoe) + +- Intl: + . Fix bug #73956 (Link use CC instead of CXX). (Remi) + +- LDAP: + . Fixed bug #73933 (error/segfault with ldap_mod_replace and opcache). + (Laruence) + +- MySQLi: + . Fixed bug #73949 (leak in mysqli_fetch_object). (krakjoe) + +- Mysqlnd: + . Fixed bug #69899 (segfault on close() after free_result() with mysqlnd). + (Richard Fussenegger) + +- Opcache: + . Fixed bug #73983 (crash on finish work with phar in cli + opcache). + (Anatol) + +- OpenSSL: + . Fixed bug #71519 (add serial hex to return value array). (xrobau) + . Fixed bug #73692 (Compile ext/openssl with openssl 1.1.0 on Win). (Anatol) + . Fixed bug #73978 (openssl_decrypt triggers bug in PDO). (Jakub Zelenka) + +- PDO_Firebird: + . Implemented FR #72583 (All data are fetched as strings). (Dorin Marcoci) + +- PDO_PgSQL: + . Fixed bug #73959 (lastInsertId fails to throw an exception for wrong + sequence name). (andrewnester) + +- Phar: + . Fixed bug #70417 (PharData::compress() doesn't close temp file). (cmb) + +- posix: + . Fixed bug #71219 (configure script incorrectly checks for ttyname_r). (atoh) + +- Session: + . Fixed bug #69582 (session not readable by root in CLI). (EvgeniySpinov) + +- SPL: + . Fixed bug #73896 (spl_autoload() crashes when calls magic _call()). (Dmitry) + +- Standard: + . Fixed bug #69442 (closing of fd incorrect when PTS enabled). (jaytaph) + . Fixed bug #47021 (SoapClient stumbles over WSDL delivered with + "Transfer-Encoding: chunked"). (Rowan Collins) + . Fixed bug #72974 (imap is undefined service on AIX). (matthieu.sarter) + . Fixed bug #72979 (money_format stores wrong length AIX). (matthieu.sarter) + . Fixed bug #73374 (intval() with base 0 should detect binary). (Leigh) + . Fixed bug #69061 (mail.log = syslog contains double information). + (Tom Sommer) + +- ZIP: + . Fixed bug #70103 (ZipArchive::addGlob ignores remove_all_path option). (cmb, + Mitch Hagstrand) + +19 Jan 2017, PHP 7.1.1 + +- Core: + . Fixed bug #73792 (invalid foreach loop hangs script). (Dmitry) + . Fixed bug #73686 (Adding settype()ed values to ArrayObject results in + references). (Nikita, Laruence) + . Fixed bug #73663 ("Invalid opcode 65/16/8" occurs with a variable created + with list()). (Laruence) + . Fixed bug #73727 (ZEND_MM_BITSET_LEN is "undefined symbol" in + zend_bitset.h). (Nikita) + . Fixed bug #73753 (unserialized array pointer not advancing). (David Walker) + . Fixed bug #73783 (SIG_IGN doesn't work when Zend Signals is enabled). + (David Walker) + +- CLI: + . Fixed bug #72555 (CLI output(japanese) on Windows). (Anatol) + +- COM: + . Fixed bug #73679 (DOTNET read access violation using invalid codepage). + (Anatol) + +- DOM: + . Fixed bug #67474 (getElementsByTagNameNS filter on default ns). (aboks) + +- Mbstring: + . Fixed bug #73646 (mb_ereg_search_init null pointer dereference). + (Laruence) + +- Mysqli: + . Fixed bug #73462 (Persistent connections don't set $connect_errno). + (darkain) + +- Mysqlnd: + . Optimized handling of BIT fields - less memory copies and lower memory + usage. (Andrey) + . Fixed bug #73800 (sporadic segfault with MYSQLI_OPT_INT_AND_FLOAT_NATIVE). + (vanviegen) + +- Opcache: + . Fixed bug #73789 (Strange behavior of class constants in switch/case block). + (Laruence) + . Fixed bug #73746 (Method that returns string returns UNKNOWN:0 instead). + (Laruence) + . Fixed bug #73654 (Segmentation fault in zend_call_function). (Nikita) + . Fixed bug #73668 ("SIGFPE Arithmetic exception" in opcache when divide by + minus 1). (Nikita) + . Fixed bug #73847 (Recursion when a variable is redefined as array). (Nikita) + +- PDO_Firebird: + . Fixed bug #72931 (PDO_FIREBIRD with Firebird 3.0 not work on returning + statement). (Dorin Marcoci) + +- phpdbg: + . Fixed bug #73794 (Crash (out of memory) when using run and # command + separator). (Bob) + . Fixed bug #73704 (phpdbg shows the wrong line in files with shebang). (Bob) + +- SQLite3: + . Reverted fix for bug #73530 (Unsetting result set may reset other result + set). (cmb) + +- Standard: + . Fixed bug #73594 (dns_get_record does not populate $additional out + parameter). (Bruce Weirdan) + . Fixed bug #70213 (Unserialize context shared on double class lookup). + (Taoguang Chen) + . Fixed bug #73154 (serialize object with __sleep function crash). (Nikita) + . Fixed bug #70490 (get_browser function is very slow). (Nikita) + . Fixed bug #73265 (Loading browscap.ini at startup causes high memory usage). + (Nikita) + . Add subject to mail log. (tomsommer) + . Fixed bug #31875 (get_defined_functions additional param to exclude + disabled functions). (willianveiga) + +- Zlib + . Fixed bug #73373 (deflate_add does not verify that output was not truncated). + (Matt Bonneau) + +01 Dec 2016, PHP 7.1.0 + +- Core: + . Added nullable types. (Levi, Dmitry) + . Added DFA optimization framework based on e-SSA form. (Dmitry, Nikita) + . Added specialized opcode handlers (e.g. ZEND_ADD_LONG_NO_OVERFLOW). + (Dmitry) + . Added [] = as alternative construct to list() =. (Bob) + . Added void return type. (Andrea) + . Added support for negative string offsets in string offset syntax and + various string functions. (Francois) + . Added a form of the list() construct where keys can be specified. (Andrea) + . Implemented safe execution timeout handling, that prevents random crashes + after "Maximum execution time exceeded" error. (Dmitry) + . Implemented the RFC `Support Class Constant Visibility`. (Sean DuBois, + Reeze Xia, Dmitry) + . Implemented the RFC `Catching multiple exception types`. (Bronislaw Bialek, + Pierrick) + . Implemented logging to syslog with dynamic error levels. (Jani Ollikainen) + . Implemented FR #72614 (Support "nmake test" on building extensions by + phpize). (Yuji Uchiyama) + . Implemented RFC: Iterable. (Aaron Piotrowski) + . Implemented RFC: Closure::fromCallable (Danack) + . Implemented RFC: Replace "Missing argument" warning with "\ArgumentCountError" + exception. (Dmitry, Davey) + . Implemented RFC: Fix inconsistent behavior of $this variable. (Dmitry) + . Fixed bug #73585 (Logging of "Internal Zend error - Missing class + information" missing class name). (Laruence) + . Fixed memory leak(null coalescing operator with Spl hash). (Tyson Andre) + . Fixed bug #72736 (Slow performance when fetching large dataset with mysqli + / PDO). (Dmitry) + . Fixed bug #72482 (Ilegal write/read access caused by gdImageAALine + overflow). (cmb) + . Fixed bug #72696 (imagefilltoborder stackoverflow on truecolor images). + (cmb) + . Fixed bug #73350 (Exception::__toString() cause circular references). + (Laruence) + . Fixed bug #73329 ((Float)"Nano" == NAN). (Anatol) + . Fixed bug #73288 (Segfault in __clone > Exception.toString > __get). + (Laruence) + . Fixed for #73240 (Write out of bounds at number_format). (Stas) + . Fix pthreads detection when cross-compiling (ffontaine) + . Fixed bug #73337 (try/catch not working with two exceptions inside a same + operation). (Dmitry) + . Fixed bug #73156 (segfault on undefined function). (Dmitry) + . Fixed bug #73163 (PHP hangs if error handler throws while accessing undef + const in default value). (Nikita) + . Fixed bug #73172 (parse error: Invalid numeric literal). (Nikita, Anatol) + . Fixed bug #73181 (parse_str() without a second argument leads to crash). + (Nikita) + . Fixed bug #73025 (Heap Buffer Overflow in virtual_popen of + zend_virtual_cwd.c). (cmb) + . Fixed bug #73058 (crypt broken when salt is 'too' long). (Anatol) + . Fixed bug #72944 (Null pointer deref in zval_delref_p). (Dmitry) + . Fixed bug #72943 (assign_dim on string doesn't reset hval). (Laruence) + . Fixed bug #72598 (Reference is lost after array_slice()) (Nikita) + . Fixed bug #72703 (Out of bounds global memory read in BF_crypt triggered by + password_verify). (Anatol) + . Fixed bug #72813 (Segfault with __get returned by ref). (Laruence) + . Fixed bug #72767 (PHP Segfaults when trying to expand an infinite operator). + (Nikita) + . TypeError messages for arg_info type checks will now say "must be ... + or null" where the parameter or return type accepts null. (Andrea) + . Fixed bug #72857 (stream_socket_recvfrom read access violation). (Anatol) + . Fixed bug #72663 (Create an Unexpected Object and Don't Invoke + __wakeup() in Deserialization). (Stas) + . Fixed bug #72681 (PHP Session Data Injection Vulnerability). (Stas) + . Fixed bug #72742 (memory allocator fails to realloc small block to large + one). (Stas) + . Fixed URL rewriter. It would not rewrite '//example.com/' URL + unconditionally. URL rewrite target hosts whitelist is implemented. (Yasuo) + . Fixed bug #72641 (phpize (on Windows) ignores PHP_PREFIX). + (Yuji Uchiyama) + . Fixed bug #72683 (getmxrr broken). (Anatol) + . Fixed bug #72629 (Caught exception assignment to variables ignores + references). (Laruence) + . Fixed bug #72594 (Calling an earlier instance of an included anonymous + class fatals). (Laruence) + . Fixed bug #72581 (previous property undefined in Exception after + deserialization). (Laruence) + . Fixed bug #72543 (Different references behavior comparing to PHP 5) + (Laruence, Dmitry, Nikita) + . Fixed bug #72347 (VERIFY_RETURN type casts visible in finally). (Dmitry) + . Fixed bug #72216 (Return by reference with finally is not memory safe). + (Dmitry) + . Fixed bug #72215 (Wrong return value if var modified in finally). (Dmitry) + . Fixed bug #71818 (Memory leak when array altered in destructor). (Dmitry) + . Fixed bug #71539 (Memory error on $arr[$a] =& $arr[$b] if RHS rehashes) + (Dmitry, Nikita) + . Added new constant PHP_FD_SETSIZE. (cmb) + . Added optind parameter to getopt(). (as) + . Added PHP to SAPI error severity mapping for logs. (Martin Vobruba) + . Fixed bug #71911 (Unable to set --enable-debug on building extensions by + phpize on Windows). (Yuji Uchiyama) + . Fixed bug #29368 (The destructor is called when an exception is thrown from + the constructor). (Dmitry) + . Implemented RFC: RNG Fixes. (Leigh) + . Implemented email validation as per RFC 6531. (Leo Feyer, Anatol) + . Fixed bug #72513 (Stack-based buffer overflow vulnerability in + virtual_file_ex). (Stas) + . Fixed bug #72573 (HTTP_PROXY is improperly trusted by some PHP libraries + and applications). (Stas) + . Fixed bug #72523 (dtrace issue with reflection (failed test)). (Laruence) + . Fixed bug #72508 (strange references after recursive function call and + "switch" statement). (Laruence) + . Fixed bug #72441 (Segmentation fault: RFC list_keys). (Laruence) + . Fixed bug #72395 (list() regression). (Laruence) + . Fixed bug #72373 (TypeError after Generator function w/declared return type + finishes). (Nikita) + . Fixed bug #69489 (tempnam() should raise notice if falling back to temp dir). + (Laruence, Anatol) + . Fixed UTF-8 and long path support on Windows. (Anatol) + . Fixed bug #53432 (Assignment via string index access on an empty string + converts to array). (Nikita) + . Fixed bug #62210 (Exceptions can leak temporary variables). (Dmitry, Bob) + . Fixed bug #62814 (It is possible to stiffen child class members visibility). + (Nikita) + . Fixed bug #69989 (Generators don't participate in cycle GC). (Nikita) + . Fixed bug #70228 (Memleak if return in finally block). (Dmitry) + . Fixed bug #71266 (Missing separation of properties HT in foreach etc). + (Dmitry) + . Fixed bug #71604 (Aborted Generators continue after nested finally). + (Nikita) + . Fixed bug #71572 (String offset assignment from an empty string inserts + null byte). (Francois) + . Fixed bug #71897 (ASCII 0x7F Delete control character permitted in + identifiers). (Andrea) + . Fixed bug #72188 (Nested try/finally blocks losing return value). (Dmitry) + . Fixed bug #72213 (Finally leaks on nested exceptions). (Dmitry, Nikita) + . Fixed bug #47517 (php-cgi.exe missing UAC manifest). + (maxdax15801 at users noreply github com) + . Change statement and fcall extension handlers to accept frame. (Joe) + . Number operators taking numeric strings now emit E_NOTICEs or E_WARNINGs + when given malformed numeric strings. (Andrea) + . (int), intval() where $base is 10 or unspecified, settype(), decbin(), + decoct(), dechex(), integer operators and other conversions now always + respect scientific notation in numeric strings. (Andrea) + . Raise a compile-time warning on octal escape sequence overflow. (Sara) + +- Apache2handler: + . Enable per-module logging in Apache 2.4+. (Martin Vobruba) + +- BCmath: + . Fix bug #73190 (memcpy negative parameter _bc_new_num_ex). (Stas) + +- Bz2: + . Fixed bug #72837 (integer overflow in bzdecompress caused heap + corruption). (Stas) + . Fixed bug #72613 (Inadequate error handling in bzread()). (Stas) + +- Calendar: + . Fix integer overflows (Joshua Rogers) + . Fixed bug #67976 (cal_days_month() fails for final month of the French + calendar). (cmb) + . Fixed bug #71894 (AddressSanitizer: global-buffer-overflow in + zif_cal_from_jd). (cmb) + +- CLI Server: + . Fixed bug #73360 (Unable to work in root with unicode chars). (Anatol) + . Fixed bug #71276 (Built-in webserver does not send Date header). + (see at seos fr) + +- COM: + . Fixed bug #73126 (Cannot pass parameter 1 by reference). (Anatol) + . Fixed bug #69579 (Invalid free in extension trait). (John Boehr) + . Fixed bug #72922 (COM called from PHP does not return out parameters). + (Anatol) + . Fixed bug #72569 (DOTNET/COM array parameters broke in PHP7). (Anatol) + . Fixed bug #72498 (variant_date_from_timestamp null dereference). (Anatol) + +- Curl + . Implement support for handling HTTP/2 Server Push. (Davey) + . Add curl_multi_errno(), curl_share_errno() and curl_share_strerror() + functions. (Pierrick) + . Fixed bug #72674 (Heap overflow in curl_escape). (Stas) + . Fixed bug #72541 (size_t overflow lead to heap corruption). (Stas). + . Fixed bug #71709 (curl_setopt segfault with empty CURLOPT_HTTPHEADER). + (Pierrick) + . Fixed bug #71929 (CURLINFO_CERTINFO data parsing error). (Pierrick) + +- Date: + . Fixed bug #69587 (DateInterval properties and isset). (jhdxr) + . Fixed bug #73426 (createFromFormat with 'z' format char results in + incorrect time). (Derick) + . Fixed bug #45554 (Inconsistent behavior of the u format char). (Derick) + . Fixed bug #48225 (DateTime parser doesn't set microseconds for "now"). + (Derick) + . Fixed bug #52514 (microseconds are missing in DateTime class). (Derick) + . Fixed bug #52519 (microseconds in DateInterval are missing). (Derick) + . Fixed bug #60089 (DateTime::createFromFormat() U after u nukes microtime). + (Derick) + . Fixed bug #64887 (Allow DateTime modification with subsecond items). + (Derick) + . Fixed bug #68506 (General DateTime improvments needed for microseconds to + become useful). (Derick) + . Fixed bug #73109 (timelib_meridian doesn't parse dots correctly). (Derick) + . Fixed bug #73247 (DateTime constructor does not initialise microseconds + property). (Derick) + . Fixed bug #73147 (Use After Free in PHP7 unserialize()). (Stas) + . Fixed bug #73189 (Memcpy negative size parameter php_resolve_path). (Stas) + . Fixed bug #66836 (DateTime::createFromFormat 'U' with pre 1970 dates fails + parsing). (derick) + . Invalid serialization data for a DateTime or DatePeriod object will now + throw an instance of Error from __wakeup() or __set_state() instead of + resulting in a fatal error. (Aaron Piotrowski) + . Timezone initialization failure from serialized data will now throw an + instance of Error from __wakeup() or __set_state() instead of resulting in + a fatal error. (Aaron Piotrowski) + . Export date_get_interface_ce() for extension use. (Jeremy Mikola) + . Fixed bug #63740 (strtotime seems to use both sunday and monday as start of + week). (Derick) + +- Dba: + . Fixed bug #70825 (Cannot fetch multiple values with group in ini file). + (cmb) + . Data modification functions (e.g.: dba_insert()) now throw an instance of + Error instead of triggering a catchable fatal error if the key is does not + contain exactly two elements. (Aaron Piotrowski) + +- DOM: + . Fixed bug #73150 (missing NULL check in dom_document_save_html). (Stas) + . Fixed bug #66502 (DOM document dangling reference). (Sean Heelan, cmb) + . Invalid schema or RelaxNG validation contexts will throw an instance of + Error instead of resulting in a fatal error. (Aaron Piotrowski) + . Attempting to register a node class that does not extend the appropriate + base class will now throw an instance of Error instead of resulting in a + fatal error. (Aaron Piotrowski) + . Attempting to read an invalid or write to a readonly property will throw + an instance of Error instead of resulting in a fatal error. (Aaron + Piotrowski) + +- DTrace: + . Disabled PHP call tracing by default (it makes significant overhead). + This may be enabled again using envirionment variable USE_ZEND_DTRACE=1. + (Dmitry) + +- EXIF: + . Fixed bug #72735 (Samsung picture thumb not read (zero size)). (Kalle, Remi) + . Fixed bug #72627 (Memory Leakage In exif_process_IFD_in_TIFF). (Stas) + . Fixed bug #72603 (Out of bound read in exif_process_IFD_in_MAKERNOTE). + (Stas) + . Fixed bug #72618 (NULL Pointer Dereference in exif_process_user_comment). + (Stas) + +- Filter: + . Fixed bug #72972 (Bad filter for the flags FILTER_FLAG_NO_RES_RANGE and + FILTER_FLAG_NO_PRIV_RANGE). (julien) + . Fixed bug #73054 (default option ignored when object passed to int filter). + (cmb) + . Fixed bug #71745 (FILTER_FLAG_NO_RES_RANGE does not cover whole 127.0.0.0/8 + range). (bugs dot php dot net at majkl578 dot cz) + +- FPM: + . Fixed bug #72575 (using --allow-to-run-as-root should ignore missing user). + (gooh) + +- FTP: + . Fixed bug #70195 (Cannot upload file using ftp_put to FTPES with + require_ssl_reuse). (Benedict Singer) + . Implemented FR #55651 (Option to ignore the returned FTP PASV address). + (abrender at elitehosts dot com) + +- GD: + . Fixed bug #73213 (Integer overflow in imageline() with antialiasing). (cmb) + . Fixed bug #73272 (imagescale() is not affected by, but affects + imagesetinterpolation()). (cmb) + . Fixed bug #73279 (Integer overflow in gdImageScaleBilinearPalette()). (cmb) + . Fixed bug #73280 (Stack Buffer Overflow in GD dynamicGetbuf). (cmb) + . Fixed bug #50194 (imagettftext broken on transparent background w/o + alphablending). (cmb) + . Fixed bug #73003 (Integer Overflow in gdImageWebpCtx of gd_webp.c). (trylab, + cmb) + . Fixed bug #53504 (imagettfbbox gives incorrect values for bounding box). + (Mark Plomer, cmb) + . Fixed bug #73157 (imagegd2() ignores 3rd param if 4 are given). (cmb) + . Fixed bug #73155 (imagegd2() writes wrong chunk sizes on boundaries). (cmb) + . Fixed bug #73159 (imagegd2(): unrecognized formats may result in corrupted + files). (cmb) + . Fixed bug #73161 (imagecreatefromgd2() may leak memory). (cmb) + . Fixed bug #67325 (imagetruecolortopalette: white is duplicated in palette). + (cmb) + . Fixed bug #66005 (imagecopy does not support 1bit transparency on truecolor + images). (cmb) + . Fixed bug #72913 (imagecopy() loses single-color transparency on palette + images). (cmb) + . Fixed bug #68716 (possible resource leaks in _php_image_convert()). (cmb) + . Fixed bug #72709 (imagesetstyle() causes OOB read for empty $styles). (cmb) + . Fixed bug #72697 (select_colors write out-of-bounds). (Stas) + . Fixed bug #72730 (imagegammacorrect allows arbitrary write access). (Stas) + . Fixed bug #72596 (imagetypes function won't advertise WEBP support). (cmb) + . Fixed bug #72604 (imagearc() ignores thickness for full arcs). (cmb) + . Fixed bug #70315 (500 Server Error but page is fully rendered). (cmb) + . Fixed bug #43828 (broken transparency of imagearc for truecolor in + blendingmode). (cmb) + . Fixed bug #72512 (gdImageTrueColorToPaletteBody allows arbitrary write/read + access). (Pierre) + . Fixed bug #72519 (imagegif/output out-of-bounds access). (Pierre) + . Fixed bug #72558 (Integer overflow error within _gdContributionsAlloc()). + (Pierre) + . Fixed bug #72482 (Ilegal write/read access caused by gdImageAALine + overflow). (Pierre) + . Fixed bug #72494 (imagecropauto out-of-bounds access). (Fernando, Pierre, + cmb) + . Fixed bug #72404 (imagecreatefromjpeg fails on selfie). (cmb) + . Fixed bug #43475 (Thick styled lines have scrambled patterns). (cmb) + . Fixed bug #53640 (XBM images require width to be multiple of 8). (cmb) + . Fixed bug #64641 (imagefilledpolygon doesn't draw horizontal line). (cmb) + +- Hash: + . Added SHA3 fixed mode algorithms (224, 256, 384, and 512 bit). (Sara) + . Added SHA512/256 and SHA512/224 algorithms. (Sara) + +- iconv: + . Fixed bug #72320 (iconv_substr returns false for empty strings). (cmb) + +- IMAP: + . Fixed bug #73418 (Integer Overflow in "_php_imap_mail" leads to crash). + (Anatol) + . An email address longer than 16385 bytes will throw an instance of Error + instead of resulting in a fatal error. (Aaron Piotrowski) + +- Interbase: + . Fixed bug #73512 (Fails to find firebird headers as don't use fb_config + output). (Remi) + +- Intl: + . Fixed bug #73007 (add locale length check). (Stas) + . Fixed bug #73218 (add mitigation for ICU int overflow). (Stas) + . Fixed bug #65732 (grapheme_*() is not Unicode compliant on CR LF + sequence). (cmb) + . Fixed bug #73007 (add locale length check). (Stas) + . Fixed bug #72639 (Segfault when instantiating class that extends + IntlCalendar and adds a property). (Laruence) + . Fixed bug #72658 (Locale::lookup() / locale_lookup() hangs if no match + found). (Anatol) + . Partially fixed #72506 (idn_to_ascii for UTS #46 incorrect for long domain + names). (cmb) + . Fixed bug #72533 (locale_accept_from_http out-of-bounds access). (Stas) + . Failure to call the parent constructor in a class extending Collator + before invoking the parent methods will throw an instance of Error + instead of resulting in a recoverable fatal error. (Aaron Piotrowski) + . Cloning a Transliterator object may will now throw an instance of Error + instead of resulting in a fatal error if cloning the internal + transliterator fails. (Aaron Piotrowski) + . Added IntlTimeZone::getWindowsID() and + IntlTimeZone::getIDForWindowsID(). (Sara) + . Fixed bug #69374 (IntlDateFormatter formatObject returns wrong utf8 value). + (lenhatanh86 at gmail com) + . Fixed bug #69398 (IntlDateFormatter formatObject returns wrong value when + time style is NONE). (lenhatanh86 at gmail com) + +- JSON: + . Introduced encoder struct instead of global which fixes bugs #66025 and + #73254 related to pretty print indentation. (Jakub Zelenka) + . Fixed bug #73113 (Segfault with throwing JsonSerializable). (julien) + . Implemented earlier return when json_encode fails, fixes bugs #68992 + (Stacking exceptions thrown by JsonSerializable) and #70275 (On recursion + error, json_encode can eat up all system memory). (Jakub Zelenka) + . Implemented FR #46600 ("_empty_" key in objects). (Jakub Zelenka) + . Exported JSON parser API including json_parser_method that can be used + for implementing custom logic when parsing JSON. (Jakub Zelenka) + . Escaped U+2028 and U+2029 when JSON_UNESCAPED_UNICODE is supplied as + json_encode options and added JSON_UNESCAPED_LINE_TERMINATORS to restore + the previous behaviour. (Eddie Kohler) + +- LDAP: + . Providing an unknown modification type to ldap_batch_modify() will now + throw an instance of Error instead of resulting in a fatal error. + (Aaron Piotrowski) + +- Mbstring: + . Fixed bug #73532 (Null pointer dereference in mb_eregi). (Laruence) + . Fixed bug #66964 (mb_convert_variables() cannot detect recursion) (Yasuo) + . Fixed bug #72992 (mbstring.internal_encoding doesn't inherit default_charset). + (Yasuo) + . Fixed bug #66797 (mb_substr only takes 32-bit signed integer). (cmb) + . Fixed bug #72711 (`mb_ereg` does not clear the `$regs` parameter on + failure). (ju1ius) + . Fixed bug #72691 (mb_ereg_search raises a warning if a match zero-width). + (cmb) + . Fixed bug #72693 (mb_ereg_search increments search position when a match + zero-width). (cmb) + . Fixed bug #72694 (mb_ereg_search_setpos does not accept a string's last + position). (cmb) + . Fixed bug #72710 (`mb_ereg` causes buffer overflow on regexp compile error). + (ju1ius) + . Deprecated mb_ereg_replace() eval option. (Rouven Weßling, cmb) + . Fixed bug #69151 (mb_ereg should reject ill-formed byte sequence). + (Masaki Kagaya) + . Fixed bug #72405 (mb_ereg_replace - mbc_to_code (oniguruma) - + oob read access). (Laruence) + . Fixed bug #72399 (Use-After-Free in MBString (search_re)). (Laruence) + . mb_ereg() and mb_eregi() will now throw an instance of ParseError if an + invalid PHP expression is provided and the 'e' option is used. (Aaron + Piotrowski) + +- Mcrypt: + . Deprecated ext/mcrypt. (Scott Arciszewski, cmb) + . Fixed bug #72782 (Heap Overflow due to integer overflows). (Stas) + . Fixed bug #72551, bug #72552 (In correct casting from size_t to int lead to + heap overflow in mdecrypt_generic). (Stas) + . mcrypt_encrypt() and mcrypt_decrypt() will throw an instance of Error + instead of resulting in a fatal error if mcrypt cannot be initialized. + (Aaron Piotrowski) + +- Mysqli: + . Attempting to read an invalid or write to a readonly property will throw + an instance of Error instead of resulting in a fatal error. (Aaron + Piotrowski) + +- Mysqlnd: + . Fixed bug #64526 (Add missing mysqlnd.* parameters to php.ini-*). (cmb) + . Fixed bug #71863 (Segfault when EXPLAIN with "Unknown column" error when + using MariaDB). (Andrey) + . Fixed bug #72701 (mysqli_get_host_info() wrong output). (Anatol) + +- OCI8 + . Fixed bug #71148 (Bind reference overwritten on PHP 7). (Oracle Corp.) + . Fixed invalid handle error with Implicit Result Sets. (Chris Jones) + . Fixed bug #72524 (Binding null values triggers ORA-24816 error). (Chris Jones) + +- IMAP: + . Fixed bug #77153 (imap_open allows to run arbitrary shell commands via + mailbox parameter). (Stas) + +- ODBC: + . Fixed bug #73448 (odbc_errormsg returns trash, always 513 bytes). + (Anatol) + +- Opcache: + . Fixed bug #73583 (Segfaults when conditionally declared class and function + have the same name). (Laruence) + . Fixed bug #69090 (check cached files permissions) + . Fixed bug #72982 (Memory leak in zend_accel_blacklist_update_regexp() + function). (Laruence) + . Fixed bug #72949 (Typo in opcache error message). (cmb) + . Fixed bug #72762 (Infinite loop while parsing a file with opcache enabled). + (Nikita) + . Fixed bug #72590 (Opcache restart with kill_all_lockers does not work). + (Keyur) + +- OpenSSL: + . Fixed bug #73478 (openssl_pkey_new() generates wrong pub/priv keys with + Diffie Hellman). (Jakub Zelenka) + . Fixed bug #73276 (crash in openssl_random_pseudo_bytes function). (Stas) + . Fixed bug #73072 (Invalid path SNI_server_certs causes segfault). + (Jakub Zelenka) + . Fixed bug #72360 (ext/openssl build failure with OpenSSL 1.1.0). + (Jakub Zelenka) + . Bumped a minimal version to 1.0.1. (Jakub Zelenka) + . Dropped support for SSL2. (Remi) + . Implemented FR #61204 (Add elliptic curve support for OpenSSL). + (Dominic Luechinger) + . Implemented FR #67304 (Added AEAD support [CCM and GCM modes] to + openssl_encrypt and openssl_decrypt). (Jakub Zelenka) + . Implemented error storing to the global queue and cleaning up the OpenSSL + error queue (resolves bugs #68276 and #69882). (Jakub Zelenka) + +- Pcntl + . Implemented asynchronous signal handling without TICKS. (Dmitry) + . Added pcntl_signal_get_handler() that returns the current signal handler + for a particular signal. Addresses FR #72409. (David Walker) + . Add signinfo to pcntl_signal() handler args (Bishop Bettini, David Walker) + +- PCRE: + . Fixed bug #73483 (Segmentation fault on pcre_replace_callback). (Laruence) + . Fixed bug #73612 (preg_*() may leak memory). (cmb) + . Fixed bug #73392 (A use-after-free in zend allocator management). + (Laruence) + . Fixed bug #73121 (Bundled PCRE doesn't compile because JIT isn't supported + on s390). (Anatol) + . Fixed bug #72688 (preg_match missing group names in matches). (cmb) + . Downgraded to PCRE 8.38. (Anatol) + . Fixed bug #72476 (Memleak in jit_stack). (Laruence) + . Fixed bug #72463 (mail fails with invalid argument). (Anatol) + . Upgraded to PCRE 8.39. (Anatol) + +- PDO: + . Fixed bug #72788 (Invalid memory access when using persistent PDO + connection). (Keyur) + . Fixed bug #72791 (Memory leak in PDO persistent connection handling). (Keyur) + . Fixed bug #60665 (call to empty() on NULL result using PDO::FETCH_LAZY + returns false). (cmb) + +- PDO_DBlib: + . Fixed bug #72414 (Never quote values as raw binary data). (Adam Baratz) + . Allow \PDO::setAttribute() to set query timeouts. (Adam Baratz) + . Handle SQLDECIMAL/SQLNUMERIC types, which are used by later TDS versions. + (Adam Baratz) + . Add common PDO test suite. (Adam Baratz) + . Free error and message strings when cleaning up PDO instances. + (Adam Baratz) + . Fixed bug #67130 (\PDOStatement::nextRowset() should succeed when all rows + in current rowset haven't been fetched). (Peter LeBrun) + . Ignore potentially misleading dberr values. (Chris Kings-Lynne) + . Implemented stringify 'uniqueidentifier' fields. + (Alexander Zhuravlev, Adam Baratz) + +- PDO_Firebird: + . Fixed bug #73087, #61183, #71494 (Memory corruption in bindParam). + (Dorin Marcoci) + . Fixed bug #60052 (Integer returned as a 64bit integer on X86_64). (Mariuz) + +- PDO_pgsql: + . Fixed bug #70313 (PDO statement fails to throw exception). (Matteo) + . Fixed bug #72570 (Segmentation fault when binding parameters on a query + without placeholders). (Matteo) + . Implemented FR #72633 (Postgres PDO lastInsertId() should work without + specifying a sequence). (Pablo Santiago Sánchez, Matteo) + +- Phar: + . Fixed bug #72928 (Out of bound when verify signature of zip phar in + phar_parse_zipfile). (Stas) + . Fixed bug #73035 (Out of bound when verify signature of tar phar in + phar_parse_tarfile). (Stas) + +- phpdbg: + . Added generator command for inspection of currently alive generators. (Bob) + +- Postgres: + . Fixed bug #73498 (Incorrect SQL generated for pg_copy_to()). (Craig Duncan) + . Implemented FR #31021 (pg_last_notice() is needed to get all notice + messages). (Yasuo) + . Implemented FR #48532 (Allow pg_fetch_all() to index numerically). (Yasuo) + +- Readline: + . Fixed bug #72538 (readline_redisplay crashes php). (Laruence) + +- Reflection + . Undo backwards compatiblity break in ReflectionType->__toString() and + deprecate via documentation instead. (Nikita) + . Reverted prepending \ for class names. (Trowski) + . Implemented request #38992 (invoke() and invokeArgs() static method calls + should match). (cmb). + . Add ReflectionNamedType::getName(). This method should be used instead of + ReflectionType::__toString() + . Prepend \ for class names and ? for nullable types returned from + ReflectionType::__toString(). (Trowski) + . Fixed bug #72661 (ReflectionType::__toString crashes with iterable). + (Laruence) + . Fixed bug #72222 (ReflectionClass::export doesn't handle array constants). + (Nikita Nefedov) + . Failure to retrieve a reflection object or retrieve an object property + will now throw an instance of Error instead of resulting in a fatal error. + (Aaron Piotrowski) + . Fix #72209 (ReflectionProperty::getValue() doesn't fail if object doesn't match type). (Joe) + +- Session: + . Fixed bug #73273 (session_unset() empties values from all variables in which + is $_session stored). (Nikita) + . Fixed bug #73100 (session_destroy null dereference in ps_files_path_create). + (cmb) + . Fixed bug #68015 (Session does not report invalid uid for files save handler). + (Yasuo) + . Fixed bug #72940 (SID always return "name=ID", even if session + cookie exist). (Yasuo) + . Implemented session_gc() (Yasuo) + https://wiki.php.net/rfc/session-create-id + . Implemented session_create_id() (Yasuo) + https://wiki.php.net/rfc/session-gc + . Implemented RFC: Session ID without hashing. (Yasuo) + https://wiki.php.net/rfc/session-id-without-hashing + . Fixed bug #72531 (ps_files_cleanup_dir Buffer overflow). (Laruence) + . Custom session handlers that do not return strings for session IDs will + now throw an instance of Error instead of resulting in a fatal error + when a function is called that must generate a session ID. + (Aaron Piotrowski) + . An invalid setting for session.hash_function will throw an instance of + Error instead of resulting in a fatal error when a session ID is created. + (Aaron Piotrowski) + . Fixed bug #72562 (Use After Free in unserialize() with Unexpected Session + Deserialization). (Stas) + . Improved fix for bug #68063 (Empty session IDs do still start sessions). + (Yasuo) + . Fixed bug #71038 (session_start() returns TRUE on failure). + Session save handlers must return 'string' always for successful read. + i.e. Non-existing session read must return empty string. PHP 7.0 is made + not to tolerate buggy return value. (Yasuo) + . Fixed bug #71394 (session_regenerate_id() must close opened session on + errors). (Yasuo) + +- SimpleXML: + . Fixed bug #73293 (NULL pointer dereference in SimpleXMLElement::asXML()). + (Stas) + . Fixed bug #72971 (SimpleXML isset/unset do not respect namespace). (Nikita) + . Fixed bug #72957 (Null coalescing operator doesn't behave as expected with + SimpleXMLElement). (Nikita) + . Fixed bug #72588 (Using global var doesn't work while accessing SimpleXML + element). (Laruence) + . Creating an unnamed or duplicate attribute will throw an instance of Error + instead of resulting in a fatal error. (Aaron Piotrowski) + +- SNMP: + . Fixed bug #72708 (php_snmp_parse_oid integer overflow in memory + allocation). (djodjo at gmail dot com) + . Fixed bug #72479 (Use After Free Vulnerability in SNMP with GC and + unserialize()). (Stas) + +- Soap: + . Fixed bug #73538 (SoapClient::__setSoapHeaders doesn't overwrite SOAP + headers). (duncan3dc) + . Fixed bug #73452 (Segfault (Regression for #69152)). (Dmitry) + . Fixed bug #73037 (SoapServer reports Bad Request when gzipped). (Anatol) + . Fixed bug #73237 (Nested object in "any" element overwrites other fields). + (Keith Smiley) + . Fixed bug #69137 (Peer verification fails when using a proxy with SoapClient) + (Keith Smiley) + . Fixed bug #71711 (Soap Server Member variables reference bug). (Nikita) + . Fixed bug #71996 (Using references in arrays doesn't work like expected). + (Nikita) + +- SPL: + . Fixed bug #73423 (Reproducible crash with GDB backtrace). (Laruence) + . Fixed bug #72888 (Segfault on clone on splFileObject). (Laruence) + . Fixed bug #73029 (Missing type check when unserializing SplArray). (Stas) + . Fixed bug #72646 (SplFileObject::getCsvControl does not return the escape + character). (cmb) + . Fixed bug #72684 (AppendIterator segfault with closed generator). (Pierrick) + . Attempting to clone an SplDirectory object will throw an instance of Error + instead of resulting in a fatal error. (Aaron Piotrowski) + . Calling ArrayIterator::append() when iterating over an object will throw an + instance of Error instead of resulting in a fatal error. (Aaron Piotrowski) + . Fixed bug #55701 (GlobIterator throws LogicException). (Valentin VĂLCIU) + +- SQLite3: + . Update to SQLite 3.15.1. (cmb) + . Fixed bug #73530 (Unsetting result set may reset other result set). (cmb) + . Fixed bug #73333 (2147483647 is fetched as string). (cmb) + . Fixed bug #72668 (Spurious warning when exception is thrown in user defined + function). (Laruence) + . Implemented FR #72653 (SQLite should allow opening with empty filename). + (cmb) + . Fixed bug #70628 (Clearing bindings on an SQLite3 statement doesn't work). + (cmb) + . Implemented FR #71159 (Upgraded bundled SQLite lib to 3.9.2). (Laruence) + +- Standard: + . Fixed bug #73297 (HTTP stream wrapper should ignore HTTP 100 Continue). + (rowan dot collins at gmail dot com) + . Fixed bug #73303 (Scope not inherited by eval in assert()). (nikic) + . Fixed bug #73192 (parse_url return wrong hostname). (Nikita) + . Fixed bug #73203 (passing additional_parameters causes mail to fail). (cmb) + . Fixed bug #73203 (passing additional_parameters causes mail to fail). (cmb) + . Fixed bug #72920 (Accessing a private constant using constant() creates + an exception AND warning). (Laruence) + . Fixed bug #65550 (get_browser() incorrectly parses entries with "+" sign). + (cmb) + . Fixed bug #71882 (Negative ftruncate() on php://memory exhausts memory). + (cmb) + . Fixed bug #55451 (substr_compare NULL length interpreted as 0). (Lauri + Kenttä) + . Fixed bug #72278 (getimagesize returning FALSE on valid jpg). (cmb) + . Fixed bug #61967 (unset array item in array_walk_recursive cause + inconsistent array). (Nikita) + . Fixed bug #62607 (array_walk_recursive move internal pointer). (Nikita) + . Fixed bug #69068 (Exchanging array during array_walk -> memory errors). + (Nikita) + . Fixed bug #70713 (Use After Free Vulnerability in array_walk()/ + array_walk_recursive()). (Nikita) + . Fixed bug #72622 (array_walk + array_replace_recursive create references + from nothing). (Laruence) + . Fixed bug #72330 (CSV fields incorrectly split if escape char followed by + UTF chars). (cmb) + . Implemented RFC: More precise float values. (Jakub Zelenka, Yasuo) + . array_multisort now uses zend_sort instead zend_qsort. (Laruence) + . Fixed bug #72505 (readfile() mangles files larger than 2G). (Cschneid) + . assert() will throw a ParseError when evaluating a string given as the first + argument if the PHP code is invalid instead of resulting in a catchable + fatal error. (Aaron Piotrowski) + . Calling forward_static_call() outside of a class scope will now throw an + instance of Error instead of resulting in a fatal error. (Aaron Piotrowski) + . Added is_iterable() function. (Aaron Piotrowski) + . Fixed bug #72306 (Heap overflow through proc_open and $env parameter). + (Laruence) + . Fixed bug #71100 (long2ip() doesn't accept integers in strict mode). + (Laruence) + . Implemented FR #55716 (Add an option to pass a custom stream context to + get_headers()). (Ferenc) + . Additional validation for parse_url() for login/pass components). + (Ilia) (Julien) + . Implemented FR #69359 (Provide a way to fetch the current environment + variables). (Ferenc) + . unpack() function accepts an additional optional argument $offset. (Dmitry) + . Implemented #51879 stream context socket option tcp_nodelay (Joe) + +- Streams: + . Fixed bug #73586 (php_user_filter::$stream is not set to the stream the + filter is working on). (Dmitry) + . Fixed bug #72853 (stream_set_blocking doesn't work). (Laruence) + . Fixed bug #72743 (Out-of-bound read in php_stream_filter_create). + (Loianhtuan) + . Implemented FR #27814 (Multiple small packets send for HTTP request). + (vhuk) + . Fixed bug #72764 (ftps:// opendir wrapper data channel encryption fails + with IIS FTP 7.5, 8.5). (vhuk) + . Fixed bug #72810 (Missing SKIP_ONLINE_TESTS checks). (vhuk) + . Fixed bug #41021 (Problems with the ftps wrapper). (vhuk) + . Fixed bug #54431 (opendir() does not work with ftps:// wrapper). (vhuk) + . Fixed bug #72667 (opendir() with ftp:// attempts to open data stream for + non-existent directories). (vhuk) + . Fixed bug #72771 (ftps:// wrapper is vulnerable to protocol downgrade + attack). (Stas) + . Fixed bug #72534 (stream_socket_get_name crashes). (Anatol) + . Fixed bug #72439 (Stream socket with remote address leads to a segmentation + fault). (Laruence) + +- sysvshm: + . Fixed bug #72858 (shm_attach null dereference). (Anatol) + +- Tidy: + . Implemented support for libtidy 5.0.0 and above. (Michael Orlitzky, Anatol) + . Creating a tidyNode manually will now throw an instance of Error instead of + resulting in a fatal error. (Aaron Piotrowski) + +- Wddx: + . Fixed bug #73331 (NULL Pointer Dereference in WDDX Packet Deserialization + with PDORow). (Stas) + . Fixed bug #72142 (WDDX Packet Injection Vulnerability in + wddx_serialize_value()). (Taoguang Chen) + . Fixed bug #72749 (wddx_deserialize allows illegal memory access) (Stas) + . Fixed bug #72750 (wddx_deserialize null dereference). (Stas) + . Fixed bug #72790 (wddx_deserialize null dereference with invalid xml). + (Stas) + . Fixed bug #72799 (wddx_deserialize null dereference in + php_wddx_pop_element). (Stas) + . Fixed bug #72860 (wddx_deserialize use-after-free). (Stas) + . Fixed bug #73065 (Out-Of-Bounds Read in php_wddx_push_element). (Stas) + . Fixed bug #72564 (boolean always deserialized as "true") (Remi) + . A circular reference when serializing will now throw an instance of Error + instead of resulting in a fatal error. (Aaron Piotrowski) + +- XML: + . Fixed bug #72135 (malformed XML causes fault) (edgarsandi) + . Fixed bug #72714 (_xml_startElementHandler() segmentation fault). (cmb) + . Fixed bug #72085 (SEGV on unknown address zif_xml_parse). (cmb) + +- XMLRPC: + . Fixed bug #72647 (xmlrpc_encode() unexpected output after referencing + array elements). (Laruence) + . Fixed bug #72606 (heap-buffer-overflow (write) simplestring_addn + simplestring.c). (Stas) + . A circular reference when serializing will now throw an instance of Error + instead of resulting in a fatal error. (Aaron Piotrowski) + +- Zip: + . Fixed bug #68302 (impossible to compile php with zip support). (cmb) + . Fixed bug #72660 (NULL Pointer dereference in zend_virtual_cwd). + (Laruence) + . Fixed bug #72520 (Stack-based buffer overflow vulnerability in + php_stream_zip_opener). (Stas) + . ZipArchive::addGlob() will throw an instance of Error instead of resulting + in a fatal error if glob support is not available. (Aaron Piotrowski) + +10 Nov 2016 PHP 7.0.13 + +- Core: + . Fixed bug #73350 (Exception::__toString() cause circular references). + (Laruence) + . Fixed bug #73181 (parse_str() without a second argument leads to crash). + (Nikita) + . Fixed bug #66773 (Autoload with Opcache allows importing conflicting class + name to namespace). (Nikita) + . Fixed bug #66862 ((Sub-)Namespaces unexpected behaviour). (Nikita) + . Fix pthreads detection when cross-compiling (ffontaine) + . Fixed bug #73337 (try/catch not working with two exceptions inside a same + operation). (Dmitry) + . Fixed bug #73338 (Exception thrown from error handler causes valgrind + warnings (and crashes)). (Bob, Dmitry) + . Fixed bug #73329 ((Float)"Nano" == NAN). (Anatol) + +- GD: + . Fixed bug #73213 (Integer overflow in imageline() with antialiasing). (cmb) + . Fixed bug #73272 (imagescale() is not affected by, but affects + imagesetinterpolation()). (cmb) + . Fixed bug #73279 (Integer overflow in gdImageScaleBilinearPalette()). (cmb) + . Fixed bug #73280 (Stack Buffer Overflow in GD dynamicGetbuf). (cmb) + . Fixed bug #72482 (Ilegal write/read access caused by gdImageAALine + overflow). (cmb) + . Fixed bug #72696 (imagefilltoborder stackoverflow on truecolor images). + (cmb) + +- IMAP: + . Fixed bug #73418 (Integer Overflow in "_php_imap_mail" leads to crash). + (Anatol) + +- OCI8 + . Fixed bug #71148 (Bind reference overwritten on PHP 7). (Oracle Corp.) + +- phpdbg: + . Properly allow for stdin input from a file. (Bob) + . Add -s command line option / stdin command for reading script from stdin. + (Bob) + . Ignore non-executable opcodes in line mode of phpdbg_end_oplog(). (Bob) + . Fixed bug #70776 (Simple SIGINT does not have any effect with -rr). (Bob) + . Fixed bug #71234 (INI files are loaded even invoked as -n --version). (Bob) + +- Session: + . Fixed bug #73273 (session_unset() empties values from all variables in which + is $_session stored). (Nikita) + +- SOAP: + . Fixed bug #73037 (SoapServer reports Bad Request when gzipped). (Anatol) + . Fixed bug #73237 (Nested object in "any" element overwrites other fields). + (Keith Smiley) + . Fixed bug #69137 (Peer verification fails when using a proxy with SoapClient) + (Keith Smiley) + +- SQLite3: + . Fixed bug #73333 (2147483647 is fetched as string). (cmb) + +- Standard: + . Fixed bug #73203 (passing additional_parameters causes mail to fail). (cmb) + . Fixed bug #71241 (array_replace_recursive sometimes mutates its parameters). + (adsr) + +- Wddx: + . Fixed bug #73331 (NULL Pointer Dereference in WDDX Packet Deserialization + with PDORow). (Stas) + +13 Oct 2016 PHP 7.0.12 + +- Core: + . Fixed bug #73025 (Heap Buffer Overflow in virtual_popen of + zend_virtual_cwd.c). (cmb) + . Fixed bug #72703 (Out of bounds global memory read in BF_crypt triggered by + password_verify). (Anatol) + . Fixed bug #73058 (crypt broken when salt is 'too' long). (Anatol) + . Fixed bug #69579 (Invalid free in extension trait). (John Boehr) + . Fixed bug #73156 (segfault on undefined function). (Dmitry) + . Fixed bug #73163 (PHP hangs if error handler throws while accessing undef + const in default value). (Nikita) + . Fixed bug #73172 (parse error: Invalid numeric literal). (Nikita, Anatol) + . Fixed for #73240 (Write out of bounds at number_format). (Stas) + . Fixed bug #73147 (Use After Free in PHP7 unserialize()). (Stas) + . Fixed bug #73189 (Memcpy negative size parameter php_resolve_path). (Stas) + +- BCmath: + . Fix bug #73190 (memcpy negative parameter _bc_new_num_ex). (Stas) + +- COM: + . Fixed bug #73126 (Cannot pass parameter 1 by reference). (Anatol) + +- Date: + . Fixed bug #73091 (Unserializing DateInterval object may lead to __toString + invocation). (Stas) + +- DOM: + . Fixed bug #73150 (missing NULL check in dom_document_save_html). (Stas) + +- Filter: + . Fixed bug #72972 (Bad filter for the flags FILTER_FLAG_NO_RES_RANGE and + FILTER_FLAG_NO_PRIV_RANGE). (julien) + . Fixed bug #73054 (default option ignored when object passed to int filter). + (cmb) + +- GD: + . Fixed bug #67325 (imagetruecolortopalette: white is duplicated in palette). + (cmb) + . Fixed bug #50194 (imagettftext broken on transparent background w/o + alphablending). (cmb) + . Fixed bug #73003 (Integer Overflow in gdImageWebpCtx of gd_webp.c). (trylab, + cmb) + . Fixed bug #53504 (imagettfbbox gives incorrect values for bounding box). + (Mark Plomer, cmb) + . Fixed bug #73157 (imagegd2() ignores 3rd param if 4 are given). (cmb) + . Fixed bug #73155 (imagegd2() writes wrong chunk sizes on boundaries). (cmb) + . Fixed bug #73159 (imagegd2(): unrecognized formats may result in corrupted + files). (cmb) + . Fixed bug #73161 (imagecreatefromgd2() may leak memory). (cmb) + +- Intl: + . Fixed bug #73218 (add mitigation for ICU int overflow). (Stas) + +- Mbstring: + . Fixed bug #66797 (mb_substr only takes 32-bit signed integer). (cmb) + . Fixed bug #66964 (mb_convert_variables() cannot detect recursion) (Yasuo) + . Fixed bug #72992 (mbstring.internal_encoding doesn't inherit default_charset). + (Yasuo) + +- Mysqlnd: + . Fixed bug #72489 (PHP Crashes When Modifying Array Containing MySQLi Result + Data). (Nikita) + +- Opcache: + . Fixed bug #72982 (Memory leak in zend_accel_blacklist_update_regexp() + function). (Laruence) + +- OpenSSL: + . Fixed bug #73072 (Invalid path SNI_server_certs causes segfault). + (Jakub Zelenka) + . Fixed bug #73276 (crash in openssl_random_pseudo_bytes function). (Stas) + . Fixed bug #73275 (crash in openssl_encrypt function). (Stas) + +- PCRE: + . Fixed bug #73121 (Bundled PCRE doesn't compile because JIT isn't supported + on s390). (Anatol) + . Fixed bug #73174 (heap overflow in php_pcre_replace_impl). (Stas) + +- PDO_DBlib: + . Fixed bug #72414 (Never quote values as raw binary data). (Adam Baratz) + . Allow \PDO::setAttribute() to set query timeouts. (Adam Baratz) + . Handle SQLDECIMAL/SQLNUMERIC types, which are used by later TDS versions. + (Adam Baratz) + . Add common PDO test suite. (Adam Baratz) + . Free error and message strings when cleaning up PDO instances. + (Adam Baratz) + . Fixed bug #67130 (\PDOStatement::nextRowset() should succeed when all rows + in current rowset haven't been fetched). (Peter LeBrun) + . Ignore potentially misleading dberr values. (Chris Kings-Lynne) + +- phpdbg: + . Fixed bug #72996 (phpdbg_prompt.c undefined reference to DL_LOAD). (Nikita) + . Fixed next command not stopping when leaving function. (Bob) + +- Session: + . Fixed bug #68015 (Session does not report invalid uid for files save handler). + (Yasuo) + . Fixed bug #73100 (session_destroy null dereference in ps_files_path_create). + (cmb) + +- SimpleXML: + . Fixed bug #73293 (NULL pointer dereference in SimpleXMLElement::asXML()). + (Stas) + +- SOAP: + . Fixed bug #71711 (Soap Server Member variables reference bug). (Nikita) + . Fixed bug #71996 (Using references in arrays doesn't work like expected). + (Nikita) + +- SPL: + . Fixed bug #73257, #73258 (SplObjectStorage unserialize allows use of + non-object as key). (Stas) + +- SQLite3: + . Updated bundled SQLite3 to 3.14.2. (cmb) + +- Zip: + . Fixed bug #70752 (Depacking with wrong password leaves 0 length files). + (cmb) + +15 Sep 2016 PHP 7.0.11 + +- Core: + . Fixed bug #72944 (Null pointer deref in zval_delref_p). (Dmitry) + . Fixed bug #72943 (assign_dim on string doesn't reset hval). (Laruence) + . Fixed bug #72911 (Memleak in zend_binary_assign_op_obj_helper). (Laruence) + . Fixed bug #72813 (Segfault with __get returned by ref). (Laruence) + . Fixed bug #72767 (PHP Segfaults when trying to expand an infinite operator). + (Nikita) + . Fixed bug #72854 (PHP Crashes on duplicate destructor call). (Nikita) + . Fixed bug #72857 (stream_socket_recvfrom read access violation). (Anatol) + +- COM: + . Fixed bug #72922 (COM called from PHP does not return out parameters). + (Anatol) + +- Dba: + . Fixed bug #70825 (Cannot fetch multiple values with group in ini file). + (cmb) + +- FTP: + . Fixed bug #70195 (Cannot upload file using ftp_put to FTPES with + require_ssl_reuse). (Benedict Singer) + +- GD: + . Fixed bug #72709 (imagesetstyle() causes OOB read for empty $styles). (cmb) + . Fixed bug #66005 (imagecopy does not support 1bit transparency on truecolor + images). (cmb) + . Fixed bug #72913 (imagecopy() loses single-color transparency on palette + images). (cmb) + . Fixed bug #68716 (possible resource leaks in _php_image_convert()). (cmb) + +- iconv: + . Fixed bug #72320 (iconv_substr returns false for empty strings). (cmb) + +- IMAP: + . Fixed bug #72852 (imap_mail null dereference). (Anatol) + +- Intl: + . Fixed bug #65732 (grapheme_*() is not Unicode compliant on CR LF + sequence). (cmb) + . Fixed bug #73007 (add locale length check). (Stas) + +- Mysqlnd: + . Fixed bug #72293 (Heap overflow in mysqlnd related to BIT fields). (Stas) + +- OCI8 + . Fixed invalid handle error with Implicit Result Sets. (Chris Jones) + . Fixed bug #72524 (Binding null values triggers ORA-24816 error). (Chris Jones) + +- Opcache: + . Fixed bug #72949 (Typo in opcache error message). (cmb) + +- PDO: + . Fixed bug #72788 (Invalid memory access when using persistent PDO + connection). (Keyur) + . Fixed bug #72791 (Memory leak in PDO persistent connection handling). (Keyur) + . Fixed bug #60665 (call to empty() on NULL result using PDO::FETCH_LAZY + returns false). (cmb) + +- PDO_DBlib: + . Implemented stringify 'uniqueidentifier' fields. + (Alexander Zhuravlev, Adam Baratz) + +- PDO_pgsql: + . Implemented FR #72633 (Postgres PDO lastInsertId() should work without + specifying a sequence). (Pablo Santiago Sánchez, Matteo) + . Fixed bug #72759 (Regression in pgo_pgsql). (Anatol) + +- Phar: + . Fixed bug #72928 (Out of bound when verify signature of zip phar in + phar_parse_zipfile). (Stas) + . Fixed bug #73035 (Out of bound when verify signature of tar phar in + phar_parse_tarfile). (Stas) + +- Reflection: + . Fixed bug #72846 (getConstant for a array constant with constant values + returns NULL/NFC/UKNOWN). (Laruence) + +- Session: + . Fixed bug #72724 (PHP7: session-uploadprogress kills httpd). (Nikita) + . Fixed bug #72940 (SID always return "name=ID", even if session + cookie exist). (Yasuo) + +- SimpleXML: + . Fixed bug #72971 (SimpleXML isset/unset do not respect namespace). (Nikita) + . Fixed bug #72957 (Null coalescing operator doesn't behave as expected with + SimpleXMLElement). (Nikita) + +- SPL: + . Fixed bug #73029 (Missing type check when unserializing SplArray). (Stas) + +- Standard: + . Fixed bug #55451 (substr_compare NULL length interpreted as 0). (Lauri + Kenttä) + . Fixed bug #72278 (getimagesize returning FALSE on valid jpg). (cmb) + . Fixed bug #65550 (get_browser() incorrectly parses entries with "+" sign). + (cmb) + +- Streams: + . Fixed bug #72853 (stream_set_blocking doesn't work). (Laruence) + . Fixed bug #72764 (ftps:// opendir wrapper data channel encryption fails + with IIS FTP 7.5, 8.5). (vhuk) + . Fixed bug #71882 (Negative ftruncate() on php://memory exhausts memory). + (cmb) + +- SQLite3: + . Downgraded bundled SQLite to 3.8.10.2. (Anatol); + +- Sysvshm: + . Fixed bug #72858 (shm_attach null dereference). (Anatol) + +- XML: + . Fixed bug #72085 (SEGV on unknown address zif_xml_parse). (cmb) + . Fixed bug #72714 (_xml_startElementHandler() segmentation fault). (cmb) + +- Wddx: + . Fixed bug #72860 (wddx_deserialize use-after-free). (Stas) + . Fixed bug #73065 (Out-Of-Bounds Read in php_wddx_push_element). (Stas) + +- ZIP: + . Fixed bug #68302 (impossible to compile php with zip support). (cmb) + +18 Aug 2016 PHP 7.0.10 + +- Core: + . Fixed bug #72629 (Caught exception assignment to variables ignores + references). (Laruence) + . Fixed bug #72594 (Calling an earlier instance of an included anonymous + class fatals). (Laruence) + . Fixed bug #72581 (previous property undefined in Exception after + deserialization). (Laruence) + . Fixed bug #72496 (Cannot declare public method with signature incompatible + with parent private method). (Pedro Magalhães) + . Fixed bug #72024 (microtime() leaks memory). (maroszek at gmx dot net) + . Fixed bug #71911 (Unable to set --enable-debug on building extensions by + phpize on Windows). (Yuji Uchiyama) + . Fixed bug causing ClosedGeneratorException being thrown into the calling + code instead of the Generator yielding from. (Bob) + . Implemented FR #72614 (Support "nmake test" on building extensions by + phpize). (Yuji Uchiyama) + . Fixed bug #72641 (phpize (on Windows) ignores PHP_PREFIX). + (Yuji Uchiyama) + . Fixed potential segfault in object storage freeing in shutdown sequence. + (Bob) + . Fixed bug #72663 (Create an Unexpected Object and Don't Invoke + __wakeup() in Deserialization). (Stas) + . Fixed bug #72681 (PHP Session Data Injection Vulnerability). (Stas) + . Fixed bug #72683 (getmxrr broken). (Anatol) + . Fixed bug #72742 (memory allocator fails to realloc small block to large + one). (Stas) + . Fixed URL rewriter partially. It would not rewrite '//example.com/' URL + unconditionally. Only requested host(HTTP_HOST) is rewritten. (Yasuo) + +- Bz2: + . Fixed bug #72837 (integer overflow in bzdecompress caused heap + corruption). (Stas) + +- Calendar: + . Fixed bug #67976 (cal_days_month() fails for final month of the French + calendar). (cmb) + . Fixed bug #71894 (AddressSanitizer: global-buffer-overflow in + zif_cal_from_jd). (cmb) + +- COM: + . Fixed bug #72569 (DOTNET/COM array parameters broke in PHP7). (Anatol) + +- CURL: + . Fixed bug #71709 (curl_setopt segfault with empty CURLOPT_HTTPHEADER). + (Pierrick) + . Fixed bug #71929 (CURLINFO_CERTINFO data parsing error). (Pierrick) + . Fixed bug #72674 (Heap overflow in curl_escape). (Stas) + +- DOM: + . Fixed bug #66502 (DOM document dangling reference). (Sean Heelan, cmb) + +- EXIF: + . Fixed bug #72735 (Samsung picture thumb not read (zero size)). (Kalle, Remi) + . Fixed bug #72627 (Memory Leakage In exif_process_IFD_in_TIFF). (Stas) + +- Filter: + . Fixed bug #71745 (FILTER_FLAG_NO_RES_RANGE does not cover whole 127.0.0.0/8 + range). (bugs dot php dot net at majkl578 dot cz) + +- FPM: + . Fixed bug #72575 (using --allow-to-run-as-root should ignore missing user). + (gooh) + +- GD: + . Fixed bug #72596 (imagetypes function won't advertise WEBP support). (cmb) + . Fixed bug #72604 (imagearc() ignores thickness for full arcs). (cmb) + . Fixed bug #70315 (500 Server Error but page is fully rendered). (cmb) + . Fixed bug #43828 (broken transparency of imagearc for truecolor in + blendingmode). (cmb) + . Fixed bug #66555 (Always false condition in ext/gd/libgd/gdkanji.c). (cmb) + . Fixed bug #68712 (suspicious if-else statements). (cmb) + . Fixed bug #72697 (select_colors write out-of-bounds). (Stas) + . Fixed bug #72730 (imagegammacorrect allows arbitrary write access). (Stas) + +- Intl: + . Fixed bug #72639 (Segfault when instantiating class that extends + IntlCalendar and adds a property). (Laruence) + . Partially fixed #72506 (idn_to_ascii for UTS #46 incorrect for long domain + names). (cmb) + +- mbstring: + . Fixed bug #72691 (mb_ereg_search raises a warning if a match zero-width). + (cmb) + . Fixed bug #72693 (mb_ereg_search increments search position when a match + zero-width). (cmb) + . Fixed bug #72694 (mb_ereg_search_setpos does not accept a string's last + position). (cmb) + . Fixed bug #72710 (`mb_ereg` causes buffer overflow on regexp compile error). + (ju1ius) + +- Mcrypt: + . Fixed bug #72782 (Heap Overflow due to integer overflows). (Stas) + +- Opcache: + . Fixed bug #72590 (Opcache restart with kill_all_lockers does not work). + (Keyur) + +- PCRE: + . Fixed bug #72688 (preg_match missing group names in matches). (cmb) + +- PDO_pgsql: + . Fixed bug #70313 (PDO statement fails to throw exception). (Matteo) + +- Reflection: + . Fixed bug #72222 (ReflectionClass::export doesn't handle array constants). + (Nikita Nefedov) + +- SimpleXML: + . Fixed bug #72588 (Using global var doesn't work while accessing SimpleXML + element). (Laruence) + +- SNMP: + . Fixed bug #72708 (php_snmp_parse_oid integer overflow in memory + allocation). (djodjo at gmail dot com) + +- SPL: + . Fixed bug #55701 (GlobIterator throws LogicException). (Valentin VĂLCIU) + . Fixed bug #72646 (SplFileObject::getCsvControl does not return the escape + character). (cmb) + . Fixed bug #72684 (AppendIterator segfault with closed generator). (Pierrick) + +- SQLite3: + . Fixed bug #72668 (Spurious warning when exception is thrown in user defined + function). (Laruence) + . Fixed bug #72571 (SQLite3::bindValue, SQLite3::bindParam crash). (Laruence) + . Implemented FR #72653 (SQLite should allow opening with empty filename). + (cmb) + . Updated to SQLite3 3.13.0. (cmb) + +- Standard: + . Fixed bug #72622 (array_walk + array_replace_recursive create references + from nothing). (Laruence) + . Fixed bug #72152 (base64_decode $strict fails to detect null byte). + (Lauri Kenttä) + . Fixed bug #72263 (base64_decode skips a character after padding in strict + mode). (Lauri Kenttä) + . Fixed bug #72264 (base64_decode $strict fails with whitespace between + padding). (Lauri Kenttä) + . Fixed bug #72330 (CSV fields incorrectly split if escape char followed by + UTF chars). (cmb) + +- Streams: + . Fixed bug #41021 (Problems with the ftps wrapper). (vhuk) + . Fixed bug #54431 (opendir() does not work with ftps:// wrapper). (vhuk) + . Fixed bug #72667 (opendir() with ftp:// attempts to open data stream for + non-existent directories). (vhuk) + . Fixed bug #72771 (ftps:// wrapper is vulnerable to protocol downgrade + attack). (Stas) + +- XMLRPC: + . Fixed bug #72647 (xmlrpc_encode() unexpected output after referencing + array elements). (Laruence) + +- Wddx: + . Fixed bug #72564 (boolean always deserialized as "true") (Remi) + . Fixed bug #72142 (WDDX Packet Injection Vulnerability in + wddx_serialize_value()). (Taoguang Chen) + . Fixed bug #72749 (wddx_deserialize allows illegal memory access) (Stas) + . Fixed bug #72750 (wddx_deserialize null dereference). (Stas) + . Fixed bug #72790 (wddx_deserialize null dereference with invalid xml). + (Stas) + . Fixed bug #72799 (wddx_deserialize null dereference in + php_wddx_pop_element). (Stas) + +- Zip: + . Fixed bug #72660 (NULL Pointer dereference in zend_virtual_cwd). + (Laruence) + +21 Jul 2016 PHP 7.0.9 + +- Core: + . Fixed bug #72508 (strange references after recursive function call and + "switch" statement). (Laruence) + . Fixed bug #72513 (Stack-based buffer overflow vulnerability in + virtual_file_ex). (Stas) + . Fixed bug #72573 (HTTP_PROXY is improperly trusted by some PHP libraries + and applications). (Stas) + +- bz2: + . Fixed bug #72613 (Inadequate error handling in bzread()). (Stas) + +- CLI: + . Fixed bug #72484 (SCRIPT_FILENAME shows wrong path if the user specify + router.php). (Laruence) + +- COM: + . Fixed bug #72498 (variant_date_from_timestamp null dereference). (Anatol) + +- Curl: + . Fixed bug #72541 (size_t overflow lead to heap corruption). (Stas) + +- Date: + . Fixed bug #66836 (DateTime::createFromFormat 'U' with pre 1970 dates fails + parsing). (derick) + +- Exif: + . Fixed bug #72603 (Out of bound read in exif_process_IFD_in_MAKERNOTE). + (Stas) + . Fixed bug #72618 (NULL Pointer Dereference in exif_process_user_comment). + (Stas) + +- GD: + . Fixed bug #43475 (Thick styled lines have scrambled patterns). (cmb) + . Fixed bug #53640 (XBM images require width to be multiple of 8). (cmb) + . Fixed bug #64641 (imagefilledpolygon doesn't draw horizontal line). (cmb) + . Fixed bug #72512 (gdImageTrueColorToPaletteBody allows arbitrary write/read + access). (Pierre) + . Fixed bug #72519 (imagegif/output out-of-bounds access). (Pierre) + . Fixed bug #72558 (Integer overflow error within _gdContributionsAlloc()). + (Pierre) + . Fixed bug #72482 (Ilegal write/read access caused by gdImageAALine + overflow). (Pierre) + . Fixed bug #72494 (imagecropauto out-of-bounds access). (Pierre) + +- Intl: + . Fixed bug #72533 (locale_accept_from_http out-of-bounds access). (Stas) + +- Mbstring: + . Fixed bug #72405 (mb_ereg_replace - mbc_to_code (oniguruma) - + oob read access). (Laruence) + . Fixed bug #72399 (Use-After-Free in MBString (search_re)). (Laruence) + +- mcrypt: + . Fixed bug #72551, bug #72552 (In correct casting from size_t to int lead to + heap overflow in mdecrypt_generic). (Stas) + +- PDO_pgsql: + . Fixed bug #72570 (Segmentation fault when binding parameters on a query + without placeholders). (Matteo) + +- PCRE: + . Fixed bug #72476 (Memleak in jit_stack). (Laruence) + . Fixed bug #72463 (mail fails with invalid argument). (Anatol) + +- Readline: + . Fixed bug #72538 (readline_redisplay crashes php). (Laruence) + +- Standard: + . Fixed bug #72505 (readfile() mangles files larger than 2G). (Cschneid) + . Fixed bug #72306 (Heap overflow through proc_open and $env parameter). + (Laruence) + +- Session: + . Fixed bug #72531 (ps_files_cleanup_dir Buffer overflow). (Laruence) + . Fixed bug #72562 (Use After Free in unserialize() with Unexpected Session + Deserialization). (Stas) + +- SNMP: + . Fixed bug #72479 (Use After Free Vulnerability in SNMP with GC and + unserialize()). (Stas) + +- Streams: + . Fixed bug #72439 (Stream socket with remote address leads to a segmentation + fault). (Laruence) + +- XMLRPC: + . Fixed bug #72606 (heap-buffer-overflow (write) simplestring_addn + simplestring.c). (Stas) + +- Zip: + . Fixed bug #72520 (Stack-based buffer overflow vulnerability in + php_stream_zip_opener). (Stas) + +23 Jun 2016 PHP 7.0.8 + +- Core: + . Fixed bug #72218 (If host name cannot be resolved then PHP 7 crashes). + (Esminis at esminis dot lt) + . Fixed bug #72221 (segfault, past-the-end access). (Lauri Kenttä) + . Fixed bug #72268 (Integer Overflow in nl2br()). (Stas) + . Fixed bug #72275 (Integer Overflow in json_encode()/json_decode()/ + json_utf8_to_utf16()). (Stas) + . Fixed bug #72400 (Integer Overflow in addcslashes/addslashes). (Stas) + . Fixed bug #72403 (Integer Overflow in Length of String-typed ZVAL). (Stas) + +- Date: + . Fixed bug #63740 (strtotime seems to use both sunday and monday as start of + week). (Derick) + +- FPM: + . Fixed bug #72308 (fastcgi_finish_request and logging environment + variables). (Laruence) + +- GD: + . Fixed bug #66387 (Stack overflow with imagefilltoborder). (CVE-2015-8874) + (cmb) + . Fixed bug #72298 (pass2_no_dither out-of-bounds access). (Stas) + . Fixed bug #72337 (invalid dimensions can lead to crash). (Pierre) + . Fixed bug #72339 (Integer Overflow in _gd2GetHeader() resulting in heap + overflow). (CVE-2016-5766) (Pierre) + . Fixed bug #72407 (NULL Pointer Dereference at _gdScaleVert). (Stas) + . Fixed bug #72446 (Integer Overflow in gdImagePaletteToTrueColor() resulting + in heap overflow). (CVE-2016-5767) (Pierre) + +- Intl: + . Fixed bug #70484 (selectordinal doesn't work with named parameters). + (Anatol) + +- mbstring: + . Fixed bug #72402 (_php_mb_regex_ereg_replace_exec - double free). + (CVE-2016-5768) (Stas) + +- mcrypt: + . Fixed bug #72455 (Heap Overflow due to integer overflows). (CVE-2016-5769) + (Stas) + +- OpenSSL: + . Fixed bug #72140 (segfault after calling ERR_free_strings()). + (Jakub Zelenka) + +- PCRE: + . Fixed bug #72143 (preg_replace uses int instead of size_t). (Joe) + +- PDO_pgsql: + . Fixed bug #71573 (Segfault (core dumped) if paramno beyond bound). + (Laruence) + . Fixed bug #72294 (Segmentation fault/invalid pointer in connection + with pgsql_stmt_dtor). (Anatol) + +- Phar: + . Fixed bug #72321 (invalid free in phar_extract_file()). + (hji at dyntopia dot com) + +- Phpdbg: + . Fixed bug #72284 (phpdbg fatal errors with coverage). (Bob) + +- Postgres: + . Fixed bug #72195 (pg_pconnect/pg_connect cause use-after-free). (Laruence) + . Fixed bug #72197 (pg_lo_create arbitrary read). (Anatol) + +- Standard: + . Fixed bug #72369 (array_merge() produces references in PHP7). (Dmitry) + . Fixed bug #72300 (ignore_user_abort(false) has no effect). (Laruence) + . Fixed bug #72229 (Wrong reference when serialize/unserialize an object). + (Laruence) + . Fixed bug #72193 (dns_get_record returns array containing elements of + type 'unknown'). (Laruence) + . Fixed bug #72017 (range() with float step produces unexpected result). + (Thomas Punt) + +- WDDX: + . Fixed bug #72340 (Double Free Courruption in wddx_deserialize). + (CVE-2016-5772) (Stas) + +- XML: + . Fixed bug #72206 (xml_parser_create/xml_parser_free leaks mem). (Joe) + +- XMLRPC: + . Fixed bug #72155 (use-after-free caused by get_zval_xmlrpc_type). + (Joe, Laruence) + +- Zip: + . Fixed ug #72258 (ZipArchive converts filenames to unrecoverable form). + (Anatol) + . Fixed bug #72434 (ZipArchive class Use After Free Vulnerability in PHP's GC + algorithm and unserialize). (CVE-2016-5773) (Dmitry) + +26 May 2016 PHP 7.0.7 + +- Core: + . Fixed bug #72162 (use-after-free - error_reporting). (Laruence) + . Add compiler option to disable special case function calls. (Joe) + . Fixed bug #72101 (crash on complex code). (Dmitry) + . Fixed bug #72100 (implode() inserts garbage into resulting string when + joins very big integer). (Mikhail Galanin) + . Fixed bug #72057 (PHP Hangs when using custom error handler and typehint). + (Nikita Nefedov) + . Fixed bug #72038 (Function calls with values to a by-ref parameter don't + always throw a notice). (Bob) + . Fixed bug #71737 (Memory leak in closure with parameter named $this). + (Nikita) + . Fixed bug #72059 (?? is not allowed on constant expressions). (Bob, Marcio) + . Fixed bug #72159 (Imported Class Overrides Local Class Name). (Nikita) + +- Curl: + . Fixed bug #68658 (Define CURLE_SSL_CACERT_BADFILE). (Pierrick) + +- DBA: + . Fixed bug #72157 (use-after-free caused by dba_open). (Shm, Laruence) + +- GD: + . Fixed bug #72227 (imagescale out-of-bounds read). (Stas) + +- Intl: + . Fixed bug #64524 (Add intl.use_exceptions to php.ini-*). (Anatol) + . Fixed bug #72241 (get_icu_value_internal out-of-bounds read). (Stas) + +- JSON: + . Fixed bug #72069 (Behavior \JsonSerializable different from json_encode). + (Laruence) + +- Mbstring: + . Fixed bug #72164 (Null Pointer Dereference - mb_ereg_replace). (Laruence) + +- OCI8: + . Fixed bug #71600 (oci_fetch_all segfaults when selecting more than eight + columns). (Tian Yang) + +- Opcache: + . Fixed bug #72014 (Including a file with anonymous classes multiple times + leads to fatal error). (Laruence) + +- OpenSSL: + . Fixed bug #72165 (Null pointer dereference - openssl_csr_new). (Anatol) + +- PCNTL: + . Fixed bug #72154 (pcntl_wait/pcntl_waitpid array internal structure + overwrite). (Laruence) + +- POSIX: + . Fixed bug #72133 (php_posix_group_to_array crashes if gr_passwd is NULL). + (esminis at esminis dot lt) + +- Postgres: + . Fixed bug #72028 (pg_query_params(): NULL converts to empty string). + (Laruence) + . Fixed bug #71062 (pg_convert() doesn't accept ISO 8601 for datatype + timestamp). (denver at timothy dot io) + . Fixed bug #72151 (mysqli_fetch_object changed behaviour). (Anatol) + +- Reflection: + . Fixed bug #72174 (ReflectionProperty#getValue() causes __isset call). + (Nikita) + +- Session: + . Fixed bug #71972 (Cyclic references causing session_start(): Failed to + decode session object). (Laruence) + +- COM: + . Fixed bug #77177 (Serializing or unserializing COM objects crashes). (cmb) + +- Sockets: + . Added socket_export_stream() function for getting a stream compatible + resource from a socket resource. (Chris Wright, Bob) + +- SPL: + . Fixed bug #72051 (The reference in CallbackFilterIterator doesn't work as + expected). (Laruence) + +- SQLite3: + . Fixed bug #68849 (bindValue is not using the right data type). (Anatol) + +- Standard: + . Fixed bug #72075 (Referencing socket resources breaks stream_select). + (Laruence) + . Fixed bug #72031 (array_column() against an array of objects discards all + values matching null). (Nikita) + +28 Apr 2016 PHP 7.0.6 + +- Core: + . Fixed bug #71930 (_zval_dtor_func: Assertion `(arr)->gc.refcount <= 1' + failed). (Laruence) + . Fixed bug #71922 (Crash on assert(new class{})). (Nikita) + . Fixed bug #71914 (Reference is lost in "switch"). (Laruence) + . Fixed bug #71871 (Interfaces allow final and abstract functions). (Nikita) + . Fixed Bug #71859 (zend_objects_store_call_destructors operates on realloced + memory, crashing). (Laruence) + . Fixed bug #71841 (EG(error_zval) is not handled well). (Laruence) + . Fixed bug #71750 (Multiple Heap Overflows in php_raw_url_encode/ + php_url_encode). (Stas) + . Fixed bug #71731 (Null coalescing operator and ArrayAccess). (Nikita) + . Fixed bug #71609 (Segmentation fault on ZTS with gethostbyname). (krakjoe) + . Fixed bug #71414 (Inheritance, traits and interfaces). (krakjoe) + . Fixed bug #71359 (Null coalescing operator and magic). (krakjoe) + . Fixed bug #71334 (Cannot access array keys while uksort()). (Nikita) + . Fixed bug #69659 (ArrayAccess, isset() and the offsetExists method). + (Nikita) + . Fixed bug #69537 (__debugInfo with empty string for key gives error). + (krakjoe) + . Fixed bug #62059 (ArrayObject and isset are not friends). (Nikita) + . Fixed bug #71980 (Decorated/Nested Generator is Uncloseable in Finally). + (Nikita) + +- BCmath: + . Fixed bug #72093 (bcpowmod accepts negative scale and corrupts + _one_ definition). (Stas) + +- Curl: + . Fixed bug #71831 (CURLOPT_NOPROXY applied as long instead of string). + (Michael Sierks) + +- Date: + . Fixed bug #71889 (DateInterval::format Segmentation fault). (Thomas Punt) + +- EXIF: + . Fixed bug #72094 (Out of bounds heap read access in exif header processing). (Stas) + +- GD: + . Fixed bug #71912 (libgd: signedness vulnerability). (CVE-2016-3074) (Stas) + +- Intl: + . Fixed bug #71516 (IntlDateFormatter looses locale if pattern is set via + constructor). (Anatol) + . Fixed bug #70455 (Missing constant: IntlChar::NO_NUMERIC_VALUE). (Anatol) + . Fixed bug #70451, #70452 (Inconsistencies in return values of IntlChar + methods). (Daniel Persson) + . Fixed bug #68893 (Stackoverflow in datefmt_create). (Anatol) + . Fixed bug #66289 (Locale::lookup incorrectly returns en or en_US if locale + is empty). (Anatol) + . Fixed bug #70484 (selectordinal doesn't work with named parameters). + (Anatol) + . Fixed bug #72061 (Out-of-bounds reads in zif_grapheme_stripos with negative + offset). (Stas) + +- ODBC: + . Fixed bug #63171 (Script hangs after max_execution_time). (Remi) + +- Opcache: + . Fixed bug #71843 (null ptr deref ZEND_RETURN_SPEC_CONST_HANDLER). + (Laruence) + +- PDO: + . Fixed bug #52098 (Own PDOStatement implementation ignore __call()). + (Daniel kalaspuffar, Julien) + . Fixed bug #71447 (Quotes inside comments not properly handled). (Matteo) + +- PDO_DBlib: + . Fixed bug #71943 (dblib_handle_quoter needs to allocate an extra byte). + (Adam Baratz) + . Add DBLIB-specific attributes for controlling timeouts. (Adam Baratz) + +- PDO_pgsql: + . Fixed bug #62498 (pdo_pgsql inefficient when getColumnMeta() is used). + (Joseph Bylund) + +- Postgres: + . Fixed bug #71820 (pg_fetch_object binds parameters before call + constructor). (Anatol) + . Fixed bug #71998 (Function pg_insert does not insert when column + type = inet). (Anatol) + +- SOAP: + . Fixed bug #71986 (Nested foreach assign-by-reference creates broken + variables). (Laruence) + +- SPL: + . Fixed bug #71838 (Deserializing serialized SPLObjectStorage-Object can't + access properties in PHP). (Nikita) + . Fixed bug #71735 (Double-free in SplDoublyLinkedList::offsetSet). (Stas) + . Fixed bug #67582 (Cloned SplObjectStorage with overwritten getHash fails + offsetExists()). (Nikita) + . Fixed bug #52339 (SPL autoloader breaks class_exists()). (Nikita) + +- Standard: + . Fixed bug #71995 (Returning the same var twice from __sleep() produces + broken serialized data). (Laruence) + . Fixed bug #71940 (Unserialize crushes on restore object reference). + (Laruence) + . Fixed bug #71969 (str_replace returns an incorrect resulting array after + a foreach by reference). (Laruence) + . Fixed bug #71891 (header_register_callback() and + register_shutdown_function()). (Laruence) + . Fixed bug #71884 (Null pointer deref (segfault) in + stream_context_get_default). (Laruence) + . Fixed bug #71840 (Unserialize accepts wrongly data). (Ryat, Laruence) + . Fixed bug #71837 (Wrong arrays behaviour). (Laruence) + . Fixed bug #71827 (substr_replace bug, string length). (krakjoe) + . Fixed bug #67512 (php_crypt() crashes if crypt_r() does not exist or + _REENTRANT is not defined). (Nikita) + . Fixed bug #72116 (array_fill optimization breaks implementation). (Bob) + +- XML: + . Fixed bug #72099 (xml_parse_into_struct segmentation fault). (Stas) + +- Zip: + . Fixed bug #71923 (integer overflow in ZipArchive::getFrom*). + (CVE-2016-3078) (Stas) + +31 Mar 2016 PHP 7.0.5 + +- Core: + . Huge pages disabled by default. (Rasmus) + . Added ability to enable huge pages in Zend Memory Manager through + the environment variable USE_ZEND_ALLOC_HUGE_PAGES=1. (Dmitry) + . Fixed bug #71756 (Call-by-reference widens scope to uninvolved functions + when used in switch). (Laruence) + . Fixed bug #71729 (Possible crash in zend_bin_strtod, zend_oct_strtod, + zend_hex_strtod). (Laruence) + . Fixed bug #71695 (Global variables are reserved before execution). + (Laruence) + . Fixed bug #71629 (Out-of-bounds access in php_url_decode in context + php_stream_url_wrap_rfc2397). (mt at debian dot org) + . Fixed bug #71622 (Strings used in pass-as-reference cannot be used to + invoke C::$callable()). (Bob) + . Fixed bug #71596 (Segmentation fault on ZTS with date function + (setlocale)). (Anatol) + . Fixed bug #71535 (Integer overflow in zend_mm_alloc_heap()). (Dmitry) + . Fixed bug #71470 (Leaked 1 hashtable iterators). (Nikita) + . Fixed bug #71575 (ISO C does not allow extra ‘;’ outside of a function). + (asgrim) + . Fixed bug #71724 (yield from does not count EOLs). (Nikita) + . Fixed bug #71767 (ReflectionMethod::getDocComment returns the wrong + comment). (Grigorii Sokolik) + . Fixed bug #71806 (php_strip_whitespace() fails on some numerical values). + (Nikita) + . Fixed bug #71624 (`php -R` (PHP_MODE_PROCESS_STDIN) is broken). + (Sean DuBois) + +- CLI Server: + . Fixed bug #69953 (Support MKCALENDAR request method). (Christoph) + +- Curl: + . Fixed bug #71694 (Support constant CURLM_ADDED_ALREADY). (mpyw) + +- Date: + . Fixed bug #71635 (DatePeriod::getEndDate segfault). (Thomas Punt) + +- Fileinfo: + . Fixed bug #71527 (Buffer over-write in finfo_open with malformed magic + file). (CVE-2015-8865) (Anatol) + +- libxml: + . Fixed bug #71536 (Access Violation crashes php-cgi.exe). (Anatol) + +- mbstring: + . Fixed bug #71906 (AddressSanitizer: negative-size-param (-1) in + mbfl_strcut). (CVE-2016-4073) (Stas) + +- ODBC: + . Fixed bug #47803, #69526 (Executing prepared statements is succesfull only + for the first two statements). (einavitamar at gmail dot com, Anatol) + +- PCRE: + . Fixed bug #71659 (segmentation fault in pcre running twig tests). + (nish dot aravamudan at canonical dot com) + +- PDO_DBlib: + . Fixed bug #54648 (PDO::MSSQL forces format of datetime fields). + (steven dot lambeth at gmx dot de, Anatol) + +- Phar: + . Fixed bug #71625 (Crash in php7.dll with bad phar filename). (Anatol) + . Fixed bug #71317 (PharData fails to open specific file). (Jos Elstgeest) + . Fixed bug #71860 (Invalid memory write in phar on filename with \0 in + name). (CVE-2016-4072) (Stas) + +- phpdbg: + . Fixed crash when advancing (except step) inside an internal function. (Bob) + +- Session: + . Fixed bug #71683 (Null pointer dereference in zend_hash_str_find_bucket). + (Yasuo) + +- SNMP: + . Fixed bug #71704 (php_snmp_error() Format String Vulnerability). + (CVE-2016-4071) (andrew at jmpesp dot org) + +- SPL: + . Fixed bug #71617 (private properties lost when unserializing ArrayObject). + (Nikita) + +- Standard: + . Fixed bug #71660 (array_column behaves incorrectly after foreach by + reference). (Laruence) + . Fixed bug #71798 (Integer Overflow in php_raw_url_encode). (CVE-2016-4070) + (taoguangchen at icloud dot com, Stas) + +- Zip: + . Update bundled libzip to 1.1.2. (Remi, Anatol) + +03 Mar 2016 PHP 7.0.4 + +- Core: + . Fixed bug (Low probability segfault in zend_arena). (Laruence) + . Fixed bug #71441 (Typehinted Generator with return in try/finally crashes). + (Bob) + . Fixed bug #71442 (forward_static_call crash). (Laruence) + . Fixed bug #71443 (Segfault using built-in webserver with intl using + symfony). (Laruence) + . Fixed bug #71449 (An integer overflow bug in php_implode()). (Stas) + . Fixed bug #71450 (An integer overflow bug in php_str_to_str_ex()). (Stas) + . Fixed bug #71474 (Crash because of VM stack corruption on Magento2). + (Dmitry) + . Fixed bug #71485 (Return typehint on internal func causes Fatal error + when it throws exception). (Laruence) + . Fixed bug #71529 (Variable references on array elements don't work when + using count). (Nikita) + . Fixed bug #71601 (finally block not executed after yield from). (Bob) + . Fixed bug #71637 (Multiple Heap Overflow due to integer overflows in + xml/filter_url/addcslashes). (CVE-2016-4344, CVE-2016-4345, CVE-2016-4346) + (Stas) + +- CLI server: + . Fixed bug #71559 (Built-in HTTP server, we can download file in web by bug). + (Johannes, Anatol) + +- CURL: + . Fixed bug #71523 (Copied handle with new option CURLOPT_HTTPHEADER crashes + while curl_multi_exec). (Laruence) + . Fixed memory leak in curl_getinfo(). (Leigh) + +- Date: + . Fixed bug #71525 (Calls to date_modify will mutate timelib_rel_time, + causing date_date_set issues). (Sean DuBois) + +- Fileinfo: + . Fixed bug #71434 (finfo throws notice for specific python file). (Laruence) + +- FPM: + . Fixed bug #62172 (FPM not working with Apache httpd 2.4 balancer/fcgi + setup). (Matt Haught, Remi) + . Fixed bug #71269 (php-fpm dumped core). (Mickaël) + +- Opcache: + . Fixed bug #71584 (Possible use-after-free of ZCG(cwd) in Zend Opcache). + (Yussuf Khalil) + +- PCRE: + . Fixed bug #71537 (PCRE segfault from Opcache). (Laruence) + +- phpdbg: + . Fixed inherited functions from unspecified files being included in + phpdbg_get_executable(). (Bob) + +- SOAP: + . Fixed bug #71610 (Type Confusion Vulnerability - SOAP / + make_http_soap_request()). (CVE-2016-3185) (Stas) + +- Standard: + . Fixed bug #71603 (compact() maintains references in php7). (Laruence) + . Fixed bug #70720 (strip_tags improper php code parsing). (Julien) + +- XMLRPC: + . Fixed bug #71501 (xmlrpc_encode_request ignores encoding option). (Hieu Le) + +- Zip: + . Fixed bug #71561 (NULL pointer dereference in Zip::ExtractTo). (Laruence) + +04 Feb 2016 PHP 7.0.3 + +- Core: + . Added support for new HTTP 451 code. (Julien) + . Fixed bug #71039 (exec functions ignore length but look for NULL + termination). (Anatol) + . Fixed bug #71089 (No check to duplicate zend_extension). (Remi) + . Fixed bug #71201 (round() segfault on 64-bit builds). (Anatol) + . Fixed bug #71221 (Null pointer deref (segfault) in get_defined_vars via + ob_start). (hugh at allthethings dot co dot nz) + . Fixed bug #71248 (Wrong interface is enforced). (Dmitry) + . Fixed bug #71273 (A wrong ext directory setup in php.ini leads to crash). + (Anatol) + . Fixed Bug #71275 (Bad method called on cloning an object having a trait). + (Bob) + . Fixed bug #71297 (Memory leak with consecutive yield from). (Bob) + . Fixed bug #71300 (Segfault in zend_fetch_string_offset). (Laruence) + . Fixed bug #71314 (var_export(INF) prints INF.0). (Andrea) + . Fixed bug #71323 (Output of stream_get_meta_data can be falsified by its + input). (Leo Gaspard) + . Fixed bug #71336 (Wrong is_ref on properties as exposed via + get_object_vars()). (Laruence) + . Fixed bug #71459 (Integer overflow in iptcembed()). (Stas) + +- Apache2handler: + . Fix >2G Content-Length headers in apache2handler. (Adam Harvey) + +- CURL: + . Fixed bug #71227 (Can't compile php_curl statically). (Anatol) + . Fixed bug #71225 (curl_setopt() fails to set CURLOPT_POSTFIELDS with + reference to CURLFile). (Laruence) + +- GD: + . Improved fix for bug #70976. (Remi) + +- Interbase: + . Fixed Bug #71305 (Crash when optional resource is omitted). + (Laruence, Anatol) + +- LDAP: + . Fixed bug #71249 (ldap_mod_replace/ldap_mod_add store value as string + "Array"). (Laruence) + +- mbstring: + . Fixed bug #71397 (mb_send_mail segmentation fault). (Andrea, Yasuo) + +- OpenSSL: + . Fixed bug #71475 (openssl_seal() uninitialized memory usage). (Stas) + +- PCRE: + . Upgraded pcrelib to 8.38. (CVE-2015-8383, CVE-2015-8386, CVE-2015-8387, + CVE-2015-8389, CVE-2015-8390, CVE-2015-8391, CVE-2015-8393, CVE-2015-8394) + +- Phar: + . Fixed bug #71354 (Heap corruption in tar/zip/phar parser). (CVE-2016-4342) + (Stas) + . Fixed bug #71331 (Uninitialized pointer in phar_make_dirstream()). + (CVE-2016-4343) (Stas) + . Fixed bug #71391 (NULL Pointer Dereference in phar_tar_setupmetadata()). + (Stas) + . Fixed bug #71488 (Stack overflow when decompressing tar archives). + (CVE-2016-2554) (Stas) + +- SOAP: + . Fixed bug #70979 (crash with bad soap request). (Anatol) + +- SPL: + . Fixed bug #71204 (segfault if clean spl_autoload_funcs while autoloading). + (Laruence) + . Fixed bug #71202 (Autoload function registered by another not activated + immediately). (Laruence) + . Fixed bug #71311 (Use-after-free vulnerability in SPL(ArrayObject, + unserialize)). (Sean Heelan) + . Fixed bug #71313 (Use-after-free vulnerability in SPL(SplObjectStorage, + unserialize)). (Sean Heelan) + +- Standard: + . Fixed bug #71287 (Error message contains hexadecimal instead of decimal + number). (Laruence) + . Fixed bug #71264 (file_put_contents() returns unexpected value when + filesystem runs full). (Laruence) + . Fixed bug #71245 (file_get_contents() ignores "header" context option if + it's a reference). (Laruence) + . Fixed bug #71220 (Null pointer deref (segfault) in compact via ob_start). + (hugh at allthethings dot co dot nz) + . Fixed bug #71190 (substr_replace converts integers in original $search + array to strings). (Laruence) + . Fixed bug #71188 (str_replace converts integers in original $search array + to strings). (Laruence) + . Fixed bug #71132, #71197 (range() segfaults). (Thomas Punt) + +- WDDX: + . Fixed bug #71335 (Type Confusion in WDDX Packet Deserialization). (Stas) + +07 Jan 2016 PHP 7.0.2 + +- Core: + . Fixed bug #71165 (-DGC_BENCH=1 doesn't work on PHP7). + (y dot uchiyama dot 1015 at gmail dot com) + . Fixed bug #71163 (Segmentation Fault: cleanup_unfinished_calls). (Laruence) + . Fixed bug #71109 (ZEND_MOD_CONFLICTS("xdebug") doesn't work). (Laruence) + . Fixed bug #71092 (Segmentation fault with return type hinting). (Laruence) + . Fixed bug memleak in header_register_callback. (Laruence) + . Fixed bug #71067 (Local object in class method stays in memory for each + call). (Laruence) + . Fixed bug #66909 (configure fails utf8_to_mutf7 test). (Michael Orlitzky) + . Fixed bug #70781 (Extension tests fail on dynamic ext dependency). + (Francois Laupretre) + . Fixed bug #71089 (No check to duplicate zend_extension). (Remi) + . Fixed bug #71086 (Invalid numeric literal parse error within + highlight_string() function). (Nikita) + . Fixed bug #71154 (Incorrect HT iterator invalidation causes iterator reuse). + (Nikita) + . Fixed bug #52355 (Negating zero does not produce negative zero). (Andrea) + . Fixed bug #66179 (var_export() exports float as integer). (Andrea) + . Fixed bug #70804 (Unary add on negative zero produces positive zero). + (Andrea) + +- CURL: + . Fixed bug #71144 (Sementation fault when using cURL with ZTS). + (Michael Maroszek, Laruence) + +- DBA: + . Fixed key leak with invalid resource. (Laruence) + +- Filter: + . Fixed bug #71063 (filter_input(INPUT_ENV, ..) does not work). (Reeze Xia) + +- FPM: + . Fixed bug #70755 (fpm_log.c memory leak and buffer overflow). (Stas) + +- FTP: + . Implemented FR #55651 (Option to ignore the returned FTP PASV address). + (abrender at elitehosts dot com) + +- GD: + . Fixed bug #70976 (Memory Read via gdImageRotateInterpolated Array Index + Out of Bounds). (CVE-2016-1903) (emmanuel dot law at gmail dot com) + +- Mbstring: + . Fixed bug #71066 (mb_send_mail: Program terminated with signal SIGSEGV, + Segmentation fault). (Laruence) + +- Opcache: + . Fixed bug #71127 (Define in auto_prepend_file is overwrite). (Laruence) + +- PCRE: + . Fixed bug #71178 (preg_replace with arrays creates [0] in replace array + if not already set). (Laruence) + +- Readline: + . Fixed bug #71094 (readline_completion_function corrupts static array on + second TAB). (Nikita) + +- Session: + . Fixed bug #71122 (Session GC may not remove obsolete session data). (Yasuo) + +- SPL: + . Fixed bug #71077 (ReflectionMethod for ArrayObject constructor returns + wrong number of parameters). (Laruence) + . Fixed bug #71153 (Performance Degradation in ArrayIterator with large + arrays). (Nikita) + +- Standard: + . Fixed bug #71270 (Heap BufferOver Flow in escapeshell functions). + (CVE-2016-1904) (emmanuel dot law at gmail dot com) + +- WDDX: + . Fixed bug #70661 (Use After Free Vulnerability in WDDX Packet + Deserialization). (taoguangchen at icloud dot com) + . Fixed bug #70741 (Session WDDX Packet Deserialization Type Confusion + Vulnerability). (taoguangchen at icloud dot com) + +- XMLRPC: + . Fixed bug #70728 (Type Confusion Vulnerability in PHP_to_XMLRPC_worker). + (Julien) + +17 Dec 2015, PHP 7.0.1 + +- Core: + . Fixed bug #71105 (Format String Vulnerability in Class Name Error Message). + (CVE-2015-8617) (andrew at jmpesp dot org) + . Fixed bug #70831 (Compile fails on system with 160 CPUs). (Daniel Axtens) + . Fixed bug #71006 (symbol referencing errors on Sparc/Solaris). (Dmitry) + . Fixed bug #70997 (When using parentClass:: instead of parent::, static + context changed). (Dmitry) + . Fixed bug #70970 (Segfault when combining error handler with output + buffering). (Laruence) + . Fixed bug #70967 (Weird error handling for __toString when Error is + thrown). (Laruence) + . Fixed bug #70958 (Invalid opcode while using ::class as trait method + paramater default value). (Laruence) + . Fixed bug #70944 (try{ } finally{} can create infinite chains of + exceptions). (Laruence) + . Fixed bug #70931 (Two errors messages are in conflict). (dams, Laruence) + . Fixed bug #70904 (yield from incorrectly marks valid generator as + finished). (Bob) + . Fixed bug #70899 (buildconf failure in extensions). (Bob, Reeze) + . Fixed bug #61751 (SAPI build problem on AIX: Undefined symbol: + php_register_internal_extensions). (Lior Kaplan) + . Fixed \int (or generally every scalar type name with leading backslash) + to not be accepted as type name. (Bob) + . Fixed exception not being thrown immediately into a generator yielding + from an array. (Bob) + . Fixed bug #70987 (static::class within Closure::call() causes segfault). + (Andrea) + . Fixed bug #71013 (Incorrect exception handler with yield from). (Bob) + . Fixed double free in error condition of format printer. (Bob) + +- CLI server: + . Fixed bug #71005 (Segfault in php_cli_server_dispatch_router()). (Adam) + +- Intl: + . Fixed bug #71020 (Use after free in Collator::sortWithSortKeys). + (CVE-2015-8616) (emmanuel dot law at gmail dot com, Laruence) + +- Mysqlnd: + . Fixed bug #68077 (LOAD DATA LOCAL INFILE / open_basedir restriction). + (Laruence) + . Fixed bug #68344 (MySQLi does not provide way to disable peer certificate + validation) by introducing MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT + connection flag. (Andrey) + +- OCI8: + . Fixed LOB implementation size_t/zend_long mismatch reported by gcov. + (Senthil) + +- Opcache: + . Fixed bug #71024 (Unable to use PHP 7.0 x64 side-by-side with PHP 5.6 x32 + on the same server). (Anatol) + . Fixed bug #70991 (zend_file_cache.c:710: error: array type has incomplete + element type). (Laruence) + . Fixed bug #70977 (Segmentation fault with opcache.huge_code_pages=1). + (Laruence) + +- PDO_Firebird: + . Fixed bug #60052 (Integer returned as a 64bit integer on X64_86). (Mariuz) + +- Phpdbg: + . Fixed stderr being written to stdout. (Bob) + +- Reflection: + . Fixed bug #71018 (ReflectionProperty::setValue() behavior changed). + (Laruence) + . Fixed bug #70982 (setStaticPropertyValue behaviors inconsistently with + 5.6). (Laruence) + +- Soap: + . Fixed bug #70993 (Array key references break argument processing). + (Laruence) + +- SPL: + . Fixed bug #71028 (Undefined index with ArrayIterator). (Laruence) + +- SQLite3: + . Fixed bug #71049 (SQLite3Stmt::execute() releases bound parameter instead + of internal buffer). (Laruence) + +- Standard: + . Fixed bug #70999 (php_random_bytes: called object is not a function). + (Scott) + . Fixed bug #70960 (ReflectionFunction for array_unique returns wrong number + of parameters). (Laruence) + +- Streams/Socket: + . Add IPV6_V6ONLY constant / make it usable in stream contexts. (Bob) + +03 Dec 2015, PHP 7.0.0 + +- Core: + . Fixed bug #70947 (INI parser segfault with INI_SCANNER_TYPED). (Laruence) + . Fixed bug #70914 (zend_throw_or_error() format string vulnerability). + (Taoguang Chen) + . Fixed bug #70912 (Null ptr dereference instantiating class with invalid + array property). (Laruence) + . Fixed bug #70895, #70898 (null ptr deref and segfault with crafted calable). + (Anatol, Laruence) + . Fixed bug #70249 (Segmentation fault while running PHPUnit tests on + phpBB 3.2-dev). (Laruence) + . Fixed bug #70805 (Segmentation faults whilst running Drupal 8 test suite). + (Dmitry, Laruence) + . Fixed bug #70842 (Persistent Stream Segmentation Fault). (Caleb Champlin) + . Fixed bug #70862 (Several functions do not check return code of + php_stream_copy_to_mem()). (Anatol) + . Fixed bug #70863 (Incorect logic to increment_function for proxy objects). + (Anatol) + . Fixed bug #70323 (Regression in zend_fetch_debug_backtrace() can cause + segfaults). (Aharvey, Laruence) + . Fixed bug #70873 (Regression on private static properties access). + (Laruence) + . Fixed bug #70748 (Segfault in ini_lex () at Zend/zend_ini_scanner.l). + (Laruence) + . Fixed bug #70689 (Exception handler does not work as expected). (Laruence) + . Fixed bug #70430 (Stack buffer overflow in zend_language_parser()). (Nikita) + . Fixed bug #70782 (null ptr deref and segfault (zend_get_class_fetch_type)). + (Nikita) + . Fixed bug #70785 (Infinite loop due to exception during identical + comparison). (Laruence) + . Fixed bug #70630 (Closure::call/bind() crash with ReflectionFunction-> + getClosure()). (Dmitry, Bob) + . Fixed bug #70662 (Duplicate array key via undefined index error handler). + (Nikita) + . Fixed bug #70681 (Segfault when binding $this of internal instance method + to null). (Nikita) + . Fixed bug #70685 (Segfault for getClosure() internal method rebind with + invalid $this). (Nikita) + . Added zend_internal_function.reserved[] fields. (Dmitry) + . Fixed bug #70557 (Memleak on return type verifying failed). (Laruence) + . Fixed bug #70555 (fun_get_arg() on unsetted vars return UNKNOW). (Laruence) + . Fixed bug #70548 (Redundant information printed in case of uncaught engine + exception). (Laruence) + . Fixed bug #70547 (unsetting function variables corrupts backtrace). + (Laruence) + . Fixed bug #70528 (assert() with instanceof adds apostrophes around class + name). (Laruence) + . Fixed bug #70481 (Memory leak in auto_global_copy_ctor() in ZTS build). + (Laruence) + . Fixed bug #70431 (Memory leak in php_ini.c). (Senthil, Laruence) + . Fixed bug #70478 (**= does no longer work). (Bob) + . Fixed bug #70398 (SIGSEGV, Segmentation fault zend_ast_destroy_ex). + (Dmitry, Bob, Laruence) + . Fixed bug #70332 (Wrong behavior while returning reference on object). + (Laruence, Dmitry) + . Fixed bug #70300 (Syntactical inconsistency with new group use syntax). + (marcio dot web2 at gmail dot com) + . Fixed bug #70321 (Magic getter breaks reference to array property). + (Laruence) + . Fixed bug #70187 (Notice: unserialize(): Unexpected end of serialized + data). (Dmitry) + . Fixed bug #70145 (From field incorrectly parsed from headers). (Anatol) + . Fixed bug #70370 (Bundled libtool.m4 doesn't handle FreeBSD 10 when + building extensions). (Adam) + . Fixed bug causing exception traces with anon classes to be truncated. (Bob) + . Fixed bug #70397 (Segmentation fault when using Closure::call and yield). + (Bob) + . Fixed bug #70299 (Memleak while assigning object offsetGet result). + (Laruence) + . Fixed bug #70288 (Apache crash related to ZEND_SEND_REF). (Laruence) + . Fixed bug #70262 (Accessing array crashes PHP 7.0beta3). + (Laruence, Dmitry) + . Fixed bug #70258 (Segfault if do_resize fails to allocated memory). + (Laruence) + . Fixed bug #70253 (segfault at _efree () in zend_alloc.c:1389). (Laruence) + . Fixed bug #70240 (Segfault when doing unset($var());). (Laruence) + . Fixed bug #70223 (Incrementing value returned by magic getter). (Laruence) + . Fixed bug #70215 (Segfault when __invoke is static). (Bob) + . Fixed bug #70207 (Finally is broken with opcache). (Laruence, Dmitry) + . Fixed bug #70173 (ZVAL_COPY_VALUE_EX broken for 32bit Solaris Sparc). + (Laruence, cmb) + . Fixed bug #69487 (SAPI may truncate POST data). (cmb) + . Fixed bug #70198 (Checking liveness does not work as expected). + (Shafreeck Sea, Anatol Belski) + . Fixed bug #70241,#70293 (Skipped assertions affect Generator returns). (Bob) + . Fixed bug #70239 (Creating a huge array doesn't result in exhausted, + but segfault). (Laruence, Anatol) + . Fixed "finally" issues. (Nikita, Dmitry) + . Fixed bug #70098 (Real memory usage doesn't decrease). (Dmitry) + . Fixed bug #70159 (__CLASS__ is lost in closures). (Julien) + . Fixed bug #70156 (Segfault in zend_find_alias_name). (Laruence) + . Fixed bug #70124 (null ptr deref / seg fault in ZEND_HANDLE_EXCEPTION). + (Laruence) + . Fixed bug #70117 (Unexpected return type error). (Laruence) + . Fixed bug #70106 (Inheritance by anonymous class). (Bob) + . Fixed bug #69674 (SIGSEGV array.c:953). (cmb) + . Fixed bug #70164 (__COMPILER_HALT_OFFSET__ under namespace is not defined). + (Bob) + . Fixed bug #70108 (sometimes empty $_SERVER['QUERY_STRING']). (Anatol) + . Fixed bug #70179 ($this refcount issue). (Bob) + . Fixed bug #69896 ('asm' operand has impossible constraints). (Anatol) + . Fixed bug #70183 (null pointer deref (segfault) in zend_eval_const_expr). + (Hugh Davenport) + . Fixed bug #70182 (Segfault in ZEND_ASSIGN_DIV_SPEC_CV_UNUSED_HANDLER). + (Hugh Davenport) + . Fixed bug #69793 (Remotely triggerable stack exhaustion via recursive + method calls). (Stas) + . Fixed bug #69892 (Different arrays compare indentical due to integer key + truncation). (Nikita) + . Fixed bug #70121 (unserialize() could lead to unexpected methods execution + / NULL pointer deref). (Stas) + . Fixed bug #70089 (segfault at ZEND_FETCH_DIM_W_SPEC_VAR_CONST_HANDLER ()). + (Laruence) + . Fixed bug #70057 (Build failure on 32-bit Mac OS X 10.6.8: recursive + inlining). (Laruence) + . Fixed bug #70012 (Exception lost with nested finally block). (Laruence) + . Fixed bug #69996 (Changing the property of a cloned object affects the + original). (Dmitry, Laruence) + . Fixed bug #70083 (Use after free with assign by ref to overloaded objects). + (Bob) + . Fixed bug #70006 (cli - function with default arg = STDOUT crash output). + (Laruence) + . Fixed bug #69521 (Segfault in gc_collect_cycles()). + (arjen at react dot com, Laruence) + . Improved zend_string API. (Francois Laupretre) + . Fixed bug #69955 (Segfault when trying to combine [] and assign-op on + ArrayAccess object). (Laruence) + . Fixed bug #69957 (Different ways of handling div/mod/intdiv). (Bob) + . Fixed bug #69900 (Too long timeout on pipes). (Anatol) + . Fixed bug #69872 (uninitialised value in strtr with array). (Laruence) + . Fixed bug #69868 (Invalid read of size 1 in zend_compile_short_circuiting). + (Laruence) + . Fixed bug #69849 (Broken output of apache_request_headers). (Kalle) + . Fixed bug #69840 (iconv_substr() doesn't work with UTF-16BE). (Kalle) + . Fixed bug #69823 (PHP 7.0.0alpha1 segmentation fault when exactly 33 + extensions are loaded). (Laruence) + . Fixed bug #69805 (null ptr deref and seg fault in zend_resolve_class_name). + (Laruence) + . Fixed bug #69802 (Reflection on Closure::__invoke borks type hint class + name). (Dmitry) + . Fixed bug #69761 (Serialization of anonymous classes should be prevented). + (Laruence) + . Fixed bug #69551 (parse_ini_file() and parse_ini_string() segmentation + fault). (Christoph M. Becker) + . Fixed bug #69781 (phpinfo() reports Professional Editions of Windows + 7/8/8.1/10 as "Business"). (Christian Wenz) + . Fixed bug #69835 (phpinfo() does not report many Windows SKUs). + (Christian Wenz) + . Fixed bug #69889 (Null coalesce operator doesn't work for string offsets). + (Nikita) + . Fixed bug #69891 (Unexpected array comparison result). (Nikita) + . Fixed bug #69892 (Different arrays compare indentical due to integer key + truncation). (Nikita) + . Fixed bug #69893 (Strict comparison between integer and empty string keys + crashes). (Nikita) + . Fixed bug #69767 (Default parameter value with wrong type segfaults). + (cmb, Laruence) + . Fixed bug #69756 (Fatal error: Nesting level too deep - recursive dependency + ? with ===). (Dmitry, Laruence) + . Fixed bug #69758 (Item added to array not being removed by array_pop/shift + ). (Laruence) + . Fixed bug #68475 (Add support for $callable() sytnax with 'Class::method'). + (Julien, Aaron Piotrowski) + . Fixed bug #69485 (Double free on zend_list_dtor). (Laruence) + . Fixed bug #69427 (Segfault on magic method __call of private method in + superclass). (Laruence) + . Improved __call() and __callStatic() magic method handling. Now they are + called in a stackless way using ZEND_CALL_TRAMPOLINE opcode, without + additional stack frame. (Laruence, Dmitry) + . Optimized strings concatenation. (Dmitry, Laruence) + . Fixed weird operators behavior. Division by zero now emits warning and + returns +/-INF, modulo by zero and intdid() throws an exception, shifts + by negative offset throw exceptions. Compile-time evaluation of division + by zero is disabled. (Dmitry, Andrea, Nikita) + . Fixed bug #69371 (Hash table collision leads to inaccessible array keys). + (Laruence) + . Fixed bug #68933 (Invalid read of size 8 in zend_std_read_property). + (Laruence, arjen at react dot com) + . Fixed bug #68252 (segfault in Zend/zend_hash.c in function + _zend_hash_del_el). (Laruence) + . Fixed bug #65598 (Closure executed via static autoload incorrectly marked as + static). (Nikita) + . Fixed bug #66811 (Cannot access static::class in lambda, writen outside of a + class). (Nikita) + . Fixed bug #69568 (call a private function in closure failed). (Nikita) + . Added PHP_INT_MIN constant. (Andrea) + . Added Closure::call() method. (Andrea) + . Fixed bug #67959 (Segfault when calling phpversion('spl')). (Florian) + . Implemented the RFC `Catchable "Call to a member function bar() on a + non-object"`. (Timm) + . Added options parameter for unserialize allowing to specify acceptable + classes (https://wiki.php.net/rfc/secure_unserialize). (Stas) + . Fixed bug #63734 (Garbage collector can free zvals that are still + referenced). (Dmitry) + . Removed ZEND_ACC_FINAL_CLASS, promoting ZEND_ACC_FINAL as final class + modifier. (Guilherme Blanco) + . is_long() & is_integer() is now an alias of is_int(). (Kalle) + . Implemented FR #55467 (phpinfo: PHP Variables with $ and single quotes). (Kalle) + . Added ?? operator. (Andrea) + . Added <=> operator. (Andrea) + . Added \u{xxxxx} Unicode Codepoint Escape Syntax. (Andrea) + . Fixed oversight where define() did not support arrays yet const syntax did. + (Andrea, Dmitry) + . Use "integer" and "float" instead of "long" and "double" in ZPP, type hint + and conversion error messages. (Andrea) + . Implemented FR #55428 (E_RECOVERABLE_ERROR when output buffering in output + buffering handler). (Kalle) + . Removed scoped calls of non-static methods from an incompatible $this + context. (Nikita) + . Removed support for #-style comments in ini files. (Nikita) + . Removed support for assigning the result of new by reference. (Nikita) + . Invalid octal literals in source code now produce compile errors, fixes + PHPSadness #31. (Andrea) + . Removed dl() function on fpm-fcgi. (Nikita) + . Removed support for hexadecimal numeric strings. (Nikita) + . Removed obsolete extensions and SAPIs. See the full list in UPGRADING. (Anatol) + . Added NULL byte protection to exec, system and passthru. (Yasuo) + . Added error_clear_last() function. (Reeze Xia) + . Fixed bug #68797 (Number 2.2250738585072012e-308 converted incorrectly). + (Anatol) + . Improved zend_qsort(using hybrid sorting algo) for better performance, + and also renamed zend_qsort to zend_sort. (Laruence) + . Added stable sorting algo zend_insert_sort. (Laruence) + . Improved zend_memnchr(using sunday algo) for better performance. (Laruence) + . Implemented the RFC `Scalar Type Decalarations v0.5`. (Anthony) + . Implemented the RFC `Group Use Declarations`. (Marcio) + . Implemented the RFC `Continue Output Buffering`. (Mike) + . Implemented the RFC `Constructor behaviour of internal classes`. (Dan, Dmitry) + . Implemented the RFC `Fix "foreach" behavior`. (Dmitry) + . Implemented the RFC `Generator Delegation`. (Bob) + . Implemented the RFC `Anonymous Class Support`. (Joe, Nikita, Dmitry) + . Implemented the RFC `Context Sensitive Lexer`. (Marcio Almada) + . Fixed bug #69511 (Off-by-one buffer overflow in php_sys_readlink). + (Jan Starke, Anatol) + +- CLI server: + . Fixed bug #68291 (404 on urls with '+'). (cmb) + . Fixed bug #66606 (Sets HTTP_CONTENT_TYPE but not CONTENT_TYPE). + (wusuopu, cmb) + . Fixed bug #70264 (CLI server directory traversal). (cmb) + . Fixed bug #69655 (php -S changes MKCALENDAR request method to MKCOL). (cmb) + . Fixed bug #64878 (304 responses return Content-Type header). (cmb) + . Refactor MIME type handling to use a hash table instead of linear search. + (Adam) + . Update the MIME type list from the one shipped by Apache HTTPD. (Adam) + . Added support for SEARCH WebDav method. (Mats Lindh) + +- COM: + . Fixed bug #69939 (Casting object to bool returns false). (Kalle) + +- Curl: + . Fixed bug #70330 (Segmentation Fault with multiple "curl_copy_handle"). + (Laruence) + . Fixed bug #70163 (curl_setopt_array() type confusion). (Laruence) + . Fixed bug #70065 (curl_getinfo() returns corrupted values). (Anatol) + . Fixed bug #69831 (Segmentation fault in curl_getinfo). (im dot denisenko at + yahoo dot com) + . Fixed bug #68937 (Segfault in curl_multi_exec). (Laruence) + . Removed support for unsafe file uploads. (Nikita) + +- Date: + . Fixed bug #70245 (strtotime does not emit warning when 2nd parameter is + object or string). (cmb) + . Fixed bug #70266 (DateInterval::__construct.interval_spec is not supposed to + be optional). (cmb) + . Fixed bug #70277 (new DateTimeZone($foo) is ignoring text after null byte). + (cmb) + . Fixed day_of_week function as it could sometimes return negative values + internally. (Derick) + . Removed $is_dst parameter from mktime() and gmmktime(). (Nikita) + . Removed date.timezone warning + (https://wiki.php.net/rfc/date.timezone_warning_removal). (Bob) + . Added "v" DateTime format modifier to get the 3-digit version of fraction + of seconds. (Mariano Iglesias) + . Implemented FR #69089 (Added DateTime::RFC3339_EXTENDED to output in + RFC3339 Extended format which includes fraction of seconds). (Mariano + Iglesias) + +- DBA: + . Fixed bug #62490 (dba_delete returns true on missing item (inifile)). (Mike) + . Fixed bug #68711 (useless comparisons). (bugreports at internot dot info) + +- DOM: + . Fixed bug #70558 ("Couldn't fetch" error in + DOMDocument::registerNodeClass()). (Laruence) + . Fixed bug #70001 (Assigning to DOMNode::textContent does additional entity + encoding). (cmb) + . Fixed bug #69846 (Segmenation fault (access violation) when iterating over + DOMNodeList). (Anatol Belski) + . Made DOMNode::textContent writeable. (Tjerk) + +- EXIF: + . Fixed bug #70385 (Buffer over-read in exif_read_data with TIFF IFD tag byte + value of 32 bytes). (Stas) + +- Fileinfo: + . Fixed bug #66242 (libmagic: don't assume char is signed). (ArdB) + +- Filter: + . New FILTER_VALIDATE_DOMAIN and better RFC conformance for FILTER_VALIDATE_URL. (Kevin Dunglas) + . Fixed bug #67167 (Wrong return value from FILTER_VALIDATE_BOOLEAN, + FILTER_NULL_ON_FAILURE). (levim) + +- FPM: + . Fixed bug #70538 ("php-fpm -i" crashes). (rainer dot jung at + kippdata dot de) + . Fixed bug #70279 (HTTP Authorization Header is sometimes passed to newer + reqeusts). (Laruence) + . Fixed bug #68945 (Unknown admin values segfault pools). (Laruence) + . Fixed bug #65933 (Cannot specify config lines longer than 1024 bytes). (Chris Wright) + . Implemented FR #67106 (Split main fpm config). (Elan Ruusamäe, Remi) + +- FTP: + . Fixed bug #69082 (FTPS support on Windows). (Anatol) + +- GD: + . Fixed bug #53156 (imagerectangle problem with point ordering). (cmb) + . Fixed bug #66387 (Stack overflow with imagefilltoborder). (CVE-2015-8874) + (cmb) + . Fixed bug #70102 (imagecreatefromwebm() shifts colors). (cmb) + . Fixed bug #66590 (imagewebp() doesn't pad to even length). (cmb) + . Fixed bug #66882 (imagerotate by -90 degrees truncates image by 1px). (cmb) + . Fixed bug #70064 (imagescale(..., IMG_BICUBIC) leaks memory). (cmb) + . Fixed bug #69024 (imagescale segfault with palette based image). (cmb) + . Fixed bug #53154 (Zero-height rectangle has whiskers). (cmb) + . Fixed bug #67447 (imagecrop() add a black line when cropping). (cmb) + . Fixed bug #68714 (copy 'n paste error). (cmb) + . Fixed bug #66339 (PHP segfaults in imagexbm). (cmb) + . Fixed bug #70047 (gd_info() doesn't report WebP support). (cmb) + . Replace libvpx with libwebp for bundled libgd. (cmb, Anatol) + . Fixed bug #61221 (imagegammacorrect function loses alpha channel). (cmb) + . Made fontFetch's path parser thread-safe. (Sara) + . Removed T1Lib support. (Kalle) + +- GMP: + . Fixed bug #70284 (Use after free vulnerability in unserialize() with GMP). + (stas) + +- hash: + . Fixed bug #70312 (HAVAL gives wrong hashes in specific cases). (letsgolee + at naver dot com) + +- IMAP: + . Fixed bug #70158 (Building with static imap fails). (cmb) + . Fixed bug #69998 (curl multi leaking memory). (Pierrick) + +- Intl: + . Fixed bug #70453 (IntlChar::foldCase() incorrect arguments and missing + constants). (cmb) + . Fixed bug #70454 (IntlChar::forDigit second parameter should be optional). + (cmb, colinodell) + . Removed deprecated aliases datefmt_set_timezone_id() and + IntlDateFormatter::setTimeZoneID(). (Nikita) + +- JSON: + . Fixed bug #62010 (json_decode produces invalid byte-sequences). + (Jakub Zelenka) + . Fixed bug #68546 (json_decode() Fatal error: Cannot access property + started with '\0'). (Jakub Zelenka) + . Replace non-free JSON parser with a parser from Jsond extension, fixes #63520 + (JSON extension includes a problematic license statement). (Jakub Zelenka) + . Fixed bug #68938 (json_decode() decodes empty string without error). + (jeremy at bat-country dot us) + +- LDAP: + . Fixed bug #47222 (Implement LDAP_OPT_DIAGNOSTIC_MESSAGE). (Andreas Heigl) + +- LiteSpeed: + . Updated LiteSpeed SAPI code from V5.5 to V6.6. (George Wang) + +- libxml: + . Fixed handling of big lines in error messages with libxml >= 2.9.0. + (Christoph M. Becker) + +- Mcrypt: + . Fixed bug #70625 (mcrypt_encrypt() won't return data when no IV was + specified under RC4). (Nikita) + . Fixed bug #69833 (mcrypt fd caching not working). (Anatol) + . Fixed possible read after end of buffer and use after free. (Dmitry) + . Removed mcrypt_generic_end() alias. (Nikita) + . Removed mcrypt_ecb(), mcrypt_cbc(), mcrypt_cfb(), mcrypt_ofb(). (Nikita) + +- Mysqli: + . Fixed bug #32490 (constructor of mysqli has wrong name). (cmb) + +- Mysqlnd: + . Fixed bug #70949 (SQL Result Sets With NULL Can Cause Fatal Memory Errors). + (Laruence) + . Fixed bug #70384 (mysqli_real_query():Unknown type 245 sent by the server). + (Andrey) + . Fixed bug #70456 (mysqlnd doesn't activate TCP keep-alive when connecting to + a server). (Sergei Turchanov) + . Fixed bug #70572 segfault in mysqlnd_connect. (Andrey, Remi) + . Fixed Bug #69796 (mysqli_stmt::fetch doesn't assign null values to + bound variables). (Laruence) + +- OCI8: + . Fixed memory leak with LOBs. (Senthil) + . Fixed bug #68298 (OCI int overflow) (Senthil). + . Corrected oci8 hash destructors to prevent segfaults, and a few other fixes. + (Cameron Porter) + +- ODBC: + . Fixed bug #69975 (PHP segfaults when accessing nvarchar(max) defined + columns). (CVE-2015-8879) (cmb) + +- Opcache: + . Fixed bug #70656 (require() statement broken after opcache_reset() or a + few hours of use). (Laruence) + . Fixed bug #70843 (Segmentation fault on MacOSX with + opcache.file_cache_only=1). (Laruence) + . Fixed bug #70724 (Undefined Symbols from opcache.so on Mac OS X 10.10). + (Laruence) + . Fixed compatibility with Windows 10 (see also bug #70652). (Anatol) + . Attmpt to fix "Unable to reattach to base address" problem. (Matt Ficken) + . Fixed bug #70423 (Warning Internal error: wrong size calculation). (Anatol) + . Fixed bug #70237 (Empty while and do-while segmentation fault with opcode + on CLI enabled). (Dmitry, Laruence) + . Fixed bug #70111 (Segfault when a function uses both an explicit return + type and an explicit cast). (Laruence) + . Fixed bug #70058 (Build fails when building for i386). (Laruence) + . Fixed bug #70022 (Crash with opcache using opcache.file_cache_only=1). + (Anatol) + . Removed opcache.load_comments configuration directive. Now doc comments + loading costs nothing and always enabled. (Dmitry) + . Fixed bug #69838 (Wrong size calculation for function table). (Anatol) + . Fixed bug #69688 (segfault with eval and opcache fast shutdown). + (Laruence) + . Added experimental (disabled by default) file based opcode cache. + (Dmitry, Laruence, Anatol) + . Fixed bug with try blocks being removed when extended_info opcode + generation is turned on. (Laruence) + . Fixed bug #68644 (strlen incorrect : mbstring + func_overload=2 +UTF-8 + + Opcache). (Laruence) + +- OpenSSL: + . Require at least OpenSSL version 0.9.8. (Jakub Zelenka) + . Fixed bug #68312 (Lookup for openssl.cnf causes a message box). (Anatol) + . Fixed bug #55259 (openssl extension does not get the DH parameters from + DH key resource). (Jakub Zelenka) + . Fixed bug #70395 (Missing ARG_INFO for openssl_seal()). (cmb) + . Fixed bug #60632 (openssl_seal fails with AES). (Jakub Zelenka) + . Implemented FR #70438 (Add IV parameter for openssl_seal and openssl_open) + (Jakub Zelenka) + . Fixed bug #70014 (openssl_random_pseudo_bytes() is not cryptographically + secure). (CVE-2015-8867) (Stas) + . Fixed bug #69882 (OpenSSL error "key values mismatch" after + openssl_pkcs12_read with extra cert). (Tomasz Sawicki) + . Added "alpn_protocols" SSL context option allowing encrypted client/server + streams to negotiate alternative protocols using the ALPN TLS extension when + built against OpenSSL 1.0.2 or newer. Negotiated protocol information is + accessible through stream_get_meta_data() output. + . Removed "CN_match" and "SNI_server_name" SSL context options. Use automatic + detection or the "peer_name" option instead. (Nikita) + +- Pcntl: + . Fixed bug #70386 (Can't compile on NetBSD because of missing WCONTINUED + and WIFCONTINUED). (Matteo) + . Fixed bug #60509 (pcntl_signal doesn't decrease ref-count of old handler + when setting SIG_DFL). (Julien) + . Implemented FR #68505 (Added wifcontinued and wcontinued). (xilon-jul) + . Added rusage support to pcntl_wait() and pcntl_waitpid(). (Anton Stepanenko, + Tony) + +- PCRE: + . Fixed bug #70232 (Incorrect bump-along behavior with \K and empty string + match). (cmb) + . Fixed bug #70345 (Multiple vulnerabilities related to PCRE functions). + (Anatol Belski) + . Fixed bug #70232 (Incorrect bump-along behavior with \K and empty string + match). (cmb) + . Fixed bug #53823 (preg_replace: * qualifier on unicode replace garbles the + string). (cmb) + . Fixed bug #69864 (Segfault in preg_replace_callback). (cmb, ab) + +- PDO: + . Fixed bug #70861 (Segmentation fault in pdo_parse_params() during Drupal 8 + test suite). (Anatol) + . Fixed bug #70389 (PDO constructor changes unrelated variables). (Laruence) + . Fixed bug #70272 (Segfault in pdo_mysql). (Laruence) + . Fixed bug #70221 (persistent sqlite connection + custom function + segfaults). (Laruence) + . Removed support for the /e (PREG_REPLACE_EVAL) modifier. (Nikita) + . Fixed bug #59450 (./configure fails with "Cannot find php_pdo_driver.h"). + (maxime dot besson at smile dot fr) + +- PDO_DBlib: + . Fixed bug #69757 (Segmentation fault on nextRowset). + (miracle at rpz dot name) + +- PDO_mysql: + . Fixed bug #68424 (Add new PDO mysql connection attr to control multi + statements option). (peter dot wolanin at acquia dot com) + +- PDO_OCI: + . Fixed bug #70308 (PDO::ATTR_PREFETCH is ignored). (Chris Jones) + +- PDO_pgsql: + . Fixed bug #69752 (PDOStatement::execute() leaks memory with DML + Statements when closeCuror() is u). (Philip Hofstetter) + . Removed PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT attribute in favor of + ATTR_EMULATE_PREPARES). (Nikita) + +- Phar: + . Fixed bug #69720 (Null pointer dereference in phar_get_fp_offset()). (Stas) + . FIxed bug #70433 (Uninitialized pointer in phar_make_dirstream when zip + entry filename is "/"). (Stas) + . Improved fix for bug #69441. (Anatol Belski) + . Fixed bug #70019 (Files extracted from archive may be placed outside of + destination directory). (Anatol Belski) + +- Phpdbg: + . Fixed bug #70614 (incorrect exit code in -rr mode with Exceptions). (Bob) + . Fixed bug #70532 (phpdbg must respect set_exception_handler). (Bob) + . Fixed bug #70531 (Run and quit mode (-qrr) should not fallback to + interactive mode). (Bob) + . Fixed bug #70533 (Help overview (-h) does not rpint anything under Windows). + (Anatol) + . Fixed bug #70449 (PHP won't compile on 10.4 and 10.5 because of missing + constants). (Bob) + . Fixed bug #70214 (FASYNC not defined, needs sys/file.h include). (Bob) + . Fixed bug #70138 (Segfault when displaying memory leaks). (Bob) + +- Reflection: + . Fixed bug #70650 (Wrong docblock assignment). (Marcio) + . Fixed bug #70674 (ReflectionFunction::getClosure() leaks memory when used + for internal functions). (Dmitry, Bob) + . Fixed bug causing bogus traces for ReflectionGenerator::getTrace(). (Bob) + . Fixed inheritance chain of Reflector interface. (Tjerk) + . Added ReflectionGenerator class. (Bob) + . Added reflection support for return types and type declarations. (Sara, + Matteo) + +- Session: + . Fixed bug #70876 (Segmentation fault when regenerating session id with + strict mode). (Laruence) + . Fixed bug #70529 (Session read causes "String is not zero-terminated" error). + (Yasuo) + . Fixed bug #70013 (Reference to $_SESSION is lost after a call to + session_regenerate_id()). (Yasuo) + . Fixed bug #69952 (Data integrity issues accessing superglobals by + reference). (Bob) + . Fixed bug #67694 (Regression in session_regenerate_id()). (Tjerk) + . Fixed bug #68941 (mod_files.sh is a bash-script). (bugzilla at ii.nl, Yasuo) + +- SOAP: + . Fixed bug #70940 (Segfault in soap / type_to_string). (Remi) + . Fixed bug #70900 (SoapClient systematic out of memory error). (Dmitry) + . Fixed bug #70875 (Segmentation fault if wsdl has no targetNamespace + attribute). (Matteo) + . Fixed bug #70715 (Segmentation fault inside soap client). (Laruence) + . Fixed bug #70709 (SOAP Client generates Segfault). (Laruence) + . Fixed bug #70388 (SOAP serialize_function_call() type confusion / RCE). + (Stas) + . Fixed bug #70081 (SoapClient info leak / null pointer dereference via + multiple type confusions). (Stas) + . Fixed bug #70079 (Segmentation fault after more than 100 SoapClient + calls). (Laruence) + . Fixed bug #70032 (make_http_soap_request calls + zend_hash_get_current_key_ex(,,,NULL). (Laruence) + . Fixed bug #68361 (Segmentation fault on SoapClient::__getTypes). (Laruence) + +- SPL: + . Fixed bug #70959 (ArrayObject unserialize does not restore protected + fields). (Laruence) + . Fixed bug #70853 (SplFixedArray throws exception when using ref variable + as index). (Laruence) + . Fixed bug #70868 (PCRE JIT and pattern reuse segfault). (Laruence) + . Fixed bug #70730 (Incorrect ArrayObject serialization if unset is called + in serialize()). (Laruence) + . Fixed bug #70573 (Cloning SplPriorityQueue leads to memory leaks). (Dmitry) + . Fixed bug #70303 (Incorrect constructor reflection for ArrayObject). (cmb) + . Fixed bug #70068 (Dangling pointer in the unserialization of ArrayObject + items). (sean.heelan) + . Fixed bug #70166 (Use After Free Vulnerability in unserialize() with + SPLArrayObject). (taoguangchen at icloud dot com) + . Fixed bug #70168 (Use After Free Vulnerability in unserialize() with + SplObjectStorage). (taoguangchen at icloud dot com) + . Fixed bug #70169 (Use After Free Vulnerability in unserialize() with + SplDoublyLinkedList). (taoguangchen at icloud dot com) + . Fixed bug #70053 (MutlitpleIterator array-keys incompatible change in + PHP 7). (Tjerk) + . Fixed bug #69970 (Use-after-free vulnerability in + spl_recursive_it_move_forward_ex()). (Laruence) + . Fixed bug #69845 (ArrayObject with ARRAY_AS_PROPS broken). (Dmitry) + . Changed ArrayIterator implementation using zend_hash_iterator_... API. + Allowed modification of iterated ArrayObject using the same behavior + as proposed in `Fix "foreach" behavior`. Removed "Array was modified + outside object and internal position is no longer valid" hack. (Dmitry) + . Implemented FR #67886 (SplPriorityQueue/SplHeap doesn't expose extractFlags + nor curruption state). (Julien) + . Fixed bug #66405 (RecursiveDirectoryIterator::CURRENT_AS_PATHNAME + breaks the RecursiveIterator). (Paul Garvin) + +- SQLite3: + . Fixed bug #70571 (Memory leak in sqlite3_do_callback). (Adam) + . Fixed bug #69972 (Use-after-free vulnerability in + sqlite3SafetyCheckSickOrOk()). (Laruence) + . Fixed bug #69897 (segfault when manually constructing SQLite3Result). + (Kalle) + . Fixed bug #68260 (SQLite3Result::fetchArray declares wrong + required_num_args). (Julien) + +- Standard: + . Fixed count on symbol tables. (Laruence) + . Fixed bug #70963 (Unserialize shows UNKNOWN in result). (Laruence) + . Fixed bug #70910 (extract() breaks variable references). (Laruence) + . Fixed bug #70808 (array_merge_recursive corrupts memory of unset items). + (Laruence) + . Fixed bug #70667 (strtr() causes invalid writes and a crashes). (Dmitry) + . Fixed bug #70668 (array_keys() doesn't respect references when $strict is + true). (Bob, Dmitry) + . Implemented the RFC `Random Functions Throwing Exceptions in PHP 7`. + (Sammy Kaye Powers, Anthony) + . Fixed bug #70487 (pack('x') produces an error). (Nikita) + . Fixed bug #70342 (changing configuration with ignore_user_abort(true) isn't + working). (Laruence) + . Fixed bug #70295 (Segmentation fault with setrawcookie). (Bob) + . Fixed bug #67131 (setcookie() conditional for empty values not met). (cmb) + . Fixed bug #70365 (Use-after-free vulnerability in unserialize() with + SplObjectStorage). (taoguangchen at icloud dot com) + . Fixed bug #70366 (Use-after-free vulnerability in unserialize() with + SplDoublyLinkedList). (taoguangchen at icloud dot com) + . Fixed bug #70250 (extract() turns array elements to references). + (Laruence) + . Fixed bug #70211 (php 7 ZEND_HASH_IF_FULL_DO_RESIZE use after free). + (Laruence) + . Fixed bug #70208 (Assert breaking access on objects). (Bob) + . Fixed bug #70140 (str_ireplace/php_string_tolower - Arbitrary Code + Execution). (CVE-2015-6527) (Laruence) + . Implemented FR #70112 (Allow "dirname" to go up various times). (Remi) + . Fixed bug #36365 (scandir duplicates file name at every 65535th file). (cmb) + . Fixed bug #70096 (Repeated iptcembed() adds superfluous FF bytes). (cmb) + . Fixed bug #70018 (exec does not strip all whitespace). (Laruence) + . Fixed bug #69983 (get_browser fails with user agent of null). + (Kalle, cmb, Laruence) + . Fixed bug #69976 (Unable to parse "all" urls with colon char). (cmb) + . Fixed bug #69768 (escapeshell*() doesn't cater to !). (cmb) + . Fixed bug #62922 (Truncating entire string should result in string). + (Nikita) + . Fixed bug #69723 (Passing parameters by reference and array_column). + (Laruence) + . Fixed bug #69523 (Cookie name cannot be empty). (Christoph M. Becker) + . Fixed bug #69325 (php_copy_file_ex does not pass the argument). + (imbolk at gmail dot com) + . Fixed bug #69299 (Regression in array_filter's $flag argument in PHP 7). + (Laruence) + . Removed call_user_method() and call_user_method_array() functions. (Kalle) + . Fixed user session handlers (See rfc:session.user.return-value). (Sara) + . Added intdiv() function. (Andrea) + . Improved precision of log() function for base 2 and 10. (Marc Bennewitz) + . Remove string category support in setlocale(). (Nikita) + . Remove set_magic_quotes_runtime() and its alias magic_quotes_runtime(). + (Nikita) + . Fixed bug #65272 (flock() out parameter not set correctly in windows). + (Daniel Lowrey) + . Added preg_replace_callback_array function. (Wei Dai) + . Deprecated salt option to password_hash. (Anthony) + . Fixed bug #69686 (password_verify reports back error on PHP7 will null + string). (Anthony) + . Added Windows support for getrusage(). (Kalle) + . Removed hardcoded limit on number of pipes in proc_open(). (Tony) + +- Streams: + . Fixed bug #70361 (HTTP stream wrapper doesn't close keep-alive connections). + (Niklas Keller) + . Fixed bug #68532 (convert.base64-encode omits padding bytes). + (blaesius at krumedia dot de) + . Removed set_socket_blocking() in favor of its alias stream_set_blocking(). + (Nikita) + +- Tokenizer: + . Fixed bug #69430 (token_get_all has new irrecoverable errors). (Nikita) + +- XMLReader: + . Fixed bug #70309 (XmlReader read generates extra output). (Anatol) + +- XMLRPC + . Fixed bug #70526 (xmlrpc_set_type returns false on success). (Laruence) + +- XSL: + . Fixed bug #70678 (PHP7 returns true when false is expected). (Felipe) + . Fixed bug #70535 (XSLT: free(): invalid pointer). (Laruence) + . Fixed bug #69782 (NULL pointer dereference). (Stas) + . Fixed bug #64776 (The XSLT extension is not thread safe). (Mike) + . Removed xsl.security_prefs ini option. (Nikita) + +- Zlib: + . Added deflate_init(), deflate_add(), inflate_init(), inflate_add() + functions allowing incremental/streaming compression/decompression. + (Daniel Lowrey & Bob Weinand) + +- Zip: + . Fixed bug #70322 (ZipArchive::close() doesn't indicate errors). + (CVE-2014-9767) (cmb) + . Fixed bug #70350 (ZipArchive::extractTo allows for directory traversal when + creating directories). (neal at fb dot com) + . Added ZipArchive::setCompressionName and ZipArchive::setCompressionIndex + methods. (Remi, Cedric Delmas) + . Update bundled libzip to 1.0.1. (Remi, Anatol) + . Fixed bug #67161 (ZipArchive::getStream() returns NULL for certain file). + (Christoph M. Becker) diff --git a/UPGRADING b/UPGRADING index 9e23d7a247e0a..a3c20a88b9f09 100644 --- a/UPGRADING +++ b/UPGRADING @@ -146,6 +146,13 @@ PHP 7.1 UPGRADE NOTES aligned, which causes slightly different behavior than before for some pathological cases. +- IMAP: + Starting with 7.2.13, rsh/ssh logins are disabled by default. Use + imap.enable_insecure_rsh if you want to enable them. Note that the IMAP + library does not filter mailbox names before passing them to rsh/ssh + command, thus passing untrusted data to this function with rsh/ssh enabled + is insecure. + ======================================== 2. New Features ======================================== diff --git a/UPGRADING.orig b/UPGRADING.orig new file mode 100644 index 0000000000000..9e23d7a247e0a --- /dev/null +++ b/UPGRADING.orig @@ -0,0 +1,526 @@ +PHP 7.1 UPGRADE NOTES + +1. Backward Incompatible Changes +2. New Features +3. Changes in SAPI modules +4. Deprecated Functionality +5. Changed Functions +6. New Functions +7. New Classes and Interfaces +8. Removed Extensions and SAPIs +9. Other Changes to Extensions +10. New Global Constants +11. Changes to INI File Handling +12. Windows Support +13. Other Changes + + +======================================== +1. Backward Incompatible Changes +======================================== + +- Core: + . 'void' can no longer be used as the name of a class, interface, or trait. + This applies to declarations, class_alias() and use statements. + . 'iterable' can no longer be used as the name of a class, interface, or + trait. This applies to declarations, class_alias() and use statements. + (RFC: https://wiki.php.net/rfc/iterable) + . (int), intval() where $base is 10 or unspecified, settype(), decbin(), + decoct(), dechex(), integer operators and other conversions now always + respect scientific notation in numeric strings. + (RFC: https://wiki.php.net/rfc/invalid_strings_in_arithmetic) + . The ASCII 0x7F Delete control character is no longer permitted in unquoted + identifiers in source code. + . The following functions may no longer be called dynamically using $func(), + call_user_func(), array_map() or similar: + . extract() + . compact() + . get_defined_vars() + . func_get_args() + . func_get_arg() + . func_num_args() + . parse_str() with one argument + . mb_parse_str() with one argument + . assert() with a string argument + (RFC: https://wiki.php.net/rfc/forbid_dynamic_scope_introspection) + . If the error_log is set to syslog, the PHP error levels are mapped to the + syslog error levels. This brings finer differentiation in the error logs + in contrary to the previous approach where all the errors are loggged with + the notice level only. + . Don't call destructors of incompletely constructed objects, even if they + are kept referenced. See bug #29368 and Zend/tests/bug29368_1.phpt. + . call_user_func() will now consistently throw a warning if a function with + reference arguments is called. However, call_user_func() will no longer + abort the call in this case. + . rand() and srand() are now aliases of mt_rand() and mt_srand(). + Consequently the output of the following functions has changed: + . rand() + . shuffle() + . str_shuffle() + . array_rand() + . Fixes to random number generators mean that mt_rand() now produces a + different sequence of outputs to previous versions. If you relied on + mt_srand() to produce a deterministic sequence, it can be called using + mt_srand($seed, MT_RAND_PHP) to produce the old sequences. + . URL rewriter has been improved. + . Use dedicated buffer for Session module rewrite and User rewrite. + . Full path URL rewrite is supported. Allowed domain can be specified. + $_SERVER['HTTP_HOST'] is allowed by default when host whitelist is empty. + . Use session.trans_sid_tags and session.trans_sid_hosts to control + session rewrite. + . Use url_rewriter.tags and url_rewriter.hosts to control user rewrite. + .
's "action" attribute is used to check if URL rewrite is allowed + and listed under hosts whitelist. + .
is no longer considered as a special tag. is the + only tag considered special. + . Calling a function with less arguments than mandatory declared ones in + signature now issues a Fatal Error (Error Exception) instead of a Warning. + (RFC https://wiki.php.net/rfc/too_few_args). + . The error message for E_RECOVERABLE errors has been changed from "Catchable + fatal error" to "Recoverable fatal error". + . The empty index operator (e.g. $str[] = $x) is not supported for strings + anymore, and throws a fatal error instead of silently converting to array. + . Array elements or object properties that are automatically created during + by-reference assignments will now result in a different order. For example + + $array = []; + $array["a"] =& $array["b"]; + $array["b"] = 1; + var_dump($array); + + now results in the array ["b" => 1, "a" => 1], while for PHP 7.0 the result + was ["a" => 1, "b" => 1]. + . The allowed_classes element of the $options parameter of unserialize() is + now strictly typed, i.e. if anything other than an array or a boolean is + given, unserialize() returns FALSE and issues an E_WARNING. + . $this, autoglobals, and variables with the same name as a parameter can no + longer be bound to a closure via the use construct. + +- JSON: + . The serialize_precision is used instead of precision when encoding double + values. + . An empty key is decoded as an empty property name instead of using _empty_ + property name when decoding object to stdClass. + . When calling json_encode with JSON_UNESCAPED_UNICODE option, U+2028 and + U+2029 are escaped. + +- mbstring: + . mb_ereg() and mb_eregi() will now set the $regs argument to an empty array, + if nothing matched. Formerly, $regs was not modified in that case. + +- OpenSSL: + . Dropped sslv2 stream. + +- Session: + . Session ID is generated from CSPRNG directly. As a result, Session ID length + could be any length between 22 and 256. Note: Max size of session ID depends + on save handler you are using. + . Following INIs are removed + . session.hash_function + . session.hash_bits_per_character + . session.entropy_file + . session.entropy_length + . New INIs and defaults + . session.sid_length (Number of session ID characters - 22 to 256. + php.ini-* default: 26 Compiled default: 32) + . session.sid_bits_per_character (Bits used per character - 4 to 6. + php.ini-* default: 5 Compiled default: 4) + . Length of old session ID string is determined as follows + . Used hash function's bits. + . session.hash_function=0 - MD5 128 bits (This was default) + . session.hash_function=1 - SHA1 160 bits + . Bits per character. (4, 5 or 6 bits per character) + . Examples + MD5 and 4 bits = 32 chars, ceil(128/4)=32 + MD5 and 5 bits = 26 chars, ceil(128/5)=26 + MD5 and 6 bits = 22 chars, ceil(128/6)=22 + SHA1 and 4 bits = 40 chars, ceil(160/4)=40 + SHA1 and 5 bits = 32 chars, ceil(160/5)=32 + SHA1 and 6 bits = 27 chars, ceil(160/6)=27 + and so on. + . session_start() returns FALSE and no longer initializes $_SESSION when + it failed to start session. + +- Reflection: + . The behavior of ReflectionMethod::invoke() and ::invokeArgs() has been + aligned, which causes slightly different behavior than before for some + pathological cases. + +======================================== +2. New Features +======================================== +- Core + . Added void return type, which requires that a function not return a value. + (RFC: https://wiki.php.net/rfc/void_return_type) + . Added iterable pseudo-type accepting any array or object implementing + Traversable. + (RFC: https://wiki.php.net/rfc/iterable) + . String offset access now supports negative references, which will be + counted from the end of the string. + (RFC: https://wiki.php.net/rfc/negative-string-offsets) + . Added a form of the list() construct where keys can be specified. + (RFC: https://wiki.php.net/rfc/list_keys) + . Added [] = as alternative construct to list() =. + (RFC: https://wiki.php.net/rfc/short_list_syntax) + . Number operators taking numeric strings now emit "A non well formed numeric + value encountered" E_NOTICEs for leading-numeric strings, and "A + non-numeric value encountered" E_WARNINGs for non-numeric strings. + This always applies to the +, -, *, /, **, %, << and >> operators, and + their assignment counterparts +=, -=, *=, /=, **=, %=, <<= and >>=. + For the bitwise operators |, & and ^, and their assignment counterparts + |=, &= and ^=, this only applies where only one operand is a string. + Note that this never applies to the bitwise NOT operator, ~, which does not + handle numeric strings, nor to the increment and decrement operators + ++ and --, which have a unique approach to handling numeric strings. + (RFC: https://wiki.php.net/rfc/invalid_strings_in_arithmetic) + . Closure::fromCallable (RFC: https://wiki.php.net/rfc/closurefromcallable) + . Added support for class constant visibility modifiers. + (RFC: https://wiki.php.net/rfc/class_const_visibility) + . TypeError messages for arg_info type checks will now say "must be ... + or null", or "must ... or be null" where the parameter or return type + accepts null. arg_info type checks are used by all userland functions with + type declarations, and some internal functions. Both nullable type + declarations (?int) and parameters with default values of null + (int $foo = NULL) are considered to "accept null" for this purpose. + . The simple syntax for variable parsing inside of string literals now + supports negative offsets. + +======================================== +3. Changes in SAPI modules +======================================== +- apache2handler: + . Implemented per module logging. + . Implemented error level mapping between PHP and Apache for the error logs. + +======================================== +4. Deprecated Functionality +======================================== + +- 'e' option of mb_ereg_replace() and mb_eregi_replace(). +- ext/mcrypt is now fully deprecated. + +======================================== +5. Changed Functions +======================================== +- get_headers() has an extra parameter which allows passing a custom stream + context. +- The first $varname argument for getenv() is no longer mandatory, the + current environment variables will be returned as an associative array + when omitted. +- json_encode() accepts new option JSON_UNESCAPED_LINE_TERMINATORS that + disables escaping of U+2028 and U+2029 characters when + JSON_UNESCAPED_UNICODE is supplied. +- long2ip() accepts integer as parameter now +- openssl_encrypt and openssl_decrypt have extra parameters for handling + authenticated encryption (tag, aad, tag_length) and decryption (tag, aad). +- pg_last_notice() accepts optional long parameter to specify operation. + PGSQL_NOTICE_LAST - Get last notice (Default) + PGSQL_NOTICE_ALL - Get all stored notices + PGSQL_NOTICE_CLEAR - Remove all stored notices + It returns empty string or array on successful PGSQL_NOTICE_LAST/ALL calls. + It returned FALSE for empty notice previously. +- pg_fetch_all() accepts 2nd optional result type parameter like + pg_fetch_row(). +- pg_select() accepts 4th optional result type parameter like pg_fetch_row(). +- parse_url() is more restrictive now and supports RFC3986. +- unpack() accepts an additional optional $offset argument. '@' format code + (that specifes an absolute position) is applyed to input data after + the $offset argument. +- strpos(), stripos(), substr_count(), grapheme_strpos(), grapheme_stripos(), + grapheme_extract(), iconv_strpos(), mb_strimwidth(), mb_ereg_search_setpos(), + mb_strpos() and mb_stripos() now accept negative string offsets. +- substr_count() and mb_strimwidth() additionally also accept negative length. +- file_get_contents() accepts a negative seek offset if the stream is seekable. +- tempnam() throws a notice when failing back to the system temp dir. +- getopt() has an extra by-ref parameter : optind +- mb_ereg() and mb_ereg_replace() reject illegal byte sequences. +- FILTER_FLAG_EMAIL_UNICODE can be used with filter_var() for email validation + according to RFC 6531. +- output_reset_rewrite_vars() no longer reset session URL rewrite vars. +- the lasinsertid() in pdo_pgsql extension triggers an error, when no nextval() + were called in in the current session. +- fopen() + Since 7.1.2, mode 'e' was added, which sets the close-on-exec flag + on the opened file descriptor. This mode is only available in PHP compiled on + POSIX.1-2008 conform systems. + + +======================================== +6. New Functions +======================================== +- Core: + . Added sapi_windows_cp_set(), sapi_windows_cp_get(), sapi_windows_cp_is_utf8(), + sapi_windows_cp_conv() for codepage handling. + +- cURL: + . Added curl_multi_errno() and curl_share_errno() to return the last error + number of curl_multi and curl_share resources. + . Added curl_share_strerror() to convert error code to error message text + describing the error. + +- Hash: + . In PHP 7.1.2: Added hash_hkdf() function, which implements the HMAC-based + Key Derivation Function (HKDF) algorithm according to RFC 5869. The + implementation combines the Extract and Expand steps. + +- pcntl: + . Added pcntl_signal_get_handler() that returns the current signal handler + for a particular signal. + +- Session: + . Added session_gc() that performs session data garbage collection. + https://wiki.php.net/rfc/session-gc + . Added session_create_id() for creating custom session ID. + https://wiki.php.net/rfc/session-create-id + +- Standard: + . Added is_iterable() that determines if a value will be accepted by the new + iterable pseudo-type. + +======================================== +7. New Classes and Interfaces +======================================== + +======================================== +8. Removed Extensions and SAPIs +======================================== + +======================================== +9. Other Changes to Extensions +======================================== + +- Date: + . Invalid serialization data for a DateTime or DatePeriod object will now + throw an instance of Error from __wakeup() or __set_state() instead of + resulting in a fatal error. + . Timezone initialization failure from serialized data will now throw an + instance of Error from __wakeup() or __set_state() instead of resulting in + a fatal error. + . DateTime and DateTimeImmutable now properly incorporate microseconds when + constructed from the current time, either explicitly or with a relative + string (e.g. "first day of next month"). This means that naive comparisons + of two newly created instances will now more likely return FALSE instead of + TRUE: + new DateTime() == new DateTime(); + +- DBA: + . Data modification functions (e.g.: dba_insert()) now throw an instance of + Error instead of triggering a catchable fatal error if the key does not + contain exactly two elements. + +- DOM: + . Invalid schema or RelaxNG validation contexts will throw an instance of + Error instead of resulting in a fatal error. + . Attempting to register a node class that does not extend the appropriate + base class will now throw an instance of Error instead of resulting in a + fatal error. + . Attempting to read an invalid or write to a readonly property will throw + an instance of Error instead of resulting in a fatal error. + +- GD: + . Changed the default of the ini setting gd.jpeg_ignore_warning to 1. + +- IMAP: + . An email address longer than 16385 bytes will throw an instance of Error + instead of resulting in a fatal error. + +- Intl: + . Failure to call the parent constructor in a class extending Collator + before invoking the parent methods will throw an instance of Error + instead of resulting in a recoverable fatal error. + . Cloning a Transliterator object may will now throw an instance of Error + instead of resulting in a fatal error if cloning the internal + transliterator fails. + +- LDAP: + . Providing an unknown modification type to ldap_batch_modify() will now + throw an instance of Error instead of resulting in a fatal error. + +- Mbstring: + . mb_ereg() and mb_eregi() will now throw an instance of ParseError if an + invalid PHP expression is provided and the 'e' option is used. + +- Mcrypt: + . mcrypt_encrypt() and mcrypt_decrypt() will throw an instance of Error + instead of resulting in a fatal error if mcrypt cannot be initialized. + +- Mysqli: + . Attempting to read an invalid or write to a readonly property will throw + an instance of Error instead of resulting in a fatal error. + +- PDO_Firebird + As of PHP 7.1.2, the fetched data for integer fields is aware of the Firebird + datatypes. Previously all integers was fetched as strings, starting with + aforementioned PHP version integer fields are translated to the PHP integer + datatype. The 64-bit integers are still fetched as strings in 32-bit PHP + builds. + +- Reflection: + . Failure to retrieve a reflection object or retrieve an object property + will now throw an instance of Error instead of resulting in a fatal error. + +- Session: + . Custom session handlers that do not return strings for session IDs will + now throw an instance of Error instead of resulting in a fatal error + when a function is called that must generate a session ID. + . Only CSPRNG is used to generate session ID. + +- SimpleXML: + . Creating an unnamed or duplicate attribute will throw an instance of Error + instead of resulting in a fatal error. + +- SPL: + . Attempting to clone an SplDirectory object will throw an instance of Error + instead of resulting in a fatal error. + . Calling ArrayIterator::append() when iterating over an object will throw an + instance of Error instead of resulting in a fatal error. + +- SQLite3: + . Upgraded bundled SQLite lib to 3.13.0 + +- Standard: + . assert() will throw a ParseError when evaluating a string given as the first + argument if the PHP code is invalid instead of resulting in a catchable + fatal error. + . Calling forward_static_call() outside of a class scope will now throw an + instance of Error instead of resulting in a fatal error. + +- Tidy: + . Creating a tidyNode manually will now throw an instance of Error instead of + resulting in a fatal error. + +- WDDX: + . A circular reference when serializing will now throw an instance of Error + instead of resulting in a fatal error. + +- XML-RPC: + . A circular reference when serializing will now throw an instance of Error + instead of resulting in a fatal error. + +- Zip: + . ZipArchive::addGlob() will throw an instance of Error instead of resulting + in a fatal error if glob support is not available. + +======================================== +10. New Global Constants +======================================== + +- Core: + . PHP_FD_SETSIZE + +- JSON: + . JSON_UNESCAPED_LINE_TERMINATORS + +- Pgsql: + PGSQL_NOTICE_LAST + PGSQL_NOTICE_ALL + PGSQL_NOTICE_CLEAR + +- Standard: + . IMAGETYPE_WEBP + +======================================== +11. Changes to INI File Handling +======================================== + +- serialize_precision + . If the value is set to -1, then the dtoa mode 0 is used. The value -1 + is now used by default. + +- precision + . If the value is set to -1, then the dtoa mode 0 is used. No changes + in default value which is still 14. + +- realpath_cache_size + . Set to 4096k by default + +======================================== +12. Windows Support +======================================== + +- Core: + . Support for long and UTF-8 path; + + If a web application is UTF-8 conform, no further action is required. For + applications depending on paths in non UTF-8 encodings for I/O, an explicit + INI directive has to be set. The encoding INI settings check relies on the + order in the core: + - internal_encoding + - default_charset + - zend.multibyte + + Several functions for codepage handling were itroduced: + - sapi_windows_cp_set() to set the default codepage + - sapi_windows_cp_get() to retrieve the current codepage + - sapi_windows_cp_is_utf8() + - sapi_windows_cp_conv() to convert between codepages, using iconv() + compatible signature + These functions are thread safe. + + The console output codepage is adjusted depending on the encoding used in + PHP. Depending on the concrete system OEM codepage, the visible output + might or might be not correct. For example, in the default cmd.exe and on + a system with the OEM codepage 437, outputs in codepages 1251, 1252, 1253 + and some others can be shown correctly when using UTF-8. On the same system, + chars in codepage like 20932 probably won't be shown correctly. This refers + to the particular system rules for codepage, font compatibility and the + particular console program used. PHP automatically sets the console codepage + according to the encoding rules from php.ini. Using alternative consoles + instead of cmd.exe directly might bring better experience in some cases. + + Nevertheless be aware, runtime codepage switch after the request start + might bring unexpected side effects on CLI. The preferrable way is php.ini, + When PHP CLI is used in a console emulator, that doesn't support Unicode, + it might possibly be required, to avoid changing the console codepage. The + best way to achieve it is by setting the default or internal encoding to + correspond the ANSI codepage. Another method is to set the INI directives + output_encoding and input_encoding to the required codepage, in which case + however the difference between internal and I/O codepage is likely to cause + mojibake. In rare cases, if PHP happens to crash gracefully, the original + console codepage might be not restored. In this case, the chcp command + can be used, to restore it manually. + + Special awareness for the DBCS systems - the codepage switch on runtime + using ini_set() is likely to cause display issues. The difference to the + non DBCS systems is, that the extended characters require two console cells + to be displayed. In certain case, only the mapping of the characters into + the glyph set of the font could happen, no actual font change. This is the + nature of DBCS systems, the most simple way to prevent display issues is + to avoid usage of ini_set() for the codepage change. + + As a result of UTF-8 support in the streams, PHP scripts are not limited + to ASCII or ANSI filenames anymore. This is supported out of the box on + CLI. For other SAPI, the documentation for the corresponding server + is useful. + + Long paths support is transparent. Paths longer than 260 bytes get + automatically prefixed with \\?\. The max path length is limited to + 2048 bytes. Be aware, that the path segment limit (basename length) still + persists. + + For the best portability, it is strongely recommended to handle filenames, + I/O and other related topics UTF-8. Additionally, for the console applications, + the usage of a TrueType font is preferrable and the usage of ini_set() for + the codepage change is discouraged. + + . Support for ftok() + +- FCGI + . PHP_FCGI_CHILDREN is respected. If this environment variable is defined, + the first php-fcgi.exe process will exec the specified number of children. + Those will share the same TCP socket. + +- readline: + . The readline extension is supported through the WinEditLine library + (http://mingweditline.sourceforge.net/). Thereby, the interactive CLI + shell is supported as well (php.exe -a). + + It is well known, but nevertheless is worth mentioning again, that + the readline extension is not thread safe and will never be. Thus, + the usage of it with any true thread safe SAPI (like Apache mod_winnt) is + strongely discouraged. + +======================================== +13. Other Changes +======================================== + diff --git a/ext/com_dotnet/com_extension.c b/ext/com_dotnet/com_extension.c index d2dc4c155e8f5..324fe7fa6b3c7 100644 --- a/ext/com_dotnet/com_extension.c +++ b/ext/com_dotnet/com_extension.c @@ -30,6 +30,7 @@ #include "php_com_dotnet.h" #include "php_com_dotnet_internal.h" #include "Zend/zend_exceptions.h" +#include "Zend/zend_interfaces.h" ZEND_DECLARE_MODULE_GLOBALS(com_dotnet) static PHP_GINIT_FUNCTION(com_dotnet); @@ -354,6 +355,8 @@ PHP_MINIT_FUNCTION(com_dotnet) { zend_class_entry ce, *tmp; + zend_hash_init(&com_dotnet_object_properties, 0, NULL, NULL, 0); + php_com_wrapper_minit(INIT_FUNC_ARGS_PASSTHRU); php_com_persist_minit(INIT_FUNC_ARGS_PASSTHRU); @@ -372,11 +375,15 @@ PHP_MINIT_FUNCTION(com_dotnet) ce.create_object = php_com_object_new; php_com_variant_class_entry = zend_register_internal_class(&ce); php_com_variant_class_entry->get_iterator = php_com_iter_get; + php_com_variant_class_entry->serialize = zend_class_serialize_deny; + php_com_variant_class_entry->unserialize = zend_class_unserialize_deny; INIT_CLASS_ENTRY(ce, "com", NULL); ce.create_object = php_com_object_new; tmp = zend_register_internal_class_ex(&ce, php_com_variant_class_entry); tmp->get_iterator = php_com_iter_get; + tmp->serialize = zend_class_serialize_deny; + tmp->unserialize = zend_class_unserialize_deny; zend_ts_hash_init(&php_com_typelibraries, 0, NULL, php_com_typelibrary_dtor, 1); @@ -385,6 +392,8 @@ PHP_MINIT_FUNCTION(com_dotnet) ce.create_object = php_com_object_new; tmp = zend_register_internal_class_ex(&ce, php_com_variant_class_entry); tmp->get_iterator = php_com_iter_get; + tmp->serialize = zend_class_serialize_deny; + tmp->unserialize = zend_class_unserialize_deny; #endif REGISTER_INI_ENTRIES(); diff --git a/ext/com_dotnet/com_handlers.c b/ext/com_dotnet/com_handlers.c index 9597ce10075a9..036165caf1412 100644 --- a/ext/com_dotnet/com_handlers.c +++ b/ext/com_dotnet/com_handlers.c @@ -29,6 +29,8 @@ #include "php_com_dotnet_internal.h" #include "Zend/zend_exceptions.h" +const HashTable com_dotnet_object_properties; + static zval *com_property_read(zval *object, zval *member, int type, void **cahce_slot, zval *rv) { php_com_dotnet_object *obj; @@ -231,7 +233,7 @@ static HashTable *com_properties_get(zval *object) * infinite recursion when the hash is displayed via var_dump(). * Perhaps it is best to leave it un-implemented. */ - return NULL; + return &com_dotnet_object_properties; } static void function_dtor(zval *zv) diff --git a/ext/com_dotnet/php_com_dotnet_internal.h b/ext/com_dotnet/php_com_dotnet_internal.h index 0cd969a72b96d..fc5fdcd32afc0 100644 --- a/ext/com_dotnet/php_com_dotnet_internal.h +++ b/ext/com_dotnet/php_com_dotnet_internal.h @@ -31,6 +31,8 @@ #include "zend_ts_hash.h" +extern const HashTable com_dotnet_object_properties; + typedef struct _php_com_dotnet_object { zend_object zo; diff --git a/ext/com_dotnet/tests/bug77177.phpt b/ext/com_dotnet/tests/bug77177.phpt new file mode 100644 index 0000000000000..901358248c340 --- /dev/null +++ b/ext/com_dotnet/tests/bug77177.phpt @@ -0,0 +1,57 @@ +--TEST-- +Bug #77177 (Serializing or unserializing COM objects crashes) +--SKIPIF-- + +--FILE-- +getMessage()}\n"; + } +} + +$strings = ['C:3:"com":0:{}', 'C:6:"dotnet":0:{}', 'C:7:"variant":0:{}']; +foreach ($strings as $string) { + try { + unserialize($string); + } catch (Exception $ex) { + echo "Exception: {$ex->getMessage()}\n"; + } +} + +$strings = ['O:3:"com":0:{}', 'O:6:"dotnet":0:{}', 'O:7:"variant":0:{}']; +foreach ($strings as $string) { + var_dump(unserialize($string)); +} +?> +===DONE=== +--EXPECTF-- +Exception: Serialization of 'com' is not allowed +Exception: Serialization of 'dotnet' is not allowed +Exception: Serialization of 'variant' is not allowed +Exception: Unserialization of 'com' is not allowed +Exception: Unserialization of 'dotnet' is not allowed +Exception: Unserialization of 'variant' is not allowed + +Warning: Erroneous data format for unserializing 'com' in %s on line %d + +Notice: unserialize(): Error at offset 13 of 14 bytes in %s on line %d +bool(false) + +Warning: Erroneous data format for unserializing 'dotnet' in %s on line %d + +Notice: unserialize(): Error at offset 16 of 17 bytes in %s on line %d +bool(false) + +Warning: Erroneous data format for unserializing 'variant' in %s on line %d + +Notice: unserialize(): Error at offset 17 of 18 bytes in %s on line %d +bool(false) +===DONE=== diff --git a/ext/dom/domimplementation.c b/ext/dom/domimplementation.c index ee050e21fdd95..486a49d52bbb1 100644 --- a/ext/dom/domimplementation.c +++ b/ext/dom/domimplementation.c @@ -114,6 +114,11 @@ PHP_METHOD(domimplementation, createDocumentType) pch2 = (xmlChar *) systemid; } + if (strstr(name, "%00")) { + php_error_docref(NULL, E_WARNING, "URI must not contain percent-encoded NUL bytes"); + RETURN_FALSE; + } + uri = xmlParseURI(name); if (uri != NULL && uri->opaque != NULL) { localname = xmlStrdup((xmlChar *) uri->opaque); diff --git a/ext/dom/domimplementation.c.orig b/ext/dom/domimplementation.c.orig new file mode 100644 index 0000000000000..ee050e21fdd95 --- /dev/null +++ b/ext/dom/domimplementation.c.orig @@ -0,0 +1,274 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2017 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Christian Stocker | + | Rob Richards | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#if HAVE_LIBXML && HAVE_DOM +#include "php_dom.h" + +/* {{{ arginfo */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_dom_implementation_get_feature, 0, 0, 2) + ZEND_ARG_INFO(0, feature) + ZEND_ARG_INFO(0, version) +ZEND_END_ARG_INFO(); + +ZEND_BEGIN_ARG_INFO_EX(arginfo_dom_implementation_has_feature, 0, 0, 0) +ZEND_END_ARG_INFO(); + +ZEND_BEGIN_ARG_INFO_EX(arginfo_dom_implementation_create_documenttype, 0, 0, 3) + ZEND_ARG_INFO(0, qualifiedName) + ZEND_ARG_INFO(0, publicId) + ZEND_ARG_INFO(0, systemId) +ZEND_END_ARG_INFO(); + +ZEND_BEGIN_ARG_INFO_EX(arginfo_dom_implementation_create_document, 0, 0, 3) + ZEND_ARG_INFO(0, namespaceURI) + ZEND_ARG_INFO(0, qualifiedName) + ZEND_ARG_OBJ_INFO(0, docType, DOMDocumentType, 0) +ZEND_END_ARG_INFO(); +/* }}} */ + +/* +* class DOMImplementation +* +* URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#ID-102161490 +* Since: +*/ + +const zend_function_entry php_dom_domimplementation_class_functions[] = { + PHP_ME(domimplementation, getFeature, arginfo_dom_implementation_get_feature, ZEND_ACC_PUBLIC|ZEND_ACC_ALLOW_STATIC) + PHP_ME(domimplementation, hasFeature, arginfo_dom_implementation_has_feature, ZEND_ACC_PUBLIC|ZEND_ACC_ALLOW_STATIC) + PHP_ME(domimplementation, createDocumentType, arginfo_dom_implementation_create_documenttype, ZEND_ACC_PUBLIC|ZEND_ACC_ALLOW_STATIC) + PHP_ME(domimplementation, createDocument, arginfo_dom_implementation_create_document, ZEND_ACC_PUBLIC|ZEND_ACC_ALLOW_STATIC) + PHP_FE_END +}; + +/* {{{ proto boolean dom_domimplementation_has_feature(string feature, string version); +URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#ID-5CED94D7 +Since: +*/ +PHP_METHOD(domimplementation, hasFeature) +{ + size_t feature_len, version_len; + char *feature, *version; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", &feature, &feature_len, &version, &version_len) == FAILURE) { + return; + } + + if (dom_has_feature(feature, version)) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} end dom_domimplementation_has_feature */ + +/* {{{ proto DOMDocumentType dom_domimplementation_create_document_type(string qualifiedName, string publicId, string systemId); +URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#Level-2-Core-DOM-createDocType +Since: DOM Level 2 +*/ +PHP_METHOD(domimplementation, createDocumentType) +{ + xmlDtd *doctype; + int ret; + size_t name_len = 0, publicid_len = 0, systemid_len = 0; + char *name = NULL, *publicid = NULL, *systemid = NULL; + xmlChar *pch1 = NULL, *pch2 = NULL, *localname = NULL; + xmlURIPtr uri; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|sss", &name, &name_len, &publicid, &publicid_len, &systemid, &systemid_len) == FAILURE) { + return; + } + + if (name_len == 0) { + php_error_docref(NULL, E_WARNING, "qualifiedName is required"); + RETURN_FALSE; + } + + if (publicid_len > 0) { + pch1 = (xmlChar *) publicid; + } + if (systemid_len > 0) { + pch2 = (xmlChar *) systemid; + } + + uri = xmlParseURI(name); + if (uri != NULL && uri->opaque != NULL) { + localname = xmlStrdup((xmlChar *) uri->opaque); + if (xmlStrchr(localname, (xmlChar) ':') != NULL) { + php_dom_throw_error(NAMESPACE_ERR, 1); + xmlFreeURI(uri); + xmlFree(localname); + RETURN_FALSE; + } + } else { + localname = xmlStrdup((xmlChar *) name); + } + + /* TODO: Test that localname has no invalid chars + php_dom_throw_error(INVALID_CHARACTER_ERR,); + */ + + if (uri) { + xmlFreeURI(uri); + } + + doctype = xmlCreateIntSubset(NULL, localname, pch1, pch2); + xmlFree(localname); + + if (doctype == NULL) { + php_error_docref(NULL, E_WARNING, "Unable to create DocumentType"); + RETURN_FALSE; + } + + DOM_RET_OBJ((xmlNodePtr) doctype, &ret, NULL); +} +/* }}} end dom_domimplementation_create_document_type */ + +/* {{{ proto DOMDocument dom_domimplementation_create_document(string namespaceURI, string qualifiedName, DOMDocumentType doctype); +URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#Level-2-Core-DOM-createDocument +Since: DOM Level 2 +*/ +PHP_METHOD(domimplementation, createDocument) +{ + zval *node = NULL; + xmlDoc *docp; + xmlNode *nodep; + xmlDtdPtr doctype = NULL; + xmlNsPtr nsptr = NULL; + int ret, errorcode = 0; + size_t uri_len = 0, name_len = 0; + char *uri = NULL, *name = NULL; + char *prefix = NULL, *localname = NULL; + dom_object *doctobj; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|ssO", &uri, &uri_len, &name, &name_len, &node, dom_documenttype_class_entry) == FAILURE) { + return; + } + + if (node != NULL) { + DOM_GET_OBJ(doctype, node, xmlDtdPtr, doctobj); + if (doctype->type == XML_DOCUMENT_TYPE_NODE) { + php_error_docref(NULL, E_WARNING, "Invalid DocumentType object"); + RETURN_FALSE; + } + if (doctype->doc != NULL) { + php_dom_throw_error(WRONG_DOCUMENT_ERR, 1); + RETURN_FALSE; + } + } else { + doctobj = NULL; + } + + if (name_len > 0) { + errorcode = dom_check_qname(name, &localname, &prefix, 1, name_len); + if (errorcode == 0 && uri_len > 0 + && ((nsptr = xmlNewNs(NULL, (xmlChar *) uri, (xmlChar *) prefix)) == NULL) + ) { + errorcode = NAMESPACE_ERR; + } + } + + if (prefix != NULL) { + xmlFree(prefix); + } + + if (errorcode != 0) { + if (localname != NULL) { + xmlFree(localname); + } + php_dom_throw_error(errorcode, 1); + RETURN_FALSE; + } + + /* currently letting libxml2 set the version string */ + docp = xmlNewDoc(NULL); + if (!docp) { + if (localname != NULL) { + xmlFree(localname); + } + RETURN_FALSE; + } + + if (doctype != NULL) { + docp->intSubset = doctype; + doctype->parent = docp; + doctype->doc = docp; + docp->children = (xmlNodePtr) doctype; + docp->last = (xmlNodePtr) doctype; + } + + if (localname != NULL) { + nodep = xmlNewDocNode(docp, nsptr, (xmlChar *) localname, NULL); + if (!nodep) { + if (doctype != NULL) { + docp->intSubset = NULL; + doctype->parent = NULL; + doctype->doc = NULL; + docp->children = NULL; + docp->last = NULL; + } + xmlFreeDoc(docp); + xmlFree(localname); + /* Need some type of error here */ + php_error_docref(NULL, E_WARNING, "Unexpected Error"); + RETURN_FALSE; + } + + nodep->nsDef = nsptr; + + xmlDocSetRootElement(docp, nodep); + xmlFree(localname); + } + + DOM_RET_OBJ((xmlNodePtr) docp, &ret, NULL); + + if (doctobj != NULL) { + doctobj->document = ((dom_object *)((php_libxml_node_ptr *)docp->_private)->_private)->document; + php_libxml_increment_doc_ref((php_libxml_node_object *)doctobj, docp); + } +} +/* }}} end dom_domimplementation_create_document */ + +/* {{{ proto DOMNode dom_domimplementation_get_feature(string feature, string version); +URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#DOMImplementation3-getFeature +Since: DOM Level 3 +*/ +PHP_METHOD(domimplementation, getFeature) +{ + DOM_NOT_IMPLEMENTED(); +} +/* }}} end dom_domimplementation_get_feature */ + +#endif + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/ext/dom/tests/bug79971_2.phpt b/ext/dom/tests/bug79971_2.phpt new file mode 100644 index 0000000000000..c4e6b1e4e0933 --- /dev/null +++ b/ext/dom/tests/bug79971_2.phpt @@ -0,0 +1,20 @@ +--TEST-- +Bug #79971 (special character is breaking the path in xml function) +--SKIPIF-- + +--FILE-- +createDocumentType("$uri%00foo")); +?> +--EXPECTF-- +Warning: DOMImplementation::createDocumentType(): URI must not contain percent-encoded NUL bytes in %s on line %d +bool(false) diff --git a/ext/exif/exif.c b/ext/exif/exif.c index 4525fb35532a0..1104f8fe2e681 100644 --- a/ext/exif/exif.c +++ b/ext/exif/exif.c @@ -2732,7 +2732,8 @@ static int exif_process_IFD_in_MAKERNOTE(image_info_type *ImageInfo, char * valu continue; if (maker_note->model && (!ImageInfo->model || strcmp(maker_note->model, ImageInfo->model))) continue; - if (maker_note->id_string && strncmp(maker_note->id_string, value_ptr, maker_note->id_string_len)) + if (maker_note->id_string && value_len >= maker_note->id_string_len + && strncmp(maker_note->id_string, value_ptr, maker_note->id_string_len)) continue; break; } @@ -3217,6 +3218,11 @@ static void exif_process_TIFF_in_JPEG(image_info_type *ImageInfo, char *CharBuf, { unsigned exif_value_2a, offset_of_ifd; + if (length < 2) { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Missing TIFF alignment marker"); + return; + } + /* set the thumbnail stuff to nothing so we can test to see if they get set up */ if (memcmp(CharBuf, "II", 2) == 0) { ImageInfo->motorola_intel = 0; @@ -3369,7 +3375,7 @@ static int exif_scan_JPEG_header(image_info_type *ImageInfo) return FALSE; } - sn = exif_file_sections_add(ImageInfo, marker, itemlen+1, NULL); + sn = exif_file_sections_add(ImageInfo, marker, itemlen, NULL); Data = ImageInfo->file.list[sn].data; /* Store first two pre-read bytes. */ @@ -3561,10 +3567,10 @@ static int exif_process_IFD_in_TIFF(image_info_type *ImageInfo, size_t dir_offse tag_table_type tag_table = exif_get_tag_table(section_index); if (ImageInfo->ifd_nesting_level > MAX_IFD_NESTING_LEVEL) { - return FALSE; - } + return FALSE; + } - if (ImageInfo->FileSize >= dir_offset+2) { + if (ImageInfo->FileSize >= 2 && ImageInfo->FileSize - 2 >= dir_offset) { sn = exif_file_sections_add(ImageInfo, M_PSEUDO, 2, NULL); #ifdef EXIF_DEBUG exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Read from TIFF: filesize(x%04X), IFD dir(x%04X + x%04X)", ImageInfo->FileSize, dir_offset, 2); @@ -3572,8 +3578,8 @@ static int exif_process_IFD_in_TIFF(image_info_type *ImageInfo, size_t dir_offse php_stream_seek(ImageInfo->infile, dir_offset, SEEK_SET); /* we do not know the order of sections */ php_stream_read(ImageInfo->infile, (char*)ImageInfo->file.list[sn].data, 2); num_entries = php_ifd_get16u(ImageInfo->file.list[sn].data, ImageInfo->motorola_intel); - dir_size = 2/*num dir entries*/ +12/*length of entry*/*num_entries +4/* offset to next ifd (points to thumbnail or NULL)*/; - if (ImageInfo->FileSize >= dir_offset+dir_size) { + dir_size = 2/*num dir entries*/ +12/*length of entry*/*(size_t)num_entries +4/* offset to next ifd (points to thumbnail or NULL)*/; + if (ImageInfo->FileSize >= dir_size && ImageInfo->FileSize - dir_size >= dir_offset) { #ifdef EXIF_DEBUG exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Read from TIFF: filesize(x%04X), IFD dir(x%04X + x%04X), IFD entries(%d)", ImageInfo->FileSize, dir_offset+2, dir_size-2, num_entries); #endif @@ -3656,9 +3662,9 @@ static int exif_process_IFD_in_TIFF(image_info_type *ImageInfo, size_t dir_offse } } } - if (ImageInfo->FileSize >= dir_offset + ImageInfo->file.list[sn].size) { + if (ImageInfo->FileSize >= ImageInfo->file.list[sn].size && ImageInfo->FileSize - ImageInfo->file.list[sn].size >= dir_offset) { if (ifd_size > dir_size) { - if (dir_offset + ifd_size > ImageInfo->FileSize) { + if (ImageInfo->FileSize < ifd_size || dir_offset > ImageInfo->FileSize - ifd_size) { exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Error in TIFF: filesize(x%04X) less than size of IFD(x%04X + x%04X)", ImageInfo->FileSize, dir_offset, ifd_size); return FALSE; } diff --git a/ext/exif/exif.c.orig b/ext/exif/exif.c.orig new file mode 100644 index 0000000000000..d8628fb2d03dd --- /dev/null +++ b/ext/exif/exif.c.orig @@ -0,0 +1,4239 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2017 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Rasmus Lerdorf | + | Marcus Boerger | + +----------------------------------------------------------------------+ + */ + +/* $Id: 4525fb35532a0563454d4f0607591cbc6f47330c $ */ + +/* ToDos + * + * See if example images from http://www.exif.org have illegal + * thumbnail sizes or if code is corrupt. + * Create/Update exif headers. + * Create/Remove/Update image thumbnails. + */ + +/* Security + * + * At current time i do not see any security problems but a potential + * attacker could generate an image with recursive ifd pointers...(Marcus) + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "ext/standard/file.h" + +#if HAVE_EXIF + +/* When EXIF_DEBUG is defined the module generates a lot of debug messages + * that help understanding what is going on. This can and should be used + * while extending the module as it shows if you are at the right position. + * You are always considered to have a copy of TIFF6.0 and EXIF2.10 standard. + */ +#undef EXIF_DEBUG + +#ifdef EXIF_DEBUG +#define EXIFERR_DC , const char *_file, size_t _line +#define EXIFERR_CC , __FILE__, __LINE__ +#else +#define EXIFERR_DC +#define EXIFERR_CC +#endif + +#undef EXIF_JPEG2000 + +#include "php_exif.h" +#include +#include "php_ini.h" +#include "ext/standard/php_string.h" +#include "ext/standard/php_image.h" +#include "ext/standard/info.h" + +/* needed for ssize_t definition */ +#include + +typedef unsigned char uchar; + +#ifndef safe_emalloc +# define safe_emalloc(a,b,c) emalloc((a)*(b)+(c)) +#endif +#ifndef safe_erealloc +# define safe_erealloc(p,a,b,c) erealloc(p, (a)*(b)+(c)) +#endif + +#ifndef TRUE +# define TRUE 1 +# define FALSE 0 +#endif + +#ifndef max +# define max(a,b) ((a)>(b) ? (a) : (b)) +#endif + +#define EFREE_IF(ptr) if (ptr) efree(ptr) + +#define MAX_IFD_NESTING_LEVEL 100 + +/* {{{ arginfo */ +ZEND_BEGIN_ARG_INFO(arginfo_exif_tagname, 0) + ZEND_ARG_INFO(0, index) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_exif_read_data, 0, 0, 1) + ZEND_ARG_INFO(0, filename) + ZEND_ARG_INFO(0, sections_needed) + ZEND_ARG_INFO(0, sub_arrays) + ZEND_ARG_INFO(0, read_thumbnail) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_exif_thumbnail, 0, 0, 1) + ZEND_ARG_INFO(0, filename) + ZEND_ARG_INFO(1, width) + ZEND_ARG_INFO(1, height) + ZEND_ARG_INFO(1, imagetype) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_exif_imagetype, 0) + ZEND_ARG_INFO(0, imagefile) +ZEND_END_ARG_INFO() + +/* }}} */ + +/* {{{ exif_functions[] + */ +const zend_function_entry exif_functions[] = { + PHP_FE(exif_read_data, arginfo_exif_read_data) + PHP_FALIAS(read_exif_data, exif_read_data, arginfo_exif_read_data) + PHP_FE(exif_tagname, arginfo_exif_tagname) + PHP_FE(exif_thumbnail, arginfo_exif_thumbnail) + PHP_FE(exif_imagetype, arginfo_exif_imagetype) + PHP_FE_END +}; +/* }}} */ + +/* {{{ PHP_MINFO_FUNCTION + */ +PHP_MINFO_FUNCTION(exif) +{ + php_info_print_table_start(); + php_info_print_table_row(2, "EXIF Support", "enabled"); + php_info_print_table_row(2, "EXIF Version", PHP_EXIF_VERSION); + php_info_print_table_row(2, "Supported EXIF Version", "0220"); + php_info_print_table_row(2, "Supported filetypes", "JPEG,TIFF"); + php_info_print_table_end(); + DISPLAY_INI_ENTRIES(); +} +/* }}} */ + +ZEND_BEGIN_MODULE_GLOBALS(exif) + char * encode_unicode; + char * decode_unicode_be; + char * decode_unicode_le; + char * encode_jis; + char * decode_jis_be; + char * decode_jis_le; +ZEND_END_MODULE_GLOBALS(exif) + +ZEND_DECLARE_MODULE_GLOBALS(exif) +#define EXIF_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(exif, v) + +#if defined(ZTS) && defined(COMPILE_DL_EXIF) +ZEND_TSRMLS_CACHE_DEFINE() +#endif + +/* {{{ PHP_INI + */ + +ZEND_INI_MH(OnUpdateEncode) +{ + if (new_value && ZSTR_LEN(new_value)) { + const zend_encoding **return_list; + size_t return_size; + if (FAILURE == zend_multibyte_parse_encoding_list(ZSTR_VAL(new_value), ZSTR_LEN(new_value), + &return_list, &return_size, 0)) { + php_error_docref(NULL, E_WARNING, "Illegal encoding ignored: '%s'", ZSTR_VAL(new_value)); + return FAILURE; + } + efree(return_list); + } + return OnUpdateString(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage); +} + +ZEND_INI_MH(OnUpdateDecode) +{ + if (new_value) { + const zend_encoding **return_list; + size_t return_size; + if (FAILURE == zend_multibyte_parse_encoding_list(ZSTR_VAL(new_value), ZSTR_LEN(new_value), + &return_list, &return_size, 0)) { + php_error_docref(NULL, E_WARNING, "Illegal encoding ignored: '%s'", ZSTR_VAL(new_value)); + return FAILURE; + } + efree(return_list); + } + return OnUpdateString(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage); +} + +PHP_INI_BEGIN() + STD_PHP_INI_ENTRY("exif.encode_unicode", "ISO-8859-15", PHP_INI_ALL, OnUpdateEncode, encode_unicode, zend_exif_globals, exif_globals) + STD_PHP_INI_ENTRY("exif.decode_unicode_motorola", "UCS-2BE", PHP_INI_ALL, OnUpdateDecode, decode_unicode_be, zend_exif_globals, exif_globals) + STD_PHP_INI_ENTRY("exif.decode_unicode_intel", "UCS-2LE", PHP_INI_ALL, OnUpdateDecode, decode_unicode_le, zend_exif_globals, exif_globals) + STD_PHP_INI_ENTRY("exif.encode_jis", "", PHP_INI_ALL, OnUpdateEncode, encode_jis, zend_exif_globals, exif_globals) + STD_PHP_INI_ENTRY("exif.decode_jis_motorola", "JIS", PHP_INI_ALL, OnUpdateDecode, decode_jis_be, zend_exif_globals, exif_globals) + STD_PHP_INI_ENTRY("exif.decode_jis_intel", "JIS", PHP_INI_ALL, OnUpdateDecode, decode_jis_le, zend_exif_globals, exif_globals) +PHP_INI_END() +/* }}} */ + +/* {{{ PHP_GINIT_FUNCTION + */ +static PHP_GINIT_FUNCTION(exif) +{ +#if defined(COMPILE_DL_EXIF) && defined(ZTS) + ZEND_TSRMLS_CACHE_UPDATE(); +#endif + exif_globals->encode_unicode = NULL; + exif_globals->decode_unicode_be = NULL; + exif_globals->decode_unicode_le = NULL; + exif_globals->encode_jis = NULL; + exif_globals->decode_jis_be = NULL; + exif_globals->decode_jis_le = NULL; +} +/* }}} */ + +/* {{{ PHP_MINIT_FUNCTION(exif) + Get the size of an image as 4-element array */ +PHP_MINIT_FUNCTION(exif) +{ + REGISTER_INI_ENTRIES(); + if (zend_hash_str_exists(&module_registry, "mbstring", sizeof("mbstring")-1)) { + REGISTER_LONG_CONSTANT("EXIF_USE_MBSTRING", 1, CONST_CS | CONST_PERSISTENT); + } else { + REGISTER_LONG_CONSTANT("EXIF_USE_MBSTRING", 0, CONST_CS | CONST_PERSISTENT); + } + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_MSHUTDOWN_FUNCTION + */ +PHP_MSHUTDOWN_FUNCTION(exif) +{ + UNREGISTER_INI_ENTRIES(); + return SUCCESS; +} +/* }}} */ + +/* {{{ exif dependencies */ +static const zend_module_dep exif_module_deps[] = { + ZEND_MOD_REQUIRED("standard") + ZEND_MOD_OPTIONAL("mbstring") + ZEND_MOD_END +}; +/* }}} */ + +/* {{{ exif_module_entry + */ +zend_module_entry exif_module_entry = { + STANDARD_MODULE_HEADER_EX, NULL, + exif_module_deps, + "exif", + exif_functions, + PHP_MINIT(exif), + PHP_MSHUTDOWN(exif), + NULL, NULL, + PHP_MINFO(exif), + PHP_EXIF_VERSION, + PHP_MODULE_GLOBALS(exif), + PHP_GINIT(exif), + NULL, + NULL, + STANDARD_MODULE_PROPERTIES_EX +}; +/* }}} */ + +#ifdef COMPILE_DL_EXIF +ZEND_GET_MODULE(exif) +#endif + +/* {{{ php_strnlen + * get length of string if buffer if less than buffer size or buffer size */ +static size_t php_strnlen(char* str, size_t maxlen) { + size_t len = 0; + + if (str && maxlen && *str) { + do { + len++; + } while (--maxlen && *(++str)); + } + return len; +} +/* }}} */ + +/* {{{ error messages +*/ +static const char * EXIF_ERROR_FILEEOF = "Unexpected end of file reached"; +static const char * EXIF_ERROR_CORRUPT = "File structure corrupted"; +static const char * EXIF_ERROR_THUMBEOF = "Thumbnail goes IFD boundary or end of file reached"; +static const char * EXIF_ERROR_FSREALLOC = "Illegal reallocating of undefined file section"; + +#define EXIF_ERRLOG_FILEEOF(ImageInfo) exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "%s", EXIF_ERROR_FILEEOF); +#define EXIF_ERRLOG_CORRUPT(ImageInfo) exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "%s", EXIF_ERROR_CORRUPT); +#define EXIF_ERRLOG_THUMBEOF(ImageInfo) exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "%s", EXIF_ERROR_THUMBEOF); +#define EXIF_ERRLOG_FSREALLOC(ImageInfo) exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "%s", EXIF_ERROR_FSREALLOC); +/* }}} */ + +/* {{{ format description defines + Describes format descriptor +*/ +static int php_tiff_bytes_per_format[] = {0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8, 1}; +#define NUM_FORMATS 13 + +#define TAG_FMT_BYTE 1 +#define TAG_FMT_STRING 2 +#define TAG_FMT_USHORT 3 +#define TAG_FMT_ULONG 4 +#define TAG_FMT_URATIONAL 5 +#define TAG_FMT_SBYTE 6 +#define TAG_FMT_UNDEFINED 7 +#define TAG_FMT_SSHORT 8 +#define TAG_FMT_SLONG 9 +#define TAG_FMT_SRATIONAL 10 +#define TAG_FMT_SINGLE 11 +#define TAG_FMT_DOUBLE 12 +#define TAG_FMT_IFD 13 + +#ifdef EXIF_DEBUG +static char *exif_get_tagformat(int format) +{ + switch(format) { + case TAG_FMT_BYTE: return "BYTE"; + case TAG_FMT_STRING: return "STRING"; + case TAG_FMT_USHORT: return "USHORT"; + case TAG_FMT_ULONG: return "ULONG"; + case TAG_FMT_URATIONAL: return "URATIONAL"; + case TAG_FMT_SBYTE: return "SBYTE"; + case TAG_FMT_UNDEFINED: return "UNDEFINED"; + case TAG_FMT_SSHORT: return "SSHORT"; + case TAG_FMT_SLONG: return "SLONG"; + case TAG_FMT_SRATIONAL: return "SRATIONAL"; + case TAG_FMT_SINGLE: return "SINGLE"; + case TAG_FMT_DOUBLE: return "DOUBLE"; + case TAG_FMT_IFD: return "IFD"; + } + return "*Illegal"; +} +#endif + +/* Describes tag values */ +#define TAG_GPS_VERSION_ID 0x0000 +#define TAG_GPS_LATITUDE_REF 0x0001 +#define TAG_GPS_LATITUDE 0x0002 +#define TAG_GPS_LONGITUDE_REF 0x0003 +#define TAG_GPS_LONGITUDE 0x0004 +#define TAG_GPS_ALTITUDE_REF 0x0005 +#define TAG_GPS_ALTITUDE 0x0006 +#define TAG_GPS_TIME_STAMP 0x0007 +#define TAG_GPS_SATELLITES 0x0008 +#define TAG_GPS_STATUS 0x0009 +#define TAG_GPS_MEASURE_MODE 0x000A +#define TAG_GPS_DOP 0x000B +#define TAG_GPS_SPEED_REF 0x000C +#define TAG_GPS_SPEED 0x000D +#define TAG_GPS_TRACK_REF 0x000E +#define TAG_GPS_TRACK 0x000F +#define TAG_GPS_IMG_DIRECTION_REF 0x0010 +#define TAG_GPS_IMG_DIRECTION 0x0011 +#define TAG_GPS_MAP_DATUM 0x0012 +#define TAG_GPS_DEST_LATITUDE_REF 0x0013 +#define TAG_GPS_DEST_LATITUDE 0x0014 +#define TAG_GPS_DEST_LONGITUDE_REF 0x0015 +#define TAG_GPS_DEST_LONGITUDE 0x0016 +#define TAG_GPS_DEST_BEARING_REF 0x0017 +#define TAG_GPS_DEST_BEARING 0x0018 +#define TAG_GPS_DEST_DISTANCE_REF 0x0019 +#define TAG_GPS_DEST_DISTANCE 0x001A +#define TAG_GPS_PROCESSING_METHOD 0x001B +#define TAG_GPS_AREA_INFORMATION 0x001C +#define TAG_GPS_DATE_STAMP 0x001D +#define TAG_GPS_DIFFERENTIAL 0x001E +#define TAG_TIFF_COMMENT 0x00FE /* SHOUDLNT HAPPEN */ +#define TAG_NEW_SUBFILE 0x00FE /* New version of subfile tag */ +#define TAG_SUBFILE_TYPE 0x00FF /* Old version of subfile tag */ +#define TAG_IMAGEWIDTH 0x0100 +#define TAG_IMAGEHEIGHT 0x0101 +#define TAG_BITS_PER_SAMPLE 0x0102 +#define TAG_COMPRESSION 0x0103 +#define TAG_PHOTOMETRIC_INTERPRETATION 0x0106 +#define TAG_TRESHHOLDING 0x0107 +#define TAG_CELL_WIDTH 0x0108 +#define TAG_CELL_HEIGHT 0x0109 +#define TAG_FILL_ORDER 0x010A +#define TAG_DOCUMENT_NAME 0x010D +#define TAG_IMAGE_DESCRIPTION 0x010E +#define TAG_MAKE 0x010F +#define TAG_MODEL 0x0110 +#define TAG_STRIP_OFFSETS 0x0111 +#define TAG_ORIENTATION 0x0112 +#define TAG_SAMPLES_PER_PIXEL 0x0115 +#define TAG_ROWS_PER_STRIP 0x0116 +#define TAG_STRIP_BYTE_COUNTS 0x0117 +#define TAG_MIN_SAMPPLE_VALUE 0x0118 +#define TAG_MAX_SAMPLE_VALUE 0x0119 +#define TAG_X_RESOLUTION 0x011A +#define TAG_Y_RESOLUTION 0x011B +#define TAG_PLANAR_CONFIGURATION 0x011C +#define TAG_PAGE_NAME 0x011D +#define TAG_X_POSITION 0x011E +#define TAG_Y_POSITION 0x011F +#define TAG_FREE_OFFSETS 0x0120 +#define TAG_FREE_BYTE_COUNTS 0x0121 +#define TAG_GRAY_RESPONSE_UNIT 0x0122 +#define TAG_GRAY_RESPONSE_CURVE 0x0123 +#define TAG_RESOLUTION_UNIT 0x0128 +#define TAG_PAGE_NUMBER 0x0129 +#define TAG_TRANSFER_FUNCTION 0x012D +#define TAG_SOFTWARE 0x0131 +#define TAG_DATETIME 0x0132 +#define TAG_ARTIST 0x013B +#define TAG_HOST_COMPUTER 0x013C +#define TAG_PREDICTOR 0x013D +#define TAG_WHITE_POINT 0x013E +#define TAG_PRIMARY_CHROMATICITIES 0x013F +#define TAG_COLOR_MAP 0x0140 +#define TAG_HALFTONE_HINTS 0x0141 +#define TAG_TILE_WIDTH 0x0142 +#define TAG_TILE_LENGTH 0x0143 +#define TAG_TILE_OFFSETS 0x0144 +#define TAG_TILE_BYTE_COUNTS 0x0145 +#define TAG_SUB_IFD 0x014A +#define TAG_INK_SETMPUTER 0x014C +#define TAG_INK_NAMES 0x014D +#define TAG_NUMBER_OF_INKS 0x014E +#define TAG_DOT_RANGE 0x0150 +#define TAG_TARGET_PRINTER 0x0151 +#define TAG_EXTRA_SAMPLE 0x0152 +#define TAG_SAMPLE_FORMAT 0x0153 +#define TAG_S_MIN_SAMPLE_VALUE 0x0154 +#define TAG_S_MAX_SAMPLE_VALUE 0x0155 +#define TAG_TRANSFER_RANGE 0x0156 +#define TAG_JPEG_TABLES 0x015B +#define TAG_JPEG_PROC 0x0200 +#define TAG_JPEG_INTERCHANGE_FORMAT 0x0201 +#define TAG_JPEG_INTERCHANGE_FORMAT_LEN 0x0202 +#define TAG_JPEG_RESTART_INTERVAL 0x0203 +#define TAG_JPEG_LOSSLESS_PREDICTOR 0x0205 +#define TAG_JPEG_POINT_TRANSFORMS 0x0206 +#define TAG_JPEG_Q_TABLES 0x0207 +#define TAG_JPEG_DC_TABLES 0x0208 +#define TAG_JPEG_AC_TABLES 0x0209 +#define TAG_YCC_COEFFICIENTS 0x0211 +#define TAG_YCC_SUB_SAMPLING 0x0212 +#define TAG_YCC_POSITIONING 0x0213 +#define TAG_REFERENCE_BLACK_WHITE 0x0214 +/* 0x0301 - 0x0302 */ +/* 0x0320 */ +/* 0x0343 */ +/* 0x5001 - 0x501B */ +/* 0x5021 - 0x503B */ +/* 0x5090 - 0x5091 */ +/* 0x5100 - 0x5101 */ +/* 0x5110 - 0x5113 */ +/* 0x80E3 - 0x80E6 */ +/* 0x828d - 0x828F */ +#define TAG_COPYRIGHT 0x8298 +#define TAG_EXPOSURETIME 0x829A +#define TAG_FNUMBER 0x829D +#define TAG_EXIF_IFD_POINTER 0x8769 +#define TAG_ICC_PROFILE 0x8773 +#define TAG_EXPOSURE_PROGRAM 0x8822 +#define TAG_SPECTRAL_SENSITY 0x8824 +#define TAG_GPS_IFD_POINTER 0x8825 +#define TAG_ISOSPEED 0x8827 +#define TAG_OPTOELECTRIC_CONVERSION_F 0x8828 +/* 0x8829 - 0x882b */ +#define TAG_EXIFVERSION 0x9000 +#define TAG_DATE_TIME_ORIGINAL 0x9003 +#define TAG_DATE_TIME_DIGITIZED 0x9004 +#define TAG_COMPONENT_CONFIG 0x9101 +#define TAG_COMPRESSED_BITS_PER_PIXEL 0x9102 +#define TAG_SHUTTERSPEED 0x9201 +#define TAG_APERTURE 0x9202 +#define TAG_BRIGHTNESS_VALUE 0x9203 +#define TAG_EXPOSURE_BIAS_VALUE 0x9204 +#define TAG_MAX_APERTURE 0x9205 +#define TAG_SUBJECT_DISTANCE 0x9206 +#define TAG_METRIC_MODULE 0x9207 +#define TAG_LIGHT_SOURCE 0x9208 +#define TAG_FLASH 0x9209 +#define TAG_FOCAL_LENGTH 0x920A +/* 0x920B - 0x920D */ +/* 0x9211 - 0x9216 */ +#define TAG_SUBJECT_AREA 0x9214 +#define TAG_MAKER_NOTE 0x927C +#define TAG_USERCOMMENT 0x9286 +#define TAG_SUB_SEC_TIME 0x9290 +#define TAG_SUB_SEC_TIME_ORIGINAL 0x9291 +#define TAG_SUB_SEC_TIME_DIGITIZED 0x9292 +/* 0x923F */ +/* 0x935C */ +#define TAG_XP_TITLE 0x9C9B +#define TAG_XP_COMMENTS 0x9C9C +#define TAG_XP_AUTHOR 0x9C9D +#define TAG_XP_KEYWORDS 0x9C9E +#define TAG_XP_SUBJECT 0x9C9F +#define TAG_FLASH_PIX_VERSION 0xA000 +#define TAG_COLOR_SPACE 0xA001 +#define TAG_COMP_IMAGE_WIDTH 0xA002 /* compressed images only */ +#define TAG_COMP_IMAGE_HEIGHT 0xA003 +#define TAG_RELATED_SOUND_FILE 0xA004 +#define TAG_INTEROP_IFD_POINTER 0xA005 /* IFD pointer */ +#define TAG_FLASH_ENERGY 0xA20B +#define TAG_SPATIAL_FREQUENCY_RESPONSE 0xA20C +#define TAG_FOCALPLANE_X_RES 0xA20E +#define TAG_FOCALPLANE_Y_RES 0xA20F +#define TAG_FOCALPLANE_RESOLUTION_UNIT 0xA210 +#define TAG_SUBJECT_LOCATION 0xA214 +#define TAG_EXPOSURE_INDEX 0xA215 +#define TAG_SENSING_METHOD 0xA217 +#define TAG_FILE_SOURCE 0xA300 +#define TAG_SCENE_TYPE 0xA301 +#define TAG_CFA_PATTERN 0xA302 +#define TAG_CUSTOM_RENDERED 0xA401 +#define TAG_EXPOSURE_MODE 0xA402 +#define TAG_WHITE_BALANCE 0xA403 +#define TAG_DIGITAL_ZOOM_RATIO 0xA404 +#define TAG_FOCAL_LENGTH_IN_35_MM_FILM 0xA405 +#define TAG_SCENE_CAPTURE_TYPE 0xA406 +#define TAG_GAIN_CONTROL 0xA407 +#define TAG_CONTRAST 0xA408 +#define TAG_SATURATION 0xA409 +#define TAG_SHARPNESS 0xA40A +#define TAG_DEVICE_SETTING_DESCRIPTION 0xA40B +#define TAG_SUBJECT_DISTANCE_RANGE 0xA40C +#define TAG_IMAGE_UNIQUE_ID 0xA420 + +/* Olympus specific tags */ +#define TAG_OLYMPUS_SPECIALMODE 0x0200 +#define TAG_OLYMPUS_JPEGQUAL 0x0201 +#define TAG_OLYMPUS_MACRO 0x0202 +#define TAG_OLYMPUS_DIGIZOOM 0x0204 +#define TAG_OLYMPUS_SOFTWARERELEASE 0x0207 +#define TAG_OLYMPUS_PICTINFO 0x0208 +#define TAG_OLYMPUS_CAMERAID 0x0209 +/* end Olympus specific tags */ + +/* Internal */ +#define TAG_NONE -1 /* note that -1 <> 0xFFFF */ +#define TAG_COMPUTED_VALUE -2 +#define TAG_END_OF_LIST 0xFFFD + +/* Values for TAG_PHOTOMETRIC_INTERPRETATION */ +#define PMI_BLACK_IS_ZERO 0 +#define PMI_WHITE_IS_ZERO 1 +#define PMI_RGB 2 +#define PMI_PALETTE_COLOR 3 +#define PMI_TRANSPARENCY_MASK 4 +#define PMI_SEPARATED 5 +#define PMI_YCBCR 6 +#define PMI_CIELAB 8 + +/* }}} */ + +/* {{{ TabTable[] + */ +typedef const struct { + unsigned short Tag; + char *Desc; +} tag_info_type; + +typedef tag_info_type tag_info_array[]; +typedef tag_info_type *tag_table_type; + +#define TAG_TABLE_END \ + {TAG_NONE, "No tag value"},\ + {TAG_COMPUTED_VALUE, "Computed value"},\ + {TAG_END_OF_LIST, ""} /* Important for exif_get_tagname() IF value != "" function result is != false */ + +static tag_info_array tag_table_IFD = { + { 0x000B, "ACDComment"}, + { 0x00FE, "NewSubFile"}, /* better name it 'ImageType' ? */ + { 0x00FF, "SubFile"}, + { 0x0100, "ImageWidth"}, + { 0x0101, "ImageLength"}, + { 0x0102, "BitsPerSample"}, + { 0x0103, "Compression"}, + { 0x0106, "PhotometricInterpretation"}, + { 0x010A, "FillOrder"}, + { 0x010D, "DocumentName"}, + { 0x010E, "ImageDescription"}, + { 0x010F, "Make"}, + { 0x0110, "Model"}, + { 0x0111, "StripOffsets"}, + { 0x0112, "Orientation"}, + { 0x0115, "SamplesPerPixel"}, + { 0x0116, "RowsPerStrip"}, + { 0x0117, "StripByteCounts"}, + { 0x0118, "MinSampleValue"}, + { 0x0119, "MaxSampleValue"}, + { 0x011A, "XResolution"}, + { 0x011B, "YResolution"}, + { 0x011C, "PlanarConfiguration"}, + { 0x011D, "PageName"}, + { 0x011E, "XPosition"}, + { 0x011F, "YPosition"}, + { 0x0120, "FreeOffsets"}, + { 0x0121, "FreeByteCounts"}, + { 0x0122, "GrayResponseUnit"}, + { 0x0123, "GrayResponseCurve"}, + { 0x0124, "T4Options"}, + { 0x0125, "T6Options"}, + { 0x0128, "ResolutionUnit"}, + { 0x0129, "PageNumber"}, + { 0x012D, "TransferFunction"}, + { 0x0131, "Software"}, + { 0x0132, "DateTime"}, + { 0x013B, "Artist"}, + { 0x013C, "HostComputer"}, + { 0x013D, "Predictor"}, + { 0x013E, "WhitePoint"}, + { 0x013F, "PrimaryChromaticities"}, + { 0x0140, "ColorMap"}, + { 0x0141, "HalfToneHints"}, + { 0x0142, "TileWidth"}, + { 0x0143, "TileLength"}, + { 0x0144, "TileOffsets"}, + { 0x0145, "TileByteCounts"}, + { 0x014A, "SubIFD"}, + { 0x014C, "InkSet"}, + { 0x014D, "InkNames"}, + { 0x014E, "NumberOfInks"}, + { 0x0150, "DotRange"}, + { 0x0151, "TargetPrinter"}, + { 0x0152, "ExtraSample"}, + { 0x0153, "SampleFormat"}, + { 0x0154, "SMinSampleValue"}, + { 0x0155, "SMaxSampleValue"}, + { 0x0156, "TransferRange"}, + { 0x0157, "ClipPath"}, + { 0x0158, "XClipPathUnits"}, + { 0x0159, "YClipPathUnits"}, + { 0x015A, "Indexed"}, + { 0x015B, "JPEGTables"}, + { 0x015F, "OPIProxy"}, + { 0x0200, "JPEGProc"}, + { 0x0201, "JPEGInterchangeFormat"}, + { 0x0202, "JPEGInterchangeFormatLength"}, + { 0x0203, "JPEGRestartInterval"}, + { 0x0205, "JPEGLosslessPredictors"}, + { 0x0206, "JPEGPointTransforms"}, + { 0x0207, "JPEGQTables"}, + { 0x0208, "JPEGDCTables"}, + { 0x0209, "JPEGACTables"}, + { 0x0211, "YCbCrCoefficients"}, + { 0x0212, "YCbCrSubSampling"}, + { 0x0213, "YCbCrPositioning"}, + { 0x0214, "ReferenceBlackWhite"}, + { 0x02BC, "ExtensibleMetadataPlatform"}, /* XAP: Extensible Authoring Publishing, obsoleted by XMP: Extensible Metadata Platform */ + { 0x0301, "Gamma"}, + { 0x0302, "ICCProfileDescriptor"}, + { 0x0303, "SRGBRenderingIntent"}, + { 0x0320, "ImageTitle"}, + { 0x5001, "ResolutionXUnit"}, + { 0x5002, "ResolutionYUnit"}, + { 0x5003, "ResolutionXLengthUnit"}, + { 0x5004, "ResolutionYLengthUnit"}, + { 0x5005, "PrintFlags"}, + { 0x5006, "PrintFlagsVersion"}, + { 0x5007, "PrintFlagsCrop"}, + { 0x5008, "PrintFlagsBleedWidth"}, + { 0x5009, "PrintFlagsBleedWidthScale"}, + { 0x500A, "HalftoneLPI"}, + { 0x500B, "HalftoneLPIUnit"}, + { 0x500C, "HalftoneDegree"}, + { 0x500D, "HalftoneShape"}, + { 0x500E, "HalftoneMisc"}, + { 0x500F, "HalftoneScreen"}, + { 0x5010, "JPEGQuality"}, + { 0x5011, "GridSize"}, + { 0x5012, "ThumbnailFormat"}, + { 0x5013, "ThumbnailWidth"}, + { 0x5014, "ThumbnailHeight"}, + { 0x5015, "ThumbnailColorDepth"}, + { 0x5016, "ThumbnailPlanes"}, + { 0x5017, "ThumbnailRawBytes"}, + { 0x5018, "ThumbnailSize"}, + { 0x5019, "ThumbnailCompressedSize"}, + { 0x501A, "ColorTransferFunction"}, + { 0x501B, "ThumbnailData"}, + { 0x5020, "ThumbnailImageWidth"}, + { 0x5021, "ThumbnailImageHeight"}, + { 0x5022, "ThumbnailBitsPerSample"}, + { 0x5023, "ThumbnailCompression"}, + { 0x5024, "ThumbnailPhotometricInterp"}, + { 0x5025, "ThumbnailImageDescription"}, + { 0x5026, "ThumbnailEquipMake"}, + { 0x5027, "ThumbnailEquipModel"}, + { 0x5028, "ThumbnailStripOffsets"}, + { 0x5029, "ThumbnailOrientation"}, + { 0x502A, "ThumbnailSamplesPerPixel"}, + { 0x502B, "ThumbnailRowsPerStrip"}, + { 0x502C, "ThumbnailStripBytesCount"}, + { 0x502D, "ThumbnailResolutionX"}, + { 0x502E, "ThumbnailResolutionY"}, + { 0x502F, "ThumbnailPlanarConfig"}, + { 0x5030, "ThumbnailResolutionUnit"}, + { 0x5031, "ThumbnailTransferFunction"}, + { 0x5032, "ThumbnailSoftwareUsed"}, + { 0x5033, "ThumbnailDateTime"}, + { 0x5034, "ThumbnailArtist"}, + { 0x5035, "ThumbnailWhitePoint"}, + { 0x5036, "ThumbnailPrimaryChromaticities"}, + { 0x5037, "ThumbnailYCbCrCoefficients"}, + { 0x5038, "ThumbnailYCbCrSubsampling"}, + { 0x5039, "ThumbnailYCbCrPositioning"}, + { 0x503A, "ThumbnailRefBlackWhite"}, + { 0x503B, "ThumbnailCopyRight"}, + { 0x5090, "LuminanceTable"}, + { 0x5091, "ChrominanceTable"}, + { 0x5100, "FrameDelay"}, + { 0x5101, "LoopCount"}, + { 0x5110, "PixelUnit"}, + { 0x5111, "PixelPerUnitX"}, + { 0x5112, "PixelPerUnitY"}, + { 0x5113, "PaletteHistogram"}, + { 0x1000, "RelatedImageFileFormat"}, + { 0x800D, "ImageID"}, + { 0x80E3, "Matteing"}, /* obsoleted by ExtraSamples */ + { 0x80E4, "DataType"}, /* obsoleted by SampleFormat */ + { 0x80E5, "ImageDepth"}, + { 0x80E6, "TileDepth"}, + { 0x828D, "CFARepeatPatternDim"}, + { 0x828E, "CFAPattern"}, + { 0x828F, "BatteryLevel"}, + { 0x8298, "Copyright"}, + { 0x829A, "ExposureTime"}, + { 0x829D, "FNumber"}, + { 0x83BB, "IPTC/NAA"}, + { 0x84E3, "IT8RasterPadding"}, + { 0x84E5, "IT8ColorTable"}, + { 0x8649, "ImageResourceInformation"}, /* PhotoShop */ + { 0x8769, "Exif_IFD_Pointer"}, + { 0x8773, "ICC_Profile"}, + { 0x8822, "ExposureProgram"}, + { 0x8824, "SpectralSensity"}, + { 0x8828, "OECF"}, + { 0x8825, "GPS_IFD_Pointer"}, + { 0x8827, "ISOSpeedRatings"}, + { 0x8828, "OECF"}, + { 0x9000, "ExifVersion"}, + { 0x9003, "DateTimeOriginal"}, + { 0x9004, "DateTimeDigitized"}, + { 0x9101, "ComponentsConfiguration"}, + { 0x9102, "CompressedBitsPerPixel"}, + { 0x9201, "ShutterSpeedValue"}, + { 0x9202, "ApertureValue"}, + { 0x9203, "BrightnessValue"}, + { 0x9204, "ExposureBiasValue"}, + { 0x9205, "MaxApertureValue"}, + { 0x9206, "SubjectDistance"}, + { 0x9207, "MeteringMode"}, + { 0x9208, "LightSource"}, + { 0x9209, "Flash"}, + { 0x920A, "FocalLength"}, + { 0x920B, "FlashEnergy"}, /* 0xA20B in JPEG */ + { 0x920C, "SpatialFrequencyResponse"}, /* 0xA20C - - */ + { 0x920D, "Noise"}, + { 0x920E, "FocalPlaneXResolution"}, /* 0xA20E - - */ + { 0x920F, "FocalPlaneYResolution"}, /* 0xA20F - - */ + { 0x9210, "FocalPlaneResolutionUnit"}, /* 0xA210 - - */ + { 0x9211, "ImageNumber"}, + { 0x9212, "SecurityClassification"}, + { 0x9213, "ImageHistory"}, + { 0x9214, "SubjectLocation"}, /* 0xA214 - - */ + { 0x9215, "ExposureIndex"}, /* 0xA215 - - */ + { 0x9216, "TIFF/EPStandardID"}, + { 0x9217, "SensingMethod"}, /* 0xA217 - - */ + { 0x923F, "StoNits"}, + { 0x927C, "MakerNote"}, + { 0x9286, "UserComment"}, + { 0x9290, "SubSecTime"}, + { 0x9291, "SubSecTimeOriginal"}, + { 0x9292, "SubSecTimeDigitized"}, + { 0x935C, "ImageSourceData"}, /* "Adobe Photoshop Document Data Block": 8BIM... */ + { 0x9c9b, "Title" }, /* Win XP specific, Unicode */ + { 0x9c9c, "Comments" }, /* Win XP specific, Unicode */ + { 0x9c9d, "Author" }, /* Win XP specific, Unicode */ + { 0x9c9e, "Keywords" }, /* Win XP specific, Unicode */ + { 0x9c9f, "Subject" }, /* Win XP specific, Unicode, not to be confused with SubjectDistance and SubjectLocation */ + { 0xA000, "FlashPixVersion"}, + { 0xA001, "ColorSpace"}, + { 0xA002, "ExifImageWidth"}, + { 0xA003, "ExifImageLength"}, + { 0xA004, "RelatedSoundFile"}, + { 0xA005, "InteroperabilityOffset"}, + { 0xA20B, "FlashEnergy"}, /* 0x920B in TIFF/EP */ + { 0xA20C, "SpatialFrequencyResponse"}, /* 0x920C - - */ + { 0xA20D, "Noise"}, + { 0xA20E, "FocalPlaneXResolution"}, /* 0x920E - - */ + { 0xA20F, "FocalPlaneYResolution"}, /* 0x920F - - */ + { 0xA210, "FocalPlaneResolutionUnit"}, /* 0x9210 - - */ + { 0xA211, "ImageNumber"}, + { 0xA212, "SecurityClassification"}, + { 0xA213, "ImageHistory"}, + { 0xA214, "SubjectLocation"}, /* 0x9214 - - */ + { 0xA215, "ExposureIndex"}, /* 0x9215 - - */ + { 0xA216, "TIFF/EPStandardID"}, + { 0xA217, "SensingMethod"}, /* 0x9217 - - */ + { 0xA300, "FileSource"}, + { 0xA301, "SceneType"}, + { 0xA302, "CFAPattern"}, + { 0xA401, "CustomRendered"}, + { 0xA402, "ExposureMode"}, + { 0xA403, "WhiteBalance"}, + { 0xA404, "DigitalZoomRatio"}, + { 0xA405, "FocalLengthIn35mmFilm"}, + { 0xA406, "SceneCaptureType"}, + { 0xA407, "GainControl"}, + { 0xA408, "Contrast"}, + { 0xA409, "Saturation"}, + { 0xA40A, "Sharpness"}, + { 0xA40B, "DeviceSettingDescription"}, + { 0xA40C, "SubjectDistanceRange"}, + { 0xA420, "ImageUniqueID"}, + TAG_TABLE_END +} ; + +static tag_info_array tag_table_GPS = { + { 0x0000, "GPSVersion"}, + { 0x0001, "GPSLatitudeRef"}, + { 0x0002, "GPSLatitude"}, + { 0x0003, "GPSLongitudeRef"}, + { 0x0004, "GPSLongitude"}, + { 0x0005, "GPSAltitudeRef"}, + { 0x0006, "GPSAltitude"}, + { 0x0007, "GPSTimeStamp"}, + { 0x0008, "GPSSatellites"}, + { 0x0009, "GPSStatus"}, + { 0x000A, "GPSMeasureMode"}, + { 0x000B, "GPSDOP"}, + { 0x000C, "GPSSpeedRef"}, + { 0x000D, "GPSSpeed"}, + { 0x000E, "GPSTrackRef"}, + { 0x000F, "GPSTrack"}, + { 0x0010, "GPSImgDirectionRef"}, + { 0x0011, "GPSImgDirection"}, + { 0x0012, "GPSMapDatum"}, + { 0x0013, "GPSDestLatitudeRef"}, + { 0x0014, "GPSDestLatitude"}, + { 0x0015, "GPSDestLongitudeRef"}, + { 0x0016, "GPSDestLongitude"}, + { 0x0017, "GPSDestBearingRef"}, + { 0x0018, "GPSDestBearing"}, + { 0x0019, "GPSDestDistanceRef"}, + { 0x001A, "GPSDestDistance"}, + { 0x001B, "GPSProcessingMode"}, + { 0x001C, "GPSAreaInformation"}, + { 0x001D, "GPSDateStamp"}, + { 0x001E, "GPSDifferential"}, + TAG_TABLE_END +}; + +static tag_info_array tag_table_IOP = { + { 0x0001, "InterOperabilityIndex"}, /* should be 'R98' or 'THM' */ + { 0x0002, "InterOperabilityVersion"}, + { 0x1000, "RelatedFileFormat"}, + { 0x1001, "RelatedImageWidth"}, + { 0x1002, "RelatedImageHeight"}, + TAG_TABLE_END +}; + +static tag_info_array tag_table_VND_CANON = { + { 0x0001, "ModeArray"}, /* guess */ + { 0x0004, "ImageInfo"}, /* guess */ + { 0x0006, "ImageType"}, + { 0x0007, "FirmwareVersion"}, + { 0x0008, "ImageNumber"}, + { 0x0009, "OwnerName"}, + { 0x000C, "Camera"}, + { 0x000F, "CustomFunctions"}, + TAG_TABLE_END +}; + +static tag_info_array tag_table_VND_CASIO = { + { 0x0001, "RecordingMode"}, + { 0x0002, "Quality"}, + { 0x0003, "FocusingMode"}, + { 0x0004, "FlashMode"}, + { 0x0005, "FlashIntensity"}, + { 0x0006, "ObjectDistance"}, + { 0x0007, "WhiteBalance"}, + { 0x000A, "DigitalZoom"}, + { 0x000B, "Sharpness"}, + { 0x000C, "Contrast"}, + { 0x000D, "Saturation"}, + { 0x0014, "CCDSensitivity"}, + TAG_TABLE_END +}; + +static tag_info_array tag_table_VND_FUJI = { + { 0x0000, "Version"}, + { 0x1000, "Quality"}, + { 0x1001, "Sharpness"}, + { 0x1002, "WhiteBalance"}, + { 0x1003, "Color"}, + { 0x1004, "Tone"}, + { 0x1010, "FlashMode"}, + { 0x1011, "FlashStrength"}, + { 0x1020, "Macro"}, + { 0x1021, "FocusMode"}, + { 0x1030, "SlowSync"}, + { 0x1031, "PictureMode"}, + { 0x1100, "ContTake"}, + { 0x1300, "BlurWarning"}, + { 0x1301, "FocusWarning"}, + { 0x1302, "AEWarning "}, + TAG_TABLE_END +}; + +static tag_info_array tag_table_VND_NIKON = { + { 0x0003, "Quality"}, + { 0x0004, "ColorMode"}, + { 0x0005, "ImageAdjustment"}, + { 0x0006, "CCDSensitivity"}, + { 0x0007, "WhiteBalance"}, + { 0x0008, "Focus"}, + { 0x000a, "DigitalZoom"}, + { 0x000b, "Converter"}, + TAG_TABLE_END +}; + +static tag_info_array tag_table_VND_NIKON_990 = { + { 0x0001, "Version"}, + { 0x0002, "ISOSetting"}, + { 0x0003, "ColorMode"}, + { 0x0004, "Quality"}, + { 0x0005, "WhiteBalance"}, + { 0x0006, "ImageSharpening"}, + { 0x0007, "FocusMode"}, + { 0x0008, "FlashSetting"}, + { 0x000F, "ISOSelection"}, + { 0x0080, "ImageAdjustment"}, + { 0x0082, "AuxiliaryLens"}, + { 0x0085, "ManualFocusDistance"}, + { 0x0086, "DigitalZoom"}, + { 0x0088, "AFFocusPosition"}, + { 0x0010, "DataDump"}, + TAG_TABLE_END +}; + +static tag_info_array tag_table_VND_OLYMPUS = { + { 0x0200, "SpecialMode"}, + { 0x0201, "JPEGQuality"}, + { 0x0202, "Macro"}, + { 0x0204, "DigitalZoom"}, + { 0x0207, "SoftwareRelease"}, + { 0x0208, "PictureInfo"}, + { 0x0209, "CameraId"}, + { 0x0F00, "DataDump"}, + TAG_TABLE_END +}; + +typedef enum mn_byte_order_t { + MN_ORDER_INTEL = 0, + MN_ORDER_MOTOROLA = 1, + MN_ORDER_NORMAL +} mn_byte_order_t; + +typedef enum mn_offset_mode_t { + MN_OFFSET_NORMAL, + MN_OFFSET_MAKER, + MN_OFFSET_GUESS +} mn_offset_mode_t; + +typedef struct { + tag_table_type tag_table; + char * make; + char * model; + char * id_string; + int id_string_len; + int offset; + mn_byte_order_t byte_order; + mn_offset_mode_t offset_mode; +} maker_note_type; + +static const maker_note_type maker_note_array[] = { + { tag_table_VND_CANON, "Canon", NULL, NULL, 0, 0, MN_ORDER_INTEL, MN_OFFSET_GUESS}, +/* { tag_table_VND_CANON, "Canon", NULL, NULL, 0, 0, MN_ORDER_NORMAL, MN_OFFSET_NORMAL},*/ + { tag_table_VND_CASIO, "CASIO", NULL, NULL, 0, 0, MN_ORDER_MOTOROLA, MN_OFFSET_NORMAL}, + { tag_table_VND_FUJI, "FUJIFILM", NULL, "FUJIFILM\x0C\x00\x00\x00", 12, 12, MN_ORDER_INTEL, MN_OFFSET_MAKER}, + { tag_table_VND_NIKON, "NIKON", NULL, "Nikon\x00\x01\x00", 8, 8, MN_ORDER_NORMAL, MN_OFFSET_NORMAL}, + { tag_table_VND_NIKON_990, "NIKON", NULL, NULL, 0, 0, MN_ORDER_NORMAL, MN_OFFSET_NORMAL}, + { tag_table_VND_OLYMPUS, "OLYMPUS OPTICAL CO.,LTD", NULL, "OLYMP\x00\x01\x00", 8, 8, MN_ORDER_NORMAL, MN_OFFSET_NORMAL}, +}; +/* }}} */ + +/* {{{ exif_get_tagname + Get headername for tag_num or NULL if not defined */ +static char * exif_get_tagname(int tag_num, char *ret, int len, tag_table_type tag_table) +{ + int i, t; + char tmp[32]; + + for (i = 0; (t = tag_table[i].Tag) != TAG_END_OF_LIST; i++) { + if (t == tag_num) { + if (ret && len) { + strlcpy(ret, tag_table[i].Desc, abs(len)); + if (len < 0) { + memset(ret + strlen(ret), ' ', -len - strlen(ret) - 1); + ret[-len - 1] = '\0'; + } + return ret; + } + return tag_table[i].Desc; + } + } + + if (ret && len) { + snprintf(tmp, sizeof(tmp), "UndefinedTag:0x%04X", tag_num); + strlcpy(ret, tmp, abs(len)); + if (len < 0) { + memset(ret + strlen(ret), ' ', -len - strlen(ret) - 1); + ret[-len - 1] = '\0'; + } + return ret; + } + return ""; +} +/* }}} */ + +/* {{{ exif_char_dump + * Do not use! This is a debug function... */ +#ifdef EXIF_DEBUG +static unsigned char* exif_char_dump(unsigned char * addr, int len, int offset) +{ + static unsigned char buf[4096+1]; + static unsigned char tmp[20]; + int c, i, p=0, n = 5+31; + + p += slprintf(buf+p, sizeof(buf)-p, "\nDump Len: %08X (%d)", len, len); + if (len) { + for(i=0; i=32 ? c : '.'; + tmp[(i%16)+1] = '\0'; + } else { + p += slprintf(buf+p, sizeof(buf)-p, " "); + } + if (i%16==15) { + p += slprintf(buf+p, sizeof(buf)-p, " %s", tmp); + if (i>=len) { + break; + } + } + } + } + buf[sizeof(buf)-1] = '\0'; + return buf; +} +#endif +/* }}} */ + +/* {{{ php_jpg_get16 + Get 16 bits motorola order (always) for jpeg header stuff. +*/ +static int php_jpg_get16(void *value) +{ + return (((uchar *)value)[0] << 8) | ((uchar *)value)[1]; +} +/* }}} */ + +/* {{{ php_ifd_get16u + * Convert a 16 bit unsigned value from file's native byte order */ +static int php_ifd_get16u(void *value, int motorola_intel) +{ + if (motorola_intel) { + return (((uchar *)value)[0] << 8) | ((uchar *)value)[1]; + } else { + return (((uchar *)value)[1] << 8) | ((uchar *)value)[0]; + } +} +/* }}} */ + +/* {{{ php_ifd_get16s + * Convert a 16 bit signed value from file's native byte order */ +static signed short php_ifd_get16s(void *value, int motorola_intel) +{ + return (signed short)php_ifd_get16u(value, motorola_intel); +} +/* }}} */ + +/* {{{ php_ifd_get32s + * Convert a 32 bit signed value from file's native byte order */ +static int php_ifd_get32s(void *value, int motorola_intel) +{ + if (motorola_intel) { + return (((char *)value)[0] << 24) + | (((uchar *)value)[1] << 16) + | (((uchar *)value)[2] << 8 ) + | (((uchar *)value)[3] ); + } else { + return (((char *)value)[3] << 24) + | (((uchar *)value)[2] << 16) + | (((uchar *)value)[1] << 8 ) + | (((uchar *)value)[0] ); + } +} +/* }}} */ + +/* {{{ php_ifd_get32u + * Write 32 bit unsigned value to data */ +static unsigned php_ifd_get32u(void *value, int motorola_intel) +{ + return (unsigned)php_ifd_get32s(value, motorola_intel) & 0xffffffff; +} +/* }}} */ + +/* {{{ php_ifd_set16u + * Write 16 bit unsigned value to data */ +static void php_ifd_set16u(char *data, unsigned int value, int motorola_intel) +{ + if (motorola_intel) { + data[0] = (value & 0xFF00) >> 8; + data[1] = (value & 0x00FF); + } else { + data[1] = (value & 0xFF00) >> 8; + data[0] = (value & 0x00FF); + } +} +/* }}} */ + +/* {{{ php_ifd_set32u + * Convert a 32 bit unsigned value from file's native byte order */ +static void php_ifd_set32u(char *data, size_t value, int motorola_intel) +{ + if (motorola_intel) { + data[0] = (value & 0xFF000000) >> 24; + data[1] = (value & 0x00FF0000) >> 16; + data[2] = (value & 0x0000FF00) >> 8; + data[3] = (value & 0x000000FF); + } else { + data[3] = (value & 0xFF000000) >> 24; + data[2] = (value & 0x00FF0000) >> 16; + data[1] = (value & 0x0000FF00) >> 8; + data[0] = (value & 0x000000FF); + } +} +/* }}} */ + +#ifdef EXIF_DEBUG +char * exif_dump_data(int *dump_free, int format, int components, int length, int motorola_intel, char *value_ptr) /* {{{ */ +{ + char *dump; + int len; + + *dump_free = 0; + if (format == TAG_FMT_STRING) { + return value_ptr ? value_ptr : ""; + } + if (format == TAG_FMT_UNDEFINED) { + return "\n"; + } + if (format == TAG_FMT_IFD) { + return ""; + } + if (format == TAG_FMT_SINGLE || format == TAG_FMT_DOUBLE) { + return ""; + } + *dump_free = 1; + if (components > 1) { + len = spprintf(&dump, 0, "(%d,%d) {", components, length); + } else { + len = spprintf(&dump, 0, "{"); + } + while(components > 0) { + switch(format) { + case TAG_FMT_BYTE: + case TAG_FMT_UNDEFINED: + case TAG_FMT_STRING: + case TAG_FMT_SBYTE: + dump = erealloc(dump, len + 4 + 1); + snprintf(dump + len, 4 + 1, "0x%02X", *value_ptr); + len += 4; + value_ptr++; + break; + case TAG_FMT_USHORT: + case TAG_FMT_SSHORT: + dump = erealloc(dump, len + 6 + 1); + snprintf(dump + len, 6 + 1, "0x%04X", php_ifd_get16s(value_ptr, motorola_intel)); + len += 6; + value_ptr += 2; + break; + case TAG_FMT_ULONG: + case TAG_FMT_SLONG: + dump = erealloc(dump, len + 6 + 1); + snprintf(dump + len, 6 + 1, "0x%04X", php_ifd_get32s(value_ptr, motorola_intel)); + len += 6; + value_ptr += 4; + break; + case TAG_FMT_URATIONAL: + case TAG_FMT_SRATIONAL: + dump = erealloc(dump, len + 13 + 1); + snprintf(dump + len, 13 + 1, "0x%04X/0x%04X", php_ifd_get32s(value_ptr, motorola_intel), php_ifd_get32s(value_ptr+4, motorola_intel)); + len += 13; + value_ptr += 8; + break; + } + if (components > 0) { + dump = erealloc(dump, len + 2 + 1); + snprintf(dump + len, 2 + 1, ", "); + len += 2; + components--; + } else{ + break; + } + } + dump = erealloc(dump, len + 1 + 1); + snprintf(dump + len, 1 + 1, "}"); + return dump; +} +/* }}} */ +#endif + +/* {{{ exif_convert_any_format + * Evaluate number, be it int, rational, or float from directory. */ +static double exif_convert_any_format(void *value, int format, int motorola_intel) +{ + int s_den; + unsigned u_den; + + switch(format) { + case TAG_FMT_SBYTE: return *(signed char *)value; + case TAG_FMT_BYTE: return *(uchar *)value; + + case TAG_FMT_USHORT: return php_ifd_get16u(value, motorola_intel); + case TAG_FMT_ULONG: return php_ifd_get32u(value, motorola_intel); + + case TAG_FMT_URATIONAL: + u_den = php_ifd_get32u(4+(char *)value, motorola_intel); + if (u_den == 0) { + return 0; + } else { + return (double)php_ifd_get32u(value, motorola_intel) / u_den; + } + + case TAG_FMT_SRATIONAL: + s_den = php_ifd_get32s(4+(char *)value, motorola_intel); + if (s_den == 0) { + return 0; + } else { + return (double)php_ifd_get32s(value, motorola_intel) / s_den; + } + + case TAG_FMT_SSHORT: return (signed short)php_ifd_get16u(value, motorola_intel); + case TAG_FMT_SLONG: return php_ifd_get32s(value, motorola_intel); + + /* Not sure if this is correct (never seen float used in Exif format) */ + case TAG_FMT_SINGLE: +#ifdef EXIF_DEBUG + php_error_docref(NULL, E_NOTICE, "Found value of type single"); +#endif + return (double)*(float *)value; + case TAG_FMT_DOUBLE: +#ifdef EXIF_DEBUG + php_error_docref(NULL, E_NOTICE, "Found value of type double"); +#endif + return *(double *)value; + } + return 0; +} +/* }}} */ + +/* {{{ exif_convert_any_to_int + * Evaluate number, be it int, rational, or float from directory. */ +static size_t exif_convert_any_to_int(void *value, int format, int motorola_intel) +{ + int s_den; + unsigned u_den; + + switch(format) { + case TAG_FMT_SBYTE: return *(signed char *)value; + case TAG_FMT_BYTE: return *(uchar *)value; + + case TAG_FMT_USHORT: return php_ifd_get16u(value, motorola_intel); + case TAG_FMT_ULONG: return php_ifd_get32u(value, motorola_intel); + + case TAG_FMT_URATIONAL: + u_den = php_ifd_get32u(4+(char *)value, motorola_intel); + if (u_den == 0) { + return 0; + } else { + return php_ifd_get32u(value, motorola_intel) / u_den; + } + + case TAG_FMT_SRATIONAL: + s_den = php_ifd_get32s(4+(char *)value, motorola_intel); + if (s_den == 0) { + return 0; + } else { + return (size_t)((double)php_ifd_get32s(value, motorola_intel) / s_den); + } + + case TAG_FMT_SSHORT: return php_ifd_get16u(value, motorola_intel); + case TAG_FMT_SLONG: return php_ifd_get32s(value, motorola_intel); + + /* Not sure if this is correct (never seen float used in Exif format) */ + case TAG_FMT_SINGLE: +#ifdef EXIF_DEBUG + php_error_docref(NULL, E_NOTICE, "Found value of type single"); +#endif + return (size_t)*(float *)value; + case TAG_FMT_DOUBLE: +#ifdef EXIF_DEBUG + php_error_docref(NULL, E_NOTICE, "Found value of type double"); +#endif + return (size_t)*(double *)value; + } + return 0; +} +/* }}} */ + +/* {{{ struct image_info_value, image_info_list +*/ +#ifndef WORD +#define WORD unsigned short +#endif +#ifndef DWORD +#define DWORD unsigned int +#endif + +typedef struct { + int num; + int den; +} signed_rational; + +typedef struct { + unsigned int num; + unsigned int den; +} unsigned_rational; + +typedef union _image_info_value { + char *s; + unsigned u; + int i; + float f; + double d; + signed_rational sr; + unsigned_rational ur; + union _image_info_value *list; +} image_info_value; + +typedef struct { + WORD tag; + WORD format; + DWORD length; + DWORD dummy; /* value ptr of tiff directory entry */ + char *name; + image_info_value value; +} image_info_data; + +typedef struct { + int count; + image_info_data *list; +} image_info_list; +/* }}} */ + +/* {{{ exif_get_sectionname + Returns the name of a section +*/ +#define SECTION_FILE 0 +#define SECTION_COMPUTED 1 +#define SECTION_ANY_TAG 2 +#define SECTION_IFD0 3 +#define SECTION_THUMBNAIL 4 +#define SECTION_COMMENT 5 +#define SECTION_APP0 6 +#define SECTION_EXIF 7 +#define SECTION_FPIX 8 +#define SECTION_GPS 9 +#define SECTION_INTEROP 10 +#define SECTION_APP12 11 +#define SECTION_WINXP 12 +#define SECTION_MAKERNOTE 13 +#define SECTION_COUNT 14 + +#define FOUND_FILE (1<2) + sections[len-2] = '\0'; + return sections; +} +/* }}} */ + +/* {{{ struct image_info_type + This structure stores Exif header image elements in a simple manner + Used to store camera data as extracted from the various ways that it can be + stored in a nexif header +*/ + +typedef struct { + int type; + size_t size; + uchar *data; +} file_section; + +typedef struct { + int count; + file_section *list; +} file_section_list; + +typedef struct { + image_filetype filetype; + size_t width, height; + size_t size; + size_t offset; + char *data; +} thumbnail_data; + +typedef struct { + char *value; + size_t size; + int tag; +} xp_field_type; + +typedef struct { + int count; + xp_field_type *list; +} xp_field_list; + +/* This structure is used to store a section of a Jpeg file. */ +typedef struct { + php_stream *infile; + char *FileName; + time_t FileDateTime; + size_t FileSize; + image_filetype FileType; + int Height, Width; + int IsColor; + + char *make; + char *model; + + float ApertureFNumber; + float ExposureTime; + double FocalplaneUnits; + float CCDWidth; + double FocalplaneXRes; + size_t ExifImageWidth; + float FocalLength; + float Distance; + + int motorola_intel; /* 1 Motorola; 0 Intel */ + + char *UserComment; + int UserCommentLength; + char *UserCommentEncoding; + char *encode_unicode; + char *decode_unicode_be; + char *decode_unicode_le; + char *encode_jis; + char *decode_jis_be; + char *decode_jis_le; + char *Copyright;/* EXIF standard defines Copyright as " [ '\0' ] ['\0']" */ + char *CopyrightPhotographer; + char *CopyrightEditor; + + xp_field_list xp_fields; + + thumbnail_data Thumbnail; + /* other */ + int sections_found; /* FOUND_ */ + image_info_list info_list[SECTION_COUNT]; + /* for parsing */ + int read_thumbnail; + int read_all; + int ifd_nesting_level; + /* internal */ + file_section_list file; +} image_info_type; +/* }}} */ + +/* {{{ exif_error_docref */ +static void exif_error_docref(const char *docref EXIFERR_DC, const image_info_type *ImageInfo, int type, const char *format, ...) +{ + va_list args; + + va_start(args, format); +#ifdef EXIF_DEBUG + { + char *buf; + + spprintf(&buf, 0, "%s(%d): %s", _file, _line, format); + php_verror(docref, ImageInfo->FileName?ImageInfo->FileName:"", type, buf, args); + efree(buf); + } +#else + php_verror(docref, ImageInfo->FileName?ImageInfo->FileName:"", type, format, args); +#endif + va_end(args); +} +/* }}} */ + +/* {{{ jpeg_sof_info + */ +typedef struct { + int bits_per_sample; + size_t width; + size_t height; + int num_components; +} jpeg_sof_info; +/* }}} */ + +/* {{{ exif_file_sections_add + Add a file_section to image_info + returns the used block or -1. if size>0 and data == NULL buffer of size is allocated +*/ +static int exif_file_sections_add(image_info_type *ImageInfo, int type, size_t size, uchar *data) +{ + file_section *tmp; + int count = ImageInfo->file.count; + + tmp = safe_erealloc(ImageInfo->file.list, (count+1), sizeof(file_section), 0); + ImageInfo->file.list = tmp; + ImageInfo->file.list[count].type = 0xFFFF; + ImageInfo->file.list[count].data = NULL; + ImageInfo->file.list[count].size = 0; + ImageInfo->file.count = count+1; + if (!size) { + data = NULL; + } else if (data == NULL) { + data = safe_emalloc(size, 1, 0); + } + ImageInfo->file.list[count].type = type; + ImageInfo->file.list[count].data = data; + ImageInfo->file.list[count].size = size; + return count; +} +/* }}} */ + +/* {{{ exif_file_sections_realloc + Reallocate a file section returns 0 on success and -1 on failure +*/ +static int exif_file_sections_realloc(image_info_type *ImageInfo, int section_index, size_t size) +{ + void *tmp; + + /* This is not a malloc/realloc check. It is a plausibility check for the + * function parameters (requirements engineering). + */ + if (section_index >= ImageInfo->file.count) { + EXIF_ERRLOG_FSREALLOC(ImageInfo) + return -1; + } + tmp = safe_erealloc(ImageInfo->file.list[section_index].data, 1, size, 0); + ImageInfo->file.list[section_index].data = tmp; + ImageInfo->file.list[section_index].size = size; + return 0; +} +/* }}} */ + +/* {{{ exif_file_section_free + Discard all file_sections in ImageInfo +*/ +static int exif_file_sections_free(image_info_type *ImageInfo) +{ + int i; + + if (ImageInfo->file.count) { + for (i=0; ifile.count; i++) { + EFREE_IF(ImageInfo->file.list[i].data); + } + } + EFREE_IF(ImageInfo->file.list); + ImageInfo->file.count = 0; + return TRUE; +} +/* }}} */ + +/* {{{ exif_iif_add_value + Add a value to image_info +*/ +static void exif_iif_add_value(image_info_type *image_info, int section_index, char *name, int tag, int format, int length, void* value, int motorola_intel) +{ + size_t idex; + void *vptr; + image_info_value *info_value; + image_info_data *info_data; + image_info_data *list; + + if (length < 0) { + return; + } + + list = safe_erealloc(image_info->info_list[section_index].list, (image_info->info_list[section_index].count+1), sizeof(image_info_data), 0); + image_info->info_list[section_index].list = list; + + info_data = &image_info->info_list[section_index].list[image_info->info_list[section_index].count]; + memset(info_data, 0, sizeof(image_info_data)); + info_data->tag = tag; + info_data->format = format; + info_data->length = length; + info_data->name = estrdup(name); + info_value = &info_data->value; + + switch (format) { + case TAG_FMT_STRING: + if (value) { + length = php_strnlen(value, length); + info_value->s = estrndup(value, length); + info_data->length = length; + } else { + info_data->length = 0; + info_value->s = estrdup(""); + } + break; + + default: + /* Standard says more types possible but skip them... + * but allow users to handle data if they know how to + * So not return but use type UNDEFINED + * return; + */ + info_data->tag = TAG_FMT_UNDEFINED;/* otherwise not freed from memory */ + case TAG_FMT_SBYTE: + case TAG_FMT_BYTE: + /* in contrast to strings bytes do not need to allocate buffer for NULL if length==0 */ + if (!length) + break; + case TAG_FMT_UNDEFINED: + if (value) { + if (tag == TAG_MAKER_NOTE) { + length = MIN(length, strlen(value)); + } + + /* do not recompute length here */ + info_value->s = estrndup(value, length); + info_data->length = length; + } else { + info_data->length = 0; + info_value->s = estrdup(""); + } + break; + + case TAG_FMT_USHORT: + case TAG_FMT_ULONG: + case TAG_FMT_URATIONAL: + case TAG_FMT_SSHORT: + case TAG_FMT_SLONG: + case TAG_FMT_SRATIONAL: + case TAG_FMT_SINGLE: + case TAG_FMT_DOUBLE: + if (length==0) { + break; + } else + if (length>1) { + info_value->list = safe_emalloc(length, sizeof(image_info_value), 0); + } else { + info_value = &info_data->value; + } + for (idex=0,vptr=value; idex<(size_t)length; idex++,vptr=(char *) vptr + php_tiff_bytes_per_format[format]) { + if (length>1) { + info_value = &info_data->value.list[idex]; + } + switch (format) { + case TAG_FMT_USHORT: + info_value->u = php_ifd_get16u(vptr, motorola_intel); + break; + + case TAG_FMT_ULONG: + info_value->u = php_ifd_get32u(vptr, motorola_intel); + break; + + case TAG_FMT_URATIONAL: + info_value->ur.num = php_ifd_get32u(vptr, motorola_intel); + info_value->ur.den = php_ifd_get32u(4+(char *)vptr, motorola_intel); + break; + + case TAG_FMT_SSHORT: + info_value->i = php_ifd_get16s(vptr, motorola_intel); + break; + + case TAG_FMT_SLONG: + info_value->i = php_ifd_get32s(vptr, motorola_intel); + break; + + case TAG_FMT_SRATIONAL: + info_value->sr.num = php_ifd_get32u(vptr, motorola_intel); + info_value->sr.den = php_ifd_get32u(4+(char *)vptr, motorola_intel); + break; + + case TAG_FMT_SINGLE: +#ifdef EXIF_DEBUG + php_error_docref(NULL, E_WARNING, "Found value of type single"); +#endif + info_value->f = *(float *)value; + + case TAG_FMT_DOUBLE: +#ifdef EXIF_DEBUG + php_error_docref(NULL, E_WARNING, "Found value of type double"); +#endif + info_value->d = *(double *)value; + break; + } + } + } + image_info->sections_found |= 1<info_list[section_index].count++; +} +/* }}} */ + +/* {{{ exif_iif_add_tag + Add a tag from IFD to image_info +*/ +static void exif_iif_add_tag(image_info_type *image_info, int section_index, char *name, int tag, int format, size_t length, void* value) +{ + exif_iif_add_value(image_info, section_index, name, tag, format, (int)length, value, image_info->motorola_intel); +} +/* }}} */ + +/* {{{ exif_iif_add_int + Add an int value to image_info +*/ +static void exif_iif_add_int(image_info_type *image_info, int section_index, char *name, int value) +{ + image_info_data *info_data; + image_info_data *list; + + list = safe_erealloc(image_info->info_list[section_index].list, (image_info->info_list[section_index].count+1), sizeof(image_info_data), 0); + image_info->info_list[section_index].list = list; + + info_data = &image_info->info_list[section_index].list[image_info->info_list[section_index].count]; + info_data->tag = TAG_NONE; + info_data->format = TAG_FMT_SLONG; + info_data->length = 1; + info_data->name = estrdup(name); + info_data->value.i = value; + image_info->sections_found |= 1<info_list[section_index].count++; +} +/* }}} */ + +/* {{{ exif_iif_add_str + Add a string value to image_info MUST BE NUL TERMINATED +*/ +static void exif_iif_add_str(image_info_type *image_info, int section_index, char *name, char *value) +{ + image_info_data *info_data; + image_info_data *list; + + if (value) { + list = safe_erealloc(image_info->info_list[section_index].list, (image_info->info_list[section_index].count+1), sizeof(image_info_data), 0); + image_info->info_list[section_index].list = list; + info_data = &image_info->info_list[section_index].list[image_info->info_list[section_index].count]; + info_data->tag = TAG_NONE; + info_data->format = TAG_FMT_STRING; + info_data->length = 1; + info_data->name = estrdup(name); + info_data->value.s = estrdup(value); + image_info->sections_found |= 1<info_list[section_index].count++; + } +} +/* }}} */ + +/* {{{ exif_iif_add_fmt + Add a format string value to image_info MUST BE NUL TERMINATED +*/ +static void exif_iif_add_fmt(image_info_type *image_info, int section_index, char *name, char *value, ...) +{ + char *tmp; + va_list arglist; + + va_start(arglist, value); + if (value) { + vspprintf(&tmp, 0, value, arglist); + exif_iif_add_str(image_info, section_index, name, tmp); + efree(tmp); + } + va_end(arglist); +} +/* }}} */ + +/* {{{ exif_iif_add_str + Add a string value to image_info MUST BE NUL TERMINATED +*/ +static void exif_iif_add_buffer(image_info_type *image_info, int section_index, char *name, int length, char *value) +{ + image_info_data *info_data; + image_info_data *list; + + if (value) { + list = safe_erealloc(image_info->info_list[section_index].list, (image_info->info_list[section_index].count+1), sizeof(image_info_data), 0); + image_info->info_list[section_index].list = list; + info_data = &image_info->info_list[section_index].list[image_info->info_list[section_index].count]; + info_data->tag = TAG_NONE; + info_data->format = TAG_FMT_UNDEFINED; + info_data->length = length; + info_data->name = estrdup(name); + info_data->value.s = safe_emalloc(length, 1, 1); + memcpy(info_data->value.s, value, length); + info_data->value.s[length] = 0; + image_info->sections_found |= 1<info_list[section_index].count++; + } +} +/* }}} */ + +/* {{{ exif_iif_free + Free memory allocated for image_info +*/ +static void exif_iif_free(image_info_type *image_info, int section_index) { + int i; + void *f; /* faster */ + + if (image_info->info_list[section_index].count) { + for (i=0; i < image_info->info_list[section_index].count; i++) { + if ((f=image_info->info_list[section_index].list[i].name) != NULL) { + efree(f); + } + switch(image_info->info_list[section_index].list[i].format) { + case TAG_FMT_SBYTE: + case TAG_FMT_BYTE: + /* in contrast to strings bytes do not need to allocate buffer for NULL if length==0 */ + if (image_info->info_list[section_index].list[i].length<1) + break; + default: + case TAG_FMT_UNDEFINED: + case TAG_FMT_STRING: + if ((f=image_info->info_list[section_index].list[i].value.s) != NULL) { + efree(f); + } + break; + + case TAG_FMT_USHORT: + case TAG_FMT_ULONG: + case TAG_FMT_URATIONAL: + case TAG_FMT_SSHORT: + case TAG_FMT_SLONG: + case TAG_FMT_SRATIONAL: + case TAG_FMT_SINGLE: + case TAG_FMT_DOUBLE: + /* nothing to do here */ + if (image_info->info_list[section_index].list[i].length > 1) { + if ((f=image_info->info_list[section_index].list[i].value.list) != NULL) { + efree(f); + } + } + break; + } + } + } + EFREE_IF(image_info->info_list[section_index].list); +} +/* }}} */ + +/* {{{ add_assoc_image_info + * Add image_info to associative array value. */ +static void add_assoc_image_info(zval *value, int sub_array, image_info_type *image_info, int section_index) +{ + char buffer[64], *val, *name, uname[64]; + int i, ap, l, b, idx=0, unknown=0; +#ifdef EXIF_DEBUG + int info_tag; +#endif + image_info_value *info_value; + image_info_data *info_data; + zval tmpi, array; + +#ifdef EXIF_DEBUG +/* php_error_docref(NULL, E_NOTICE, "Adding %d infos from section %s", image_info->info_list[section_index].count, exif_get_sectionname(section_index));*/ +#endif + if (image_info->info_list[section_index].count) { + if (sub_array) { + array_init(&tmpi); + } else { + ZVAL_COPY_VALUE(&tmpi, value); + } + + for(i=0; iinfo_list[section_index].count; i++) { + info_data = &image_info->info_list[section_index].list[i]; +#ifdef EXIF_DEBUG + info_tag = info_data->tag; /* conversion */ +#endif + info_value = &info_data->value; + if (!(name = info_data->name)) { + snprintf(uname, sizeof(uname), "%d", unknown++); + name = uname; + } +#ifdef EXIF_DEBUG +/* php_error_docref(NULL, E_NOTICE, "Adding infos: tag(0x%04X,%12s,L=0x%04X): %s", info_tag, exif_get_tagname(info_tag, buffer, -12, exif_get_tag_table(section_index)), info_data->length, info_data->format==TAG_FMT_STRING?(info_value&&info_value->s?info_value->s:""):exif_get_tagformat(info_data->format));*/ +#endif + if (info_data->length==0) { + add_assoc_null(&tmpi, name); + } else { + switch (info_data->format) { + default: + /* Standard says more types possible but skip them... + * but allow users to handle data if they know how to + * So not return but use type UNDEFINED + * return; + */ + case TAG_FMT_BYTE: + case TAG_FMT_SBYTE: + case TAG_FMT_UNDEFINED: + if (!info_value->s) { + add_assoc_stringl(&tmpi, name, "", 0); + } else { + add_assoc_stringl(&tmpi, name, info_value->s, info_data->length); + } + break; + + case TAG_FMT_STRING: + if (!(val = info_value->s)) { + val = ""; + } + if (section_index==SECTION_COMMENT) { + add_index_string(&tmpi, idx++, val); + } else { + add_assoc_string(&tmpi, name, val); + } + break; + + case TAG_FMT_URATIONAL: + case TAG_FMT_SRATIONAL: + /*case TAG_FMT_BYTE: + case TAG_FMT_SBYTE:*/ + case TAG_FMT_USHORT: + case TAG_FMT_SSHORT: + case TAG_FMT_SINGLE: + case TAG_FMT_DOUBLE: + case TAG_FMT_ULONG: + case TAG_FMT_SLONG: + /* now the rest, first see if it becomes an array */ + if ((l = info_data->length) > 1) { + array_init(&array); + } + for(ap=0; ap1) { + info_value = &info_data->value.list[ap]; + } + switch (info_data->format) { + case TAG_FMT_BYTE: + if (l>1) { + info_value = &info_data->value; + for (b=0;bs[b])); + } + break; + } + case TAG_FMT_USHORT: + case TAG_FMT_ULONG: + if (l==1) { + add_assoc_long(&tmpi, name, (int)info_value->u); + } else { + add_index_long(&array, ap, (int)info_value->u); + } + break; + + case TAG_FMT_URATIONAL: + snprintf(buffer, sizeof(buffer), "%i/%i", info_value->ur.num, info_value->ur.den); + if (l==1) { + add_assoc_string(&tmpi, name, buffer); + } else { + add_index_string(&array, ap, buffer); + } + break; + + case TAG_FMT_SBYTE: + if (l>1) { + info_value = &info_data->value; + for (b=0;bs[b]); + } + break; + } + case TAG_FMT_SSHORT: + case TAG_FMT_SLONG: + if (l==1) { + add_assoc_long(&tmpi, name, info_value->i); + } else { + add_index_long(&array, ap, info_value->i); + } + break; + + case TAG_FMT_SRATIONAL: + snprintf(buffer, sizeof(buffer), "%i/%i", info_value->sr.num, info_value->sr.den); + if (l==1) { + add_assoc_string(&tmpi, name, buffer); + } else { + add_index_string(&array, ap, buffer); + } + break; + + case TAG_FMT_SINGLE: + if (l==1) { + add_assoc_double(&tmpi, name, info_value->f); + } else { + add_index_double(&array, ap, info_value->f); + } + break; + + case TAG_FMT_DOUBLE: + if (l==1) { + add_assoc_double(&tmpi, name, info_value->d); + } else { + add_index_double(&array, ap, info_value->d); + } + break; + } + info_value = &info_data->value.list[ap]; + } + if (l>1) { + add_assoc_zval(&tmpi, name, &array); + } + break; + } + } + } + if (sub_array) { + add_assoc_zval(value, exif_get_sectionname(section_index), &tmpi); + } + } +} +/* }}} */ + +/* {{{ Markers + JPEG markers consist of one or more 0xFF bytes, followed by a marker + code byte (which is not an FF). Here are the marker codes of interest + in this program. (See jdmarker.c for a more complete list.) +*/ + +#define M_TEM 0x01 /* temp for arithmetic coding */ +#define M_RES 0x02 /* reserved */ +#define M_SOF0 0xC0 /* Start Of Frame N */ +#define M_SOF1 0xC1 /* N indicates which compression process */ +#define M_SOF2 0xC2 /* Only SOF0-SOF2 are now in common use */ +#define M_SOF3 0xC3 +#define M_DHT 0xC4 +#define M_SOF5 0xC5 /* NB: codes C4 and CC are NOT SOF markers */ +#define M_SOF6 0xC6 +#define M_SOF7 0xC7 +#define M_JPEG 0x08 /* reserved for extensions */ +#define M_SOF9 0xC9 +#define M_SOF10 0xCA +#define M_SOF11 0xCB +#define M_DAC 0xCC /* arithmetic table */ +#define M_SOF13 0xCD +#define M_SOF14 0xCE +#define M_SOF15 0xCF +#define M_RST0 0xD0 /* restart segment */ +#define M_RST1 0xD1 +#define M_RST2 0xD2 +#define M_RST3 0xD3 +#define M_RST4 0xD4 +#define M_RST5 0xD5 +#define M_RST6 0xD6 +#define M_RST7 0xD7 +#define M_SOI 0xD8 /* Start Of Image (beginning of datastream) */ +#define M_EOI 0xD9 /* End Of Image (end of datastream) */ +#define M_SOS 0xDA /* Start Of Scan (begins compressed data) */ +#define M_DQT 0xDB +#define M_DNL 0xDC +#define M_DRI 0xDD +#define M_DHP 0xDE +#define M_EXP 0xDF +#define M_APP0 0xE0 /* JPEG: 'JFIFF' AND (additional 'JFXX') */ +#define M_EXIF 0xE1 /* Exif Attribute Information */ +#define M_APP2 0xE2 /* Flash Pix Extension Data? */ +#define M_APP3 0xE3 +#define M_APP4 0xE4 +#define M_APP5 0xE5 +#define M_APP6 0xE6 +#define M_APP7 0xE7 +#define M_APP8 0xE8 +#define M_APP9 0xE9 +#define M_APP10 0xEA +#define M_APP11 0xEB +#define M_APP12 0xEC +#define M_APP13 0xED /* IPTC International Press Telecommunications Council */ +#define M_APP14 0xEE /* Software, Copyright? */ +#define M_APP15 0xEF +#define M_JPG0 0xF0 +#define M_JPG1 0xF1 +#define M_JPG2 0xF2 +#define M_JPG3 0xF3 +#define M_JPG4 0xF4 +#define M_JPG5 0xF5 +#define M_JPG6 0xF6 +#define M_JPG7 0xF7 +#define M_JPG8 0xF8 +#define M_JPG9 0xF9 +#define M_JPG10 0xFA +#define M_JPG11 0xFB +#define M_JPG12 0xFC +#define M_JPG13 0xFD +#define M_COM 0xFE /* COMment */ + +#define M_PSEUDO 0x123 /* Extra value. */ + +/* }}} */ + +/* {{{ jpeg2000 markers + */ +/* Markers x30 - x3F do not have a segment */ +/* Markers x00, x01, xFE, xC0 - xDF ISO/IEC 10918-1 -> M_ */ +/* Markers xF0 - xF7 ISO/IEC 10918-3 */ +/* Markers xF7 - xF8 ISO/IEC 14495-1 */ +/* XY=Main/Tile-header:(R:required, N:not_allowed, O:optional, L:last_marker) */ +#define JC_SOC 0x4F /* NN, Start of codestream */ +#define JC_SIZ 0x51 /* RN, Image and tile size */ +#define JC_COD 0x52 /* RO, Codeing style defaulte */ +#define JC_COC 0x53 /* OO, Coding style component */ +#define JC_TLM 0x55 /* ON, Tile part length main header */ +#define JC_PLM 0x57 /* ON, Packet length main header */ +#define JC_PLT 0x58 /* NO, Packet length tile part header */ +#define JC_QCD 0x5C /* RO, Quantization default */ +#define JC_QCC 0x5D /* OO, Quantization component */ +#define JC_RGN 0x5E /* OO, Region of interest */ +#define JC_POD 0x5F /* OO, Progression order default */ +#define JC_PPM 0x60 /* ON, Packed packet headers main header */ +#define JC_PPT 0x61 /* NO, Packet packet headers tile part header */ +#define JC_CME 0x64 /* OO, Comment: "LL E " E=0:binary, E=1:ascii */ +#define JC_SOT 0x90 /* NR, Start of tile */ +#define JC_SOP 0x91 /* NO, Start of packeter default */ +#define JC_EPH 0x92 /* NO, End of packet header */ +#define JC_SOD 0x93 /* NL, Start of data */ +#define JC_EOC 0xD9 /* NN, End of codestream */ +/* }}} */ + +/* {{{ exif_process_COM + Process a COM marker. + We want to print out the marker contents as legible text; + we must guard against random junk and varying newline representations. +*/ +static void exif_process_COM (image_info_type *image_info, char *value, size_t length) +{ + exif_iif_add_tag(image_info, SECTION_COMMENT, "Comment", TAG_COMPUTED_VALUE, TAG_FMT_STRING, length-2, value+2); +} +/* }}} */ + +/* {{{ exif_process_CME + Process a CME marker. + We want to print out the marker contents as legible text; + we must guard against random junk and varying newline representations. +*/ +#ifdef EXIF_JPEG2000 +static void exif_process_CME (image_info_type *image_info, char *value, size_t length) +{ + if (length>3) { + switch(value[2]) { + case 0: + exif_iif_add_tag(image_info, SECTION_COMMENT, "Comment", TAG_COMPUTED_VALUE, TAG_FMT_UNDEFINED, length, value); + break; + case 1: + exif_iif_add_tag(image_info, SECTION_COMMENT, "Comment", TAG_COMPUTED_VALUE, TAG_FMT_STRING, length, value); + break; + default: + php_error_docref(NULL, E_NOTICE, "Undefined JPEG2000 comment encoding"); + break; + } + } else { + exif_iif_add_tag(image_info, SECTION_COMMENT, "Comment", TAG_COMPUTED_VALUE, TAG_FMT_UNDEFINED, 0, NULL); + php_error_docref(NULL, E_NOTICE, "JPEG2000 comment section too small"); + } +} +#endif +/* }}} */ + +/* {{{ exif_process_SOFn + * Process a SOFn marker. This is useful for the image dimensions */ +static void exif_process_SOFn (uchar *Data, int marker, jpeg_sof_info *result) +{ +/* 0xFF SOSn SectLen(2) Bits(1) Height(2) Width(2) Channels(1) 3*Channels (1) */ + result->bits_per_sample = Data[2]; + result->height = php_jpg_get16(Data+3); + result->width = php_jpg_get16(Data+5); + result->num_components = Data[7]; + +/* switch (marker) { + case M_SOF0: process = "Baseline"; break; + case M_SOF1: process = "Extended sequential"; break; + case M_SOF2: process = "Progressive"; break; + case M_SOF3: process = "Lossless"; break; + case M_SOF5: process = "Differential sequential"; break; + case M_SOF6: process = "Differential progressive"; break; + case M_SOF7: process = "Differential lossless"; break; + case M_SOF9: process = "Extended sequential, arithmetic coding"; break; + case M_SOF10: process = "Progressive, arithmetic coding"; break; + case M_SOF11: process = "Lossless, arithmetic coding"; break; + case M_SOF13: process = "Differential sequential, arithmetic coding"; break; + case M_SOF14: process = "Differential progressive, arithmetic coding"; break; + case M_SOF15: process = "Differential lossless, arithmetic coding"; break; + default: process = "Unknown"; break; + }*/ +} +/* }}} */ + +/* forward declarations */ +static int exif_process_IFD_in_JPEG(image_info_type *ImageInfo, char *dir_start, char *offset_base, size_t IFDlength, size_t displacement, int section_index); +static int exif_process_IFD_TAG( image_info_type *ImageInfo, char *dir_entry, char *offset_base, size_t IFDlength, size_t displacement, int section_index, int ReadNextIFD, tag_table_type tag_table); + +/* {{{ exif_get_markername + Get name of marker */ +#ifdef EXIF_DEBUG +static char * exif_get_markername(int marker) +{ + switch(marker) { + case 0xC0: return "SOF0"; + case 0xC1: return "SOF1"; + case 0xC2: return "SOF2"; + case 0xC3: return "SOF3"; + case 0xC4: return "DHT"; + case 0xC5: return "SOF5"; + case 0xC6: return "SOF6"; + case 0xC7: return "SOF7"; + case 0xC9: return "SOF9"; + case 0xCA: return "SOF10"; + case 0xCB: return "SOF11"; + case 0xCD: return "SOF13"; + case 0xCE: return "SOF14"; + case 0xCF: return "SOF15"; + case 0xD8: return "SOI"; + case 0xD9: return "EOI"; + case 0xDA: return "SOS"; + case 0xDB: return "DQT"; + case 0xDC: return "DNL"; + case 0xDD: return "DRI"; + case 0xDE: return "DHP"; + case 0xDF: return "EXP"; + case 0xE0: return "APP0"; + case 0xE1: return "EXIF"; + case 0xE2: return "FPIX"; + case 0xE3: return "APP3"; + case 0xE4: return "APP4"; + case 0xE5: return "APP5"; + case 0xE6: return "APP6"; + case 0xE7: return "APP7"; + case 0xE8: return "APP8"; + case 0xE9: return "APP9"; + case 0xEA: return "APP10"; + case 0xEB: return "APP11"; + case 0xEC: return "APP12"; + case 0xED: return "APP13"; + case 0xEE: return "APP14"; + case 0xEF: return "APP15"; + case 0xF0: return "JPG0"; + case 0xFD: return "JPG13"; + case 0xFE: return "COM"; + case 0x01: return "TEM"; + } + return "Unknown"; +} +#endif +/* }}} */ + +/* {{{ proto string exif_tagname(int index) + Get headername for index or false if not defined */ +PHP_FUNCTION(exif_tagname) +{ + zend_long tag; + char *szTemp; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &tag) == FAILURE) { + return; + } + + szTemp = exif_get_tagname(tag, NULL, 0, tag_table_IFD); + + if (tag < 0 || !szTemp || !szTemp[0]) { + RETURN_FALSE; + } + + RETURN_STRING(szTemp) +} +/* }}} */ + +/* {{{ exif_ifd_make_value + * Create a value for an ifd from an info_data pointer */ +static void* exif_ifd_make_value(image_info_data *info_data, int motorola_intel) { + size_t byte_count; + char *value_ptr, *data_ptr; + size_t i; + + image_info_value *info_value; + + byte_count = php_tiff_bytes_per_format[info_data->format] * info_data->length; + value_ptr = safe_emalloc(max(byte_count, 4), 1, 0); + memset(value_ptr, 0, 4); + if (!info_data->length) { + return value_ptr; + } + if (info_data->format == TAG_FMT_UNDEFINED || info_data->format == TAG_FMT_STRING + || (byte_count>1 && (info_data->format == TAG_FMT_BYTE || info_data->format == TAG_FMT_SBYTE)) + ) { + memmove(value_ptr, info_data->value.s, byte_count); + return value_ptr; + } else if (info_data->format == TAG_FMT_BYTE) { + *value_ptr = info_data->value.u; + return value_ptr; + } else if (info_data->format == TAG_FMT_SBYTE) { + *value_ptr = info_data->value.i; + return value_ptr; + } else { + data_ptr = value_ptr; + for(i=0; ilength; i++) { + if (info_data->length==1) { + info_value = &info_data->value; + } else { + info_value = &info_data->value.list[i]; + } + switch(info_data->format) { + case TAG_FMT_USHORT: + php_ifd_set16u(data_ptr, info_value->u, motorola_intel); + data_ptr += 2; + break; + case TAG_FMT_ULONG: + php_ifd_set32u(data_ptr, info_value->u, motorola_intel); + data_ptr += 4; + break; + case TAG_FMT_SSHORT: + php_ifd_set16u(data_ptr, info_value->i, motorola_intel); + data_ptr += 2; + break; + case TAG_FMT_SLONG: + php_ifd_set32u(data_ptr, info_value->i, motorola_intel); + data_ptr += 4; + break; + case TAG_FMT_URATIONAL: + php_ifd_set32u(data_ptr, info_value->sr.num, motorola_intel); + php_ifd_set32u(data_ptr+4, info_value->sr.den, motorola_intel); + data_ptr += 8; + break; + case TAG_FMT_SRATIONAL: + php_ifd_set32u(data_ptr, info_value->ur.num, motorola_intel); + php_ifd_set32u(data_ptr+4, info_value->ur.den, motorola_intel); + data_ptr += 8; + break; + case TAG_FMT_SINGLE: + memmove(data_ptr, &info_value->f, 4); + data_ptr += 4; + break; + case TAG_FMT_DOUBLE: + memmove(data_ptr, &info_value->d, 8); + data_ptr += 8; + break; + } + } + } + return value_ptr; +} +/* }}} */ + +/* {{{ exif_thumbnail_build + * Check and build thumbnail */ +static void exif_thumbnail_build(image_info_type *ImageInfo) { + size_t new_size, new_move, new_value; + char *new_data; + void *value_ptr; + int i, byte_count; + image_info_list *info_list; + image_info_data *info_data; +#ifdef EXIF_DEBUG + char tagname[64]; +#endif + + if (!ImageInfo->read_thumbnail || !ImageInfo->Thumbnail.offset || !ImageInfo->Thumbnail.size) { + return; /* ignore this call */ + } +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Thumbnail: filetype = %d", ImageInfo->Thumbnail.filetype); +#endif + switch(ImageInfo->Thumbnail.filetype) { + default: + case IMAGE_FILETYPE_JPEG: + /* done */ + break; + case IMAGE_FILETYPE_TIFF_II: + case IMAGE_FILETYPE_TIFF_MM: + info_list = &ImageInfo->info_list[SECTION_THUMBNAIL]; + new_size = 8 + 2 + info_list->count * 12 + 4; +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Thumbnail: size of signature + directory(%d): 0x%02X", info_list->count, new_size); +#endif + new_value= new_size; /* offset for ifd values outside ifd directory */ + for (i=0; icount; i++) { + info_data = &info_list->list[i]; + byte_count = php_tiff_bytes_per_format[info_data->format] * info_data->length; + if (byte_count > 4) { + new_size += byte_count; + } + } + new_move = new_size; + new_data = safe_erealloc(ImageInfo->Thumbnail.data, 1, ImageInfo->Thumbnail.size, new_size); + ImageInfo->Thumbnail.data = new_data; + memmove(ImageInfo->Thumbnail.data + new_move, ImageInfo->Thumbnail.data, ImageInfo->Thumbnail.size); + ImageInfo->Thumbnail.size += new_size; + /* fill in data */ + if (ImageInfo->motorola_intel) { + memmove(new_data, "MM\x00\x2a\x00\x00\x00\x08", 8); + } else { + memmove(new_data, "II\x2a\x00\x08\x00\x00\x00", 8); + } + new_data += 8; + php_ifd_set16u(new_data, info_list->count, ImageInfo->motorola_intel); + new_data += 2; + for (i=0; icount; i++) { + info_data = &info_list->list[i]; + byte_count = php_tiff_bytes_per_format[info_data->format] * info_data->length; +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Thumbnail: process tag(x%04X=%s): %s%s (%d bytes)", info_data->tag, exif_get_tagname(info_data->tag, tagname, -12, tag_table_IFD), (info_data->length>1)&&info_data->format!=TAG_FMT_UNDEFINED&&info_data->format!=TAG_FMT_STRING?"ARRAY OF ":"", exif_get_tagformat(info_data->format), byte_count); +#endif + if (info_data->tag==TAG_STRIP_OFFSETS || info_data->tag==TAG_JPEG_INTERCHANGE_FORMAT) { + php_ifd_set16u(new_data + 0, info_data->tag, ImageInfo->motorola_intel); + php_ifd_set16u(new_data + 2, TAG_FMT_ULONG, ImageInfo->motorola_intel); + php_ifd_set32u(new_data + 4, 1, ImageInfo->motorola_intel); + php_ifd_set32u(new_data + 8, new_move, ImageInfo->motorola_intel); + } else { + php_ifd_set16u(new_data + 0, info_data->tag, ImageInfo->motorola_intel); + php_ifd_set16u(new_data + 2, info_data->format, ImageInfo->motorola_intel); + php_ifd_set32u(new_data + 4, info_data->length, ImageInfo->motorola_intel); + value_ptr = exif_ifd_make_value(info_data, ImageInfo->motorola_intel); + if (byte_count <= 4) { + memmove(new_data+8, value_ptr, 4); + } else { + php_ifd_set32u(new_data+8, new_value, ImageInfo->motorola_intel); +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Thumbnail: writing with value offset: 0x%04X + 0x%02X", new_value, byte_count); +#endif + memmove(ImageInfo->Thumbnail.data+new_value, value_ptr, byte_count); + new_value += byte_count; + } + efree(value_ptr); + } + new_data += 12; + } + memset(new_data, 0, 4); /* next ifd pointer */ +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Thumbnail: created"); +#endif + break; + } +} +/* }}} */ + +/* {{{ exif_thumbnail_extract + * Grab the thumbnail, corrected */ +static void exif_thumbnail_extract(image_info_type *ImageInfo, char *offset, size_t length) { + if (ImageInfo->Thumbnail.data) { + exif_error_docref("exif_read_data#error_mult_thumb" EXIFERR_CC, ImageInfo, E_WARNING, "Multiple possible thumbnails"); + return; /* Should not happen */ + } + if (!ImageInfo->read_thumbnail) { + return; /* ignore this call */ + } + /* according to exif2.1, the thumbnail is not supposed to be greater than 64K */ + if (ImageInfo->Thumbnail.size >= 65536 + || ImageInfo->Thumbnail.size <= 0 + || ImageInfo->Thumbnail.offset <= 0 + ) { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Illegal thumbnail size/offset"); + return; + } + /* Check to make sure we are not going to go past the ExifLength */ + if ((ImageInfo->Thumbnail.offset + ImageInfo->Thumbnail.size) > length) { + EXIF_ERRLOG_THUMBEOF(ImageInfo) + return; + } + ImageInfo->Thumbnail.data = estrndup(offset + ImageInfo->Thumbnail.offset, ImageInfo->Thumbnail.size); + exif_thumbnail_build(ImageInfo); +} +/* }}} */ + +/* {{{ exif_process_undefined + * Copy a string/buffer in Exif header to a character string and return length of allocated buffer if any. */ +static int exif_process_undefined(char **result, char *value, size_t byte_count) { + /* we cannot use strlcpy - here the problem is that we have to copy NUL + * chars up to byte_count, we also have to add a single NUL character to + * force end of string. + * estrndup does not return length + */ + if (byte_count) { + (*result) = estrndup(value, byte_count); /* NULL @ byte_count!!! */ + return byte_count+1; + } + return 0; +} +/* }}} */ + +/* {{{ exif_process_string_raw + * Copy a string in Exif header to a character string returns length of allocated buffer if any. */ +static int exif_process_string_raw(char **result, char *value, size_t byte_count) { + /* we cannot use strlcpy - here the problem is that we have to copy NUL + * chars up to byte_count, we also have to add a single NUL character to + * force end of string. + */ + if (byte_count) { + (*result) = safe_emalloc(byte_count, 1, 1); + memcpy(*result, value, byte_count); + (*result)[byte_count] = '\0'; + return byte_count+1; + } + return 0; +} +/* }}} */ + +/* {{{ exif_process_string + * Copy a string in Exif header to a character string and return length of allocated buffer if any. + * In contrast to exif_process_string this function does always return a string buffer */ +static int exif_process_string(char **result, char *value, size_t byte_count) { + /* we cannot use strlcpy - here the problem is that we cannot use strlen to + * determin length of string and we cannot use strlcpy with len=byte_count+1 + * because then we might get into an EXCEPTION if we exceed an allocated + * memory page...so we use php_strnlen in conjunction with memcpy and add the NUL + * char. + * estrdup would sometimes allocate more memory and does not return length + */ + if ((byte_count=php_strnlen(value, byte_count)) > 0) { + return exif_process_undefined(result, value, byte_count); + } + (*result) = estrndup("", 1); /* force empty string */ + return byte_count+1; +} +/* }}} */ + +/* {{{ exif_process_user_comment + * Process UserComment in IFD. */ +static int exif_process_user_comment(image_info_type *ImageInfo, char **pszInfoPtr, char **pszEncoding, char *szValuePtr, int ByteCount) +{ + int a; + char *decode; + size_t len;; + + *pszEncoding = NULL; + /* Copy the comment */ + if (ByteCount>=8) { + const zend_encoding *from, *to; + if (!memcmp(szValuePtr, "UNICODE\0", 8)) { + *pszEncoding = estrdup((const char*)szValuePtr); + szValuePtr = szValuePtr+8; + ByteCount -= 8; + /* First try to detect BOM: ZERO WIDTH NOBREAK SPACE (FEFF 16) + * since we have no encoding support for the BOM yet we skip that. + */ + if (!memcmp(szValuePtr, "\xFE\xFF", 2)) { + decode = "UCS-2BE"; + szValuePtr = szValuePtr+2; + ByteCount -= 2; + } else if (!memcmp(szValuePtr, "\xFF\xFE", 2)) { + decode = "UCS-2LE"; + szValuePtr = szValuePtr+2; + ByteCount -= 2; + } else if (ImageInfo->motorola_intel) { + decode = ImageInfo->decode_unicode_be; + } else { + decode = ImageInfo->decode_unicode_le; + } + to = zend_multibyte_fetch_encoding(ImageInfo->encode_unicode); + from = zend_multibyte_fetch_encoding(decode); + /* XXX this will fail again if encoding_converter returns on error something different than SIZE_MAX */ + if (!to || !from || zend_multibyte_encoding_converter( + (unsigned char**)pszInfoPtr, + &len, + (unsigned char*)szValuePtr, + ByteCount, + to, + from) == (size_t)-1) { + len = exif_process_string_raw(pszInfoPtr, szValuePtr, ByteCount); + } + return len; + } else if (!memcmp(szValuePtr, "ASCII\0\0\0", 8)) { + *pszEncoding = estrdup((const char*)szValuePtr); + szValuePtr = szValuePtr+8; + ByteCount -= 8; + } else if (!memcmp(szValuePtr, "JIS\0\0\0\0\0", 8)) { + /* JIS should be tanslated to MB or we leave it to the user - leave it to the user */ + *pszEncoding = estrdup((const char*)szValuePtr); + szValuePtr = szValuePtr+8; + ByteCount -= 8; + /* XXX this will fail again if encoding_converter returns on error something different than SIZE_MAX */ + to = zend_multibyte_fetch_encoding(ImageInfo->encode_jis); + from = zend_multibyte_fetch_encoding(ImageInfo->motorola_intel ? ImageInfo->decode_jis_be : ImageInfo->decode_jis_le); + if (!to || !from || zend_multibyte_encoding_converter( + (unsigned char**)pszInfoPtr, + &len, + (unsigned char*)szValuePtr, + ByteCount, + to, + from) == (size_t)-1) { + len = exif_process_string_raw(pszInfoPtr, szValuePtr, ByteCount); + } + return len; + } else if (!memcmp(szValuePtr, "\0\0\0\0\0\0\0\0", 8)) { + /* 8 NULL means undefined and should be ASCII... */ + *pszEncoding = estrdup("UNDEFINED"); + szValuePtr = szValuePtr+8; + ByteCount -= 8; + } + } + + /* Olympus has this padded with trailing spaces. Remove these first. */ + if (ByteCount>0) { + for (a=ByteCount-1;a && szValuePtr[a]==' ';a--) { + (szValuePtr)[a] = '\0'; + } + } + + /* normal text without encoding */ + exif_process_string(pszInfoPtr, szValuePtr, ByteCount); + return strlen(*pszInfoPtr); +} +/* }}} */ + +/* {{{ exif_process_unicode + * Process unicode field in IFD. */ +static int exif_process_unicode(image_info_type *ImageInfo, xp_field_type *xp_field, int tag, char *szValuePtr, int ByteCount) +{ + xp_field->tag = tag; + xp_field->value = NULL; + /* XXX this will fail again if encoding_converter returns on error something different than SIZE_MAX */ + if (zend_multibyte_encoding_converter( + (unsigned char**)&xp_field->value, + &xp_field->size, + (unsigned char*)szValuePtr, + ByteCount, + zend_multibyte_fetch_encoding(ImageInfo->encode_unicode), + zend_multibyte_fetch_encoding(ImageInfo->motorola_intel ? ImageInfo->decode_unicode_be : ImageInfo->decode_unicode_le) + ) == (size_t)-1) { + xp_field->size = exif_process_string_raw(&xp_field->value, szValuePtr, ByteCount); + } + return xp_field->size; +} +/* }}} */ + +/* {{{ exif_process_IFD_in_MAKERNOTE + * Process nested IFDs directories in Maker Note. */ +static int exif_process_IFD_in_MAKERNOTE(image_info_type *ImageInfo, char * value_ptr, int value_len, char *offset_base, size_t IFDlength, size_t displacement) +{ + size_t i; + int de, section_index = SECTION_MAKERNOTE; + int NumDirEntries, old_motorola_intel, offset_diff; + const maker_note_type *maker_note; + char *dir_start; + + for (i=0; i<=sizeof(maker_note_array)/sizeof(maker_note_type); i++) { + if (i==sizeof(maker_note_array)/sizeof(maker_note_type)) { +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "No maker note data found. Detected maker: %s (length = %d)", ImageInfo->make, strlen(ImageInfo->make)); +#endif + /* unknown manufacturer, not an error, use it as a string */ + return TRUE; + } + + maker_note = maker_note_array+i; + + /*exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "check (%s,%s)", maker_note->make?maker_note->make:"", maker_note->model?maker_note->model:"");*/ + if (maker_note->make && (!ImageInfo->make || strcmp(maker_note->make, ImageInfo->make))) + continue; + if (maker_note->model && (!ImageInfo->model || strcmp(maker_note->model, ImageInfo->model))) + continue; + if (maker_note->id_string && value_len >= maker_note->id_string_len + && strncmp(maker_note->id_string, value_ptr, maker_note->id_string_len)) + continue; + break; + } + + if (maker_note->offset >= value_len) { + /* Do not go past the value end */ + exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "IFD data too short: 0x%04X offset 0x%04X", value_len, maker_note->offset); + return FALSE; + } + + dir_start = value_ptr + maker_note->offset; + +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Process %s @x%04X + 0x%04X=%d: %s", exif_get_sectionname(section_index), (int)dir_start-(int)offset_base+maker_note->offset+displacement, value_len, value_len, exif_char_dump(value_ptr, value_len, (int)dir_start-(int)offset_base+maker_note->offset+displacement)); +#endif + + ImageInfo->sections_found |= FOUND_MAKERNOTE; + + old_motorola_intel = ImageInfo->motorola_intel; + switch (maker_note->byte_order) { + case MN_ORDER_INTEL: + ImageInfo->motorola_intel = 0; + break; + case MN_ORDER_MOTOROLA: + ImageInfo->motorola_intel = 1; + break; + default: + case MN_ORDER_NORMAL: + break; + } + + NumDirEntries = php_ifd_get16u(dir_start, ImageInfo->motorola_intel); + + switch (maker_note->offset_mode) { + case MN_OFFSET_MAKER: + offset_base = value_ptr; + break; + case MN_OFFSET_GUESS: + if (maker_note->offset + 10 + 4 >= value_len) { + /* Can not read dir_start+10 since it's beyond value end */ + exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "IFD data too short: 0x%04X", value_len); + return FALSE; + } + offset_diff = 2 + NumDirEntries*12 + 4 - php_ifd_get32u(dir_start+10, ImageInfo->motorola_intel); +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Using automatic offset correction: 0x%04X", ((int)dir_start-(int)offset_base+maker_note->offset+displacement) + offset_diff); +#endif + if (offset_diff < 0 || offset_diff >= value_len ) { + exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "IFD data bad offset: 0x%04X length 0x%04X", offset_diff, value_len); + return FALSE; + } + offset_base = value_ptr + offset_diff; + break; + default: + case MN_OFFSET_NORMAL: + break; + } + + if ((2+NumDirEntries*12) > value_len) { + exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "Illegal IFD size: 2 + 0x%04X*12 = 0x%04X > 0x%04X", NumDirEntries, 2+NumDirEntries*12, value_len); + return FALSE; + } + + for (de=0;detag_table)) { + return FALSE; + } + } + ImageInfo->motorola_intel = old_motorola_intel; +/* NextDirOffset (must be NULL) = php_ifd_get32u(dir_start+2+12*de, ImageInfo->motorola_intel);*/ +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Subsection %s done", exif_get_sectionname(SECTION_MAKERNOTE)); +#endif + return TRUE; +} +/* }}} */ + +/* {{{ exif_process_IFD_TAG + * Process one of the nested IFDs directories. */ +static int exif_process_IFD_TAG(image_info_type *ImageInfo, char *dir_entry, char *offset_base, size_t IFDlength, size_t displacement, int section_index, int ReadNextIFD, tag_table_type tag_table) +{ + size_t length; + int tag, format, components; + char *value_ptr, tagname[64], cbuf[32], *outside=NULL; + size_t byte_count, offset_val, fpos, fgot; + int64_t byte_count_signed; + xp_field_type *tmp_xp; +#ifdef EXIF_DEBUG + char *dump_data; + int dump_free; +#endif /* EXIF_DEBUG */ + + /* Protect against corrupt headers */ + if (ImageInfo->ifd_nesting_level > MAX_IFD_NESTING_LEVEL) { + exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "corrupt EXIF header: maximum directory nesting level reached"); + return FALSE; + } + ImageInfo->ifd_nesting_level++; + + tag = php_ifd_get16u(dir_entry, ImageInfo->motorola_intel); + format = php_ifd_get16u(dir_entry+2, ImageInfo->motorola_intel); + components = php_ifd_get32u(dir_entry+4, ImageInfo->motorola_intel); + + if (!format || format > NUM_FORMATS) { + /* (-1) catches illegal zero case as unsigned underflows to positive large. */ + exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "Process tag(x%04X=%s): Illegal format code 0x%04X, suppose BYTE", tag, exif_get_tagname(tag, tagname, -12, tag_table), format); + format = TAG_FMT_BYTE; + /*return TRUE;*/ + } + + if (components < 0) { + exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "Process tag(x%04X=%s): Illegal components(%d)", tag, exif_get_tagname(tag, tagname, -12, tag_table), components); + return FALSE; + } + + byte_count_signed = (int64_t)components * php_tiff_bytes_per_format[format]; + + if (byte_count_signed < 0 || (byte_count_signed > INT32_MAX)) { + exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "Process tag(x%04X=%s): Illegal byte_count", tag, exif_get_tagname(tag, tagname, -12, tag_table)); + return FALSE; + } + + byte_count = (size_t)byte_count_signed; + + if (byte_count > 4) { + offset_val = php_ifd_get32u(dir_entry+8, ImageInfo->motorola_intel); + /* If its bigger than 4 bytes, the dir entry contains an offset. */ + value_ptr = offset_base+offset_val; + /* + dir_entry is ImageInfo->file.list[sn].data+2+i*12 + offset_base is ImageInfo->file.list[sn].data-dir_offset + dir_entry - offset_base is dir_offset+2+i*12 + */ + if (byte_count > IFDlength || offset_val > IFDlength-byte_count || value_ptr < dir_entry || offset_val < (size_t)(dir_entry-offset_base)) { + /* It is important to check for IMAGE_FILETYPE_TIFF + * JPEG does not use absolute pointers instead its pointers are + * relative to the start of the TIFF header in APP1 section. */ + if (byte_count > ImageInfo->FileSize || offset_val>ImageInfo->FileSize-byte_count || (ImageInfo->FileType!=IMAGE_FILETYPE_TIFF_II && ImageInfo->FileType!=IMAGE_FILETYPE_TIFF_MM && ImageInfo->FileType!=IMAGE_FILETYPE_JPEG)) { + if (value_ptr < dir_entry) { + /* we can read this if offset_val > 0 */ + /* some files have their values in other parts of the file */ + exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "Process tag(x%04X=%s): Illegal pointer offset(x%04X < x%04X)", tag, exif_get_tagname(tag, tagname, -12, tag_table), offset_val, dir_entry); + } else { + /* this is for sure not allowed */ + /* exception are IFD pointers */ + exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "Process tag(x%04X=%s): Illegal pointer offset(x%04X + x%04X = x%04X > x%04X)", tag, exif_get_tagname(tag, tagname, -12, tag_table), offset_val, byte_count, offset_val+byte_count, IFDlength); + } + return FALSE; + } + if (byte_count>sizeof(cbuf)) { + /* mark as outside range and get buffer */ + value_ptr = safe_emalloc(byte_count, 1, 0); + outside = value_ptr; + } else { + /* In most cases we only access a small range so + * it is faster to use a static buffer there + * BUT it offers also the possibility to have + * pointers read without the need to free them + * explicitley before returning. */ + memset(&cbuf, 0, sizeof(cbuf)); + value_ptr = cbuf; + } + + fpos = php_stream_tell(ImageInfo->infile); + php_stream_seek(ImageInfo->infile, displacement+offset_val, SEEK_SET); + fgot = php_stream_tell(ImageInfo->infile); + if (fgot!=displacement+offset_val) { + EFREE_IF(outside); + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Wrong file pointer: 0x%08X != 0x%08X", fgot, displacement+offset_val); + return FALSE; + } + fgot = php_stream_read(ImageInfo->infile, value_ptr, byte_count); + php_stream_seek(ImageInfo->infile, fpos, SEEK_SET); + if (fgotsections_found |= FOUND_ANY_TAG; +#ifdef EXIF_DEBUG + dump_data = exif_dump_data(&dump_free, format, components, length, ImageInfo->motorola_intel, value_ptr); + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Process tag(x%04X=%s,@x%04X + x%04X(=%d)): %s%s %s", tag, exif_get_tagname(tag, tagname, -12, tag_table), offset_val+displacement, byte_count, byte_count, (components>1)&&format!=TAG_FMT_UNDEFINED&&format!=TAG_FMT_STRING?"ARRAY OF ":"", exif_get_tagformat(format), dump_data); + if (dump_free) { + efree(dump_data); + } +#endif + + if (section_index==SECTION_THUMBNAIL) { + if (!ImageInfo->Thumbnail.data) { + switch(tag) { + case TAG_IMAGEWIDTH: + case TAG_COMP_IMAGE_WIDTH: + ImageInfo->Thumbnail.width = exif_convert_any_to_int(value_ptr, format, ImageInfo->motorola_intel); + break; + + case TAG_IMAGEHEIGHT: + case TAG_COMP_IMAGE_HEIGHT: + ImageInfo->Thumbnail.height = exif_convert_any_to_int(value_ptr, format, ImageInfo->motorola_intel); + break; + + case TAG_STRIP_OFFSETS: + case TAG_JPEG_INTERCHANGE_FORMAT: + /* accept both formats */ + ImageInfo->Thumbnail.offset = exif_convert_any_to_int(value_ptr, format, ImageInfo->motorola_intel); + break; + + case TAG_STRIP_BYTE_COUNTS: + if (ImageInfo->FileType == IMAGE_FILETYPE_TIFF_II || ImageInfo->FileType == IMAGE_FILETYPE_TIFF_MM) { + ImageInfo->Thumbnail.filetype = ImageInfo->FileType; + } else { + /* motorola is easier to read */ + ImageInfo->Thumbnail.filetype = IMAGE_FILETYPE_TIFF_MM; + } + ImageInfo->Thumbnail.size = exif_convert_any_to_int(value_ptr, format, ImageInfo->motorola_intel); + break; + + case TAG_JPEG_INTERCHANGE_FORMAT_LEN: + if (ImageInfo->Thumbnail.filetype == IMAGE_FILETYPE_UNKNOWN) { + ImageInfo->Thumbnail.filetype = IMAGE_FILETYPE_JPEG; + ImageInfo->Thumbnail.size = exif_convert_any_to_int(value_ptr, format, ImageInfo->motorola_intel); + } + break; + } + } + } else { + if (section_index==SECTION_IFD0 || section_index==SECTION_EXIF) + switch(tag) { + case TAG_COPYRIGHT: + /* check for " NUL NUL" */ + if (byte_count>1 && (length=php_strnlen(value_ptr, byte_count)) > 0) { + if (lengthCopyrightPhotographer = estrdup(value_ptr); + ImageInfo->CopyrightEditor = estrndup(value_ptr+length+1, byte_count-length-1); + spprintf(&ImageInfo->Copyright, 0, "%s, %s", ImageInfo->CopyrightPhotographer, ImageInfo->CopyrightEditor); + /* format = TAG_FMT_UNDEFINED; this musn't be ASCII */ + /* but we are not supposed to change this */ + /* keep in mind that image_info does not store editor value */ + } else { + ImageInfo->Copyright = estrndup(value_ptr, byte_count); + } + } + break; + + case TAG_USERCOMMENT: + ImageInfo->UserCommentLength = exif_process_user_comment(ImageInfo, &(ImageInfo->UserComment), &(ImageInfo->UserCommentEncoding), value_ptr, byte_count); + break; + + case TAG_XP_TITLE: + case TAG_XP_COMMENTS: + case TAG_XP_AUTHOR: + case TAG_XP_KEYWORDS: + case TAG_XP_SUBJECT: + tmp_xp = (xp_field_type*)safe_erealloc(ImageInfo->xp_fields.list, (ImageInfo->xp_fields.count+1), sizeof(xp_field_type), 0); + ImageInfo->sections_found |= FOUND_WINXP; + ImageInfo->xp_fields.list = tmp_xp; + ImageInfo->xp_fields.count++; + exif_process_unicode(ImageInfo, &(ImageInfo->xp_fields.list[ImageInfo->xp_fields.count-1]), tag, value_ptr, byte_count); + break; + + case TAG_FNUMBER: + /* Simplest way of expressing aperture, so I trust it the most. + (overwrite previously computed value if there is one) */ + ImageInfo->ApertureFNumber = (float)exif_convert_any_format(value_ptr, format, ImageInfo->motorola_intel); + break; + + case TAG_APERTURE: + case TAG_MAX_APERTURE: + /* More relevant info always comes earlier, so only use this field if we don't + have appropriate aperture information yet. */ + if (ImageInfo->ApertureFNumber == 0) { + ImageInfo->ApertureFNumber + = (float)exp(exif_convert_any_format(value_ptr, format, ImageInfo->motorola_intel)*log(2)*0.5); + } + break; + + case TAG_SHUTTERSPEED: + /* More complicated way of expressing exposure time, so only use + this value if we don't already have it from somewhere else. + SHUTTERSPEED comes after EXPOSURE TIME + */ + if (ImageInfo->ExposureTime == 0) { + ImageInfo->ExposureTime + = (float)(1/exp(exif_convert_any_format(value_ptr, format, ImageInfo->motorola_intel)*log(2))); + } + break; + case TAG_EXPOSURETIME: + ImageInfo->ExposureTime = -1; + break; + + case TAG_COMP_IMAGE_WIDTH: + ImageInfo->ExifImageWidth = exif_convert_any_to_int(value_ptr, format, ImageInfo->motorola_intel); + break; + + case TAG_FOCALPLANE_X_RES: + ImageInfo->FocalplaneXRes = exif_convert_any_format(value_ptr, format, ImageInfo->motorola_intel); + break; + + case TAG_SUBJECT_DISTANCE: + /* Inidcates the distacne the autofocus camera is focused to. + Tends to be less accurate as distance increases. */ + ImageInfo->Distance = (float)exif_convert_any_format(value_ptr, format, ImageInfo->motorola_intel); + break; + + case TAG_FOCALPLANE_RESOLUTION_UNIT: + switch((int)exif_convert_any_format(value_ptr, format, ImageInfo->motorola_intel)) { + case 1: ImageInfo->FocalplaneUnits = 25.4; break; /* inch */ + case 2: + /* According to the information I was using, 2 measn meters. + But looking at the Cannon powershot's files, inches is the only + sensible value. */ + ImageInfo->FocalplaneUnits = 25.4; + break; + + case 3: ImageInfo->FocalplaneUnits = 10; break; /* centimeter */ + case 4: ImageInfo->FocalplaneUnits = 1; break; /* milimeter */ + case 5: ImageInfo->FocalplaneUnits = .001; break; /* micrometer */ + } + break; + + case TAG_SUB_IFD: + if (format==TAG_FMT_IFD) { + /* If this is called we are either in a TIFFs thumbnail or a JPEG where we cannot handle it */ + /* TIFF thumbnail: our data structure cannot store a thumbnail of a thumbnail */ + /* JPEG do we have the data area and what to do with it */ + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Skip SUB IFD"); + } + break; + + case TAG_MAKE: + ImageInfo->make = estrndup(value_ptr, byte_count); + break; + case TAG_MODEL: + ImageInfo->model = estrndup(value_ptr, byte_count); + break; + + case TAG_MAKER_NOTE: + if (!exif_process_IFD_in_MAKERNOTE(ImageInfo, value_ptr, byte_count, offset_base, IFDlength, displacement)) { + EFREE_IF(outside); + return FALSE; + } + break; + + case TAG_EXIF_IFD_POINTER: + case TAG_GPS_IFD_POINTER: + case TAG_INTEROP_IFD_POINTER: + if (ReadNextIFD) { + char *Subdir_start; + int sub_section_index = 0; + switch(tag) { + case TAG_EXIF_IFD_POINTER: +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Found EXIF"); +#endif + ImageInfo->sections_found |= FOUND_EXIF; + sub_section_index = SECTION_EXIF; + break; + case TAG_GPS_IFD_POINTER: +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Found GPS"); +#endif + ImageInfo->sections_found |= FOUND_GPS; + sub_section_index = SECTION_GPS; + break; + case TAG_INTEROP_IFD_POINTER: +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Found INTEROPERABILITY"); +#endif + ImageInfo->sections_found |= FOUND_INTEROP; + sub_section_index = SECTION_INTEROP; + break; + } + Subdir_start = offset_base + php_ifd_get32u(value_ptr, ImageInfo->motorola_intel); + if (Subdir_start < offset_base || Subdir_start > offset_base+IFDlength) { + exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "Illegal IFD Pointer"); + return FALSE; + } + if (!exif_process_IFD_in_JPEG(ImageInfo, Subdir_start, offset_base, IFDlength, displacement, sub_section_index)) { + return FALSE; + } +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Subsection %s done", exif_get_sectionname(sub_section_index)); +#endif + } + } + } + exif_iif_add_tag(ImageInfo, section_index, exif_get_tagname(tag, tagname, sizeof(tagname), tag_table), tag, format, components, value_ptr); + EFREE_IF(outside); + return TRUE; +} +/* }}} */ + +/* {{{ exif_process_IFD_in_JPEG + * Process one of the nested IFDs directories. */ +static int exif_process_IFD_in_JPEG(image_info_type *ImageInfo, char *dir_start, char *offset_base, size_t IFDlength, size_t displacement, int section_index) +{ + int de; + int NumDirEntries; + int NextDirOffset; + +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Process %s (x%04X(=%d))", exif_get_sectionname(section_index), IFDlength, IFDlength); +#endif + + ImageInfo->sections_found |= FOUND_IFD0; + + if ((dir_start + 2) >= (offset_base+IFDlength)) { + exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "Illegal IFD size"); + return FALSE; + } + + NumDirEntries = php_ifd_get16u(dir_start, ImageInfo->motorola_intel); + + if ((dir_start+2+NumDirEntries*12) > (offset_base+IFDlength)) { + exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "Illegal IFD size: x%04X + 2 + x%04X*12 = x%04X > x%04X", (int)((size_t)dir_start+2-(size_t)offset_base), NumDirEntries, (int)((size_t)dir_start+2+NumDirEntries*12-(size_t)offset_base), IFDlength); + return FALSE; + } + + for (de=0;de= (offset_base+IFDlength)) { + exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "Illegal IFD size"); + return FALSE; + } + NextDirOffset = php_ifd_get32u(dir_start+2+12*de, ImageInfo->motorola_intel); + if (NextDirOffset) { + /* the next line seems false but here IFDlength means length of all IFDs */ + if (offset_base + NextDirOffset < offset_base || offset_base + NextDirOffset > offset_base+IFDlength) { + exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "Illegal IFD offset"); + return FALSE; + } + /* That is the IFD for the first thumbnail */ +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Expect next IFD to be thumbnail"); +#endif + if (exif_process_IFD_in_JPEG(ImageInfo, offset_base + NextDirOffset, offset_base, IFDlength, displacement, SECTION_THUMBNAIL)) { +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Thumbnail size: 0x%04X", ImageInfo->Thumbnail.size); +#endif + if (ImageInfo->Thumbnail.filetype != IMAGE_FILETYPE_UNKNOWN + && ImageInfo->Thumbnail.size + && ImageInfo->Thumbnail.offset + && ImageInfo->read_thumbnail + ) { + exif_thumbnail_extract(ImageInfo, offset_base, IFDlength); + } + return TRUE; + } else { + return FALSE; + } + } + return TRUE; +} +/* }}} */ + +/* {{{ exif_process_TIFF_in_JPEG + Process a TIFF header in a JPEG file +*/ +static void exif_process_TIFF_in_JPEG(image_info_type *ImageInfo, char *CharBuf, size_t length, size_t displacement) +{ + unsigned exif_value_2a, offset_of_ifd; + + /* set the thumbnail stuff to nothing so we can test to see if they get set up */ + if (memcmp(CharBuf, "II", 2) == 0) { + ImageInfo->motorola_intel = 0; + } else if (memcmp(CharBuf, "MM", 2) == 0) { + ImageInfo->motorola_intel = 1; + } else { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid TIFF alignment marker"); + return; + } + + /* Check the next two values for correctness. */ + if (length < 8) { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid TIFF start (1)"); + return; + } + exif_value_2a = php_ifd_get16u(CharBuf+2, ImageInfo->motorola_intel); + offset_of_ifd = php_ifd_get32u(CharBuf+4, ImageInfo->motorola_intel); + if (exif_value_2a != 0x2a || offset_of_ifd < 0x08) { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid TIFF start (1)"); + return; + } + if (offset_of_ifd > length) { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid IFD start"); + return; + } + + ImageInfo->sections_found |= FOUND_IFD0; + /* First directory starts at offset 8. Offsets starts at 0. */ + exif_process_IFD_in_JPEG(ImageInfo, CharBuf+offset_of_ifd, CharBuf, length/*-14*/, displacement, SECTION_IFD0); + +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Process TIFF in JPEG done"); +#endif + + /* Compute the CCD width, in milimeters. */ + if (ImageInfo->FocalplaneXRes != 0) { + ImageInfo->CCDWidth = (float)(ImageInfo->ExifImageWidth * ImageInfo->FocalplaneUnits / ImageInfo->FocalplaneXRes); + } +} +/* }}} */ + +/* {{{ exif_process_APP1 + Process an JPEG APP1 block marker + Describes all the drivel that most digital cameras include... +*/ +static void exif_process_APP1(image_info_type *ImageInfo, char *CharBuf, size_t length, size_t displacement) +{ + /* Check the APP1 for Exif Identifier Code */ + static const uchar ExifHeader[] = {0x45, 0x78, 0x69, 0x66, 0x00, 0x00}; + if (length <= 8 || memcmp(CharBuf+2, ExifHeader, 6)) { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Incorrect APP1 Exif Identifier Code"); + return; + } + exif_process_TIFF_in_JPEG(ImageInfo, CharBuf + 8, length - 8, displacement+8); +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Process APP1/EXIF done"); +#endif +} +/* }}} */ + +/* {{{ exif_process_APP12 + Process an JPEG APP12 block marker used by OLYMPUS +*/ +static void exif_process_APP12(image_info_type *ImageInfo, char *buffer, size_t length) +{ + size_t l1, l2=0; + + if ((l1 = php_strnlen(buffer+2, length-2)) > 0) { + exif_iif_add_tag(ImageInfo, SECTION_APP12, "Company", TAG_NONE, TAG_FMT_STRING, l1, buffer+2); + if (length > 2+l1+1) { + l2 = php_strnlen(buffer+2+l1+1, length-2-l1-1); + exif_iif_add_tag(ImageInfo, SECTION_APP12, "Info", TAG_NONE, TAG_FMT_STRING, l2, buffer+2+l1+1); + } + } +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Process section APP12 with l1=%d, l2=%d done", l1, l2); +#endif +} +/* }}} */ + +/* {{{ exif_scan_JPEG_header + * Parse the marker stream until SOS or EOI is seen; */ +static int exif_scan_JPEG_header(image_info_type *ImageInfo) +{ + int section, sn; + int marker = 0, last_marker = M_PSEUDO, comment_correction=1; + unsigned int ll, lh; + uchar *Data; + size_t fpos, size, got, itemlen; + jpeg_sof_info sof_info; + + for(section=0;;section++) { +#ifdef EXIF_DEBUG + fpos = php_stream_tell(ImageInfo->infile); + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Needing section %d @ 0x%08X", ImageInfo->file.count, fpos); +#endif + + /* get marker byte, swallowing possible padding */ + /* some software does not count the length bytes of COM section */ + /* one company doing so is very much envolved in JPEG... so we accept too */ + if (last_marker==M_COM && comment_correction) { + comment_correction = 2; + } + do { + if ((marker = php_stream_getc(ImageInfo->infile)) == EOF) { + EXIF_ERRLOG_CORRUPT(ImageInfo) + return FALSE; + } + if (last_marker==M_COM && comment_correction>0) { + if (marker!=0xFF) { + marker = 0xff; + comment_correction--; + } else { + last_marker = M_PSEUDO; /* stop skipping 0 for M_COM */ + } + } + } while (marker == 0xff); + if (last_marker==M_COM && !comment_correction) { + exif_error_docref("exif_read_data#error_mcom" EXIFERR_CC, ImageInfo, E_NOTICE, "Image has corrupt COM section: some software set wrong length information"); + } + if (last_marker==M_COM && comment_correction) + return M_EOI; /* ah illegal: char after COM section not 0xFF */ + + fpos = php_stream_tell(ImageInfo->infile); + + if (marker == 0xff) { + /* 0xff is legal padding, but if we get that many, something's wrong. */ + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "To many padding bytes"); + return FALSE; + } + + /* Read the length of the section. */ + if ((lh = php_stream_getc(ImageInfo->infile)) == (unsigned int)EOF) { + EXIF_ERRLOG_CORRUPT(ImageInfo) + return FALSE; + } + if ((ll = php_stream_getc(ImageInfo->infile)) == (unsigned int)EOF) { + EXIF_ERRLOG_CORRUPT(ImageInfo) + return FALSE; + } + + itemlen = (lh << 8) | ll; + + if (itemlen < 2) { +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "%s, Section length: 0x%02X%02X", EXIF_ERROR_CORRUPT, lh, ll); +#else + EXIF_ERRLOG_CORRUPT(ImageInfo) +#endif + return FALSE; + } + + sn = exif_file_sections_add(ImageInfo, marker, itemlen+1, NULL); + Data = ImageInfo->file.list[sn].data; + + /* Store first two pre-read bytes. */ + Data[0] = (uchar)lh; + Data[1] = (uchar)ll; + + got = php_stream_read(ImageInfo->infile, (char*)(Data+2), itemlen-2); /* Read the whole section. */ + if (got != itemlen-2) { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Error reading from file: got=x%04X(=%d) != itemlen-2=x%04X(=%d)", got, got, itemlen-2, itemlen-2); + return FALSE; + } + +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Process section(x%02X=%s) @ x%04X + x%04X(=%d)", marker, exif_get_markername(marker), fpos, itemlen, itemlen); +#endif + switch(marker) { + case M_SOS: /* stop before hitting compressed data */ + /* If reading entire image is requested, read the rest of the data. */ + if (ImageInfo->read_all) { + /* Determine how much file is left. */ + fpos = php_stream_tell(ImageInfo->infile); + size = ImageInfo->FileSize - fpos; + sn = exif_file_sections_add(ImageInfo, M_PSEUDO, size, NULL); + Data = ImageInfo->file.list[sn].data; + got = php_stream_read(ImageInfo->infile, (char*)Data, size); + if (got != size) { + EXIF_ERRLOG_FILEEOF(ImageInfo) + return FALSE; + } + } + return TRUE; + + case M_EOI: /* in case it's a tables-only JPEG stream */ + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "No image in jpeg!"); + return (ImageInfo->sections_found&(~FOUND_COMPUTED)) ? TRUE : FALSE; + + case M_COM: /* Comment section */ + exif_process_COM(ImageInfo, (char *)Data, itemlen); + break; + + case M_EXIF: + if (!(ImageInfo->sections_found&FOUND_IFD0)) { + /*ImageInfo->sections_found |= FOUND_EXIF;*/ + /* Seen files from some 'U-lead' software with Vivitar scanner + that uses marker 31 later in the file (no clue what for!) */ + exif_process_APP1(ImageInfo, (char *)Data, itemlen, fpos); + } + break; + + case M_APP12: + exif_process_APP12(ImageInfo, (char *)Data, itemlen); + break; + + + case M_SOF0: + case M_SOF1: + case M_SOF2: + case M_SOF3: + case M_SOF5: + case M_SOF6: + case M_SOF7: + case M_SOF9: + case M_SOF10: + case M_SOF11: + case M_SOF13: + case M_SOF14: + case M_SOF15: + if ((itemlen - 2) < 6) { + return FALSE; + } + + exif_process_SOFn(Data, marker, &sof_info); + ImageInfo->Width = sof_info.width; + ImageInfo->Height = sof_info.height; + if (sof_info.num_components == 3) { + ImageInfo->IsColor = 1; + } else { + ImageInfo->IsColor = 0; + } + break; + default: + /* skip any other marker silently. */ + break; + } + + /* keep track of last marker */ + last_marker = marker; + } +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Done"); +#endif + return TRUE; +} +/* }}} */ + +/* {{{ exif_scan_thumbnail + * scan JPEG in thumbnail (memory) */ +static int exif_scan_thumbnail(image_info_type *ImageInfo) +{ + uchar c, *data = (uchar*)ImageInfo->Thumbnail.data; + int n, marker; + size_t length=2, pos=0; + jpeg_sof_info sof_info; + + if (!data) { + return FALSE; /* nothing to do here */ + } + if (memcmp(data, "\xFF\xD8\xFF", 3)) { + if (!ImageInfo->Thumbnail.width && !ImageInfo->Thumbnail.height) { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Thumbnail is not a JPEG image"); + } + return FALSE; + } + for (;;) { + pos += length; + if (pos>=ImageInfo->Thumbnail.size) + return FALSE; + c = data[pos++]; + if (pos>=ImageInfo->Thumbnail.size) + return FALSE; + if (c != 0xFF) { + return FALSE; + } + n = 8; + while ((c = data[pos++]) == 0xFF && n--) { + if (pos+3>=ImageInfo->Thumbnail.size) + return FALSE; + /* +3 = pos++ of next check when reaching marker + 2 bytes for length */ + } + if (c == 0xFF) + return FALSE; + marker = c; + length = php_jpg_get16(data+pos); + if (pos+length>=ImageInfo->Thumbnail.size) { + return FALSE; + } +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Thumbnail: process section(x%02X=%s) @ x%04X + x%04X", marker, exif_get_markername(marker), pos, length); +#endif + switch (marker) { + case M_SOF0: + case M_SOF1: + case M_SOF2: + case M_SOF3: + case M_SOF5: + case M_SOF6: + case M_SOF7: + case M_SOF9: + case M_SOF10: + case M_SOF11: + case M_SOF13: + case M_SOF14: + case M_SOF15: + /* handle SOFn block */ + exif_process_SOFn(data+pos, marker, &sof_info); + ImageInfo->Thumbnail.height = sof_info.height; + ImageInfo->Thumbnail.width = sof_info.width; +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Thumbnail: size: %d * %d", sof_info.width, sof_info.height); +#endif + return TRUE; + + case M_SOS: + case M_EOI: + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Could not compute size of thumbnail"); + return FALSE; + break; + + default: + /* just skip */ + break; + } + } + + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Could not compute size of thumbnail"); + return FALSE; +} +/* }}} */ + +/* {{{ exif_process_IFD_in_TIFF + * Parse the TIFF header; */ +static int exif_process_IFD_in_TIFF(image_info_type *ImageInfo, size_t dir_offset, int section_index) +{ + int i, sn, num_entries, sub_section_index = 0; + unsigned char *dir_entry; + char tagname[64]; + size_t ifd_size, dir_size, entry_offset, next_offset, entry_length, entry_value=0, fgot; + int entry_tag , entry_type; + tag_table_type tag_table = exif_get_tag_table(section_index); + + if (ImageInfo->ifd_nesting_level > MAX_IFD_NESTING_LEVEL) { + return FALSE; + } + + if (ImageInfo->FileSize >= 2 && ImageInfo->FileSize - 2 >= dir_offset) { + sn = exif_file_sections_add(ImageInfo, M_PSEUDO, 2, NULL); +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Read from TIFF: filesize(x%04X), IFD dir(x%04X + x%04X)", ImageInfo->FileSize, dir_offset, 2); +#endif + php_stream_seek(ImageInfo->infile, dir_offset, SEEK_SET); /* we do not know the order of sections */ + php_stream_read(ImageInfo->infile, (char*)ImageInfo->file.list[sn].data, 2); + num_entries = php_ifd_get16u(ImageInfo->file.list[sn].data, ImageInfo->motorola_intel); + dir_size = 2/*num dir entries*/ +12/*length of entry*/*(size_t)num_entries +4/* offset to next ifd (points to thumbnail or NULL)*/; + if (ImageInfo->FileSize >= dir_size && ImageInfo->FileSize - dir_size >= dir_offset) { +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Read from TIFF: filesize(x%04X), IFD dir(x%04X + x%04X), IFD entries(%d)", ImageInfo->FileSize, dir_offset+2, dir_size-2, num_entries); +#endif + if (exif_file_sections_realloc(ImageInfo, sn, dir_size)) { + return FALSE; + } + php_stream_read(ImageInfo->infile, (char*)(ImageInfo->file.list[sn].data+2), dir_size-2); + /*exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Dump: %s", exif_char_dump(ImageInfo->file.list[sn].data, dir_size, 0));*/ + next_offset = php_ifd_get32u(ImageInfo->file.list[sn].data + dir_size - 4, ImageInfo->motorola_intel); +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Read from TIFF done, next offset x%04X", next_offset); +#endif + /* now we have the directory we can look how long it should be */ + ifd_size = dir_size; + for(i=0;ifile.list[sn].data+2+i*12; + entry_tag = php_ifd_get16u(dir_entry+0, ImageInfo->motorola_intel); + entry_type = php_ifd_get16u(dir_entry+2, ImageInfo->motorola_intel); + if (entry_type > NUM_FORMATS) { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Read from TIFF: tag(0x%04X,%12s): Illegal format code 0x%04X, switching to BYTE", entry_tag, exif_get_tagname(entry_tag, tagname, -12, tag_table), entry_type); + /* Since this is repeated in exif_process_IFD_TAG make it a notice here */ + /* and make it a warning in the exif_process_IFD_TAG which is called */ + /* elsewhere. */ + entry_type = TAG_FMT_BYTE; + /*The next line would break the image on writeback: */ + /* php_ifd_set16u(dir_entry+2, entry_type, ImageInfo->motorola_intel);*/ + } + entry_length = php_ifd_get32u(dir_entry+4, ImageInfo->motorola_intel) * php_tiff_bytes_per_format[entry_type]; + if (entry_length <= 4) { + switch(entry_type) { + case TAG_FMT_USHORT: + entry_value = php_ifd_get16u(dir_entry+8, ImageInfo->motorola_intel); + break; + case TAG_FMT_SSHORT: + entry_value = php_ifd_get16s(dir_entry+8, ImageInfo->motorola_intel); + break; + case TAG_FMT_ULONG: + entry_value = php_ifd_get32u(dir_entry+8, ImageInfo->motorola_intel); + break; + case TAG_FMT_SLONG: + entry_value = php_ifd_get32s(dir_entry+8, ImageInfo->motorola_intel); + break; + } + switch(entry_tag) { + case TAG_IMAGEWIDTH: + case TAG_COMP_IMAGE_WIDTH: + ImageInfo->Width = entry_value; + break; + case TAG_IMAGEHEIGHT: + case TAG_COMP_IMAGE_HEIGHT: + ImageInfo->Height = entry_value; + break; + case TAG_PHOTOMETRIC_INTERPRETATION: + switch (entry_value) { + case PMI_BLACK_IS_ZERO: + case PMI_WHITE_IS_ZERO: + case PMI_TRANSPARENCY_MASK: + ImageInfo->IsColor = 0; + break; + case PMI_RGB: + case PMI_PALETTE_COLOR: + case PMI_SEPARATED: + case PMI_YCBCR: + case PMI_CIELAB: + ImageInfo->IsColor = 1; + break; + } + break; + } + } else { + entry_offset = php_ifd_get32u(dir_entry+8, ImageInfo->motorola_intel); + /* if entry needs expading ifd cache and entry is at end of current ifd cache. */ + /* otherwise there may be huge holes between two entries */ + if (entry_offset + entry_length > dir_offset + ifd_size + && entry_offset == dir_offset + ifd_size) { + ifd_size = entry_offset + entry_length - dir_offset; +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Resize struct: x%04X + x%04X - x%04X = x%04X", entry_offset, entry_length, dir_offset, ifd_size); +#endif + } + } + } + if (ImageInfo->FileSize >= ImageInfo->file.list[sn].size && ImageInfo->FileSize - ImageInfo->file.list[sn].size >= dir_offset) { + if (ifd_size > dir_size) { + if (ImageInfo->FileSize < ifd_size || dir_offset > ImageInfo->FileSize - ifd_size) { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Error in TIFF: filesize(x%04X) less than size of IFD(x%04X + x%04X)", ImageInfo->FileSize, dir_offset, ifd_size); + return FALSE; + } + if (exif_file_sections_realloc(ImageInfo, sn, ifd_size)) { + return FALSE; + } + /* read values not stored in directory itself */ +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Read from TIFF: filesize(x%04X), IFD(x%04X + x%04X)", ImageInfo->FileSize, dir_offset, ifd_size); +#endif + php_stream_read(ImageInfo->infile, (char*)(ImageInfo->file.list[sn].data+dir_size), ifd_size-dir_size); +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Read from TIFF, done"); +#endif + } + /* now process the tags */ + for(i=0;ifile.list[sn].data+2+i*12; + entry_tag = php_ifd_get16u(dir_entry+0, ImageInfo->motorola_intel); + entry_type = php_ifd_get16u(dir_entry+2, ImageInfo->motorola_intel); + /*entry_length = php_ifd_get32u(dir_entry+4, ImageInfo->motorola_intel);*/ + if (entry_tag == TAG_EXIF_IFD_POINTER || + entry_tag == TAG_INTEROP_IFD_POINTER || + entry_tag == TAG_GPS_IFD_POINTER || + entry_tag == TAG_SUB_IFD + ) { + switch(entry_tag) { + case TAG_EXIF_IFD_POINTER: + ImageInfo->sections_found |= FOUND_EXIF; + sub_section_index = SECTION_EXIF; + break; + case TAG_GPS_IFD_POINTER: + ImageInfo->sections_found |= FOUND_GPS; + sub_section_index = SECTION_GPS; + break; + case TAG_INTEROP_IFD_POINTER: + ImageInfo->sections_found |= FOUND_INTEROP; + sub_section_index = SECTION_INTEROP; + break; + case TAG_SUB_IFD: + ImageInfo->sections_found |= FOUND_THUMBNAIL; + sub_section_index = SECTION_THUMBNAIL; + break; + } + entry_offset = php_ifd_get32u(dir_entry+8, ImageInfo->motorola_intel); +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Next IFD: %s @x%04X", exif_get_sectionname(sub_section_index), entry_offset); +#endif + ImageInfo->ifd_nesting_level++; + exif_process_IFD_in_TIFF(ImageInfo, entry_offset, sub_section_index); + if (section_index!=SECTION_THUMBNAIL && entry_tag==TAG_SUB_IFD) { + if (ImageInfo->Thumbnail.filetype != IMAGE_FILETYPE_UNKNOWN + && ImageInfo->Thumbnail.size + && ImageInfo->Thumbnail.offset + && ImageInfo->read_thumbnail + ) { +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "%s THUMBNAIL @0x%04X + 0x%04X", ImageInfo->Thumbnail.data ? "Ignore" : "Read", ImageInfo->Thumbnail.offset, ImageInfo->Thumbnail.size); +#endif + if (!ImageInfo->Thumbnail.data) { + ImageInfo->Thumbnail.data = safe_emalloc(ImageInfo->Thumbnail.size, 1, 0); + php_stream_seek(ImageInfo->infile, ImageInfo->Thumbnail.offset, SEEK_SET); + fgot = php_stream_read(ImageInfo->infile, ImageInfo->Thumbnail.data, ImageInfo->Thumbnail.size); + if (fgot < ImageInfo->Thumbnail.size) { + EXIF_ERRLOG_THUMBEOF(ImageInfo) + efree(ImageInfo->Thumbnail.data); + ImageInfo->Thumbnail.data = NULL; + } else { + exif_thumbnail_build(ImageInfo); + } + } + } + } +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Next IFD: %s done", exif_get_sectionname(sub_section_index)); +#endif + } else { + if (!exif_process_IFD_TAG(ImageInfo, (char*)dir_entry, + (char*)(ImageInfo->file.list[sn].data-dir_offset), + ifd_size, 0, section_index, 0, tag_table)) { + return FALSE; + } + } + } + /* If we had a thumbnail in a SUB_IFD we have ANOTHER image in NEXT IFD */ + if (next_offset && section_index != SECTION_THUMBNAIL) { + /* this should be a thumbnail IFD */ + /* the thumbnail itself is stored at Tag=StripOffsets */ +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Read next IFD (THUMBNAIL) at x%04X", next_offset); +#endif + ImageInfo->ifd_nesting_level++; + exif_process_IFD_in_TIFF(ImageInfo, next_offset, SECTION_THUMBNAIL); +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "%s THUMBNAIL @0x%04X + 0x%04X", ImageInfo->Thumbnail.data ? "Ignore" : "Read", ImageInfo->Thumbnail.offset, ImageInfo->Thumbnail.size); +#endif + if (!ImageInfo->Thumbnail.data && ImageInfo->Thumbnail.offset && ImageInfo->Thumbnail.size && ImageInfo->read_thumbnail) { + ImageInfo->Thumbnail.data = safe_emalloc(ImageInfo->Thumbnail.size, 1, 0); + php_stream_seek(ImageInfo->infile, ImageInfo->Thumbnail.offset, SEEK_SET); + fgot = php_stream_read(ImageInfo->infile, ImageInfo->Thumbnail.data, ImageInfo->Thumbnail.size); + if (fgot < ImageInfo->Thumbnail.size) { + EXIF_ERRLOG_THUMBEOF(ImageInfo) + efree(ImageInfo->Thumbnail.data); + ImageInfo->Thumbnail.data = NULL; + } else { + exif_thumbnail_build(ImageInfo); + } + } +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Read next IFD (THUMBNAIL) done"); +#endif + } + return TRUE; + } else { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Error in TIFF: filesize(x%04X) less than size of IFD(x%04X)", ImageInfo->FileSize, dir_offset+ImageInfo->file.list[sn].size); + return FALSE; + } + } else { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Error in TIFF: filesize(x%04X) less than size of IFD dir(x%04X)", ImageInfo->FileSize, dir_offset+dir_size); + return FALSE; + } + } else { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Error in TIFF: filesize(x%04X) less than start of IFD dir(x%04X)", ImageInfo->FileSize, dir_offset+2); + return FALSE; + } +} +/* }}} */ + +/* {{{ exif_scan_FILE_header + * Parse the marker stream until SOS or EOI is seen; */ +static int exif_scan_FILE_header(image_info_type *ImageInfo) +{ + unsigned char file_header[8]; + int ret = FALSE; + + ImageInfo->FileType = IMAGE_FILETYPE_UNKNOWN; + + if (ImageInfo->FileSize >= 2) { + php_stream_seek(ImageInfo->infile, 0, SEEK_SET); + if (php_stream_read(ImageInfo->infile, (char*)file_header, 2) != 2) { + return FALSE; + } + if ((file_header[0]==0xff) && (file_header[1]==M_SOI)) { + ImageInfo->FileType = IMAGE_FILETYPE_JPEG; + if (exif_scan_JPEG_header(ImageInfo)) { + ret = TRUE; + } else { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid JPEG file"); + } + } else if (ImageInfo->FileSize >= 8) { + if (php_stream_read(ImageInfo->infile, (char*)(file_header+2), 6) != 6) { + return FALSE; + } + if (!memcmp(file_header, "II\x2A\x00", 4)) { + ImageInfo->FileType = IMAGE_FILETYPE_TIFF_II; + ImageInfo->motorola_intel = 0; +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "File has TIFF/II format"); +#endif + ImageInfo->sections_found |= FOUND_IFD0; + if (exif_process_IFD_in_TIFF(ImageInfo, + php_ifd_get32u(file_header + 4, ImageInfo->motorola_intel), + SECTION_IFD0)) { + ret = TRUE; + } else { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid TIFF file"); + } + } else if (!memcmp(file_header, "MM\x00\x2a", 4)) { + ImageInfo->FileType = IMAGE_FILETYPE_TIFF_MM; + ImageInfo->motorola_intel = 1; +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "File has TIFF/MM format"); +#endif + ImageInfo->sections_found |= FOUND_IFD0; + if (exif_process_IFD_in_TIFF(ImageInfo, + php_ifd_get32u(file_header + 4, ImageInfo->motorola_intel), + SECTION_IFD0)) { + ret = TRUE; + } else { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid TIFF file"); + } + } else { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "File not supported"); + return FALSE; + } + } + } else { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "File too small (%d)", ImageInfo->FileSize); + } + return ret; +} +/* }}} */ + +/* {{{ exif_discard_imageinfo + Discard data scanned by exif_read_file. +*/ +static int exif_discard_imageinfo(image_info_type *ImageInfo) +{ + int i; + + EFREE_IF(ImageInfo->FileName); + EFREE_IF(ImageInfo->UserComment); + EFREE_IF(ImageInfo->UserCommentEncoding); + EFREE_IF(ImageInfo->Copyright); + EFREE_IF(ImageInfo->CopyrightPhotographer); + EFREE_IF(ImageInfo->CopyrightEditor); + EFREE_IF(ImageInfo->Thumbnail.data); + EFREE_IF(ImageInfo->encode_unicode); + EFREE_IF(ImageInfo->decode_unicode_be); + EFREE_IF(ImageInfo->decode_unicode_le); + EFREE_IF(ImageInfo->encode_jis); + EFREE_IF(ImageInfo->decode_jis_be); + EFREE_IF(ImageInfo->decode_jis_le); + EFREE_IF(ImageInfo->make); + EFREE_IF(ImageInfo->model); + for (i=0; ixp_fields.count; i++) { + EFREE_IF(ImageInfo->xp_fields.list[i].value); + } + EFREE_IF(ImageInfo->xp_fields.list); + for (i=0; imotorola_intel = -1; /* flag as unknown */ + + ImageInfo->infile = php_stream_open_wrapper(FileName, "rb", STREAM_MUST_SEEK|IGNORE_PATH, NULL); + if (!ImageInfo->infile) { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Unable to open file"); + return FALSE; + } + + if (php_stream_is(ImageInfo->infile, PHP_STREAM_IS_STDIO)) { + if (VCWD_STAT(FileName, &st) >= 0) { + if ((st.st_mode & S_IFMT) != S_IFREG) { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Not a file"); + php_stream_close(ImageInfo->infile); + return FALSE; + } + + /* Store file date/time. */ + ImageInfo->FileDateTime = st.st_mtime; + ImageInfo->FileSize = st.st_size; + /*exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Opened stream is file: %d", ImageInfo->FileSize);*/ + } + } else { + if (!ImageInfo->FileSize) { + php_stream_seek(ImageInfo->infile, 0, SEEK_END); + ImageInfo->FileSize = php_stream_tell(ImageInfo->infile); + php_stream_seek(ImageInfo->infile, 0, SEEK_SET); + } + } + + base = php_basename(FileName, strlen(FileName), NULL, 0); + ImageInfo->FileName = estrndup(ZSTR_VAL(base), ZSTR_LEN(base)); + zend_string_release(base); + ImageInfo->read_thumbnail = read_thumbnail; + ImageInfo->read_all = read_all; + ImageInfo->Thumbnail.filetype = IMAGE_FILETYPE_UNKNOWN; + + ImageInfo->encode_unicode = estrdup(EXIF_G(encode_unicode)); + ImageInfo->decode_unicode_be = estrdup(EXIF_G(decode_unicode_be)); + ImageInfo->decode_unicode_le = estrdup(EXIF_G(decode_unicode_le)); + ImageInfo->encode_jis = estrdup(EXIF_G(encode_jis)); + ImageInfo->decode_jis_be = estrdup(EXIF_G(decode_jis_be)); + ImageInfo->decode_jis_le = estrdup(EXIF_G(decode_jis_le)); + + + ImageInfo->ifd_nesting_level = 0; + + /* Scan the JPEG headers. */ + ret = exif_scan_FILE_header(ImageInfo); + + php_stream_close(ImageInfo->infile); + return ret; +} +/* }}} */ + +/* {{{ proto array exif_read_data(string filename [, string sections_needed [, bool sub_arrays[, bool read_thumbnail]]]) + Reads header data from the JPEG/TIFF image filename and optionally reads the internal thumbnails */ +PHP_FUNCTION(exif_read_data) +{ + char *p_name, *p_sections_needed = NULL; + size_t p_name_len, p_sections_needed_len = 0; + zend_bool sub_arrays=0, read_thumbnail=0, read_all=0; + + int i, ret, sections_needed=0; + image_info_type ImageInfo; + char tmp[64], *sections_str, *s; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p|sbb", &p_name, &p_name_len, &p_sections_needed, &p_sections_needed_len, &sub_arrays, &read_thumbnail) == FAILURE) { + return; + } + + memset(&ImageInfo, 0, sizeof(ImageInfo)); + + if (p_sections_needed) { + spprintf(§ions_str, 0, ",%s,", p_sections_needed); + /* sections_str DOES start with , and SPACES are NOT allowed in names */ + s = sections_str; + while (*++s) { + if (*s == ' ') { + *s = ','; + } + } + + for (i = 0; i < SECTION_COUNT; i++) { + snprintf(tmp, sizeof(tmp), ",%s,", exif_get_sectionname(i)); + if (strstr(sections_str, tmp)) { + sections_needed |= 1<0 && ImageInfo.Height>0) { + exif_iif_add_fmt(&ImageInfo, SECTION_COMPUTED, "html" , "width=\"%d\" height=\"%d\"", ImageInfo.Width, ImageInfo.Height); + exif_iif_add_int(&ImageInfo, SECTION_COMPUTED, "Height", ImageInfo.Height); + exif_iif_add_int(&ImageInfo, SECTION_COMPUTED, "Width", ImageInfo.Width); + } + exif_iif_add_int(&ImageInfo, SECTION_COMPUTED, "IsColor", ImageInfo.IsColor); + if (ImageInfo.motorola_intel != -1) { + exif_iif_add_int(&ImageInfo, SECTION_COMPUTED, "ByteOrderMotorola", ImageInfo.motorola_intel); + } + if (ImageInfo.FocalLength) { + exif_iif_add_fmt(&ImageInfo, SECTION_COMPUTED, "FocalLength", "%4.1Fmm", ImageInfo.FocalLength); + if(ImageInfo.CCDWidth) { + exif_iif_add_fmt(&ImageInfo, SECTION_COMPUTED, "35mmFocalLength", "%dmm", (int)(ImageInfo.FocalLength/ImageInfo.CCDWidth*35+0.5)); + } + } + if(ImageInfo.CCDWidth) { + exif_iif_add_fmt(&ImageInfo, SECTION_COMPUTED, "CCDWidth", "%dmm", (int)ImageInfo.CCDWidth); + } + if(ImageInfo.ExposureTime>0) { + if(ImageInfo.ExposureTime <= 0.5) { + exif_iif_add_fmt(&ImageInfo, SECTION_COMPUTED, "ExposureTime", "%0.3F s (1/%d)", ImageInfo.ExposureTime, (int)(0.5 + 1/ImageInfo.ExposureTime)); + } else { + exif_iif_add_fmt(&ImageInfo, SECTION_COMPUTED, "ExposureTime", "%0.3F s", ImageInfo.ExposureTime); + } + } + if(ImageInfo.ApertureFNumber) { + exif_iif_add_fmt(&ImageInfo, SECTION_COMPUTED, "ApertureFNumber", "f/%.1F", ImageInfo.ApertureFNumber); + } + if(ImageInfo.Distance) { + if(ImageInfo.Distance<0) { + exif_iif_add_str(&ImageInfo, SECTION_COMPUTED, "FocusDistance", "Infinite"); + } else { + exif_iif_add_fmt(&ImageInfo, SECTION_COMPUTED, "FocusDistance", "%0.2Fm", ImageInfo.Distance); + } + } + if (ImageInfo.UserComment) { + exif_iif_add_buffer(&ImageInfo, SECTION_COMPUTED, "UserComment", ImageInfo.UserCommentLength, ImageInfo.UserComment); + if (ImageInfo.UserCommentEncoding && strlen(ImageInfo.UserCommentEncoding)) { + exif_iif_add_str(&ImageInfo, SECTION_COMPUTED, "UserCommentEncoding", ImageInfo.UserCommentEncoding); + } + } + + exif_iif_add_str(&ImageInfo, SECTION_COMPUTED, "Copyright", ImageInfo.Copyright); + exif_iif_add_str(&ImageInfo, SECTION_COMPUTED, "Copyright.Photographer", ImageInfo.CopyrightPhotographer); + exif_iif_add_str(&ImageInfo, SECTION_COMPUTED, "Copyright.Editor", ImageInfo.CopyrightEditor); + + for (i=0; i= 3) { + if (!ImageInfo.Thumbnail.width || !ImageInfo.Thumbnail.height) { + exif_scan_thumbnail(&ImageInfo); + } + zval_dtor(p_width); + zval_dtor(p_height); + ZVAL_LONG(p_width, ImageInfo.Thumbnail.width); + ZVAL_LONG(p_height, ImageInfo.Thumbnail.height); + } + if (arg_c >= 4) { + zval_dtor(p_imagetype); + ZVAL_LONG(p_imagetype, ImageInfo.Thumbnail.filetype); + } + +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, &ImageInfo, E_NOTICE, "Discarding info"); +#endif + + exif_discard_imageinfo(&ImageInfo); + +#ifdef EXIF_DEBUG + php_error_docref1(NULL, p_name, E_NOTICE, "Done"); +#endif +} +/* }}} */ + +/* {{{ proto int exif_imagetype(string imagefile) + Get the type of an image */ +PHP_FUNCTION(exif_imagetype) +{ + char *imagefile; + size_t imagefile_len; + php_stream * stream; + int itype = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p", &imagefile, &imagefile_len) == FAILURE) { + return; + } + + stream = php_stream_open_wrapper(imagefile, "rb", IGNORE_PATH|REPORT_ERRORS, NULL); + + if (stream == NULL) { + RETURN_FALSE; + } + + itype = php_getimagetype(stream, NULL); + + php_stream_close(stream); + + if (itype == IMAGE_FILETYPE_UNKNOWN) { + RETURN_FALSE; + } else { + ZVAL_LONG(return_value, itype); + } +} +/* }}} */ + +#endif + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 tw=78 fdm=marker + * vim<600: sw=4 ts=4 tw=78 + */ diff --git a/ext/exif/tests/bug78910.phpt b/ext/exif/tests/bug78910.phpt new file mode 100644 index 0000000000000..f5b1c32c1bd02 --- /dev/null +++ b/ext/exif/tests/bug78910.phpt @@ -0,0 +1,17 @@ +--TEST-- +Bug #78910: Heap-buffer-overflow READ in exif (OSS-Fuzz #19044) +--FILE-- + +--EXPECTF-- +Notice: exif_read_data(): Read from TIFF: tag(0x927C, MakerNote ): Illegal format code 0x2020, switching to BYTE in %s on line %d + +Warning: exif_read_data(): Process tag(x927C=MakerNote ): Illegal format code 0x2020, suppose BYTE in %s on line %d + +Warning: exif_read_data(): IFD data too short: 0x0000 offset 0x000C in %s on line %d + +Warning: exif_read_data(): Invalid TIFF file in %s on line %d +bool(false) diff --git a/ext/exif/tests/bug79282.phpt b/ext/exif/tests/bug79282.phpt new file mode 100644 index 0000000000000..7b7e36565791f --- /dev/null +++ b/ext/exif/tests/bug79282.phpt @@ -0,0 +1,15 @@ +--TEST-- +Bug #79282: Use-of-uninitialized-value in exif +--FILE-- + +--EXPECTF-- +Warning: exif_read_data(): Invalid TIFF alignment marker in %s on line %d + +Warning: exif_read_data(): File structure corrupted in %s on line %d + +Warning: exif_read_data(): Invalid JPEG file in %s on line %d +bool(false) diff --git a/ext/gd/gd.c b/ext/gd/gd.c index 9d0dcca690687..a9d62f81f52d0 100644 --- a/ext/gd/gd.c +++ b/ext/gd/gd.c @@ -1392,6 +1392,12 @@ PHP_FUNCTION(imageloadfont) font->w = FLIPWORD(font->w); font->h = FLIPWORD(font->h); font->nchars = FLIPWORD(font->nchars); + if (overflow2(font->nchars, font->h) || overflow2(font->nchars * font->h, font->w )) { + php_error_docref(NULL, E_WARNING, "Error reading font, invalid font header"); + efree(font); + php_stream_close(stream); + RETURN_FALSE; + } body_size = font->w * font->h * font->nchars; } @@ -1409,6 +1415,7 @@ PHP_FUNCTION(imageloadfont) RETURN_FALSE; } + ZEND_ASSERT(body_size > 0); font->data = emalloc(body_size); b = 0; while (b < body_size && (n = php_stream_read(stream, &font->data[b], body_size - b))) { diff --git a/ext/gd/gd.c.orig b/ext/gd/gd.c.orig new file mode 100644 index 0000000000000..9d0dcca690687 --- /dev/null +++ b/ext/gd/gd.c.orig @@ -0,0 +1,5019 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2017 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Rasmus Lerdorf | + | Stig Bakken | + | Jim Winstead | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +/* gd 1.2 is copyright 1994, 1995, Quest Protein Database Center, + Cold Spring Harbor Labs. */ + +/* Note that there is no code from the gd package in this file */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/head.h" +#include +#include "SAPI.h" +#include "php_gd.h" +#include "ext/standard/info.h" +#include "php_open_temporary_file.h" + + +#if HAVE_SYS_WAIT_H +# include +#endif +#if HAVE_UNISTD_H +# include +#endif +#ifdef PHP_WIN32 +# include +# include +# include +# include +# include +#endif + +#ifdef HAVE_GD_XPM +# include +#endif + +# include "gd_compat.h" + + +static int le_gd, le_gd_font; + +#include +#ifndef HAVE_GD_BUNDLED +# include +#endif +#include /* 1 Tiny font */ +#include /* 2 Small font */ +#include /* 3 Medium bold font */ +#include /* 4 Large font */ +#include /* 5 Giant font */ + +#ifdef ENABLE_GD_TTF +# ifdef HAVE_LIBFREETYPE +# include +# include FT_FREETYPE_H +# endif +#endif + +#if defined(HAVE_GD_XPM) && defined(HAVE_GD_BUNDLED) +# include "X11/xpm.h" +#endif + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +#ifdef ENABLE_GD_TTF +static void php_imagettftext_common(INTERNAL_FUNCTION_PARAMETERS, int, int); +#endif + +#include "gd_ctx.c" + +/* as it is not really public, duplicate declaration here to avoid + pointless warnings */ +int overflow2(int a, int b); + +/* Section Filters Declarations */ +/* IMPORTANT NOTE FOR NEW FILTER + * Do not forget to update: + * IMAGE_FILTER_MAX: define the last filter index + * IMAGE_FILTER_MAX_ARGS: define the biggest amount of arguments + * image_filter array in PHP_FUNCTION(imagefilter) + * */ +#define IMAGE_FILTER_NEGATE 0 +#define IMAGE_FILTER_GRAYSCALE 1 +#define IMAGE_FILTER_BRIGHTNESS 2 +#define IMAGE_FILTER_CONTRAST 3 +#define IMAGE_FILTER_COLORIZE 4 +#define IMAGE_FILTER_EDGEDETECT 5 +#define IMAGE_FILTER_EMBOSS 6 +#define IMAGE_FILTER_GAUSSIAN_BLUR 7 +#define IMAGE_FILTER_SELECTIVE_BLUR 8 +#define IMAGE_FILTER_MEAN_REMOVAL 9 +#define IMAGE_FILTER_SMOOTH 10 +#define IMAGE_FILTER_PIXELATE 11 +#define IMAGE_FILTER_MAX 11 +#define IMAGE_FILTER_MAX_ARGS 6 +static void php_image_filter_negate(INTERNAL_FUNCTION_PARAMETERS); +static void php_image_filter_grayscale(INTERNAL_FUNCTION_PARAMETERS); +static void php_image_filter_brightness(INTERNAL_FUNCTION_PARAMETERS); +static void php_image_filter_contrast(INTERNAL_FUNCTION_PARAMETERS); +static void php_image_filter_colorize(INTERNAL_FUNCTION_PARAMETERS); +static void php_image_filter_edgedetect(INTERNAL_FUNCTION_PARAMETERS); +static void php_image_filter_emboss(INTERNAL_FUNCTION_PARAMETERS); +static void php_image_filter_gaussian_blur(INTERNAL_FUNCTION_PARAMETERS); +static void php_image_filter_selective_blur(INTERNAL_FUNCTION_PARAMETERS); +static void php_image_filter_mean_removal(INTERNAL_FUNCTION_PARAMETERS); +static void php_image_filter_smooth(INTERNAL_FUNCTION_PARAMETERS); +static void php_image_filter_pixelate(INTERNAL_FUNCTION_PARAMETERS); + +/* End Section filters declarations */ +static gdImagePtr _php_image_create_from_string (zval *Data, char *tn, gdImagePtr (*ioctx_func_p)()); +static void _php_image_create_from(INTERNAL_FUNCTION_PARAMETERS, int image_type, char *tn, gdImagePtr (*func_p)(), gdImagePtr (*ioctx_func_p)()); +static void _php_image_output(INTERNAL_FUNCTION_PARAMETERS, int image_type, char *tn, void (*func_p)()); +static int _php_image_type(char data[8]); +static void _php_image_convert(INTERNAL_FUNCTION_PARAMETERS, int image_type); +static void _php_image_bw_convert(gdImagePtr im_org, gdIOCtx *out, int threshold); + +/* {{{ arginfo */ +ZEND_BEGIN_ARG_INFO(arginfo_gd_info, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imageloadfont, 0) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagesetstyle, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, styles) /* ARRAY_INFO(0, styles, 0) */ +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecreatetruecolor, 0) + ZEND_ARG_INFO(0, x_size) + ZEND_ARG_INFO(0, y_size) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imageistruecolor, 0) + ZEND_ARG_INFO(0, im) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagetruecolortopalette, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, ditherFlag) + ZEND_ARG_INFO(0, colorsWanted) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagepalettetotruecolor, 0) + ZEND_ARG_INFO(0, im) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolormatch, 0) + ZEND_ARG_INFO(0, im1) + ZEND_ARG_INFO(0, im2) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagesetthickness, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, thickness) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagefilledellipse, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, cx) + ZEND_ARG_INFO(0, cy) + ZEND_ARG_INFO(0, w) + ZEND_ARG_INFO(0, h) + ZEND_ARG_INFO(0, color) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagefilledarc, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, cx) + ZEND_ARG_INFO(0, cy) + ZEND_ARG_INFO(0, w) + ZEND_ARG_INFO(0, h) + ZEND_ARG_INFO(0, s) + ZEND_ARG_INFO(0, e) + ZEND_ARG_INFO(0, col) + ZEND_ARG_INFO(0, style) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagealphablending, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, blend) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagesavealpha, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, save) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagelayereffect, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, effect) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolorallocatealpha, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, red) + ZEND_ARG_INFO(0, green) + ZEND_ARG_INFO(0, blue) + ZEND_ARG_INFO(0, alpha) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolorresolvealpha, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, red) + ZEND_ARG_INFO(0, green) + ZEND_ARG_INFO(0, blue) + ZEND_ARG_INFO(0, alpha) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolorclosestalpha, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, red) + ZEND_ARG_INFO(0, green) + ZEND_ARG_INFO(0, blue) + ZEND_ARG_INFO(0, alpha) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolorexactalpha, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, red) + ZEND_ARG_INFO(0, green) + ZEND_ARG_INFO(0, blue) + ZEND_ARG_INFO(0, alpha) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecopyresampled, 0) + ZEND_ARG_INFO(0, dst_im) + ZEND_ARG_INFO(0, src_im) + ZEND_ARG_INFO(0, dst_x) + ZEND_ARG_INFO(0, dst_y) + ZEND_ARG_INFO(0, src_x) + ZEND_ARG_INFO(0, src_y) + ZEND_ARG_INFO(0, dst_w) + ZEND_ARG_INFO(0, dst_h) + ZEND_ARG_INFO(0, src_w) + ZEND_ARG_INFO(0, src_h) +ZEND_END_ARG_INFO() + +#ifdef PHP_WIN32 +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagegrabwindow, 0, 0, 1) + ZEND_ARG_INFO(0, handle) + ZEND_ARG_INFO(0, client_area) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagegrabscreen, 0) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagerotate, 0, 0, 3) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, angle) + ZEND_ARG_INFO(0, bgdcolor) + ZEND_ARG_INFO(0, ignoretransparent) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagesettile, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, tile) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagesetbrush, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, brush) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecreate, 0) + ZEND_ARG_INFO(0, x_size) + ZEND_ARG_INFO(0, y_size) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagetypes, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecreatefromstring, 0) + ZEND_ARG_INFO(0, image) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecreatefromgif, 0) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() + +#ifdef HAVE_GD_JPG +ZEND_BEGIN_ARG_INFO(arginfo_imagecreatefromjpeg, 0) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() +#endif + +#ifdef HAVE_GD_PNG +ZEND_BEGIN_ARG_INFO(arginfo_imagecreatefrompng, 0) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() +#endif + +#ifdef HAVE_GD_WEBP +ZEND_BEGIN_ARG_INFO(arginfo_imagecreatefromwebp, 0) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO(arginfo_imagecreatefromxbm, 0) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() + +#if defined(HAVE_GD_XPM) +ZEND_BEGIN_ARG_INFO(arginfo_imagecreatefromxpm, 0) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO(arginfo_imagecreatefromwbmp, 0) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecreatefromgd, 0) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecreatefromgd2, 0) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecreatefromgd2part, 0) + ZEND_ARG_INFO(0, filename) + ZEND_ARG_INFO(0, srcX) + ZEND_ARG_INFO(0, srcY) + ZEND_ARG_INFO(0, width) + ZEND_ARG_INFO(0, height) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagexbm, 0, 0, 2) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, filename) + ZEND_ARG_INFO(0, foreground) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagegif, 0, 0, 1) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, to) +ZEND_END_ARG_INFO() + +#ifdef HAVE_GD_PNG +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagepng, 0, 0, 1) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, to) + ZEND_ARG_INFO(0, quality) + ZEND_ARG_INFO(0, filters) +ZEND_END_ARG_INFO() +#endif + +#ifdef HAVE_GD_WEBP +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagewebp, 0, 0, 1) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, to) +ZEND_END_ARG_INFO() +#endif + +#ifdef HAVE_GD_JPG +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagejpeg, 0, 0, 1) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, to) + ZEND_ARG_INFO(0, quality) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagewbmp, 0, 0, 1) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, to) + ZEND_ARG_INFO(0, foreground) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagegd, 0, 0, 1) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, to) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagegd2, 0, 0, 1) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, to) + ZEND_ARG_INFO(0, chunk_size) + ZEND_ARG_INFO(0, type) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagedestroy, 0) + ZEND_ARG_INFO(0, im) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolorallocate, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, red) + ZEND_ARG_INFO(0, green) + ZEND_ARG_INFO(0, blue) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagepalettecopy, 0) + ZEND_ARG_INFO(0, dst) + ZEND_ARG_INFO(0, src) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolorat, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, x) + ZEND_ARG_INFO(0, y) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolorclosest, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, red) + ZEND_ARG_INFO(0, green) + ZEND_ARG_INFO(0, blue) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolorclosesthwb, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, red) + ZEND_ARG_INFO(0, green) + ZEND_ARG_INFO(0, blue) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolordeallocate, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, index) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolorresolve, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, red) + ZEND_ARG_INFO(0, green) + ZEND_ARG_INFO(0, blue) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolorexact, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, red) + ZEND_ARG_INFO(0, green) + ZEND_ARG_INFO(0, blue) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagecolorset, 0, 0, 5) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, color) + ZEND_ARG_INFO(0, red) + ZEND_ARG_INFO(0, green) + ZEND_ARG_INFO(0, blue) + ZEND_ARG_INFO(0, alpha) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolorsforindex, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, index) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagegammacorrect, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, inputgamma) + ZEND_ARG_INFO(0, outputgamma) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagesetpixel, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, x) + ZEND_ARG_INFO(0, y) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imageline, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, x1) + ZEND_ARG_INFO(0, y1) + ZEND_ARG_INFO(0, x2) + ZEND_ARG_INFO(0, y2) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagedashedline, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, x1) + ZEND_ARG_INFO(0, y1) + ZEND_ARG_INFO(0, x2) + ZEND_ARG_INFO(0, y2) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagerectangle, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, x1) + ZEND_ARG_INFO(0, y1) + ZEND_ARG_INFO(0, x2) + ZEND_ARG_INFO(0, y2) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagefilledrectangle, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, x1) + ZEND_ARG_INFO(0, y1) + ZEND_ARG_INFO(0, x2) + ZEND_ARG_INFO(0, y2) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagearc, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, cx) + ZEND_ARG_INFO(0, cy) + ZEND_ARG_INFO(0, w) + ZEND_ARG_INFO(0, h) + ZEND_ARG_INFO(0, s) + ZEND_ARG_INFO(0, e) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imageellipse, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, cx) + ZEND_ARG_INFO(0, cy) + ZEND_ARG_INFO(0, w) + ZEND_ARG_INFO(0, h) + ZEND_ARG_INFO(0, color) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagefilltoborder, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, x) + ZEND_ARG_INFO(0, y) + ZEND_ARG_INFO(0, border) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagefill, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, x) + ZEND_ARG_INFO(0, y) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecolorstotal, 0) + ZEND_ARG_INFO(0, im) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagecolortransparent, 0, 0, 1) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imageinterlace, 0, 0, 1) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, interlace) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagepolygon, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, points) /* ARRAY_INFO(0, points, 0) */ + ZEND_ARG_INFO(0, num_pos) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagefilledpolygon, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, points) /* ARRAY_INFO(0, points, 0) */ + ZEND_ARG_INFO(0, num_pos) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagefontwidth, 0) + ZEND_ARG_INFO(0, font) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagefontheight, 0) + ZEND_ARG_INFO(0, font) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagechar, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, font) + ZEND_ARG_INFO(0, x) + ZEND_ARG_INFO(0, y) + ZEND_ARG_INFO(0, c) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecharup, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, font) + ZEND_ARG_INFO(0, x) + ZEND_ARG_INFO(0, y) + ZEND_ARG_INFO(0, c) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagestring, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, font) + ZEND_ARG_INFO(0, x) + ZEND_ARG_INFO(0, y) + ZEND_ARG_INFO(0, str) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagestringup, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, font) + ZEND_ARG_INFO(0, x) + ZEND_ARG_INFO(0, y) + ZEND_ARG_INFO(0, str) + ZEND_ARG_INFO(0, col) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecopy, 0) + ZEND_ARG_INFO(0, dst_im) + ZEND_ARG_INFO(0, src_im) + ZEND_ARG_INFO(0, dst_x) + ZEND_ARG_INFO(0, dst_y) + ZEND_ARG_INFO(0, src_x) + ZEND_ARG_INFO(0, src_y) + ZEND_ARG_INFO(0, src_w) + ZEND_ARG_INFO(0, src_h) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecopymerge, 0) + ZEND_ARG_INFO(0, src_im) + ZEND_ARG_INFO(0, dst_im) + ZEND_ARG_INFO(0, dst_x) + ZEND_ARG_INFO(0, dst_y) + ZEND_ARG_INFO(0, src_x) + ZEND_ARG_INFO(0, src_y) + ZEND_ARG_INFO(0, src_w) + ZEND_ARG_INFO(0, src_h) + ZEND_ARG_INFO(0, pct) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecopymergegray, 0) + ZEND_ARG_INFO(0, src_im) + ZEND_ARG_INFO(0, dst_im) + ZEND_ARG_INFO(0, dst_x) + ZEND_ARG_INFO(0, dst_y) + ZEND_ARG_INFO(0, src_x) + ZEND_ARG_INFO(0, src_y) + ZEND_ARG_INFO(0, src_w) + ZEND_ARG_INFO(0, src_h) + ZEND_ARG_INFO(0, pct) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecopyresized, 0) + ZEND_ARG_INFO(0, dst_im) + ZEND_ARG_INFO(0, src_im) + ZEND_ARG_INFO(0, dst_x) + ZEND_ARG_INFO(0, dst_y) + ZEND_ARG_INFO(0, src_x) + ZEND_ARG_INFO(0, src_y) + ZEND_ARG_INFO(0, dst_w) + ZEND_ARG_INFO(0, dst_h) + ZEND_ARG_INFO(0, src_w) + ZEND_ARG_INFO(0, src_h) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagesx, 0) + ZEND_ARG_INFO(0, im) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagesy, 0) + ZEND_ARG_INFO(0, im) +ZEND_END_ARG_INFO() + +#ifdef ENABLE_GD_TTF +#if HAVE_LIBFREETYPE +ZEND_BEGIN_ARG_INFO_EX(arginfo_imageftbbox, 0, 0, 4) + ZEND_ARG_INFO(0, size) + ZEND_ARG_INFO(0, angle) + ZEND_ARG_INFO(0, font_file) + ZEND_ARG_INFO(0, text) + ZEND_ARG_INFO(0, extrainfo) /* ARRAY_INFO(0, extrainfo, 0) */ +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagefttext, 0, 0, 8) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, size) + ZEND_ARG_INFO(0, angle) + ZEND_ARG_INFO(0, x) + ZEND_ARG_INFO(0, y) + ZEND_ARG_INFO(0, col) + ZEND_ARG_INFO(0, font_file) + ZEND_ARG_INFO(0, text) + ZEND_ARG_INFO(0, extrainfo) /* ARRAY_INFO(0, extrainfo, 0) */ +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO(arginfo_imagettfbbox, 0) + ZEND_ARG_INFO(0, size) + ZEND_ARG_INFO(0, angle) + ZEND_ARG_INFO(0, font_file) + ZEND_ARG_INFO(0, text) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagettftext, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, size) + ZEND_ARG_INFO(0, angle) + ZEND_ARG_INFO(0, x) + ZEND_ARG_INFO(0, y) + ZEND_ARG_INFO(0, col) + ZEND_ARG_INFO(0, font_file) + ZEND_ARG_INFO(0, text) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO_EX(arginfo_image2wbmp, 0, 0, 1) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, filename) + ZEND_ARG_INFO(0, threshold) +ZEND_END_ARG_INFO() + +#if defined(HAVE_GD_JPG) +ZEND_BEGIN_ARG_INFO(arginfo_jpeg2wbmp, 0) + ZEND_ARG_INFO(0, f_org) + ZEND_ARG_INFO(0, f_dest) + ZEND_ARG_INFO(0, d_height) + ZEND_ARG_INFO(0, d_width) + ZEND_ARG_INFO(0, d_threshold) +ZEND_END_ARG_INFO() +#endif + +#if defined(HAVE_GD_PNG) +ZEND_BEGIN_ARG_INFO(arginfo_png2wbmp, 0) + ZEND_ARG_INFO(0, f_org) + ZEND_ARG_INFO(0, f_dest) + ZEND_ARG_INFO(0, d_height) + ZEND_ARG_INFO(0, d_width) + ZEND_ARG_INFO(0, d_threshold) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagefilter, 0, 0, 2) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, filtertype) + ZEND_ARG_INFO(0, arg1) + ZEND_ARG_INFO(0, arg2) + ZEND_ARG_INFO(0, arg3) + ZEND_ARG_INFO(0, arg4) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imageconvolution, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, matrix3x3) /* ARRAY_INFO(0, matrix3x3, 0) */ + ZEND_ARG_INFO(0, div) + ZEND_ARG_INFO(0, offset) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imageflip, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, mode) +ZEND_END_ARG_INFO() + +#ifdef HAVE_GD_BUNDLED +ZEND_BEGIN_ARG_INFO(arginfo_imageantialias, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, on) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO(arginfo_imagecrop, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, rect) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagecropauto, 0, 0, 1) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, mode) + ZEND_ARG_INFO(0, threshold) + ZEND_ARG_INFO(0, color) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imagescale, 0, 0, 2) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, new_width) + ZEND_ARG_INFO(0, new_height) + ZEND_ARG_INFO(0, mode) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imageaffine, 0, 0, 2) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, affine) + ZEND_ARG_INFO(0, clip) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imageaffinematrixget, 0, 0, 1) + ZEND_ARG_INFO(0, type) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imageaffinematrixconcat, 0) + ZEND_ARG_INFO(0, m1) + ZEND_ARG_INFO(0, m2) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagesetinterpolation, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, method) +ZEND_END_ARG_INFO() + +/* }}} */ + +/* {{{ gd_functions[] + */ +const zend_function_entry gd_functions[] = { + PHP_FE(gd_info, arginfo_gd_info) + PHP_FE(imagearc, arginfo_imagearc) + PHP_FE(imageellipse, arginfo_imageellipse) + PHP_FE(imagechar, arginfo_imagechar) + PHP_FE(imagecharup, arginfo_imagecharup) + PHP_FE(imagecolorat, arginfo_imagecolorat) + PHP_FE(imagecolorallocate, arginfo_imagecolorallocate) + PHP_FE(imagepalettecopy, arginfo_imagepalettecopy) + PHP_FE(imagecreatefromstring, arginfo_imagecreatefromstring) + PHP_FE(imagecolorclosest, arginfo_imagecolorclosest) + PHP_FE(imagecolorclosesthwb, arginfo_imagecolorclosesthwb) + PHP_FE(imagecolordeallocate, arginfo_imagecolordeallocate) + PHP_FE(imagecolorresolve, arginfo_imagecolorresolve) + PHP_FE(imagecolorexact, arginfo_imagecolorexact) + PHP_FE(imagecolorset, arginfo_imagecolorset) + PHP_FE(imagecolortransparent, arginfo_imagecolortransparent) + PHP_FE(imagecolorstotal, arginfo_imagecolorstotal) + PHP_FE(imagecolorsforindex, arginfo_imagecolorsforindex) + PHP_FE(imagecopy, arginfo_imagecopy) + PHP_FE(imagecopymerge, arginfo_imagecopymerge) + PHP_FE(imagecopymergegray, arginfo_imagecopymergegray) + PHP_FE(imagecopyresized, arginfo_imagecopyresized) + PHP_FE(imagecreate, arginfo_imagecreate) + PHP_FE(imagecreatetruecolor, arginfo_imagecreatetruecolor) + PHP_FE(imageistruecolor, arginfo_imageistruecolor) + PHP_FE(imagetruecolortopalette, arginfo_imagetruecolortopalette) + PHP_FE(imagepalettetotruecolor, arginfo_imagepalettetotruecolor) + PHP_FE(imagesetthickness, arginfo_imagesetthickness) + PHP_FE(imagefilledarc, arginfo_imagefilledarc) + PHP_FE(imagefilledellipse, arginfo_imagefilledellipse) + PHP_FE(imagealphablending, arginfo_imagealphablending) + PHP_FE(imagesavealpha, arginfo_imagesavealpha) + PHP_FE(imagecolorallocatealpha, arginfo_imagecolorallocatealpha) + PHP_FE(imagecolorresolvealpha, arginfo_imagecolorresolvealpha) + PHP_FE(imagecolorclosestalpha, arginfo_imagecolorclosestalpha) + PHP_FE(imagecolorexactalpha, arginfo_imagecolorexactalpha) + PHP_FE(imagecopyresampled, arginfo_imagecopyresampled) + +#ifdef PHP_WIN32 + PHP_FE(imagegrabwindow, arginfo_imagegrabwindow) + PHP_FE(imagegrabscreen, arginfo_imagegrabscreen) +#endif + + PHP_FE(imagerotate, arginfo_imagerotate) + PHP_FE(imageflip, arginfo_imageflip) + +#ifdef HAVE_GD_BUNDLED + PHP_FE(imageantialias, arginfo_imageantialias) +#endif + PHP_FE(imagecrop, arginfo_imagecrop) + PHP_FE(imagecropauto, arginfo_imagecropauto) + PHP_FE(imagescale, arginfo_imagescale) + PHP_FE(imageaffine, arginfo_imageaffine) + PHP_FE(imageaffinematrixconcat, arginfo_imageaffinematrixconcat) + PHP_FE(imageaffinematrixget, arginfo_imageaffinematrixget) + PHP_FE(imagesetinterpolation, arginfo_imagesetinterpolation) + PHP_FE(imagesettile, arginfo_imagesettile) + PHP_FE(imagesetbrush, arginfo_imagesetbrush) + PHP_FE(imagesetstyle, arginfo_imagesetstyle) + +#ifdef HAVE_GD_PNG + PHP_FE(imagecreatefrompng, arginfo_imagecreatefrompng) +#endif +#ifdef HAVE_GD_WEBP + PHP_FE(imagecreatefromwebp, arginfo_imagecreatefromwebp) +#endif + PHP_FE(imagecreatefromgif, arginfo_imagecreatefromgif) +#ifdef HAVE_GD_JPG + PHP_FE(imagecreatefromjpeg, arginfo_imagecreatefromjpeg) +#endif + PHP_FE(imagecreatefromwbmp, arginfo_imagecreatefromwbmp) + PHP_FE(imagecreatefromxbm, arginfo_imagecreatefromxbm) +#if defined(HAVE_GD_XPM) + PHP_FE(imagecreatefromxpm, arginfo_imagecreatefromxpm) +#endif + PHP_FE(imagecreatefromgd, arginfo_imagecreatefromgd) + PHP_FE(imagecreatefromgd2, arginfo_imagecreatefromgd2) + PHP_FE(imagecreatefromgd2part, arginfo_imagecreatefromgd2part) +#ifdef HAVE_GD_PNG + PHP_FE(imagepng, arginfo_imagepng) +#endif +#ifdef HAVE_GD_WEBP + PHP_FE(imagewebp, arginfo_imagewebp) +#endif + PHP_FE(imagegif, arginfo_imagegif) +#ifdef HAVE_GD_JPG + PHP_FE(imagejpeg, arginfo_imagejpeg) +#endif + PHP_FE(imagewbmp, arginfo_imagewbmp) + PHP_FE(imagegd, arginfo_imagegd) + PHP_FE(imagegd2, arginfo_imagegd2) + + PHP_FE(imagedestroy, arginfo_imagedestroy) + PHP_FE(imagegammacorrect, arginfo_imagegammacorrect) + PHP_FE(imagefill, arginfo_imagefill) + PHP_FE(imagefilledpolygon, arginfo_imagefilledpolygon) + PHP_FE(imagefilledrectangle, arginfo_imagefilledrectangle) + PHP_FE(imagefilltoborder, arginfo_imagefilltoborder) + PHP_FE(imagefontwidth, arginfo_imagefontwidth) + PHP_FE(imagefontheight, arginfo_imagefontheight) + PHP_FE(imageinterlace, arginfo_imageinterlace) + PHP_FE(imageline, arginfo_imageline) + PHP_FE(imageloadfont, arginfo_imageloadfont) + PHP_FE(imagepolygon, arginfo_imagepolygon) + PHP_FE(imagerectangle, arginfo_imagerectangle) + PHP_FE(imagesetpixel, arginfo_imagesetpixel) + PHP_FE(imagestring, arginfo_imagestring) + PHP_FE(imagestringup, arginfo_imagestringup) + PHP_FE(imagesx, arginfo_imagesx) + PHP_FE(imagesy, arginfo_imagesy) + PHP_FE(imagedashedline, arginfo_imagedashedline) + +#ifdef ENABLE_GD_TTF + PHP_FE(imagettfbbox, arginfo_imagettfbbox) + PHP_FE(imagettftext, arginfo_imagettftext) +#if HAVE_GD_FREETYPE && HAVE_LIBFREETYPE + PHP_FE(imageftbbox, arginfo_imageftbbox) + PHP_FE(imagefttext, arginfo_imagefttext) +#endif +#endif + + PHP_FE(imagetypes, arginfo_imagetypes) + +#if defined(HAVE_GD_JPG) + PHP_FE(jpeg2wbmp, arginfo_jpeg2wbmp) +#endif +#if defined(HAVE_GD_PNG) + PHP_FE(png2wbmp, arginfo_png2wbmp) +#endif + PHP_FE(image2wbmp, arginfo_image2wbmp) + PHP_FE(imagelayereffect, arginfo_imagelayereffect) + PHP_FE(imagexbm, arginfo_imagexbm) + + PHP_FE(imagecolormatch, arginfo_imagecolormatch) + +/* gd filters */ + PHP_FE(imagefilter, arginfo_imagefilter) + PHP_FE(imageconvolution, arginfo_imageconvolution) + + PHP_FE_END +}; +/* }}} */ + +zend_module_entry gd_module_entry = { + STANDARD_MODULE_HEADER, + "gd", + gd_functions, + PHP_MINIT(gd), + NULL, + NULL, +#if HAVE_GD_FREETYPE && HAVE_LIBFREETYPE + PHP_RSHUTDOWN(gd), +#else + NULL, +#endif + PHP_MINFO(gd), + PHP_GD_VERSION, + STANDARD_MODULE_PROPERTIES +}; + +#ifdef COMPILE_DL_GD +ZEND_GET_MODULE(gd) +#endif + +/* {{{ PHP_INI_BEGIN */ +PHP_INI_BEGIN() + PHP_INI_ENTRY("gd.jpeg_ignore_warning", "1", PHP_INI_ALL, NULL) +PHP_INI_END() +/* }}} */ + +/* {{{ php_free_gd_image + */ +static void php_free_gd_image(zend_resource *rsrc) +{ + gdImageDestroy((gdImagePtr) rsrc->ptr); +} +/* }}} */ + +/* {{{ php_free_gd_font + */ +static void php_free_gd_font(zend_resource *rsrc) +{ + gdFontPtr fp = (gdFontPtr) rsrc->ptr; + + if (fp->data) { + efree(fp->data); + } + + efree(fp); +} +/* }}} */ + +#ifndef HAVE_GD_BUNDLED +/* {{{ php_gd_error_method + */ +void php_gd_error_method(int type, const char *format, va_list args) +{ + + switch (type) { + case GD_DEBUG: + case GD_INFO: + case GD_NOTICE: + type = E_NOTICE; + break; + case GD_WARNING: + type = E_WARNING; + break; + default: + type = E_ERROR; + } + php_verror(NULL, "", type, format, args); +} +/* }}} */ +#endif + +/* {{{ PHP_MINIT_FUNCTION + */ +PHP_MINIT_FUNCTION(gd) +{ + le_gd = zend_register_list_destructors_ex(php_free_gd_image, NULL, "gd", module_number); + le_gd_font = zend_register_list_destructors_ex(php_free_gd_font, NULL, "gd font", module_number); + +#if HAVE_GD_BUNDLED && HAVE_LIBFREETYPE + gdFontCacheMutexSetup(); +#endif +#ifndef HAVE_GD_BUNDLED + gdSetErrorMethod(php_gd_error_method); +#endif + REGISTER_INI_ENTRIES(); + + REGISTER_LONG_CONSTANT("IMG_GIF", 1, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_JPG", 2, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_JPEG", 2, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_PNG", 4, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_WBMP", 8, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_XPM", 16, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_WEBP", 32, CONST_CS | CONST_PERSISTENT); + + /* special colours for gd */ + REGISTER_LONG_CONSTANT("IMG_COLOR_TILED", gdTiled, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_COLOR_STYLED", gdStyled, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_COLOR_BRUSHED", gdBrushed, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_COLOR_STYLEDBRUSHED", gdStyledBrushed, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_COLOR_TRANSPARENT", gdTransparent, CONST_CS | CONST_PERSISTENT); + + /* for imagefilledarc */ + REGISTER_LONG_CONSTANT("IMG_ARC_ROUNDED", gdArc, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_ARC_PIE", gdPie, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_ARC_CHORD", gdChord, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_ARC_NOFILL", gdNoFill, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_ARC_EDGED", gdEdged, CONST_CS | CONST_PERSISTENT); + + /* GD2 image format types */ + REGISTER_LONG_CONSTANT("IMG_GD2_RAW", GD2_FMT_RAW, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_GD2_COMPRESSED", GD2_FMT_COMPRESSED, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FLIP_HORIZONTAL", GD_FLIP_HORINZONTAL, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FLIP_VERTICAL", GD_FLIP_VERTICAL, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FLIP_BOTH", GD_FLIP_BOTH, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_EFFECT_REPLACE", gdEffectReplace, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_EFFECT_ALPHABLEND", gdEffectAlphaBlend, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_EFFECT_NORMAL", gdEffectNormal, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_EFFECT_OVERLAY", gdEffectOverlay, CONST_CS | CONST_PERSISTENT); + + REGISTER_LONG_CONSTANT("IMG_CROP_DEFAULT", GD_CROP_DEFAULT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_CROP_TRANSPARENT", GD_CROP_TRANSPARENT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_CROP_BLACK", GD_CROP_BLACK, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_CROP_WHITE", GD_CROP_WHITE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_CROP_SIDES", GD_CROP_SIDES, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_CROP_THRESHOLD", GD_CROP_THRESHOLD, CONST_CS | CONST_PERSISTENT); + + + REGISTER_LONG_CONSTANT("IMG_BELL", GD_BELL, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_BESSEL", GD_BESSEL, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_BILINEAR_FIXED", GD_BILINEAR_FIXED, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_BICUBIC", GD_BICUBIC, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_BICUBIC_FIXED", GD_BICUBIC_FIXED, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_BLACKMAN", GD_BLACKMAN, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_BOX", GD_BOX, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_BSPLINE", GD_BSPLINE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_CATMULLROM", GD_CATMULLROM, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_GAUSSIAN", GD_GAUSSIAN, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_GENERALIZED_CUBIC", GD_GENERALIZED_CUBIC, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_HERMITE", GD_HERMITE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_HAMMING", GD_HAMMING, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_HANNING", GD_HANNING, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_MITCHELL", GD_MITCHELL, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_POWER", GD_POWER, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_QUADRATIC", GD_QUADRATIC, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_SINC", GD_SINC, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_NEAREST_NEIGHBOUR", GD_NEAREST_NEIGHBOUR, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_WEIGHTED4", GD_WEIGHTED4, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_TRIANGLE", GD_TRIANGLE, CONST_CS | CONST_PERSISTENT); + + REGISTER_LONG_CONSTANT("IMG_AFFINE_TRANSLATE", GD_AFFINE_TRANSLATE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_AFFINE_SCALE", GD_AFFINE_SCALE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_AFFINE_ROTATE", GD_AFFINE_ROTATE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_AFFINE_SHEAR_HORIZONTAL", GD_AFFINE_SHEAR_HORIZONTAL, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_AFFINE_SHEAR_VERTICAL", GD_AFFINE_SHEAR_VERTICAL, CONST_CS | CONST_PERSISTENT); + +#if defined(HAVE_GD_BUNDLED) + REGISTER_LONG_CONSTANT("GD_BUNDLED", 1, CONST_CS | CONST_PERSISTENT); +#else + REGISTER_LONG_CONSTANT("GD_BUNDLED", 0, CONST_CS | CONST_PERSISTENT); +#endif + + /* Section Filters */ + REGISTER_LONG_CONSTANT("IMG_FILTER_NEGATE", IMAGE_FILTER_NEGATE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FILTER_GRAYSCALE", IMAGE_FILTER_GRAYSCALE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FILTER_BRIGHTNESS", IMAGE_FILTER_BRIGHTNESS, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FILTER_CONTRAST", IMAGE_FILTER_CONTRAST, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FILTER_COLORIZE", IMAGE_FILTER_COLORIZE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FILTER_EDGEDETECT", IMAGE_FILTER_EDGEDETECT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FILTER_GAUSSIAN_BLUR", IMAGE_FILTER_GAUSSIAN_BLUR, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FILTER_SELECTIVE_BLUR", IMAGE_FILTER_SELECTIVE_BLUR, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FILTER_EMBOSS", IMAGE_FILTER_EMBOSS, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FILTER_MEAN_REMOVAL", IMAGE_FILTER_MEAN_REMOVAL, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FILTER_SMOOTH", IMAGE_FILTER_SMOOTH, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_FILTER_PIXELATE", IMAGE_FILTER_PIXELATE, CONST_CS | CONST_PERSISTENT); + /* End Section Filters */ + +#ifdef GD_VERSION_STRING + REGISTER_STRING_CONSTANT("GD_VERSION", GD_VERSION_STRING, CONST_CS | CONST_PERSISTENT); +#endif + +#if defined(GD_MAJOR_VERSION) && defined(GD_MINOR_VERSION) && defined(GD_RELEASE_VERSION) && defined(GD_EXTRA_VERSION) + REGISTER_LONG_CONSTANT("GD_MAJOR_VERSION", GD_MAJOR_VERSION, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("GD_MINOR_VERSION", GD_MINOR_VERSION, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("GD_RELEASE_VERSION", GD_RELEASE_VERSION, CONST_CS | CONST_PERSISTENT); + REGISTER_STRING_CONSTANT("GD_EXTRA_VERSION", GD_EXTRA_VERSION, CONST_CS | CONST_PERSISTENT); +#endif + + +#ifdef HAVE_GD_PNG + + /* + * cannot include #include "png.h" + * /usr/include/pngconf.h:310:2: error: #error png.h already includes setjmp.h with some additional fixup. + * as error, use the values for now... + */ + REGISTER_LONG_CONSTANT("PNG_NO_FILTER", 0x00, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PNG_FILTER_NONE", 0x08, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PNG_FILTER_SUB", 0x10, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PNG_FILTER_UP", 0x20, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PNG_FILTER_AVG", 0x40, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PNG_FILTER_PAETH", 0x80, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PNG_ALL_FILTERS", 0x08 | 0x10 | 0x20 | 0x40 | 0x80, CONST_CS | CONST_PERSISTENT); +#endif + + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_RSHUTDOWN_FUNCTION + */ +#if HAVE_GD_FREETYPE && HAVE_LIBFREETYPE +PHP_RSHUTDOWN_FUNCTION(gd) +{ + gdFontCacheShutdown(); + return SUCCESS; +} +#endif +/* }}} */ + +#if defined(HAVE_GD_BUNDLED) +#define PHP_GD_VERSION_STRING "bundled (2.1.0 compatible)" +#else +# define PHP_GD_VERSION_STRING GD_VERSION_STRING +#endif + +/* {{{ PHP_MINFO_FUNCTION + */ +PHP_MINFO_FUNCTION(gd) +{ + php_info_print_table_start(); + php_info_print_table_row(2, "GD Support", "enabled"); + + /* need to use a PHPAPI function here because it is external module in windows */ + +#if defined(HAVE_GD_BUNDLED) + php_info_print_table_row(2, "GD Version", PHP_GD_VERSION_STRING); +#else + php_info_print_table_row(2, "GD headers Version", PHP_GD_VERSION_STRING); +#if defined(HAVE_GD_LIBVERSION) + php_info_print_table_row(2, "GD library Version", gdVersionString()); +#endif +#endif + +#ifdef ENABLE_GD_TTF + php_info_print_table_row(2, "FreeType Support", "enabled"); +#if HAVE_LIBFREETYPE + php_info_print_table_row(2, "FreeType Linkage", "with freetype"); + { + char tmp[256]; + +#ifdef FREETYPE_PATCH + snprintf(tmp, sizeof(tmp), "%d.%d.%d", FREETYPE_MAJOR, FREETYPE_MINOR, FREETYPE_PATCH); +#elif defined(FREETYPE_MAJOR) + snprintf(tmp, sizeof(tmp), "%d.%d", FREETYPE_MAJOR, FREETYPE_MINOR); +#else + snprintf(tmp, sizeof(tmp), "1.x"); +#endif + php_info_print_table_row(2, "FreeType Version", tmp); + } +#else + php_info_print_table_row(2, "FreeType Linkage", "with unknown library"); +#endif +#endif + + php_info_print_table_row(2, "GIF Read Support", "enabled"); + php_info_print_table_row(2, "GIF Create Support", "enabled"); + +#ifdef HAVE_GD_JPG + { + php_info_print_table_row(2, "JPEG Support", "enabled"); + php_info_print_table_row(2, "libJPEG Version", gdJpegGetVersionString()); + } +#endif + +#ifdef HAVE_GD_PNG + php_info_print_table_row(2, "PNG Support", "enabled"); + php_info_print_table_row(2, "libPNG Version", gdPngGetVersionString()); +#endif + php_info_print_table_row(2, "WBMP Support", "enabled"); +#if defined(HAVE_GD_XPM) + php_info_print_table_row(2, "XPM Support", "enabled"); + { + char tmp[12]; + snprintf(tmp, sizeof(tmp), "%d", XpmLibraryVersion()); + php_info_print_table_row(2, "libXpm Version", tmp); + } +#endif + php_info_print_table_row(2, "XBM Support", "enabled"); +#if defined(USE_GD_JISX0208) + php_info_print_table_row(2, "JIS-mapped Japanese Font Support", "enabled"); +#endif +#ifdef HAVE_GD_WEBP + php_info_print_table_row(2, "WebP Support", "enabled"); +#endif + php_info_print_table_end(); + DISPLAY_INI_ENTRIES(); +} +/* }}} */ + +/* {{{ proto array gd_info() + */ +PHP_FUNCTION(gd_info) +{ + if (zend_parse_parameters_none() == FAILURE) { + RETURN_FALSE; + } + + array_init(return_value); + + add_assoc_string(return_value, "GD Version", PHP_GD_VERSION_STRING); + +#ifdef ENABLE_GD_TTF + add_assoc_bool(return_value, "FreeType Support", 1); +#if HAVE_LIBFREETYPE + add_assoc_string(return_value, "FreeType Linkage", "with freetype"); +#else + add_assoc_string(return_value, "FreeType Linkage", "with unknown library"); +#endif +#else + add_assoc_bool(return_value, "FreeType Support", 0); +#endif + add_assoc_bool(return_value, "GIF Read Support", 1); + add_assoc_bool(return_value, "GIF Create Support", 1); +#ifdef HAVE_GD_JPG + add_assoc_bool(return_value, "JPEG Support", 1); +#else + add_assoc_bool(return_value, "JPEG Support", 0); +#endif +#ifdef HAVE_GD_PNG + add_assoc_bool(return_value, "PNG Support", 1); +#else + add_assoc_bool(return_value, "PNG Support", 0); +#endif + add_assoc_bool(return_value, "WBMP Support", 1); +#if defined(HAVE_GD_XPM) + add_assoc_bool(return_value, "XPM Support", 1); +#else + add_assoc_bool(return_value, "XPM Support", 0); +#endif + add_assoc_bool(return_value, "XBM Support", 1); +#ifdef HAVE_GD_WEBP + add_assoc_bool(return_value, "WebP Support", 1); +#else + add_assoc_bool(return_value, "WebP Support", 0); +#endif +#if defined(USE_GD_JISX0208) + add_assoc_bool(return_value, "JIS-mapped Japanese Font Support", 1); +#else + add_assoc_bool(return_value, "JIS-mapped Japanese Font Support", 0); +#endif +} +/* }}} */ + +/* Need this for cpdf. See also comment in file.c php3i_get_le_fp() */ +PHP_GD_API int phpi_get_le_gd(void) +{ + return le_gd; +} +/* }}} */ + +#define FLIPWORD(a) (((a & 0xff000000) >> 24) | ((a & 0x00ff0000) >> 8) | ((a & 0x0000ff00) << 8) | ((a & 0x000000ff) << 24)) + +/* {{{ proto int imageloadfont(string filename) + Load a new font */ +PHP_FUNCTION(imageloadfont) +{ + zval *ind; + zend_string *file; + int hdr_size = sizeof(gdFont) - sizeof(char *); + int body_size, n = 0, b, i, body_size_check; + gdFontPtr font; + php_stream *stream; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "P", &file) == FAILURE) { + return; + } + + stream = php_stream_open_wrapper(ZSTR_VAL(file), "rb", IGNORE_PATH | IGNORE_URL_WIN | REPORT_ERRORS, NULL); + if (stream == NULL) { + RETURN_FALSE; + } + + /* Only supports a architecture-dependent binary dump format + * at the moment. + * The file format is like this on machines with 32-byte integers: + * + * byte 0-3: (int) number of characters in the font + * byte 4-7: (int) value of first character in the font (often 32, space) + * byte 8-11: (int) pixel width of each character + * byte 12-15: (int) pixel height of each character + * bytes 16-: (char) array with character data, one byte per pixel + * in each character, for a total of + * (nchars*width*height) bytes. + */ + font = (gdFontPtr) emalloc(sizeof(gdFont)); + b = 0; + while (b < hdr_size && (n = php_stream_read(stream, (char*)&font[b], hdr_size - b))) { + b += n; + } + + if (!n) { + efree(font); + if (php_stream_eof(stream)) { + php_error_docref(NULL, E_WARNING, "End of file while reading header"); + } else { + php_error_docref(NULL, E_WARNING, "Error while reading header"); + } + php_stream_close(stream); + RETURN_FALSE; + } + i = php_stream_tell(stream); + php_stream_seek(stream, 0, SEEK_END); + body_size_check = php_stream_tell(stream) - hdr_size; + php_stream_seek(stream, i, SEEK_SET); + + body_size = font->w * font->h * font->nchars; + if (body_size != body_size_check) { + font->w = FLIPWORD(font->w); + font->h = FLIPWORD(font->h); + font->nchars = FLIPWORD(font->nchars); + body_size = font->w * font->h * font->nchars; + } + + if (overflow2(font->nchars, font->h) || overflow2(font->nchars * font->h, font->w )) { + php_error_docref(NULL, E_WARNING, "Error reading font, invalid font header"); + efree(font); + php_stream_close(stream); + RETURN_FALSE; + } + + if (body_size != body_size_check) { + php_error_docref(NULL, E_WARNING, "Error reading font"); + efree(font); + php_stream_close(stream); + RETURN_FALSE; + } + + font->data = emalloc(body_size); + b = 0; + while (b < body_size && (n = php_stream_read(stream, &font->data[b], body_size - b))) { + b += n; + } + + if (!n) { + efree(font->data); + efree(font); + if (php_stream_eof(stream)) { + php_error_docref(NULL, E_WARNING, "End of file while reading body"); + } else { + php_error_docref(NULL, E_WARNING, "Error while reading body"); + } + php_stream_close(stream); + RETURN_FALSE; + } + php_stream_close(stream); + + ind = zend_list_insert(font, le_gd_font); + + /* Adding 5 to the font index so we will never have font indices + * that overlap with the old fonts (with indices 1-5). The first + * list index given out is always 1. + */ + RETURN_LONG(Z_RES_HANDLE_P(ind) + 5); +} +/* }}} */ + +/* {{{ proto bool imagesetstyle(resource im, array styles) + Set the line drawing styles for use with imageline and IMG_COLOR_STYLED. */ +PHP_FUNCTION(imagesetstyle) +{ + zval *IM, *styles, *item; + gdImagePtr im; + int *stylearr; + int index = 0; + uint32_t num_styles; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ra", &IM, &styles) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + num_styles = zend_hash_num_elements(Z_ARRVAL_P(styles)); + if (num_styles == 0) { + php_error_docref(NULL, E_WARNING, "styles array must not be empty"); + RETURN_FALSE; + } + + /* copy the style values in the stylearr */ + stylearr = safe_emalloc(sizeof(int), num_styles, 0); + + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(styles), item) { + stylearr[index++] = zval_get_long(item); + } ZEND_HASH_FOREACH_END(); + + gdImageSetStyle(im, stylearr, index); + + efree(stylearr); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto resource imagecreatetruecolor(int x_size, int y_size) + Create a new true color image */ +PHP_FUNCTION(imagecreatetruecolor) +{ + zend_long x_size, y_size; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ll", &x_size, &y_size) == FAILURE) { + return; + } + + if (x_size <= 0 || y_size <= 0 || x_size >= INT_MAX || y_size >= INT_MAX) { + php_error_docref(NULL, E_WARNING, "Invalid image dimensions"); + RETURN_FALSE; + } + + im = gdImageCreateTrueColor(x_size, y_size); + + if (!im) { + RETURN_FALSE; + } + + RETURN_RES(zend_register_resource(im, le_gd)); +} +/* }}} */ + +/* {{{ proto bool imageistruecolor(resource im) + return true if the image uses truecolor */ +PHP_FUNCTION(imageistruecolor) +{ + zval *IM; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &IM) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + RETURN_BOOL(im->trueColor); +} +/* }}} */ + +/* {{{ proto void imagetruecolortopalette(resource im, bool ditherFlag, int colorsWanted) + Convert a true color image to a palette based image with a number of colors, optionally using dithering. */ +PHP_FUNCTION(imagetruecolortopalette) +{ + zval *IM; + zend_bool dither; + zend_long ncolors; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rbl", &IM, &dither, &ncolors) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (ncolors <= 0 || ZEND_LONG_INT_OVFL(ncolors)) { + php_error_docref(NULL, E_WARNING, "Number of colors has to be greater than zero and no more than %d", INT_MAX); + RETURN_FALSE; + } + gdImageTrueColorToPalette(im, dither, (int)ncolors); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto void imagepalettetotruecolor(resource im) + Convert a palette based image to a true color image. */ +PHP_FUNCTION(imagepalettetotruecolor) +{ + zval *IM; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &IM) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (gdImagePaletteToTrueColor(im) == 0) { + RETURN_FALSE; + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagecolormatch(resource im1, resource im2) + Makes the colors of the palette version of an image more closely match the true color version */ +PHP_FUNCTION(imagecolormatch) +{ + zval *IM1, *IM2; + gdImagePtr im1, im2; + int result; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rr", &IM1, &IM2) == FAILURE) { + return; + } + + if ((im1 = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM1), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + if ((im2 = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM2), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + result = gdImageColorMatch(im1, im2); + switch (result) { + case -1: + php_error_docref(NULL, E_WARNING, "Image1 must be TrueColor" ); + RETURN_FALSE; + break; + case -2: + php_error_docref(NULL, E_WARNING, "Image2 must be Palette" ); + RETURN_FALSE; + break; + case -3: + php_error_docref(NULL, E_WARNING, "Image1 and Image2 must be the same size" ); + RETURN_FALSE; + break; + case -4: + php_error_docref(NULL, E_WARNING, "Image2 must have at least one color" ); + RETURN_FALSE; + break; + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagesetthickness(resource im, int thickness) + Set line thickness for drawing lines, ellipses, rectangles, polygons etc. */ +PHP_FUNCTION(imagesetthickness) +{ + zval *IM; + zend_long thick; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rl", &IM, &thick) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageSetThickness(im, thick); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagefilledellipse(resource im, int cx, int cy, int w, int h, int color) + Draw an ellipse */ +PHP_FUNCTION(imagefilledellipse) +{ + zval *IM; + zend_long cx, cy, w, h, color; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlllll", &IM, &cx, &cy, &w, &h, &color) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageFilledEllipse(im, cx, cy, w, h, color); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagefilledarc(resource im, int cx, int cy, int w, int h, int s, int e, int col, int style) + Draw a filled partial ellipse */ +PHP_FUNCTION(imagefilledarc) +{ + zval *IM; + zend_long cx, cy, w, h, ST, E, col, style; + gdImagePtr im; + int e, st; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rllllllll", &IM, &cx, &cy, &w, &h, &ST, &E, &col, &style) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + e = E; + if (e < 0) { + e %= 360; + } + + st = ST; + if (st < 0) { + st %= 360; + } + + gdImageFilledArc(im, cx, cy, w, h, st, e, col, style); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagealphablending(resource im, bool on) + Turn alpha blending mode on or off for the given image */ +PHP_FUNCTION(imagealphablending) +{ + zval *IM; + zend_bool blend; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rb", &IM, &blend) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageAlphaBlending(im, blend); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagesavealpha(resource im, bool on) + Include alpha channel to a saved image */ +PHP_FUNCTION(imagesavealpha) +{ + zval *IM; + zend_bool save; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rb", &IM, &save) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageSaveAlpha(im, save); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagelayereffect(resource im, int effect) + Set the alpha blending flag to use the bundled libgd layering effects */ +PHP_FUNCTION(imagelayereffect) +{ + zval *IM; + zend_long effect; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rl", &IM, &effect) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageAlphaBlending(im, effect); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto int imagecolorallocatealpha(resource im, int red, int green, int blue, int alpha) + Allocate a color with an alpha level. Works for true color and palette based images */ +PHP_FUNCTION(imagecolorallocatealpha) +{ + zval *IM; + zend_long red, green, blue, alpha; + gdImagePtr im; + int ct = (-1); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rllll", &IM, &red, &green, &blue, &alpha) == FAILURE) { + RETURN_FALSE; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + ct = gdImageColorAllocateAlpha(im, red, green, blue, alpha); + if (ct < 0) { + RETURN_FALSE; + } + RETURN_LONG((zend_long)ct); +} +/* }}} */ + +/* {{{ proto int imagecolorresolvealpha(resource im, int red, int green, int blue, int alpha) + Resolve/Allocate a colour with an alpha level. Works for true colour and palette based images */ +PHP_FUNCTION(imagecolorresolvealpha) +{ + zval *IM; + zend_long red, green, blue, alpha; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rllll", &IM, &red, &green, &blue, &alpha) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(gdImageColorResolveAlpha(im, red, green, blue, alpha)); +} +/* }}} */ + +/* {{{ proto int imagecolorclosestalpha(resource im, int red, int green, int blue, int alpha) + Find the closest matching colour with alpha transparency */ +PHP_FUNCTION(imagecolorclosestalpha) +{ + zval *IM; + zend_long red, green, blue, alpha; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rllll", &IM, &red, &green, &blue, &alpha) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(gdImageColorClosestAlpha(im, red, green, blue, alpha)); +} +/* }}} */ + +/* {{{ proto int imagecolorexactalpha(resource im, int red, int green, int blue, int alpha) + Find exact match for colour with transparency */ +PHP_FUNCTION(imagecolorexactalpha) +{ + zval *IM; + zend_long red, green, blue, alpha; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rllll", &IM, &red, &green, &blue, &alpha) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(gdImageColorExactAlpha(im, red, green, blue, alpha)); +} +/* }}} */ + +/* {{{ proto bool imagecopyresampled(resource dst_im, resource src_im, int dst_x, int dst_y, int src_x, int src_y, int dst_w, int dst_h, int src_w, int src_h) + Copy and resize part of an image using resampling to help ensure clarity */ +PHP_FUNCTION(imagecopyresampled) +{ + zval *SIM, *DIM; + zend_long SX, SY, SW, SH, DX, DY, DW, DH; + gdImagePtr im_dst, im_src; + int srcH, srcW, dstH, dstW, srcY, srcX, dstY, dstX; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rrllllllll", &DIM, &SIM, &DX, &DY, &SX, &SY, &DW, &DH, &SW, &SH) == FAILURE) { + return; + } + + if ((im_dst = (gdImagePtr)zend_fetch_resource(Z_RES_P(DIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if ((im_src = (gdImagePtr)zend_fetch_resource(Z_RES_P(SIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + srcX = SX; + srcY = SY; + srcH = SH; + srcW = SW; + dstX = DX; + dstY = DY; + dstH = DH; + dstW = DW; + + gdImageCopyResampled(im_dst, im_src, dstX, dstY, srcX, srcY, dstW, dstH, srcW, srcH); + + RETURN_TRUE; +} +/* }}} */ + +#ifdef PHP_WIN32 +/* {{{ proto resource imagegrabwindow(int window_handle [, int client_area]) + Grab a window or its client area using a windows handle (HWND property in COM instance) */ +PHP_FUNCTION(imagegrabwindow) +{ + HWND window; + zend_long client_area = 0; + RECT rc = {0}; + RECT rc_win = {0}; + int Width, Height; + HDC hdc; + HDC memDC; + HBITMAP memBM; + HBITMAP hOld; + HINSTANCE handle; + zend_long lwindow_handle; + typedef BOOL (WINAPI *tPrintWindow)(HWND, HDC,UINT); + tPrintWindow pPrintWindow = 0; + gdImagePtr im = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l|l", &lwindow_handle, &client_area) == FAILURE) { + RETURN_FALSE; + } + + window = (HWND) lwindow_handle; + + if (!IsWindow(window)) { + php_error_docref(NULL, E_NOTICE, "Invalid window handle"); + RETURN_FALSE; + } + + hdc = GetDC(0); + + if (client_area) { + GetClientRect(window, &rc); + Width = rc.right; + Height = rc.bottom; + } else { + GetWindowRect(window, &rc); + Width = rc.right - rc.left; + Height = rc.bottom - rc.top; + } + + Width = (Width/4)*4; + + memDC = CreateCompatibleDC(hdc); + memBM = CreateCompatibleBitmap(hdc, Width, Height); + hOld = (HBITMAP) SelectObject (memDC, memBM); + + + handle = LoadLibrary("User32.dll"); + if ( handle == 0 ) { + goto clean; + } + pPrintWindow = (tPrintWindow) GetProcAddress(handle, "PrintWindow"); + + if ( pPrintWindow ) { + pPrintWindow(window, memDC, (UINT) client_area); + } else { + php_error_docref(NULL, E_WARNING, "Windows API too old"); + goto clean; + } + + FreeLibrary(handle); + + im = gdImageCreateTrueColor(Width, Height); + if (im) { + int x,y; + for (y=0; y <= Height; y++) { + for (x=0; x <= Width; x++) { + int c = GetPixel(memDC, x,y); + gdImageSetPixel(im, x, y, gdTrueColor(GetRValue(c), GetGValue(c), GetBValue(c))); + } + } + } + +clean: + SelectObject(memDC,hOld); + DeleteObject(memBM); + DeleteDC(memDC); + ReleaseDC( 0, hdc ); + + if (!im) { + RETURN_FALSE; + } else { + RETURN_RES(zend_register_resource(im, le_gd)); + } +} +/* }}} */ + +/* {{{ proto resource imagegrabscreen() + Grab a screenshot */ +PHP_FUNCTION(imagegrabscreen) +{ + HWND window = GetDesktopWindow(); + RECT rc = {0}; + int Width, Height; + HDC hdc; + HDC memDC; + HBITMAP memBM; + HBITMAP hOld; + typedef BOOL (WINAPI *tPrintWindow)(HWND, HDC,UINT); + tPrintWindow pPrintWindow = 0; + gdImagePtr im; + hdc = GetDC(0); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (!hdc) { + RETURN_FALSE; + } + + GetWindowRect(window, &rc); + Width = rc.right - rc.left; + Height = rc.bottom - rc.top; + + Width = (Width/4)*4; + + memDC = CreateCompatibleDC(hdc); + memBM = CreateCompatibleBitmap(hdc, Width, Height); + hOld = (HBITMAP) SelectObject (memDC, memBM); + BitBlt( memDC, 0, 0, Width, Height , hdc, rc.left, rc.top , SRCCOPY ); + + im = gdImageCreateTrueColor(Width, Height); + if (im) { + int x,y; + for (y=0; y <= Height; y++) { + for (x=0; x <= Width; x++) { + int c = GetPixel(memDC, x,y); + gdImageSetPixel(im, x, y, gdTrueColor(GetRValue(c), GetGValue(c), GetBValue(c))); + } + } + } + + SelectObject(memDC,hOld); + DeleteObject(memBM); + DeleteDC(memDC); + ReleaseDC( 0, hdc ); + + if (!im) { + RETURN_FALSE; + } else { + RETURN_RES(zend_register_resource(im, le_gd)); + } +} +/* }}} */ +#endif /* PHP_WIN32 */ + +/* {{{ proto resource imagerotate(resource src_im, float angle, int bgdcolor [, int ignoretransparent]) + Rotate an image using a custom angle */ +PHP_FUNCTION(imagerotate) +{ + zval *SIM; + gdImagePtr im_dst, im_src; + double degrees; + zend_long color; + zend_long ignoretransparent = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rdl|l", &SIM, °rees, &color, &ignoretransparent) == FAILURE) { + RETURN_FALSE; + } + + if ((im_src = (gdImagePtr)zend_fetch_resource(Z_RES_P(SIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + im_dst = gdImageRotateInterpolated(im_src, (const float)degrees, color); + + if (im_dst != NULL) { + RETURN_RES(zend_register_resource(im_dst, le_gd)); + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto bool imagesettile(resource image, resource tile) + Set the tile image to $tile when filling $image with the "IMG_COLOR_TILED" color */ +PHP_FUNCTION(imagesettile) +{ + zval *IM, *TILE; + gdImagePtr im, tile; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rr", &IM, &TILE) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if ((tile = (gdImagePtr)zend_fetch_resource(Z_RES_P(TILE), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageSetTile(im, tile); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagesetbrush(resource image, resource brush) + Set the brush image to $brush when filling $image with the "IMG_COLOR_BRUSHED" color */ +PHP_FUNCTION(imagesetbrush) +{ + zval *IM, *TILE; + gdImagePtr im, tile; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rr", &IM, &TILE) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if ((tile = (gdImagePtr)zend_fetch_resource(Z_RES_P(TILE), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageSetBrush(im, tile); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto resource imagecreate(int x_size, int y_size) + Create a new image */ +PHP_FUNCTION(imagecreate) +{ + zend_long x_size, y_size; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ll", &x_size, &y_size) == FAILURE) { + return; + } + + if (x_size <= 0 || y_size <= 0 || x_size >= INT_MAX || y_size >= INT_MAX) { + php_error_docref(NULL, E_WARNING, "Invalid image dimensions"); + RETURN_FALSE; + } + + im = gdImageCreate(x_size, y_size); + + if (!im) { + RETURN_FALSE; + } + + RETURN_RES(zend_register_resource(im, le_gd)); +} +/* }}} */ + +/* {{{ proto int imagetypes(void) + Return the types of images supported in a bitfield - 1=GIF, 2=JPEG, 4=PNG, 8=WBMP, 16=XPM */ +PHP_FUNCTION(imagetypes) +{ + int ret=0; + ret = 1; +#ifdef HAVE_GD_JPG + ret |= 2; +#endif +#ifdef HAVE_GD_PNG + ret |= 4; +#endif + ret |= 8; +#if defined(HAVE_GD_XPM) + ret |= 16; +#endif +#ifdef HAVE_GD_WEBP + ret |= 32; +#endif + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + RETURN_LONG(ret); +} +/* }}} */ + +/* {{{ _php_ctx_getmbi + */ + +static int _php_ctx_getmbi(gdIOCtx *ctx) +{ + int i, mbi = 0; + + do { + i = (ctx->getC)(ctx); + if (i < 0) { + return -1; + } + mbi = (mbi << 7) | (i & 0x7f); + } while (i & 0x80); + + return mbi; +} +/* }}} */ + +/* {{{ _php_image_type + */ +static const char php_sig_gd2[3] = {'g', 'd', '2'}; + +static int _php_image_type (char data[8]) +{ + /* Based on ext/standard/image.c */ + + if (data == NULL) { + return -1; + } + + if (!memcmp(data, php_sig_gd2, 3)) { + return PHP_GDIMG_TYPE_GD2; + } else if (!memcmp(data, php_sig_jpg, 3)) { + return PHP_GDIMG_TYPE_JPG; + } else if (!memcmp(data, php_sig_png, 3)) { + if (!memcmp(data, php_sig_png, 8)) { + return PHP_GDIMG_TYPE_PNG; + } + } else if (!memcmp(data, php_sig_gif, 3)) { + return PHP_GDIMG_TYPE_GIF; + } + else { + gdIOCtx *io_ctx; + io_ctx = gdNewDynamicCtxEx(8, data, 0); + if (io_ctx) { + if (_php_ctx_getmbi(io_ctx) == 0 && _php_ctx_getmbi(io_ctx) >= 0) { + io_ctx->gd_free(io_ctx); + return PHP_GDIMG_TYPE_WBM; + } else { + io_ctx->gd_free(io_ctx); + } + } + } + return -1; +} +/* }}} */ + +/* {{{ _php_image_create_from_string + */ +gdImagePtr _php_image_create_from_string(zval *data, char *tn, gdImagePtr (*ioctx_func_p)()) +{ + gdImagePtr im; + gdIOCtx *io_ctx; + + io_ctx = gdNewDynamicCtxEx(Z_STRLEN_P(data), Z_STRVAL_P(data), 0); + + if (!io_ctx) { + return NULL; + } + + im = (*ioctx_func_p)(io_ctx); + if (!im) { + php_error_docref(NULL, E_WARNING, "Passed data is not in '%s' format", tn); + io_ctx->gd_free(io_ctx); + return NULL; + } + + io_ctx->gd_free(io_ctx); + + return im; +} +/* }}} */ + +/* {{{ proto resource imagecreatefromstring(string image) + Create a new image from the image stream in the string */ +PHP_FUNCTION(imagecreatefromstring) +{ + zval *data; + gdImagePtr im; + int imtype; + char sig[8]; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &data) == FAILURE) { + return; + } + + convert_to_string_ex(data); + if (Z_STRLEN_P(data) < 8) { + php_error_docref(NULL, E_WARNING, "Empty string or invalid image"); + RETURN_FALSE; + } + + memcpy(sig, Z_STRVAL_P(data), 8); + + imtype = _php_image_type(sig); + + switch (imtype) { + case PHP_GDIMG_TYPE_JPG: +#ifdef HAVE_GD_JPG + im = _php_image_create_from_string(data, "JPEG", gdImageCreateFromJpegCtx); +#else + php_error_docref(NULL, E_WARNING, "No JPEG support in this PHP build"); + RETURN_FALSE; +#endif + break; + + case PHP_GDIMG_TYPE_PNG: +#ifdef HAVE_GD_PNG + im = _php_image_create_from_string(data, "PNG", gdImageCreateFromPngCtx); +#else + php_error_docref(NULL, E_WARNING, "No PNG support in this PHP build"); + RETURN_FALSE; +#endif + break; + + case PHP_GDIMG_TYPE_GIF: + im = _php_image_create_from_string(data, "GIF", gdImageCreateFromGifCtx); + break; + + case PHP_GDIMG_TYPE_WBM: + im = _php_image_create_from_string(data, "WBMP", gdImageCreateFromWBMPCtx); + break; + + case PHP_GDIMG_TYPE_GD2: + im = _php_image_create_from_string(data, "GD2", gdImageCreateFromGd2Ctx); + break; + + default: + php_error_docref(NULL, E_WARNING, "Data is not in a recognized format"); + RETURN_FALSE; + } + + if (!im) { + php_error_docref(NULL, E_WARNING, "Couldn't create GD Image Stream out of Data"); + RETURN_FALSE; + } + + RETURN_RES(zend_register_resource(im, le_gd)); +} +/* }}} */ + +/* {{{ _php_image_create_from + */ +static void _php_image_create_from(INTERNAL_FUNCTION_PARAMETERS, int image_type, char *tn, gdImagePtr (*func_p)(), gdImagePtr (*ioctx_func_p)()) +{ + char *file; + size_t file_len; + zend_long srcx, srcy, width, height; + gdImagePtr im = NULL; + php_stream *stream; + FILE * fp = NULL; +#ifdef HAVE_GD_JPG + long ignore_warning; +#endif + + if (image_type == PHP_GDIMG_TYPE_GD2PART) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "pllll", &file, &file_len, &srcx, &srcy, &width, &height) == FAILURE) { + return; + } + if (width < 1 || height < 1) { + php_error_docref(NULL, E_WARNING, "Zero width or height not allowed"); + RETURN_FALSE; + } + } else { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p", &file, &file_len) == FAILURE) { + return; + } + } + + + stream = php_stream_open_wrapper(file, "rb", REPORT_ERRORS|IGNORE_PATH|IGNORE_URL_WIN, NULL); + if (stream == NULL) { + RETURN_FALSE; + } + + /* try and avoid allocating a FILE* if the stream is not naturally a FILE* */ + if (php_stream_is(stream, PHP_STREAM_IS_STDIO)) { + if (FAILURE == php_stream_cast(stream, PHP_STREAM_AS_STDIO, (void**)&fp, REPORT_ERRORS)) { + goto out_err; + } + } else if (ioctx_func_p) { + /* we can create an io context */ + gdIOCtx* io_ctx; + zend_string *buff; + char *pstr; + + buff = php_stream_copy_to_mem(stream, PHP_STREAM_COPY_ALL, 0); + + if (!buff) { + php_error_docref(NULL, E_WARNING,"Cannot read image data"); + goto out_err; + } + + /* needs to be malloc (persistent) - GD will free() it later */ + pstr = pestrndup(ZSTR_VAL(buff), ZSTR_LEN(buff), 1); + io_ctx = gdNewDynamicCtxEx(ZSTR_LEN(buff), pstr, 0); + if (!io_ctx) { + pefree(pstr, 1); + zend_string_release(buff); + php_error_docref(NULL, E_WARNING,"Cannot allocate GD IO context"); + goto out_err; + } + + if (image_type == PHP_GDIMG_TYPE_GD2PART) { + im = (*ioctx_func_p)(io_ctx, srcx, srcy, width, height); + } else { + im = (*ioctx_func_p)(io_ctx); + } + io_ctx->gd_free(io_ctx); + pefree(pstr, 1); + zend_string_release(buff); + } + else if (php_stream_can_cast(stream, PHP_STREAM_AS_STDIO)) { + /* try and force the stream to be FILE* */ + if (FAILURE == php_stream_cast(stream, PHP_STREAM_AS_STDIO | PHP_STREAM_CAST_TRY_HARD, (void **) &fp, REPORT_ERRORS)) { + goto out_err; + } + } + + if (!im && fp) { + switch (image_type) { + case PHP_GDIMG_TYPE_GD2PART: + im = (*func_p)(fp, srcx, srcy, width, height); + break; +#if defined(HAVE_GD_XPM) + case PHP_GDIMG_TYPE_XPM: + im = gdImageCreateFromXpm(file); + break; +#endif + +#ifdef HAVE_GD_JPG + case PHP_GDIMG_TYPE_JPG: + ignore_warning = INI_INT("gd.jpeg_ignore_warning"); + im = gdImageCreateFromJpegEx(fp, ignore_warning); + break; +#endif + + default: + im = (*func_p)(fp); + break; + } + + fflush(fp); + } + +/* register_im: */ + if (im) { + RETVAL_RES(zend_register_resource(im, le_gd)); + php_stream_close(stream); + return; + } + + php_error_docref(NULL, E_WARNING, "'%s' is not a valid %s file", file, tn); +out_err: + php_stream_close(stream); + RETURN_FALSE; + +} +/* }}} */ + +/* {{{ proto resource imagecreatefromgif(string filename) + Create a new image from GIF file or URL */ +PHP_FUNCTION(imagecreatefromgif) +{ + _php_image_create_from(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_GIF, "GIF", gdImageCreateFromGif, gdImageCreateFromGifCtx); +} +/* }}} */ + +#ifdef HAVE_GD_JPG +/* {{{ proto resource imagecreatefromjpeg(string filename) + Create a new image from JPEG file or URL */ +PHP_FUNCTION(imagecreatefromjpeg) +{ + _php_image_create_from(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_JPG, "JPEG", gdImageCreateFromJpeg, gdImageCreateFromJpegCtx); +} +/* }}} */ +#endif /* HAVE_GD_JPG */ + +#ifdef HAVE_GD_PNG +/* {{{ proto resource imagecreatefrompng(string filename) + Create a new image from PNG file or URL */ +PHP_FUNCTION(imagecreatefrompng) +{ + _php_image_create_from(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_PNG, "PNG", gdImageCreateFromPng, gdImageCreateFromPngCtx); +} +/* }}} */ +#endif /* HAVE_GD_PNG */ + +#ifdef HAVE_GD_WEBP +/* {{{ proto resource imagecreatefromwebp(string filename) + Create a new image from WEBP file or URL */ +PHP_FUNCTION(imagecreatefromwebp) +{ + _php_image_create_from(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_WEBP, "WEBP", gdImageCreateFromWebp, gdImageCreateFromWebpCtx); +} +/* }}} */ +#endif /* HAVE_GD_WEBP */ + +/* {{{ proto resource imagecreatefromxbm(string filename) + Create a new image from XBM file or URL */ +PHP_FUNCTION(imagecreatefromxbm) +{ + _php_image_create_from(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_XBM, "XBM", gdImageCreateFromXbm, NULL); +} +/* }}} */ + +#if defined(HAVE_GD_XPM) +/* {{{ proto resource imagecreatefromxpm(string filename) + Create a new image from XPM file or URL */ +PHP_FUNCTION(imagecreatefromxpm) +{ + _php_image_create_from(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_XPM, "XPM", gdImageCreateFromXpm, NULL); +} +/* }}} */ +#endif + +/* {{{ proto resource imagecreatefromwbmp(string filename) + Create a new image from WBMP file or URL */ +PHP_FUNCTION(imagecreatefromwbmp) +{ + _php_image_create_from(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_WBM, "WBMP", gdImageCreateFromWBMP, gdImageCreateFromWBMPCtx); +} +/* }}} */ + +/* {{{ proto resource imagecreatefromgd(string filename) + Create a new image from GD file or URL */ +PHP_FUNCTION(imagecreatefromgd) +{ + _php_image_create_from(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_GD, "GD", gdImageCreateFromGd, gdImageCreateFromGdCtx); +} +/* }}} */ + +/* {{{ proto resource imagecreatefromgd2(string filename) + Create a new image from GD2 file or URL */ +PHP_FUNCTION(imagecreatefromgd2) +{ + _php_image_create_from(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_GD2, "GD2", gdImageCreateFromGd2, gdImageCreateFromGd2Ctx); +} +/* }}} */ + +/* {{{ proto resource imagecreatefromgd2part(string filename, int srcX, int srcY, int width, int height) + Create a new image from a given part of GD2 file or URL */ +PHP_FUNCTION(imagecreatefromgd2part) +{ + _php_image_create_from(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_GD2PART, "GD2", gdImageCreateFromGd2Part, gdImageCreateFromGd2PartCtx); +} +/* }}} */ + +/* {{{ _php_image_output + */ +static void _php_image_output(INTERNAL_FUNCTION_PARAMETERS, int image_type, char *tn, void (*func_p)()) +{ + zval *imgind; + char *file = NULL; + zend_long quality = 0, type = 0; + gdImagePtr im; + char *fn = NULL; + FILE *fp; + size_t file_len = 0; + int argc = ZEND_NUM_ARGS(); + int q = -1, i, t = 1; + + /* The quality parameter for Wbmp stands for the threshold when called from image2wbmp() */ + /* When called from imagewbmp() the quality parameter stands for the foreground color. Default: black. */ + /* The quality parameter for gd2 stands for chunk size */ + + if (zend_parse_parameters(argc, "r|pll", &imgind, &file, &file_len, &quality, &type) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(imgind), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (argc > 1) { + fn = file; + if (argc >= 3) { + q = quality; + if (argc == 4) { + t = type; + } + } + } + + if (argc >= 2 && file_len) { + PHP_GD_CHECK_OPEN_BASEDIR(fn, "Invalid filename"); + + fp = VCWD_FOPEN(fn, "wb"); + if (!fp) { + php_error_docref(NULL, E_WARNING, "Unable to open '%s' for writing", fn); + RETURN_FALSE; + } + + switch (image_type) { + case PHP_GDIMG_CONVERT_WBM: + if (q == -1) { + q = 0; + } else if (q < 0 || q > 255) { + php_error_docref(NULL, E_WARNING, "Invalid threshold value '%d'. It must be between 0 and 255", q); + q = 0; + } + gdImageWBMP(im, q, fp); + break; + case PHP_GDIMG_TYPE_JPG: + (*func_p)(im, fp, q); + break; + case PHP_GDIMG_TYPE_WBM: + for (i = 0; i < gdImageColorsTotal(im); i++) { + if (gdImageRed(im, i) == 0) break; + } + (*func_p)(im, i, fp); + break; + case PHP_GDIMG_TYPE_GD: + if (im->trueColor){ + gdImageTrueColorToPalette(im,1,256); + } + (*func_p)(im, fp); + break; + case PHP_GDIMG_TYPE_GD2: + if (q == -1) { + q = 128; + } + (*func_p)(im, fp, q, t); + break; + default: + if (q == -1) { + q = 128; + } + (*func_p)(im, fp, q, t); + break; + } + fflush(fp); + fclose(fp); + } else { + int b; + FILE *tmp; + char buf[4096]; + zend_string *path; + + tmp = php_open_temporary_file(NULL, NULL, &path); + if (tmp == NULL) { + php_error_docref(NULL, E_WARNING, "Unable to open temporary file"); + RETURN_FALSE; + } + + switch (image_type) { + case PHP_GDIMG_CONVERT_WBM: + if (q == -1) { + q = 0; + } else if (q < 0 || q > 255) { + php_error_docref(NULL, E_WARNING, "Invalid threshold value '%d'. It must be between 0 and 255", q); + q = 0; + } + gdImageWBMP(im, q, tmp); + break; + case PHP_GDIMG_TYPE_JPG: + (*func_p)(im, tmp, q); + break; + case PHP_GDIMG_TYPE_WBM: + for (i = 0; i < gdImageColorsTotal(im); i++) { + if (gdImageRed(im, i) == 0) { + break; + } + } + (*func_p)(im, q, tmp); + break; + case PHP_GDIMG_TYPE_GD: + if (im->trueColor) { + gdImageTrueColorToPalette(im,1,256); + } + (*func_p)(im, tmp); + break; + case PHP_GDIMG_TYPE_GD2: + if (q == -1) { + q = 128; + } + (*func_p)(im, tmp, q, t); + break; + default: + (*func_p)(im, tmp); + break; + } + + fseek(tmp, 0, SEEK_SET); + +#if APACHE && defined(CHARSET_EBCDIC) + /* XXX this is unlikely to work any more thies@thieso.net */ + + /* This is a binary file already: avoid EBCDIC->ASCII conversion */ + ap_bsetflag(php3_rqst->connection->client, B_EBCDIC2ASCII, 0); +#endif + while ((b = fread(buf, 1, sizeof(buf), tmp)) > 0) { + php_write(buf, b); + } + + fclose(tmp); + VCWD_UNLINK((const char *)ZSTR_VAL(path)); /* make sure that the temporary file is removed */ + zend_string_release(path); + } + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto int imagexbm(int im, string filename [, int foreground]) + Output XBM image to browser or file */ +PHP_FUNCTION(imagexbm) +{ + _php_image_output_ctx(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_XBM, "XBM", gdImageXbmCtx); +} +/* }}} */ + +/* {{{ proto bool imagegif(resource im [, mixed to]) + Output GIF image to browser or file */ +PHP_FUNCTION(imagegif) +{ + _php_image_output_ctx(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_GIF, "GIF", gdImageGifCtx); +} +/* }}} */ + +#ifdef HAVE_GD_PNG +/* {{{ proto bool imagepng(resource im [, mixed to]) + Output PNG image to browser or file */ +PHP_FUNCTION(imagepng) +{ + _php_image_output_ctx(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_PNG, "PNG", gdImagePngCtxEx); +} +/* }}} */ +#endif /* HAVE_GD_PNG */ + + +#ifdef HAVE_GD_WEBP +/* {{{ proto bool imagewebp(resource im [, mixed to[, int quality]] ) + Output WEBP image to browser or file */ +PHP_FUNCTION(imagewebp) +{ + _php_image_output_ctx(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_WEBP, "WEBP", gdImageWebpCtx); +} +/* }}} */ +#endif /* HAVE_GD_WEBP */ + + +#ifdef HAVE_GD_JPG +/* {{{ proto bool imagejpeg(resource im [, mixed to [, int quality]]) + Output JPEG image to browser or file */ +PHP_FUNCTION(imagejpeg) +{ + _php_image_output_ctx(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_JPG, "JPEG", gdImageJpegCtx); +} +/* }}} */ +#endif /* HAVE_GD_JPG */ + +/* {{{ proto bool imagewbmp(resource im [, mixed to [, int foreground]]) + Output WBMP image to browser or file */ +PHP_FUNCTION(imagewbmp) +{ + _php_image_output_ctx(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_WBM, "WBMP", gdImageWBMPCtx); +} +/* }}} */ + +/* {{{ proto bool imagegd(resource im [, mixed to]) + Output GD image to browser or file */ +PHP_FUNCTION(imagegd) +{ + _php_image_output(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_GD, "GD", gdImageGd); +} +/* }}} */ + +/* {{{ proto bool imagegd2(resource im [, mixed to [, int chunk_size [, int type]]]) + Output GD2 image to browser or file */ +PHP_FUNCTION(imagegd2) +{ + _php_image_output(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_GD2, "GD2", gdImageGd2); +} +/* }}} */ + +/* {{{ proto bool imagedestroy(resource im) + Destroy an image */ +PHP_FUNCTION(imagedestroy) +{ + zval *IM; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &IM) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + zend_list_close(Z_RES_P(IM)); + + RETURN_TRUE; +} +/* }}} */ + + +/* {{{ proto int imagecolorallocate(resource im, int red, int green, int blue) + Allocate a color for an image */ +PHP_FUNCTION(imagecolorallocate) +{ + zval *IM; + zend_long red, green, blue; + gdImagePtr im; + int ct = (-1); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlll", &IM, &red, &green, &blue) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + ct = gdImageColorAllocate(im, red, green, blue); + if (ct < 0) { + RETURN_FALSE; + } + RETURN_LONG(ct); +} +/* }}} */ + +/* {{{ proto void imagepalettecopy(resource dst, resource src) + Copy the palette from the src image onto the dst image */ +PHP_FUNCTION(imagepalettecopy) +{ + zval *dstim, *srcim; + gdImagePtr dst, src; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rr", &dstim, &srcim) == FAILURE) { + return; + } + + if ((dst = (gdImagePtr)zend_fetch_resource(Z_RES_P(dstim), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if ((src = (gdImagePtr)zend_fetch_resource(Z_RES_P(srcim), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImagePaletteCopy(dst, src); +} +/* }}} */ + +/* {{{ proto int imagecolorat(resource im, int x, int y) + Get the index of the color of a pixel */ +PHP_FUNCTION(imagecolorat) +{ + zval *IM; + zend_long x, y; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rll", &IM, &x, &y) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (gdImageTrueColor(im)) { + if (im->tpixels && gdImageBoundsSafe(im, x, y)) { + RETURN_LONG(gdImageTrueColorPixel(im, x, y)); + } else { + php_error_docref(NULL, E_NOTICE, "" ZEND_LONG_FMT "," ZEND_LONG_FMT " is out of bounds", x, y); + RETURN_FALSE; + } + } else { + if (im->pixels && gdImageBoundsSafe(im, x, y)) { + RETURN_LONG(im->pixels[y][x]); + } else { + php_error_docref(NULL, E_NOTICE, "" ZEND_LONG_FMT "," ZEND_LONG_FMT " is out of bounds", x, y); + RETURN_FALSE; + } + } +} +/* }}} */ + +/* {{{ proto int imagecolorclosest(resource im, int red, int green, int blue) + Get the index of the closest color to the specified color */ +PHP_FUNCTION(imagecolorclosest) +{ + zval *IM; + zend_long red, green, blue; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlll", &IM, &red, &green, &blue) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(gdImageColorClosest(im, red, green, blue)); +} +/* }}} */ + +/* {{{ proto int imagecolorclosesthwb(resource im, int red, int green, int blue) + Get the index of the color which has the hue, white and blackness nearest to the given color */ +PHP_FUNCTION(imagecolorclosesthwb) +{ + zval *IM; + zend_long red, green, blue; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlll", &IM, &red, &green, &blue) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(gdImageColorClosestHWB(im, red, green, blue)); +} +/* }}} */ + +/* {{{ proto bool imagecolordeallocate(resource im, int index) + De-allocate a color for an image */ +PHP_FUNCTION(imagecolordeallocate) +{ + zval *IM; + zend_long index; + int col; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rl", &IM, &index) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + /* We can return right away for a truecolor image as deallocating colours is meaningless here */ + if (gdImageTrueColor(im)) { + RETURN_TRUE; + } + + col = index; + + if (col >= 0 && col < gdImageColorsTotal(im)) { + gdImageColorDeallocate(im, col); + RETURN_TRUE; + } else { + php_error_docref(NULL, E_WARNING, "Color index %d out of range", col); + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto int imagecolorresolve(resource im, int red, int green, int blue) + Get the index of the specified color or its closest possible alternative */ +PHP_FUNCTION(imagecolorresolve) +{ + zval *IM; + zend_long red, green, blue; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlll", &IM, &red, &green, &blue) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(gdImageColorResolve(im, red, green, blue)); +} +/* }}} */ + +/* {{{ proto int imagecolorexact(resource im, int red, int green, int blue) + Get the index of the specified color */ +PHP_FUNCTION(imagecolorexact) +{ + zval *IM; + zend_long red, green, blue; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlll", &IM, &red, &green, &blue) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(gdImageColorExact(im, red, green, blue)); +} +/* }}} */ + +/* {{{ proto void imagecolorset(resource im, int col, int red, int green, int blue) + Set the color for the specified palette index */ +PHP_FUNCTION(imagecolorset) +{ + zval *IM; + zend_long color, red, green, blue, alpha = 0; + int col; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rllll|l", &IM, &color, &red, &green, &blue, &alpha) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + col = color; + + if (col >= 0 && col < gdImageColorsTotal(im)) { + im->red[col] = red; + im->green[col] = green; + im->blue[col] = blue; + im->alpha[col] = alpha; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto array imagecolorsforindex(resource im, int col) + Get the colors for an index */ +PHP_FUNCTION(imagecolorsforindex) +{ + zval *IM; + zend_long index; + int col; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rl", &IM, &index) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + col = index; + + if ((col >= 0 && gdImageTrueColor(im)) || (!gdImageTrueColor(im) && col >= 0 && col < gdImageColorsTotal(im))) { + array_init(return_value); + + add_assoc_long(return_value,"red", gdImageRed(im,col)); + add_assoc_long(return_value,"green", gdImageGreen(im,col)); + add_assoc_long(return_value,"blue", gdImageBlue(im,col)); + add_assoc_long(return_value,"alpha", gdImageAlpha(im,col)); + } else { + php_error_docref(NULL, E_WARNING, "Color index %d out of range", col); + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto bool imagegammacorrect(resource im, float inputgamma, float outputgamma) + Apply a gamma correction to a GD image */ +PHP_FUNCTION(imagegammacorrect) +{ + zval *IM; + gdImagePtr im; + int i; + double input, output; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rdd", &IM, &input, &output) == FAILURE) { + return; + } + + if ( input <= 0.0 || output <= 0.0 ) { + php_error_docref(NULL, E_WARNING, "Gamma values should be positive"); + RETURN_FALSE; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (gdImageTrueColor(im)) { + int x, y, c; + + for (y = 0; y < gdImageSY(im); y++) { + for (x = 0; x < gdImageSX(im); x++) { + c = gdImageGetPixel(im, x, y); + gdImageSetPixel(im, x, y, + gdTrueColorAlpha( + (int) ((pow((pow((gdTrueColorGetRed(c) / 255.0), input)), 1.0 / output) * 255) + .5), + (int) ((pow((pow((gdTrueColorGetGreen(c) / 255.0), input)), 1.0 / output) * 255) + .5), + (int) ((pow((pow((gdTrueColorGetBlue(c) / 255.0), input)), 1.0 / output) * 255) + .5), + gdTrueColorGetAlpha(c) + ) + ); + } + } + RETURN_TRUE; + } + + for (i = 0; i < gdImageColorsTotal(im); i++) { + im->red[i] = (int)((pow((pow((im->red[i] / 255.0), input)), 1.0 / output) * 255) + .5); + im->green[i] = (int)((pow((pow((im->green[i] / 255.0), input)), 1.0 / output) * 255) + .5); + im->blue[i] = (int)((pow((pow((im->blue[i] / 255.0), input)), 1.0 / output) * 255) + .5); + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagesetpixel(resource im, int x, int y, int col) + Set a single pixel */ +PHP_FUNCTION(imagesetpixel) +{ + zval *IM; + zend_long x, y, col; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlll", &IM, &x, &y, &col) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageSetPixel(im, x, y, col); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imageline(resource im, int x1, int y1, int x2, int y2, int col) + Draw a line */ +PHP_FUNCTION(imageline) +{ + zval *IM; + zend_long x1, y1, x2, y2, col; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlllll", &IM, &x1, &y1, &x2, &y2, &col) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + +#ifdef HAVE_GD_BUNDLED + if (im->antialias) { + gdImageAALine(im, x1, y1, x2, y2, col); + } else +#endif + { + gdImageLine(im, x1, y1, x2, y2, col); + } + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagedashedline(resource im, int x1, int y1, int x2, int y2, int col) + Draw a dashed line */ +PHP_FUNCTION(imagedashedline) +{ + zval *IM; + zend_long x1, y1, x2, y2, col; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlllll", &IM, &x1, &y1, &x2, &y2, &col) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageDashedLine(im, x1, y1, x2, y2, col); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagerectangle(resource im, int x1, int y1, int x2, int y2, int col) + Draw a rectangle */ +PHP_FUNCTION(imagerectangle) +{ + zval *IM; + zend_long x1, y1, x2, y2, col; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlllll", &IM, &x1, &y1, &x2, &y2, &col) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageRectangle(im, x1, y1, x2, y2, col); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagefilledrectangle(resource im, int x1, int y1, int x2, int y2, int col) + Draw a filled rectangle */ +PHP_FUNCTION(imagefilledrectangle) +{ + zval *IM; + zend_long x1, y1, x2, y2, col; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlllll", &IM, &x1, &y1, &x2, &y2, &col) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + gdImageFilledRectangle(im, x1, y1, x2, y2, col); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagearc(resource im, int cx, int cy, int w, int h, int s, int e, int col) + Draw a partial ellipse */ +PHP_FUNCTION(imagearc) +{ + zval *IM; + zend_long cx, cy, w, h, ST, E, col; + gdImagePtr im; + int e, st; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlllllll", &IM, &cx, &cy, &w, &h, &ST, &E, &col) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + e = E; + if (e < 0) { + e %= 360; + } + + st = ST; + if (st < 0) { + st %= 360; + } + + gdImageArc(im, cx, cy, w, h, st, e, col); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imageellipse(resource im, int cx, int cy, int w, int h, int color) + Draw an ellipse */ +PHP_FUNCTION(imageellipse) +{ + zval *IM; + zend_long cx, cy, w, h, color; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlllll", &IM, &cx, &cy, &w, &h, &color) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageEllipse(im, cx, cy, w, h, color); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagefilltoborder(resource im, int x, int y, int border, int col) + Flood fill to specific color */ +PHP_FUNCTION(imagefilltoborder) +{ + zval *IM; + zend_long x, y, border, col; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rllll", &IM, &x, &y, &border, &col) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageFillToBorder(im, x, y, border, col); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagefill(resource im, int x, int y, int col) + Flood fill */ +PHP_FUNCTION(imagefill) +{ + zval *IM; + zend_long x, y, col; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlll", &IM, &x, &y, &col) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + gdImageFill(im, x, y, col); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto int imagecolorstotal(resource im) + Find out the number of colors in an image's palette */ +PHP_FUNCTION(imagecolorstotal) +{ + zval *IM; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &IM) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(gdImageColorsTotal(im)); +} +/* }}} */ + +/* {{{ proto int imagecolortransparent(resource im [, int col]) + Define a color as transparent */ +PHP_FUNCTION(imagecolortransparent) +{ + zval *IM; + zend_long COL = 0; + gdImagePtr im; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "r|l", &IM, &COL) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (argc > 1) { + gdImageColorTransparent(im, COL); + } + + RETURN_LONG(gdImageGetTransparent(im)); +} +/* }}} */ + +/* {{{ proto int imageinterlace(resource im [, int interlace]) + Enable or disable interlace */ +PHP_FUNCTION(imageinterlace) +{ + zval *IM; + int argc = ZEND_NUM_ARGS(); + zend_long INT = 0; + gdImagePtr im; + + if (zend_parse_parameters(argc, "r|l", &IM, &INT) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (argc > 1) { + gdImageInterlace(im, INT); + } + + RETURN_LONG(gdImageGetInterlaced(im)); +} +/* }}} */ + +/* {{{ php_imagepolygon + arg = 0 normal polygon + arg = 1 filled polygon */ +/* im, points, num_points, col */ +static void php_imagepolygon(INTERNAL_FUNCTION_PARAMETERS, int filled) +{ + zval *IM, *POINTS; + zend_long NPOINTS, COL; + zval *var = NULL; + gdImagePtr im; + gdPointPtr points; + int npoints, col, nelem, i; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rall", &IM, &POINTS, &NPOINTS, &COL) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + npoints = NPOINTS; + col = COL; + + nelem = zend_hash_num_elements(Z_ARRVAL_P(POINTS)); + if (nelem < 6) { + php_error_docref(NULL, E_WARNING, "You must have at least 3 points in your array"); + RETURN_FALSE; + } + if (npoints <= 0) { + php_error_docref(NULL, E_WARNING, "You must give a positive number of points"); + RETURN_FALSE; + } + if (nelem < npoints * 2) { + php_error_docref(NULL, E_WARNING, "Trying to use %d points in array with only %d points", npoints, nelem/2); + RETURN_FALSE; + } + + points = (gdPointPtr) safe_emalloc(npoints, sizeof(gdPoint), 0); + + for (i = 0; i < npoints; i++) { + if ((var = zend_hash_index_find(Z_ARRVAL_P(POINTS), (i * 2))) != NULL) { + points[i].x = zval_get_long(var); + } + if ((var = zend_hash_index_find(Z_ARRVAL_P(POINTS), (i * 2) + 1)) != NULL) { + points[i].y = zval_get_long(var); + } + } + + if (filled) { + gdImageFilledPolygon(im, points, npoints, col); + } else { + gdImagePolygon(im, points, npoints, col); + } + + efree(points); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagepolygon(resource im, array point, int num_points, int col) + Draw a polygon */ +PHP_FUNCTION(imagepolygon) +{ + php_imagepolygon(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); +} +/* }}} */ + +/* {{{ proto bool imagefilledpolygon(resource im, array point, int num_points, int col) + Draw a filled polygon */ +PHP_FUNCTION(imagefilledpolygon) +{ + php_imagepolygon(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); +} +/* }}} */ + +/* {{{ php_find_gd_font + */ +static gdFontPtr php_find_gd_font(int size) +{ + gdFontPtr font; + + switch (size) { + case 1: + font = gdFontTiny; + break; + case 2: + font = gdFontSmall; + break; + case 3: + font = gdFontMediumBold; + break; + case 4: + font = gdFontLarge; + break; + case 5: + font = gdFontGiant; + break; + default: { + zval *zv = zend_hash_index_find(&EG(regular_list), size - 5); + if (!zv || (Z_RES_P(zv))->type != le_gd_font) { + if (size < 1) { + font = gdFontTiny; + } else { + font = gdFontGiant; + } + } else { + font = (gdFontPtr)Z_RES_P(zv)->ptr; + } + } + break; + } + + return font; +} +/* }}} */ + +/* {{{ php_imagefontsize + * arg = 0 ImageFontWidth + * arg = 1 ImageFontHeight + */ +static void php_imagefontsize(INTERNAL_FUNCTION_PARAMETERS, int arg) +{ + zend_long SIZE; + gdFontPtr font; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &SIZE) == FAILURE) { + return; + } + + font = php_find_gd_font(SIZE); + RETURN_LONG(arg ? font->h : font->w); +} +/* }}} */ + +/* {{{ proto int imagefontwidth(int font) + Get font width */ +PHP_FUNCTION(imagefontwidth) +{ + php_imagefontsize(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); +} +/* }}} */ + +/* {{{ proto int imagefontheight(int font) + Get font height */ +PHP_FUNCTION(imagefontheight) +{ + php_imagefontsize(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); +} +/* }}} */ + +/* {{{ php_gdimagecharup + * workaround for a bug in gd 1.2 */ +static void php_gdimagecharup(gdImagePtr im, gdFontPtr f, int x, int y, int c, int color) +{ + int cx, cy, px, py, fline; + cx = 0; + cy = 0; + + if ((c < f->offset) || (c >= (f->offset + f->nchars))) { + return; + } + + fline = (c - f->offset) * f->h * f->w; + for (py = y; (py > (y - f->w)); py--) { + for (px = x; (px < (x + f->h)); px++) { + if (f->data[fline + cy * f->w + cx]) { + gdImageSetPixel(im, px, py, color); + } + cy++; + } + cy = 0; + cx++; + } +} +/* }}} */ + +/* {{{ php_imagechar + * arg = 0 ImageChar + * arg = 1 ImageCharUp + * arg = 2 ImageString + * arg = 3 ImageStringUp + */ +static void php_imagechar(INTERNAL_FUNCTION_PARAMETERS, int mode) +{ + zval *IM; + zend_long SIZE, X, Y, COL; + char *C; + size_t C_len; + gdImagePtr im; + int ch = 0, col, x, y, size, i, l = 0; + unsigned char *str = NULL; + gdFontPtr font; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlllsl", &IM, &SIZE, &X, &Y, &C, &C_len, &COL) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + col = COL; + + if (mode < 2) { + ch = (int)((unsigned char)*C); + } else { + str = (unsigned char *) estrndup(C, C_len); + l = strlen((char *)str); + } + + y = Y; + x = X; + size = SIZE; + + font = php_find_gd_font(size); + + switch (mode) { + case 0: + gdImageChar(im, font, x, y, ch, col); + break; + case 1: + php_gdimagecharup(im, font, x, y, ch, col); + break; + case 2: + for (i = 0; (i < l); i++) { + gdImageChar(im, font, x, y, (int) ((unsigned char) str[i]), col); + x += font->w; + } + break; + case 3: { + for (i = 0; (i < l); i++) { + /* php_gdimagecharup(im, font, x, y, (int) str[i], col); */ + gdImageCharUp(im, font, x, y, (int) str[i], col); + y -= font->w; + } + break; + } + } + if (str) { + efree(str); + } + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagechar(resource im, int font, int x, int y, string c, int col) + Draw a character */ +PHP_FUNCTION(imagechar) +{ + php_imagechar(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); +} +/* }}} */ + +/* {{{ proto bool imagecharup(resource im, int font, int x, int y, string c, int col) + Draw a character rotated 90 degrees counter-clockwise */ +PHP_FUNCTION(imagecharup) +{ + php_imagechar(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); +} +/* }}} */ + +/* {{{ proto bool imagestring(resource im, int font, int x, int y, string str, int col) + Draw a string horizontally */ +PHP_FUNCTION(imagestring) +{ + php_imagechar(INTERNAL_FUNCTION_PARAM_PASSTHRU, 2); +} +/* }}} */ + +/* {{{ proto bool imagestringup(resource im, int font, int x, int y, string str, int col) + Draw a string vertically - rotated 90 degrees counter-clockwise */ +PHP_FUNCTION(imagestringup) +{ + php_imagechar(INTERNAL_FUNCTION_PARAM_PASSTHRU, 3); +} +/* }}} */ + +/* {{{ proto bool imagecopy(resource dst_im, resource src_im, int dst_x, int dst_y, int src_x, int src_y, int src_w, int src_h) + Copy part of an image */ +PHP_FUNCTION(imagecopy) +{ + zval *SIM, *DIM; + zend_long SX, SY, SW, SH, DX, DY; + gdImagePtr im_dst, im_src; + int srcH, srcW, srcY, srcX, dstY, dstX; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rrllllll", &DIM, &SIM, &DX, &DY, &SX, &SY, &SW, &SH) == FAILURE) { + return; + } + + if ((im_dst = (gdImagePtr)zend_fetch_resource(Z_RES_P(DIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if ((im_src = (gdImagePtr)zend_fetch_resource(Z_RES_P(SIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + srcX = SX; + srcY = SY; + srcH = SH; + srcW = SW; + dstX = DX; + dstY = DY; + + gdImageCopy(im_dst, im_src, dstX, dstY, srcX, srcY, srcW, srcH); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagecopymerge(resource src_im, resource dst_im, int dst_x, int dst_y, int src_x, int src_y, int src_w, int src_h, int pct) + Merge one part of an image with another */ +PHP_FUNCTION(imagecopymerge) +{ + zval *SIM, *DIM; + zend_long SX, SY, SW, SH, DX, DY, PCT; + gdImagePtr im_dst, im_src; + int srcH, srcW, srcY, srcX, dstY, dstX, pct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rrlllllll", &DIM, &SIM, &DX, &DY, &SX, &SY, &SW, &SH, &PCT) == FAILURE) { + return; + } + + if ((im_dst = (gdImagePtr)zend_fetch_resource(Z_RES_P(DIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if ((im_src = (gdImagePtr)zend_fetch_resource(Z_RES_P(SIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + srcX = SX; + srcY = SY; + srcH = SH; + srcW = SW; + dstX = DX; + dstY = DY; + pct = PCT; + + gdImageCopyMerge(im_dst, im_src, dstX, dstY, srcX, srcY, srcW, srcH, pct); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagecopymergegray(resource src_im, resource dst_im, int dst_x, int dst_y, int src_x, int src_y, int src_w, int src_h, int pct) + Merge one part of an image with another */ +PHP_FUNCTION(imagecopymergegray) +{ + zval *SIM, *DIM; + zend_long SX, SY, SW, SH, DX, DY, PCT; + gdImagePtr im_dst, im_src; + int srcH, srcW, srcY, srcX, dstY, dstX, pct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rrlllllll", &DIM, &SIM, &DX, &DY, &SX, &SY, &SW, &SH, &PCT) == FAILURE) { + return; + } + + if ((im_dst = (gdImagePtr)zend_fetch_resource(Z_RES_P(DIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if ((im_src = (gdImagePtr)zend_fetch_resource(Z_RES_P(SIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + srcX = SX; + srcY = SY; + srcH = SH; + srcW = SW; + dstX = DX; + dstY = DY; + pct = PCT; + + gdImageCopyMergeGray(im_dst, im_src, dstX, dstY, srcX, srcY, srcW, srcH, pct); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imagecopyresized(resource dst_im, resource src_im, int dst_x, int dst_y, int src_x, int src_y, int dst_w, int dst_h, int src_w, int src_h) + Copy and resize part of an image */ +PHP_FUNCTION(imagecopyresized) +{ + zval *SIM, *DIM; + zend_long SX, SY, SW, SH, DX, DY, DW, DH; + gdImagePtr im_dst, im_src; + int srcH, srcW, dstH, dstW, srcY, srcX, dstY, dstX; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rrllllllll", &DIM, &SIM, &DX, &DY, &SX, &SY, &DW, &DH, &SW, &SH) == FAILURE) { + return; + } + + if ((im_dst = (gdImagePtr)zend_fetch_resource(Z_RES_P(DIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if ((im_src = (gdImagePtr)zend_fetch_resource(Z_RES_P(SIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + srcX = SX; + srcY = SY; + srcH = SH; + srcW = SW; + dstX = DX; + dstY = DY; + dstH = DH; + dstW = DW; + + if (dstW <= 0 || dstH <= 0 || srcW <= 0 || srcH <= 0) { + php_error_docref(NULL, E_WARNING, "Invalid image dimensions"); + RETURN_FALSE; + } + + gdImageCopyResized(im_dst, im_src, dstX, dstY, srcX, srcY, dstW, dstH, srcW, srcH); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto int imagesx(resource im) + Get image width */ +PHP_FUNCTION(imagesx) +{ + zval *IM; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &IM) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(gdImageSX(im)); +} +/* }}} */ + +/* {{{ proto int imagesy(resource im) + Get image height */ +PHP_FUNCTION(imagesy) +{ + zval *IM; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &IM) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(gdImageSY(im)); +} +/* }}} */ + +#ifdef ENABLE_GD_TTF +#define TTFTEXT_DRAW 0 +#define TTFTEXT_BBOX 1 +#endif + +#ifdef ENABLE_GD_TTF + +#if HAVE_GD_FREETYPE && HAVE_LIBFREETYPE +/* {{{ proto array imageftbbox(float size, float angle, string font_file, string text [, array extrainfo]) + Give the bounding box of a text using fonts via freetype2 */ +PHP_FUNCTION(imageftbbox) +{ + php_imagettftext_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, TTFTEXT_BBOX, 1); +} +/* }}} */ + +/* {{{ proto array imagefttext(resource im, float size, float angle, int x, int y, int col, string font_file, string text [, array extrainfo]) + Write text to the image using fonts via freetype2 */ +PHP_FUNCTION(imagefttext) +{ + php_imagettftext_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, TTFTEXT_DRAW, 1); +} +/* }}} */ +#endif /* HAVE_GD_FREETYPE && HAVE_LIBFREETYPE */ + +/* {{{ proto array imagettfbbox(float size, float angle, string font_file, string text) + Give the bounding box of a text using TrueType fonts */ +PHP_FUNCTION(imagettfbbox) +{ + php_imagettftext_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, TTFTEXT_BBOX, 0); +} +/* }}} */ + +/* {{{ proto array imagettftext(resource im, float size, float angle, int x, int y, int col, string font_file, string text) + Write text to the image using a TrueType font */ +PHP_FUNCTION(imagettftext) +{ + php_imagettftext_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, TTFTEXT_DRAW, 0); +} +/* }}} */ + +/* {{{ php_imagettftext_common + */ +static void php_imagettftext_common(INTERNAL_FUNCTION_PARAMETERS, int mode, int extended) +{ + zval *IM, *EXT = NULL; + gdImagePtr im=NULL; + zend_long col = -1, x = 0, y = 0; + size_t str_len, fontname_len; + int i, brect[8]; + double ptsize, angle; + char *str = NULL, *fontname = NULL; + char *error = NULL; + int argc = ZEND_NUM_ARGS(); + gdFTStringExtra strex = {0}; + + if (mode == TTFTEXT_BBOX) { + if (argc < 4 || argc > ((extended) ? 5 : 4)) { + ZEND_WRONG_PARAM_COUNT(); + } else if (zend_parse_parameters(argc, "ddss|a", &ptsize, &angle, &fontname, &fontname_len, &str, &str_len, &EXT) == FAILURE) { + RETURN_FALSE; + } + } else { + if (argc < 8 || argc > ((extended) ? 9 : 8)) { + ZEND_WRONG_PARAM_COUNT(); + } else if (zend_parse_parameters(argc, "rddlllss|a", &IM, &ptsize, &angle, &x, &y, &col, &fontname, &fontname_len, &str, &str_len, &EXT) == FAILURE) { + RETURN_FALSE; + } + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + } + + /* convert angle to radians */ + angle = angle * (M_PI/180); + + if (extended && EXT) { /* parse extended info */ + zval *item; + zend_string *key; + + /* walk the assoc array */ + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(EXT), key, item) { + if (key == NULL) { + continue; + } + if (strcmp("linespacing", ZSTR_VAL(key)) == 0) { + strex.flags |= gdFTEX_LINESPACE; + strex.linespacing = zval_get_double(item); + } + } ZEND_HASH_FOREACH_END(); + } + +#ifdef VIRTUAL_DIR + { + char tmp_font_path[MAXPATHLEN]; + + if (!VCWD_REALPATH(fontname, tmp_font_path)) { + fontname = NULL; + } + } +#endif /* VIRTUAL_DIR */ + + PHP_GD_CHECK_OPEN_BASEDIR(fontname, "Invalid font filename"); + +#ifdef HAVE_GD_FREETYPE + if (extended) { + error = gdImageStringFTEx(im, brect, col, fontname, ptsize, angle, x, y, str, &strex); + } + else + error = gdImageStringFT(im, brect, col, fontname, ptsize, angle, x, y, str); + +#endif /* HAVE_GD_FREETYPE */ + + if (error) { + php_error_docref(NULL, E_WARNING, "%s", error); + RETURN_FALSE; + } + + array_init(return_value); + + /* return array with the text's bounding box */ + for (i = 0; i < 8; i++) { + add_next_index_long(return_value, brect[i]); + } +} +/* }}} */ +#endif /* ENABLE_GD_TTF */ + +/* {{{ proto bool image2wbmp(resource im [, string filename [, int threshold]]) + Output WBMP image to browser or file */ +PHP_FUNCTION(image2wbmp) +{ + _php_image_output(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_CONVERT_WBM, "WBMP", _php_image_bw_convert); +} +/* }}} */ + +#if defined(HAVE_GD_JPG) +/* {{{ proto bool jpeg2wbmp (string f_org, string f_dest, int d_height, int d_width, int threshold) + Convert JPEG image to WBMP image */ +PHP_FUNCTION(jpeg2wbmp) +{ + _php_image_convert(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_JPG); +} +/* }}} */ +#endif + +#if defined(HAVE_GD_PNG) +/* {{{ proto bool png2wbmp (string f_org, string f_dest, int d_height, int d_width, int threshold) + Convert PNG image to WBMP image */ +PHP_FUNCTION(png2wbmp) +{ + _php_image_convert(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_GDIMG_TYPE_PNG); +} +/* }}} */ +#endif + +/* {{{ _php_image_bw_convert + * It converts a gd Image to bw using a threshold value */ +static void _php_image_bw_convert(gdImagePtr im_org, gdIOCtx *out, int threshold) +{ + gdImagePtr im_dest; + int white, black; + int color, color_org, median; + int dest_height = gdImageSY(im_org); + int dest_width = gdImageSX(im_org); + int x, y; + + im_dest = gdImageCreate(dest_width, dest_height); + if (im_dest == NULL) { + php_error_docref(NULL, E_WARNING, "Unable to allocate temporary buffer"); + return; + } + + white = gdImageColorAllocate(im_dest, 255, 255, 255); + if (white == -1) { + php_error_docref(NULL, E_WARNING, "Unable to allocate the colors for the destination buffer"); + return; + } + + black = gdImageColorAllocate(im_dest, 0, 0, 0); + if (black == -1) { + php_error_docref(NULL, E_WARNING, "Unable to allocate the colors for the destination buffer"); + return; + } + + if (im_org->trueColor) { + gdImageTrueColorToPalette(im_org, 1, 256); + } + + for (y = 0; y < dest_height; y++) { + for (x = 0; x < dest_width; x++) { + color_org = gdImageGetPixel(im_org, x, y); + median = (im_org->red[color_org] + im_org->green[color_org] + im_org->blue[color_org]) / 3; + if (median < threshold) { + color = black; + } else { + color = white; + } + gdImageSetPixel (im_dest, x, y, color); + } + } + gdImageWBMPCtx (im_dest, black, out); + +} +/* }}} */ + +/* {{{ _php_image_convert + * _php_image_convert converts jpeg/png images to wbmp and resizes them as needed */ +static void _php_image_convert(INTERNAL_FUNCTION_PARAMETERS, int image_type ) +{ + char *f_org, *f_dest; + size_t f_org_len, f_dest_len; + zend_long height, width, threshold; + gdImagePtr im_org, im_dest, im_tmp; + char *fn_org = NULL; + char *fn_dest = NULL; + FILE *org, *dest; + int dest_height = -1; + int dest_width = -1; + int org_height, org_width; + int white, black; + int color, color_org, median; + int int_threshold; + int x, y; + float x_ratio, y_ratio; +#ifdef HAVE_GD_JPG + zend_long ignore_warning; +#endif + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "pplll", &f_org, &f_org_len, &f_dest, &f_dest_len, &height, &width, &threshold) == FAILURE) { + return; + } + + fn_org = f_org; + fn_dest = f_dest; + dest_height = height; + dest_width = width; + int_threshold = threshold; + + /* Check threshold value */ + if (int_threshold < 0 || int_threshold > 8) { + php_error_docref(NULL, E_WARNING, "Invalid threshold value '%d'", int_threshold); + RETURN_FALSE; + } + + /* Check origin file */ + PHP_GD_CHECK_OPEN_BASEDIR(fn_org, "Invalid origin filename"); + + /* Check destination file */ + PHP_GD_CHECK_OPEN_BASEDIR(fn_dest, "Invalid destination filename"); + + /* Open origin file */ + org = VCWD_FOPEN(fn_org, "rb"); + if (!org) { + php_error_docref(NULL, E_WARNING, "Unable to open '%s' for reading", fn_org); + RETURN_FALSE; + } + + /* Open destination file */ + dest = VCWD_FOPEN(fn_dest, "wb"); + if (!dest) { + php_error_docref(NULL, E_WARNING, "Unable to open '%s' for writing", fn_dest); + fclose(org); + RETURN_FALSE; + } + + switch (image_type) { + case PHP_GDIMG_TYPE_GIF: + im_org = gdImageCreateFromGif(org); + if (im_org == NULL) { + php_error_docref(NULL, E_WARNING, "Unable to open '%s' Not a valid GIF file", fn_dest); + fclose(org); + fclose(dest); + RETURN_FALSE; + } + break; + +#ifdef HAVE_GD_JPG + case PHP_GDIMG_TYPE_JPG: + ignore_warning = INI_INT("gd.jpeg_ignore_warning"); + im_org = gdImageCreateFromJpegEx(org, ignore_warning); + if (im_org == NULL) { + php_error_docref(NULL, E_WARNING, "Unable to open '%s' Not a valid JPEG file", fn_dest); + fclose(org); + fclose(dest); + RETURN_FALSE; + } + break; +#endif /* HAVE_GD_JPG */ + +#ifdef HAVE_GD_PNG + case PHP_GDIMG_TYPE_PNG: + im_org = gdImageCreateFromPng(org); + if (im_org == NULL) { + php_error_docref(NULL, E_WARNING, "Unable to open '%s' Not a valid PNG file", fn_dest); + fclose(org); + fclose(dest); + RETURN_FALSE; + } + break; +#endif /* HAVE_GD_PNG */ + + default: + php_error_docref(NULL, E_WARNING, "Format not supported"); + fclose(org); + fclose(dest); + RETURN_FALSE; + break; + } + + fclose(org); + + org_width = gdImageSX (im_org); + org_height = gdImageSY (im_org); + + x_ratio = (float) org_width / (float) dest_width; + y_ratio = (float) org_height / (float) dest_height; + + if (x_ratio > 1 && y_ratio > 1) { + if (y_ratio > x_ratio) { + x_ratio = y_ratio; + } else { + y_ratio = x_ratio; + } + dest_width = (int) (org_width / x_ratio); + dest_height = (int) (org_height / y_ratio); + } else { + x_ratio = (float) dest_width / (float) org_width; + y_ratio = (float) dest_height / (float) org_height; + + if (y_ratio < x_ratio) { + x_ratio = y_ratio; + } else { + y_ratio = x_ratio; + } + dest_width = (int) (org_width * x_ratio); + dest_height = (int) (org_height * y_ratio); + } + + im_tmp = gdImageCreate (dest_width, dest_height); + if (im_tmp == NULL ) { + php_error_docref(NULL, E_WARNING, "Unable to allocate temporary buffer"); + fclose(dest); + gdImageDestroy(im_org); + RETURN_FALSE; + } + + gdImageCopyResized (im_tmp, im_org, 0, 0, 0, 0, dest_width, dest_height, org_width, org_height); + + gdImageDestroy(im_org); + + im_dest = gdImageCreate(dest_width, dest_height); + if (im_dest == NULL) { + php_error_docref(NULL, E_WARNING, "Unable to allocate destination buffer"); + fclose(dest); + gdImageDestroy(im_tmp); + RETURN_FALSE; + } + + white = gdImageColorAllocate(im_dest, 255, 255, 255); + if (white == -1) { + php_error_docref(NULL, E_WARNING, "Unable to allocate the colors for the destination buffer"); + fclose(dest); + gdImageDestroy(im_tmp); + gdImageDestroy(im_dest); + RETURN_FALSE; + } + + black = gdImageColorAllocate(im_dest, 0, 0, 0); + if (black == -1) { + php_error_docref(NULL, E_WARNING, "Unable to allocate the colors for the destination buffer"); + fclose(dest); + gdImageDestroy(im_tmp); + gdImageDestroy(im_dest); + RETURN_FALSE; + } + + int_threshold = int_threshold * 32; + + for (y = 0; y < dest_height; y++) { + for (x = 0; x < dest_width; x++) { + color_org = gdImageGetPixel (im_tmp, x, y); + median = (im_tmp->red[color_org] + im_tmp->green[color_org] + im_tmp->blue[color_org]) / 3; + if (median < int_threshold) { + color = black; + } else { + color = white; + } + gdImageSetPixel (im_dest, x, y, color); + } + } + + gdImageDestroy (im_tmp ); + + gdImageWBMP(im_dest, black , dest); + + fflush(dest); + fclose(dest); + + gdImageDestroy(im_dest); + + RETURN_TRUE; +} +/* }}} */ + +/* Section Filters */ +#define PHP_GD_SINGLE_RES \ + zval *SIM; \ + gdImagePtr im_src; \ + if (zend_parse_parameters(1, "r", &SIM) == FAILURE) { \ + RETURN_FALSE; \ + } \ + if ((im_src = (gdImagePtr)zend_fetch_resource(Z_RES_P(SIM), "Image", le_gd)) == NULL) { \ + RETURN_FALSE; \ + } + +static void php_image_filter_negate(INTERNAL_FUNCTION_PARAMETERS) +{ + PHP_GD_SINGLE_RES + + if (gdImageNegate(im_src) == 1) { + RETURN_TRUE; + } + + RETURN_FALSE; +} + +static void php_image_filter_grayscale(INTERNAL_FUNCTION_PARAMETERS) +{ + PHP_GD_SINGLE_RES + + if (gdImageGrayScale(im_src) == 1) { + RETURN_TRUE; + } + + RETURN_FALSE; +} + +static void php_image_filter_brightness(INTERNAL_FUNCTION_PARAMETERS) +{ + zval *SIM; + gdImagePtr im_src; + zend_long brightness, tmp; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zll", &SIM, &tmp, &brightness) == FAILURE) { + RETURN_FALSE; + } + + if ((im_src = (gdImagePtr)zend_fetch_resource(Z_RES_P(SIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (im_src == NULL) { + RETURN_FALSE; + } + + if (gdImageBrightness(im_src, (int)brightness) == 1) { + RETURN_TRUE; + } + + RETURN_FALSE; +} + +static void php_image_filter_contrast(INTERNAL_FUNCTION_PARAMETERS) +{ + zval *SIM; + gdImagePtr im_src; + zend_long contrast, tmp; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rll", &SIM, &tmp, &contrast) == FAILURE) { + RETURN_FALSE; + } + + if ((im_src = (gdImagePtr)zend_fetch_resource(Z_RES_P(SIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (im_src == NULL) { + RETURN_FALSE; + } + + if (gdImageContrast(im_src, (int)contrast) == 1) { + RETURN_TRUE; + } + + RETURN_FALSE; +} + +static void php_image_filter_colorize(INTERNAL_FUNCTION_PARAMETERS) +{ + zval *SIM; + gdImagePtr im_src; + zend_long r,g,b,tmp; + zend_long a = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rllll|l", &SIM, &tmp, &r, &g, &b, &a) == FAILURE) { + RETURN_FALSE; + } + + if ((im_src = (gdImagePtr)zend_fetch_resource(Z_RES_P(SIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (im_src == NULL) { + RETURN_FALSE; + } + + if (gdImageColor(im_src, (int) r, (int) g, (int) b, (int) a) == 1) { + RETURN_TRUE; + } + + RETURN_FALSE; +} + +static void php_image_filter_edgedetect(INTERNAL_FUNCTION_PARAMETERS) +{ + PHP_GD_SINGLE_RES + + if (gdImageEdgeDetectQuick(im_src) == 1) { + RETURN_TRUE; + } + + RETURN_FALSE; +} + +static void php_image_filter_emboss(INTERNAL_FUNCTION_PARAMETERS) +{ + PHP_GD_SINGLE_RES + + if (gdImageEmboss(im_src) == 1) { + RETURN_TRUE; + } + + RETURN_FALSE; +} + +static void php_image_filter_gaussian_blur(INTERNAL_FUNCTION_PARAMETERS) +{ + PHP_GD_SINGLE_RES + + if (gdImageGaussianBlur(im_src) == 1) { + RETURN_TRUE; + } + + RETURN_FALSE; +} + +static void php_image_filter_selective_blur(INTERNAL_FUNCTION_PARAMETERS) +{ + PHP_GD_SINGLE_RES + + if (gdImageSelectiveBlur(im_src) == 1) { + RETURN_TRUE; + } + + RETURN_FALSE; +} + +static void php_image_filter_mean_removal(INTERNAL_FUNCTION_PARAMETERS) +{ + PHP_GD_SINGLE_RES + + if (gdImageMeanRemoval(im_src) == 1) { + RETURN_TRUE; + } + + RETURN_FALSE; +} + +static void php_image_filter_smooth(INTERNAL_FUNCTION_PARAMETERS) +{ + zval *SIM; + zend_long tmp; + gdImagePtr im_src; + double weight; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rld", &SIM, &tmp, &weight) == FAILURE) { + RETURN_FALSE; + } + + if ((im_src = (gdImagePtr)zend_fetch_resource(Z_RES_P(SIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (im_src == NULL) { + RETURN_FALSE; + } + + if (gdImageSmooth(im_src, (float)weight)==1) { + RETURN_TRUE; + } + + RETURN_FALSE; +} + +static void php_image_filter_pixelate(INTERNAL_FUNCTION_PARAMETERS) +{ + zval *IM; + gdImagePtr im; + zend_long tmp, blocksize; + zend_bool mode = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rll|b", &IM, &tmp, &blocksize, &mode) == FAILURE) { + RETURN_FALSE; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (im == NULL) { + RETURN_FALSE; + } + + if (gdImagePixelate(im, (int) blocksize, (const unsigned int) mode)) { + RETURN_TRUE; + } + + RETURN_FALSE; +} + +/* {{{ proto bool imagefilter(resource src_im, int filtertype[, int arg1 [, int arg2 [, int arg3 [, int arg4 ]]]] ) + Applies Filter an image using a custom angle */ +PHP_FUNCTION(imagefilter) +{ + zval *tmp; + + typedef void (*image_filter)(INTERNAL_FUNCTION_PARAMETERS); + zend_long filtertype; + image_filter filters[] = + { + php_image_filter_negate , + php_image_filter_grayscale, + php_image_filter_brightness, + php_image_filter_contrast, + php_image_filter_colorize, + php_image_filter_edgedetect, + php_image_filter_emboss, + php_image_filter_gaussian_blur, + php_image_filter_selective_blur, + php_image_filter_mean_removal, + php_image_filter_smooth, + php_image_filter_pixelate + }; + + if (ZEND_NUM_ARGS() < 2 || ZEND_NUM_ARGS() > IMAGE_FILTER_MAX_ARGS) { + WRONG_PARAM_COUNT; + } else if (zend_parse_parameters(2, "rl", &tmp, &filtertype) == FAILURE) { + return; + } + + if (filtertype >= 0 && filtertype <= IMAGE_FILTER_MAX) { + filters[filtertype](INTERNAL_FUNCTION_PARAM_PASSTHRU); + } +} +/* }}} */ + +/* {{{ proto resource imageconvolution(resource src_im, array matrix3x3, double div, double offset) + Apply a 3x3 convolution matrix, using coefficient div and offset */ +PHP_FUNCTION(imageconvolution) +{ + zval *SIM, *hash_matrix; + zval *var = NULL, *var2 = NULL; + gdImagePtr im_src = NULL; + double div, offset; + int nelem, i, j, res; + float matrix[3][3] = {{0,0,0}, {0,0,0}, {0,0,0}}; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "radd", &SIM, &hash_matrix, &div, &offset) == FAILURE) { + RETURN_FALSE; + } + + if ((im_src = (gdImagePtr)zend_fetch_resource(Z_RES_P(SIM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + nelem = zend_hash_num_elements(Z_ARRVAL_P(hash_matrix)); + if (nelem != 3) { + php_error_docref(NULL, E_WARNING, "You must have 3x3 array"); + RETURN_FALSE; + } + + for (i=0; i<3; i++) { + if ((var = zend_hash_index_find(Z_ARRVAL_P(hash_matrix), (i))) != NULL && Z_TYPE_P(var) == IS_ARRAY) { + if (zend_hash_num_elements(Z_ARRVAL_P(var)) != 3 ) { + php_error_docref(NULL, E_WARNING, "You must have 3x3 array"); + RETURN_FALSE; + } + + for (j=0; j<3; j++) { + if ((var2 = zend_hash_index_find(Z_ARRVAL_P(var), j)) != NULL) { + matrix[i][j] = (float) zval_get_double(var2); + } else { + php_error_docref(NULL, E_WARNING, "You must have a 3x3 matrix"); + RETURN_FALSE; + } + } + } + } + res = gdImageConvolution(im_src, matrix, (float)div, (float)offset); + + if (res) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ +/* End section: Filters */ + +/* {{{ proto void imageflip(resource im, int mode) + Flip an image (in place) horizontally, vertically or both directions. */ +PHP_FUNCTION(imageflip) +{ + zval *IM; + zend_long mode; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rl", &IM, &mode) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + switch (mode) { + case GD_FLIP_VERTICAL: + gdImageFlipVertical(im); + break; + + case GD_FLIP_HORINZONTAL: + gdImageFlipHorizontal(im); + break; + + case GD_FLIP_BOTH: + gdImageFlipBoth(im); + break; + + default: + php_error_docref(NULL, E_WARNING, "Unknown flip mode"); + RETURN_FALSE; + } + + RETURN_TRUE; +} +/* }}} */ + +#ifdef HAVE_GD_BUNDLED +/* {{{ proto bool imageantialias(resource im, bool on) + Should antialiased functions used or not*/ +PHP_FUNCTION(imageantialias) +{ + zval *IM; + zend_bool alias; + gdImagePtr im; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rb", &IM, &alias) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + gdImageAntialias(im, alias); + RETURN_TRUE; +} +/* }}} */ +#endif + +/* {{{ proto void imagecrop(resource im, array rect) + Crop an image using the given coordinates and size, x, y, width and height. */ +PHP_FUNCTION(imagecrop) +{ + zval *IM; + gdImagePtr im; + gdImagePtr im_crop; + gdRect rect; + zval *z_rect; + zval *tmp; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ra", &IM, &z_rect) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if ((tmp = zend_hash_str_find(Z_ARRVAL_P(z_rect), "x", sizeof("x") -1)) != NULL) { + rect.x = zval_get_long(tmp); + } else { + php_error_docref(NULL, E_WARNING, "Missing x position"); + RETURN_FALSE; + } + + if ((tmp = zend_hash_str_find(Z_ARRVAL_P(z_rect), "y", sizeof("y") - 1)) != NULL) { + rect.y = zval_get_long(tmp); + } else { + php_error_docref(NULL, E_WARNING, "Missing y position"); + RETURN_FALSE; + } + + if ((tmp = zend_hash_str_find(Z_ARRVAL_P(z_rect), "width", sizeof("width") - 1)) != NULL) { + rect.width = zval_get_long(tmp); + } else { + php_error_docref(NULL, E_WARNING, "Missing width"); + RETURN_FALSE; + } + + if ((tmp = zend_hash_str_find(Z_ARRVAL_P(z_rect), "height", sizeof("height") - 1)) != NULL) { + rect.height = zval_get_long(tmp); + } else { + php_error_docref(NULL, E_WARNING, "Missing height"); + RETURN_FALSE; + } + + im_crop = gdImageCrop(im, &rect); + + if (im_crop == NULL) { + RETURN_FALSE; + } else { + RETURN_RES(zend_register_resource(im_crop, le_gd)); + } +} +/* }}} */ + +/* {{{ proto void imagecropauto(resource im [, int mode [, float threshold [, int color]]]) + Crop an image automatically using one of the available modes. */ +PHP_FUNCTION(imagecropauto) +{ + zval *IM; + zend_long mode = -1; + zend_long color = -1; + double threshold = 0.5f; + gdImagePtr im; + gdImagePtr im_crop; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r|ldl", &IM, &mode, &threshold, &color) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + switch (mode) { + case -1: + mode = GD_CROP_DEFAULT; + case GD_CROP_DEFAULT: + case GD_CROP_TRANSPARENT: + case GD_CROP_BLACK: + case GD_CROP_WHITE: + case GD_CROP_SIDES: + im_crop = gdImageCropAuto(im, mode); + break; + + case GD_CROP_THRESHOLD: + if (color < 0 || (!gdImageTrueColor(im) && color >= gdImageColorsTotal(im))) { + php_error_docref(NULL, E_WARNING, "Color argument missing with threshold mode"); + RETURN_FALSE; + } + im_crop = gdImageCropThreshold(im, color, (float) threshold); + break; + + default: + php_error_docref(NULL, E_WARNING, "Unknown crop mode"); + RETURN_FALSE; + } + if (im_crop == NULL) { + RETURN_FALSE; + } else { + RETURN_RES(zend_register_resource(im_crop, le_gd)); + } +} +/* }}} */ + +/* {{{ proto resource imagescale(resource im, int new_width[, int new_height[, int method]]) + Scale an image using the given new width and height. */ +PHP_FUNCTION(imagescale) +{ + zval *IM; + gdImagePtr im; + gdImagePtr im_scaled = NULL; + int new_width, new_height; + zend_long tmp_w, tmp_h=-1, tmp_m = GD_BILINEAR_FIXED; + gdInterpolationMethod method, old_method; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rl|ll", &IM, &tmp_w, &tmp_h, &tmp_m) == FAILURE) { + return; + } + method = tmp_m; + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (tmp_h < 0) { + /* preserve ratio */ + long src_x, src_y; + + src_x = gdImageSX(im); + src_y = gdImageSY(im); + if (src_x) { + tmp_h = tmp_w * src_y / src_x; + } + } + + if (tmp_h <= 0 || tmp_w <= 0) { + RETURN_FALSE; + } + + new_width = tmp_w; + new_height = tmp_h; + + /* gdImageGetInterpolationMethod() is only available as of GD 2.1.1 */ + old_method = im->interpolation_id; + if (gdImageSetInterpolationMethod(im, method)) { + im_scaled = gdImageScale(im, new_width, new_height); + } + gdImageSetInterpolationMethod(im, old_method); + + if (im_scaled == NULL) { + RETURN_FALSE; + } else { + RETURN_RES(zend_register_resource(im_scaled, le_gd)); + } +} +/* }}} */ + +/* {{{ proto resource imageaffine(resource src, array affine[, array clip]) + Return an image containing the affine tramsformed src image, using an optional clipping area */ +PHP_FUNCTION(imageaffine) +{ + zval *IM; + gdImagePtr src; + gdImagePtr dst; + gdRect rect; + gdRectPtr pRect = NULL; + zval *z_rect = NULL; + zval *z_affine; + zval *tmp; + double affine[6]; + int i, nelems; + zval *zval_affine_elem = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ra|a", &IM, &z_affine, &z_rect) == FAILURE) { + return; + } + + if ((src = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if ((nelems = zend_hash_num_elements(Z_ARRVAL_P(z_affine))) != 6) { + php_error_docref(NULL, E_WARNING, "Affine array must have six elements"); + RETURN_FALSE; + } + + for (i = 0; i < nelems; i++) { + if ((zval_affine_elem = zend_hash_index_find(Z_ARRVAL_P(z_affine), i)) != NULL) { + switch (Z_TYPE_P(zval_affine_elem)) { + case IS_LONG: + affine[i] = Z_LVAL_P(zval_affine_elem); + break; + case IS_DOUBLE: + affine[i] = Z_DVAL_P(zval_affine_elem); + break; + case IS_STRING: + affine[i] = zval_get_double(zval_affine_elem); + break; + default: + php_error_docref(NULL, E_WARNING, "Invalid type for element %i", i); + RETURN_FALSE; + } + } + } + + if (z_rect != NULL) { + if ((tmp = zend_hash_str_find(Z_ARRVAL_P(z_rect), "x", sizeof("x") - 1)) != NULL) { + rect.x = zval_get_long(tmp); + } else { + php_error_docref(NULL, E_WARNING, "Missing x position"); + RETURN_FALSE; + } + + if ((tmp = zend_hash_str_find(Z_ARRVAL_P(z_rect), "y", sizeof("y") - 1)) != NULL) { + rect.y = zval_get_long(tmp); + } else { + php_error_docref(NULL, E_WARNING, "Missing y position"); + RETURN_FALSE; + } + + if ((tmp = zend_hash_str_find(Z_ARRVAL_P(z_rect), "width", sizeof("width") - 1)) != NULL) { + rect.width = zval_get_long(tmp); + } else { + php_error_docref(NULL, E_WARNING, "Missing width"); + RETURN_FALSE; + } + + if ((tmp = zend_hash_str_find(Z_ARRVAL_P(z_rect), "height", sizeof("height") - 1)) != NULL) { + rect.height = zval_get_long(tmp); + } else { + php_error_docref(NULL, E_WARNING, "Missing height"); + RETURN_FALSE; + } + pRect = ▭ + } else { + rect.x = -1; + rect.y = -1; + rect.width = gdImageSX(src); + rect.height = gdImageSY(src); + pRect = NULL; + } + + if (gdTransformAffineGetImage(&dst, src, pRect, affine) != GD_TRUE) { + RETURN_FALSE; + } + + if (dst == NULL) { + RETURN_FALSE; + } else { + RETURN_RES(zend_register_resource(dst, le_gd)); + } +} +/* }}} */ + +/* {{{ proto array imageaffinematrixget(int type[, array options]) + Return an image containing the affine tramsformed src image, using an optional clipping area */ +PHP_FUNCTION(imageaffinematrixget) +{ + double affine[6]; + zend_long type; + zval *options = NULL; + zval *tmp; + int res = GD_FALSE, i; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l|z", &type, &options) == FAILURE) { + return; + } + + switch((gdAffineStandardMatrix)type) { + case GD_AFFINE_TRANSLATE: + case GD_AFFINE_SCALE: { + double x, y; + if (!options || Z_TYPE_P(options) != IS_ARRAY) { + php_error_docref(NULL, E_WARNING, "Array expected as options"); + RETURN_FALSE; + } + if ((tmp = zend_hash_str_find(Z_ARRVAL_P(options), "x", sizeof("x") - 1)) != NULL) { + x = zval_get_double(tmp); + } else { + php_error_docref(NULL, E_WARNING, "Missing x position"); + RETURN_FALSE; + } + + if ((tmp = zend_hash_str_find(Z_ARRVAL_P(options), "y", sizeof("y") - 1)) != NULL) { + y = zval_get_double(tmp); + } else { + php_error_docref(NULL, E_WARNING, "Missing y position"); + RETURN_FALSE; + } + + if (type == GD_AFFINE_TRANSLATE) { + res = gdAffineTranslate(affine, x, y); + } else { + res = gdAffineScale(affine, x, y); + } + break; + } + + case GD_AFFINE_ROTATE: + case GD_AFFINE_SHEAR_HORIZONTAL: + case GD_AFFINE_SHEAR_VERTICAL: { + double angle; + + if (!options) { + php_error_docref(NULL, E_WARNING, "Number is expected as option"); + RETURN_FALSE; + } + + angle = zval_get_double(options); + + if (type == GD_AFFINE_SHEAR_HORIZONTAL) { + res = gdAffineShearHorizontal(affine, angle); + } else if (type == GD_AFFINE_SHEAR_VERTICAL) { + res = gdAffineShearVertical(affine, angle); + } else { + res = gdAffineRotate(affine, angle); + } + break; + } + + default: + php_error_docref(NULL, E_WARNING, "Invalid type for element " ZEND_LONG_FMT, type); + RETURN_FALSE; + } + + if (res == GD_FALSE) { + RETURN_FALSE; + } else { + array_init(return_value); + for (i = 0; i < 6; i++) { + add_index_double(return_value, i, affine[i]); + } + } +} /* }}} */ + +/* {{{ proto array imageaffineconcat(array m1, array m2) + Concat two matrices (as in doing many ops in one go) */ +PHP_FUNCTION(imageaffinematrixconcat) +{ + double m1[6]; + double m2[6]; + double mr[6]; + + zval *tmp; + zval *z_m1; + zval *z_m2; + int i, nelems; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "aa", &z_m1, &z_m2) == FAILURE) { + return; + } + + if (((nelems = zend_hash_num_elements(Z_ARRVAL_P(z_m1))) != 6) || (nelems = zend_hash_num_elements(Z_ARRVAL_P(z_m2))) != 6) { + php_error_docref(NULL, E_WARNING, "Affine arrays must have six elements"); + RETURN_FALSE; + } + + for (i = 0; i < 6; i++) { + if ((tmp = zend_hash_index_find(Z_ARRVAL_P(z_m1), i)) != NULL) { + switch (Z_TYPE_P(tmp)) { + case IS_LONG: + m1[i] = Z_LVAL_P(tmp); + break; + case IS_DOUBLE: + m1[i] = Z_DVAL_P(tmp); + break; + case IS_STRING: + m1[i] = zval_get_double(tmp); + break; + default: + php_error_docref(NULL, E_WARNING, "Invalid type for element %i", i); + RETURN_FALSE; + } + } + if ((tmp = zend_hash_index_find(Z_ARRVAL_P(z_m2), i)) != NULL) { + switch (Z_TYPE_P(tmp)) { + case IS_LONG: + m2[i] = Z_LVAL_P(tmp); + break; + case IS_DOUBLE: + m2[i] = Z_DVAL_P(tmp); + break; + case IS_STRING: + m2[i] = zval_get_double(tmp); + break; + default: + php_error_docref(NULL, E_WARNING, "Invalid type for element %i", i); + RETURN_FALSE; + } + } + } + + if (gdAffineConcat (mr, m1, m2) != GD_TRUE) { + RETURN_FALSE; + } + + array_init(return_value); + for (i = 0; i < 6; i++) { + add_index_double(return_value, i, mr[i]); + } +} /* }}} */ + +/* {{{ proto resource imagesetinterpolation(resource im [, int method]]) + Set the default interpolation method, passing -1 or 0 sets it to the libgd default (bilinear). */ +PHP_FUNCTION(imagesetinterpolation) +{ + zval *IM; + gdImagePtr im; + zend_long method = GD_BILINEAR_FIXED; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r|l", &IM, &method) == FAILURE) { + return; + } + + if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) { + RETURN_FALSE; + } + + if (method == -1) { + method = GD_BILINEAR_FIXED; + } + RETURN_BOOL(gdImageSetInterpolationMethod(im, (gdInterpolationMethod) method)); +} +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/ext/gd/libgd/gd_color.c b/ext/gd/libgd/gd_color.c index a4e56b1c40157..e6f539bc75b55 100644 --- a/ext/gd/libgd/gd_color.c +++ b/ext/gd/libgd/gd_color.c @@ -33,8 +33,8 @@ int gdImageColorMatch (gdImagePtr im1, gdImagePtr im2) return -4; /* At least 1 color must be allocated */ } - buf = (unsigned long *)safe_emalloc(sizeof(unsigned long), 5 * im2->colorsTotal, 0); - memset( buf, 0, sizeof(unsigned long) * 5 * im2->colorsTotal ); + buf = (unsigned long *)safe_emalloc(sizeof(unsigned long), 5 * gdMaxColors, 0); + memset( buf, 0, sizeof(unsigned long) * 5 * gdMaxColors ); for (x=0; xsx; x++) { for( y=0; ysy; y++ ) { diff --git a/ext/gd/libgd/gd_interpolation.c b/ext/gd/libgd/gd_interpolation.c index 1c151b55090d4..d456c0a596d29 100644 --- a/ext/gd/libgd/gd_interpolation.c +++ b/ext/gd/libgd/gd_interpolation.c @@ -880,8 +880,13 @@ static inline LineContribType * _gdContributionsAlloc(unsigned int line_length, { unsigned int u = 0; LineContribType *res; - int overflow_error = 0; + size_t weights_size; + if (overflow2(windows_size, sizeof(double))) { + return NULL; + } else { + weights_size = windows_size * sizeof(double); + } res = (LineContribType *) gdMalloc(sizeof(LineContribType)); if (!res) { return NULL; @@ -898,15 +903,10 @@ static inline LineContribType * _gdContributionsAlloc(unsigned int line_length, return NULL; } for (u = 0 ; u < line_length ; u++) { - if (overflow2(windows_size, sizeof(double))) { - overflow_error = 1; - } else { - res->ContribRow[u].Weights = (double *) gdMalloc(windows_size * sizeof(double)); - } - if (overflow_error == 1 || res->ContribRow[u].Weights == NULL) { + res->ContribRow[u].Weights = (double *) gdMalloc(weights_size); + if (res->ContribRow[u].Weights == NULL) { unsigned int i; - u--; - for (i=0;i<=u;i++) { + for (i=0;iContribRow[i].Weights); } gdFree(res->ContribRow); diff --git a/ext/gd/libgd/xbm.c b/ext/gd/libgd/xbm.c index 044159db12c68..16978a158afe6 100644 --- a/ext/gd/libgd/xbm.c +++ b/ext/gd/libgd/xbm.c @@ -135,7 +135,11 @@ gdImagePtr gdImageCreateFromXbm(FILE * fd) } h[3] = ch; } - sscanf(h, "%x", &b); + if (sscanf(h, "%x", &b) != 1) { + php_gd_error("invalid XBM"); + gdImageDestroy(im); + return 0; + } for (bit = 1; bit <= max_bit; bit = bit << 1) { gdImageSetPixel(im, x++, y, (b & bit) ? 1 : 0); if (x == im->sx) { diff --git a/ext/gd/tests/bug77269.phpt b/ext/gd/tests/bug77269.phpt new file mode 100644 index 0000000000000..3bdc23e80a717 --- /dev/null +++ b/ext/gd/tests/bug77269.phpt @@ -0,0 +1,21 @@ +--TEST-- +Bug #77269 (Potential unsigned underflow in gdImageScale) +--SKIPIF-- + +--INI-- +memory_limit=2G +--FILE-- + +===DONE=== +--EXPECTF-- +Warning: imagecreate():%S product of memory allocation multiplication would exceed INT_MAX, failing operation gracefully + in %s on line %d +===DONE=== diff --git a/ext/gd/tests/bug77270.phpt b/ext/gd/tests/bug77270.phpt new file mode 100644 index 0000000000000..1c4555a64d74a --- /dev/null +++ b/ext/gd/tests/bug77270.phpt @@ -0,0 +1,18 @@ +--TEST-- +Bug #77270 (imagecolormatch Out Of Bounds Write on Heap) +--SKIPIF-- + +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/ext/gd/tests/bug77973.phpt b/ext/gd/tests/bug77973.phpt new file mode 100644 index 0000000000000..2545dbe1281b2 --- /dev/null +++ b/ext/gd/tests/bug77973.phpt @@ -0,0 +1,26 @@ +--TEST-- +Bug #77973 (Uninitialized read in gdImageCreateFromXbm) +--SKIPIF-- + +--FILE-- + +===DONE=== +--EXPECTF-- +Warning: imagecreatefromxbm(): invalid XBM in %s on line %d + +Warning: imagecreatefromxbm(): '%s' is not a valid XBM file in %s on line %d +bool(false) +===DONE=== +--CLEAN-- + diff --git a/ext/gd/tests/bug81739.phpt b/ext/gd/tests/bug81739.phpt new file mode 100644 index 0000000000000..cc2a90381bab4 --- /dev/null +++ b/ext/gd/tests/bug81739.phpt @@ -0,0 +1,24 @@ +--TEST-- +Bug #81739 (OOB read due to insufficient validation in imageloadfont()) +--SKIPIF-- + +--FILE-- + +--CLEAN-- + +--EXPECTF-- +Warning: imageloadfont(): %croduct of memory allocation multiplication would exceed INT_MAX, failing operation gracefully + in %s on line %d + +Warning: imageloadfont(): Error reading font, invalid font header in %s on line %d +bool(false) \ No newline at end of file diff --git a/ext/iconv/iconv.c b/ext/iconv/iconv.c index 53bebcea2cc63..61ef471635973 100644 --- a/ext/iconv/iconv.c +++ b/ext/iconv/iconv.c @@ -2656,6 +2656,9 @@ static int php_iconv_stream_filter_append_bucket( tcnt = 0; break; } + } else { + php_error_docref(NULL, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): invalid multibyte sequence", self->from_charset, self->to_charset); + goto out_failure; } break; diff --git a/ext/iconv/iconv.c.orig b/ext/iconv/iconv.c.orig new file mode 100644 index 0000000000000..53bebcea2cc63 --- /dev/null +++ b/ext/iconv/iconv.c.orig @@ -0,0 +1,2952 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2017 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Rui Hirokawa | + | Stig Bakken | + | Moriyoshi Koizumi | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_globals.h" +#include "ext/standard/info.h" +#include "main/php_output.h" +#include "SAPI.h" +#include "php_ini.h" + +#ifdef HAVE_STDLIB_H +# include +#endif + +#include + +#include "php_iconv.h" + +#ifdef HAVE_ICONV + +#ifdef PHP_ICONV_H_PATH +#include PHP_ICONV_H_PATH +#else +#include +#endif + +#ifdef HAVE_GLIBC_ICONV +#include +#endif + +#ifdef HAVE_LIBICONV +#undef iconv +#endif + +#include "zend_smart_str.h" +#include "ext/standard/base64.h" +#include "ext/standard/quot_print.h" + +#define _php_iconv_memequal(a, b, c) \ + ((c) == sizeof(zend_ulong) ? *((zend_ulong *)(a)) == *((zend_ulong *)(b)) : ((c) == sizeof(unsigned int) ? *((unsigned int *)(a)) == *((unsigned int *)(b)) : memcmp(a, b, c) == 0)) + +/* {{{ arginfo */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_iconv_strlen, 0, 0, 1) + ZEND_ARG_INFO(0, str) + ZEND_ARG_INFO(0, charset) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_iconv_substr, 0, 0, 2) + ZEND_ARG_INFO(0, str) + ZEND_ARG_INFO(0, offset) + ZEND_ARG_INFO(0, length) + ZEND_ARG_INFO(0, charset) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_iconv_strpos, 0, 0, 2) + ZEND_ARG_INFO(0, haystack) + ZEND_ARG_INFO(0, needle) + ZEND_ARG_INFO(0, offset) + ZEND_ARG_INFO(0, charset) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_iconv_strrpos, 0, 0, 2) + ZEND_ARG_INFO(0, haystack) + ZEND_ARG_INFO(0, needle) + ZEND_ARG_INFO(0, charset) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_iconv_mime_encode, 0, 0, 2) + ZEND_ARG_INFO(0, field_name) + ZEND_ARG_INFO(0, field_value) + ZEND_ARG_INFO(0, preference) /* ZEND_ARG_ARRAY_INFO(0, preference, 1) */ +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_iconv_mime_decode, 0, 0, 1) + ZEND_ARG_INFO(0, encoded_string) + ZEND_ARG_INFO(0, mode) + ZEND_ARG_INFO(0, charset) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_iconv_mime_decode_headers, 0, 0, 1) + ZEND_ARG_INFO(0, headers) + ZEND_ARG_INFO(0, mode) + ZEND_ARG_INFO(0, charset) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_iconv, 0) + ZEND_ARG_INFO(0, in_charset) + ZEND_ARG_INFO(0, out_charset) + ZEND_ARG_INFO(0, str) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_iconv_set_encoding, 0) + ZEND_ARG_INFO(0, type) + ZEND_ARG_INFO(0, charset) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_iconv_get_encoding, 0, 0, 0) + ZEND_ARG_INFO(0, type) +ZEND_END_ARG_INFO() + +/* }}} */ + +/* {{{ iconv_functions[] + */ +const zend_function_entry iconv_functions[] = { + PHP_RAW_NAMED_FE(iconv,php_if_iconv, arginfo_iconv) + PHP_FE(iconv_get_encoding, arginfo_iconv_get_encoding) + PHP_FE(iconv_set_encoding, arginfo_iconv_set_encoding) + PHP_FE(iconv_strlen, arginfo_iconv_strlen) + PHP_FE(iconv_substr, arginfo_iconv_substr) + PHP_FE(iconv_strpos, arginfo_iconv_strpos) + PHP_FE(iconv_strrpos, arginfo_iconv_strrpos) + PHP_FE(iconv_mime_encode, arginfo_iconv_mime_encode) + PHP_FE(iconv_mime_decode, arginfo_iconv_mime_decode) + PHP_FE(iconv_mime_decode_headers, arginfo_iconv_mime_decode_headers) + PHP_FE_END +}; +/* }}} */ + +ZEND_DECLARE_MODULE_GLOBALS(iconv) +static PHP_GINIT_FUNCTION(iconv); + +/* {{{ iconv_module_entry + */ +zend_module_entry iconv_module_entry = { + STANDARD_MODULE_HEADER, + "iconv", + iconv_functions, + PHP_MINIT(miconv), + PHP_MSHUTDOWN(miconv), + NULL, + NULL, + PHP_MINFO(miconv), + PHP_ICONV_VERSION, + PHP_MODULE_GLOBALS(iconv), + PHP_GINIT(iconv), + NULL, + NULL, + STANDARD_MODULE_PROPERTIES_EX +}; +/* }}} */ + +#ifdef COMPILE_DL_ICONV +#ifdef ZTS +ZEND_TSRMLS_CACHE_DEFINE() +#endif +ZEND_GET_MODULE(iconv) +#endif + +/* {{{ PHP_GINIT_FUNCTION */ +static PHP_GINIT_FUNCTION(iconv) +{ +#if defined(COMPILE_DL_ICONV) && defined(ZTS) + ZEND_TSRMLS_CACHE_UPDATE(); +#endif + iconv_globals->input_encoding = NULL; + iconv_globals->output_encoding = NULL; + iconv_globals->internal_encoding = NULL; +} +/* }}} */ + +#if defined(HAVE_LIBICONV) && defined(ICONV_ALIASED_LIBICONV) +#define iconv libiconv +#endif + +/* {{{ typedef enum php_iconv_enc_scheme_t */ +typedef enum _php_iconv_enc_scheme_t { + PHP_ICONV_ENC_SCHEME_BASE64, + PHP_ICONV_ENC_SCHEME_QPRINT +} php_iconv_enc_scheme_t; +/* }}} */ + +#define PHP_ICONV_MIME_DECODE_STRICT (1<<0) +#define PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR (1<<1) + +/* {{{ prototypes */ +static php_iconv_err_t _php_iconv_appendl(smart_str *d, const char *s, size_t l, iconv_t cd); +static php_iconv_err_t _php_iconv_appendc(smart_str *d, const char c, iconv_t cd); + +static void _php_iconv_show_error(php_iconv_err_t err, const char *out_charset, const char *in_charset); + +static php_iconv_err_t _php_iconv_strlen(size_t *pretval, const char *str, size_t nbytes, const char *enc); + +static php_iconv_err_t _php_iconv_substr(smart_str *pretval, const char *str, size_t nbytes, zend_long offset, zend_long len, const char *enc); + +static php_iconv_err_t _php_iconv_strpos(size_t *pretval, const char *haystk, size_t haystk_nbytes, const char *ndl, size_t ndl_nbytes, zend_long offset, const char *enc); + +static php_iconv_err_t _php_iconv_mime_encode(smart_str *pretval, const char *fname, size_t fname_nbytes, const char *fval, size_t fval_nbytes, size_t max_line_len, const char *lfchars, php_iconv_enc_scheme_t enc_scheme, const char *out_charset, const char *enc); + +static php_iconv_err_t _php_iconv_mime_decode(smart_str *pretval, const char *str, size_t str_nbytes, const char *enc, const char **next_pos, int mode); + +static php_iconv_err_t php_iconv_stream_filter_register_factory(void); +static php_iconv_err_t php_iconv_stream_filter_unregister_factory(void); + +static int php_iconv_output_conflict(const char *handler_name, size_t handler_name_len); +static php_output_handler *php_iconv_output_handler_init(const char *name, size_t name_len, size_t chunk_size, int flags); +static int php_iconv_output_handler(void **nothing, php_output_context *output_context); +/* }}} */ + +/* {{{ static globals */ +static char _generic_superset_name[] = ICONV_UCS4_ENCODING; +#define GENERIC_SUPERSET_NAME _generic_superset_name +#define GENERIC_SUPERSET_NBYTES 4 +/* }}} */ + + +static PHP_INI_MH(OnUpdateInputEncoding) +{ + if (ZSTR_LEN(new_value) >= ICONV_CSNMAXLEN) { + return FAILURE; + } + if (stage & (PHP_INI_STAGE_ACTIVATE | PHP_INI_STAGE_RUNTIME)) { + php_error_docref("ref.iconv", E_DEPRECATED, "Use of iconv.input_encoding is deprecated"); + } + OnUpdateString(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage); + return SUCCESS; +} + + +static PHP_INI_MH(OnUpdateOutputEncoding) +{ + if (ZSTR_LEN(new_value) >= ICONV_CSNMAXLEN) { + return FAILURE; + } + if (stage & (PHP_INI_STAGE_ACTIVATE | PHP_INI_STAGE_RUNTIME)) { + php_error_docref("ref.iconv", E_DEPRECATED, "Use of iconv.output_encoding is deprecated"); + } + OnUpdateString(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage); + return SUCCESS; +} + + +static PHP_INI_MH(OnUpdateInternalEncoding) +{ + if (ZSTR_LEN(new_value) >= ICONV_CSNMAXLEN) { + return FAILURE; + } + if (stage & (PHP_INI_STAGE_ACTIVATE | PHP_INI_STAGE_RUNTIME)) { + php_error_docref("ref.iconv", E_DEPRECATED, "Use of iconv.internal_encoding is deprecated"); + } + OnUpdateString(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage); + return SUCCESS; +} + + +/* {{{ PHP_INI + */ +PHP_INI_BEGIN() + STD_PHP_INI_ENTRY("iconv.input_encoding", "", PHP_INI_ALL, OnUpdateInputEncoding, input_encoding, zend_iconv_globals, iconv_globals) + STD_PHP_INI_ENTRY("iconv.output_encoding", "", PHP_INI_ALL, OnUpdateOutputEncoding, output_encoding, zend_iconv_globals, iconv_globals) + STD_PHP_INI_ENTRY("iconv.internal_encoding", "", PHP_INI_ALL, OnUpdateInternalEncoding, internal_encoding, zend_iconv_globals, iconv_globals) +PHP_INI_END() +/* }}} */ + +/* {{{ PHP_MINIT_FUNCTION */ +PHP_MINIT_FUNCTION(miconv) +{ + char *version = "unknown"; + + REGISTER_INI_ENTRIES(); + +#if HAVE_LIBICONV + { + static char buf[16]; + snprintf(buf, sizeof(buf), "%d.%d", + ((_libiconv_version >> 8) & 0x0f), (_libiconv_version & 0x0f)); + version = buf; + } +#elif HAVE_GLIBC_ICONV + version = (char *)gnu_get_libc_version(); +#elif defined(NETWARE) + version = "OS built-in"; +#endif + +#ifdef PHP_ICONV_IMPL + REGISTER_STRING_CONSTANT("ICONV_IMPL", PHP_ICONV_IMPL, CONST_CS | CONST_PERSISTENT); +#elif HAVE_LIBICONV + REGISTER_STRING_CONSTANT("ICONV_IMPL", "libiconv", CONST_CS | CONST_PERSISTENT); +#elif defined(NETWARE) + REGISTER_STRING_CONSTANT("ICONV_IMPL", "Novell", CONST_CS | CONST_PERSISTENT); +#else + REGISTER_STRING_CONSTANT("ICONV_IMPL", "unknown", CONST_CS | CONST_PERSISTENT); +#endif + REGISTER_STRING_CONSTANT("ICONV_VERSION", version, CONST_CS | CONST_PERSISTENT); + + REGISTER_LONG_CONSTANT("ICONV_MIME_DECODE_STRICT", PHP_ICONV_MIME_DECODE_STRICT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("ICONV_MIME_DECODE_CONTINUE_ON_ERROR", PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR, CONST_CS | CONST_PERSISTENT); + + if (php_iconv_stream_filter_register_factory() != PHP_ICONV_ERR_SUCCESS) { + return FAILURE; + } + + php_output_handler_alias_register(ZEND_STRL("ob_iconv_handler"), php_iconv_output_handler_init); + php_output_handler_conflict_register(ZEND_STRL("ob_iconv_handler"), php_iconv_output_conflict); + + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_MSHUTDOWN_FUNCTION */ +PHP_MSHUTDOWN_FUNCTION(miconv) +{ + php_iconv_stream_filter_unregister_factory(); + UNREGISTER_INI_ENTRIES(); + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_MINFO_FUNCTION */ +PHP_MINFO_FUNCTION(miconv) +{ + zval *iconv_impl, *iconv_ver; + + iconv_impl = zend_get_constant_str("ICONV_IMPL", sizeof("ICONV_IMPL")-1); + iconv_ver = zend_get_constant_str("ICONV_VERSION", sizeof("ICONV_VERSION")-1); + + php_info_print_table_start(); + php_info_print_table_row(2, "iconv support", "enabled"); + php_info_print_table_row(2, "iconv implementation", Z_STRVAL_P(iconv_impl)); + php_info_print_table_row(2, "iconv library version", Z_STRVAL_P(iconv_ver)); + php_info_print_table_end(); + + DISPLAY_INI_ENTRIES(); +} +/* }}} */ + +static char *get_internal_encoding(void) { + if (ICONVG(internal_encoding) && ICONVG(internal_encoding)[0]) { + return ICONVG(internal_encoding); + } else if (PG(internal_encoding) && PG(internal_encoding)[0]) { + return PG(internal_encoding); + } else if (SG(default_charset)) { + return SG(default_charset); + } + return ""; +} + +static char *get_input_encoding(void) { + if (ICONVG(input_encoding) && ICONVG(input_encoding)[0]) { + return ICONVG(input_encoding); + } else if (PG(input_encoding) && PG(input_encoding)[0]) { + return PG(input_encoding); + } else if (SG(default_charset)) { + return SG(default_charset); + } + return ""; +} + +static char *get_output_encoding(void) { + if (ICONVG(output_encoding) && ICONVG(output_encoding)[0]) { + return ICONVG(output_encoding); + } else if (PG(output_encoding) && PG(output_encoding)[0]) { + return PG(output_encoding); + } else if (SG(default_charset)) { + return SG(default_charset); + } + return ""; +} + + +static int php_iconv_output_conflict(const char *handler_name, size_t handler_name_len) +{ + if (php_output_get_level()) { + if (php_output_handler_conflict(handler_name, handler_name_len, ZEND_STRL("ob_iconv_handler")) + || php_output_handler_conflict(handler_name, handler_name_len, ZEND_STRL("mb_output_handler"))) { + return FAILURE; + } + } + return SUCCESS; +} + +static php_output_handler *php_iconv_output_handler_init(const char *handler_name, size_t handler_name_len, size_t chunk_size, int flags) +{ + return php_output_handler_create_internal(handler_name, handler_name_len, php_iconv_output_handler, chunk_size, flags); +} + +static int php_iconv_output_handler(void **nothing, php_output_context *output_context) +{ + char *s, *content_type, *mimetype = NULL; + int output_status, mimetype_len = 0; + + if (output_context->op & PHP_OUTPUT_HANDLER_START) { + output_status = php_output_get_status(); + if (output_status & PHP_OUTPUT_SENT) { + return FAILURE; + } + + if (SG(sapi_headers).mimetype && !strncasecmp(SG(sapi_headers).mimetype, "text/", 5)) { + if ((s = strchr(SG(sapi_headers).mimetype,';')) == NULL){ + mimetype = SG(sapi_headers).mimetype; + } else { + mimetype = SG(sapi_headers).mimetype; + mimetype_len = (int)(s - SG(sapi_headers).mimetype); + } + } else if (SG(sapi_headers).send_default_content_type) { + mimetype = SG(default_mimetype) ? SG(default_mimetype) : SAPI_DEFAULT_MIMETYPE; + } + + if (mimetype != NULL && !(output_context->op & PHP_OUTPUT_HANDLER_CLEAN)) { + size_t len; + char *p = strstr(get_output_encoding(), "//"); + + if (p) { + len = spprintf(&content_type, 0, "Content-Type:%.*s; charset=%.*s", mimetype_len ? mimetype_len : (int) strlen(mimetype), mimetype, (int) (p - get_output_encoding()), get_output_encoding()); + } else { + len = spprintf(&content_type, 0, "Content-Type:%.*s; charset=%s", mimetype_len ? mimetype_len : (int) strlen(mimetype), mimetype, get_output_encoding()); + } + if (content_type && SUCCESS == sapi_add_header(content_type, (uint)len, 0)) { + SG(sapi_headers).send_default_content_type = 0; + php_output_handler_hook(PHP_OUTPUT_HANDLER_HOOK_IMMUTABLE, NULL); + } + } + } + + if (output_context->in.used) { + zend_string *out; + output_context->out.free = 1; + _php_iconv_show_error(php_iconv_string(output_context->in.data, output_context->in.used, &out, get_output_encoding(), get_internal_encoding()), get_output_encoding(), get_internal_encoding()); + if (out) { + output_context->out.data = estrndup(ZSTR_VAL(out), ZSTR_LEN(out)); + output_context->out.used = ZSTR_LEN(out); + zend_string_free(out); + } else { + output_context->out.data = NULL; + output_context->out.used = 0; + } + } + + return SUCCESS; +} + +/* {{{ _php_iconv_appendl() */ +static php_iconv_err_t _php_iconv_appendl(smart_str *d, const char *s, size_t l, iconv_t cd) +{ + const char *in_p = s; + size_t in_left = l; + char *out_p; + size_t out_left = 0; + size_t buf_growth = 128; +#if !ICONV_SUPPORTS_ERRNO + size_t prev_in_left = in_left; +#endif + + if (in_p != NULL) { + while (in_left > 0) { + out_left = buf_growth - out_left; + smart_str_alloc(d, out_left, 0); + + out_p = ZSTR_VAL((d)->s) + ZSTR_LEN((d)->s); + + if (iconv(cd, (char **)&in_p, &in_left, (char **) &out_p, &out_left) == (size_t)-1) { +#if ICONV_SUPPORTS_ERRNO + switch (errno) { + case EINVAL: + return PHP_ICONV_ERR_ILLEGAL_CHAR; + + case EILSEQ: + return PHP_ICONV_ERR_ILLEGAL_SEQ; + + case E2BIG: + break; + + default: + return PHP_ICONV_ERR_UNKNOWN; + } +#else + if (prev_in_left == in_left) { + return PHP_ICONV_ERR_UNKNOWN; + } +#endif + } +#if !ICONV_SUPPORTS_ERRNO + prev_in_left = in_left; +#endif + ZSTR_LEN((d)->s) += (buf_growth - out_left); + buf_growth <<= 1; + } + } else { + for (;;) { + out_left = buf_growth - out_left; + smart_str_alloc(d, out_left, 0); + + out_p = ZSTR_VAL((d)->s) + ZSTR_LEN((d)->s); + + if (iconv(cd, NULL, NULL, (char **) &out_p, &out_left) == (size_t)0) { + ZSTR_LEN((d)->s) += (buf_growth - out_left); + break; + } else { +#if ICONV_SUPPORTS_ERRNO + if (errno != E2BIG) { + return PHP_ICONV_ERR_UNKNOWN; + } +#else + if (out_left != 0) { + return PHP_ICONV_ERR_UNKNOWN; + } +#endif + } + ZSTR_LEN((d)->s) += (buf_growth - out_left); + buf_growth <<= 1; + } + } + return PHP_ICONV_ERR_SUCCESS; +} +/* }}} */ + +/* {{{ _php_iconv_appendc() */ +static php_iconv_err_t _php_iconv_appendc(smart_str *d, const char c, iconv_t cd) +{ + return _php_iconv_appendl(d, &c, 1, cd); +} +/* }}} */ + +/* {{{ */ +#if ICONV_BROKEN_IGNORE +static int _php_check_ignore(const char *charset) +{ + size_t clen = strlen(charset); + if (clen >= 9 && strcmp("//IGNORE", charset+clen-8) == 0) { + return 1; + } + if (clen >= 19 && strcmp("//IGNORE//TRANSLIT", charset+clen-18) == 0) { + return 1; + } + return 0; +} +#else +#define _php_check_ignore(x) (0) +#endif +/* }}} */ + +/* {{{ php_iconv_string() + */ +PHP_ICONV_API php_iconv_err_t php_iconv_string(const char *in_p, size_t in_len, zend_string **out, const char *out_charset, const char *in_charset) +{ +#if !ICONV_SUPPORTS_ERRNO + size_t in_size, out_size, out_left; + char *out_p; + iconv_t cd; + size_t result; + zend_string *ret, *out_buffer; + + /* + This is not the right way to get output size... + This is not space efficient for large text. + This is also problem for encoding like UTF-7/UTF-8/ISO-2022 which + a single char can be more than 4 bytes. + I added 15 extra bytes for safety. + */ + out_size = in_len * sizeof(int) + 15; + out_left = out_size; + + in_size = in_len; + + cd = iconv_open(out_charset, in_charset); + + if (cd == (iconv_t)(-1)) { + return PHP_ICONV_ERR_UNKNOWN; + } + + out_buffer = zend_string_alloc(out_size, 0); + out_p = ZSTR_VAL(out_buffer); + +#ifdef NETWARE + result = iconv(cd, (char **) &in_p, &in_size, (char **) +#else + result = iconv(cd, (const char **) &in_p, &in_size, (char **) +#endif + &out_p, &out_left); + + if (result == (size_t)(-1)) { + zend_string_free(out_buffer); + return PHP_ICONV_ERR_UNKNOWN; + } + + if (out_left < 8) { + size_t pos = out_p - ZSTR_VAL(out_buffer); + out_buffer = zend_string_extend(out_buffer, out_size + 8, 0); + out_p = ZSTR_VAL(out_buffer) + pos; + out_size += 7; + out_left += 7; + } + + /* flush the shift-out sequences */ + result = iconv(cd, NULL, NULL, &out_p, &out_left); + + if (result == (size_t)(-1)) { + zend_string_free(out_buffer); + return PHP_ICONV_ERR_UNKNOWN; + } + + ZSTR_VAL(out_buffer)[out_size - out_left] = '\0'; + ZSTR_LEN(out_buffer) = out_size - out_left; + + iconv_close(cd); + + *out = out_buffer; + return PHP_ICONV_ERR_SUCCESS; + +#else + /* + iconv supports errno. Handle it better way. + */ + iconv_t cd; + size_t in_left, out_size, out_left; + char *out_p; + size_t bsz, result = 0; + php_iconv_err_t retval = PHP_ICONV_ERR_SUCCESS; + zend_string *out_buf; + int ignore_ilseq = _php_check_ignore(out_charset); + + *out = NULL; + + cd = iconv_open(out_charset, in_charset); + + if (cd == (iconv_t)(-1)) { + if (errno == EINVAL) { + return PHP_ICONV_ERR_WRONG_CHARSET; + } else { + return PHP_ICONV_ERR_CONVERTER; + } + } + in_left= in_len; + out_left = in_len + 32; /* Avoid realloc() most cases */ + out_size = 0; + bsz = out_left; + out_buf = zend_string_alloc(bsz, 0); + out_p = ZSTR_VAL(out_buf); + + while (in_left > 0) { + result = iconv(cd, (char **) &in_p, &in_left, (char **) &out_p, &out_left); + out_size = bsz - out_left; + if (result == (size_t)(-1)) { + if (ignore_ilseq && errno == EILSEQ) { + if (in_left <= 1) { + result = 0; + } else { + errno = 0; + in_p++; + in_left--; + continue; + } + } + + if (errno == E2BIG && in_left > 0) { + /* converted string is longer than out buffer */ + bsz += in_len; + + out_buf = zend_string_extend(out_buf, bsz, 0); + out_p = ZSTR_VAL(out_buf); + out_p += out_size; + out_left = bsz - out_size; + continue; + } + } + break; + } + + if (result != (size_t)(-1)) { + /* flush the shift-out sequences */ + for (;;) { + result = iconv(cd, NULL, NULL, (char **) &out_p, &out_left); + out_size = bsz - out_left; + + if (result != (size_t)(-1)) { + break; + } + + if (errno == E2BIG) { + bsz += 16; + out_buf = zend_string_extend(out_buf, bsz, 0); + out_p = ZSTR_VAL(out_buf); + out_p += out_size; + out_left = bsz - out_size; + } else { + break; + } + } + } + + iconv_close(cd); + + if (result == (size_t)(-1)) { + switch (errno) { + case EINVAL: + retval = PHP_ICONV_ERR_ILLEGAL_CHAR; + break; + + case EILSEQ: + retval = PHP_ICONV_ERR_ILLEGAL_SEQ; + break; + + case E2BIG: + /* should not happen */ + retval = PHP_ICONV_ERR_TOO_BIG; + break; + + default: + /* other error */ + zend_string_free(out_buf); + return PHP_ICONV_ERR_UNKNOWN; + } + } + *out_p = '\0'; + ZSTR_LEN(out_buf) = out_size; + *out = out_buf; + return retval; +#endif +} +/* }}} */ + +/* {{{ _php_iconv_strlen() */ +static php_iconv_err_t _php_iconv_strlen(size_t *pretval, const char *str, size_t nbytes, const char *enc) +{ + char buf[GENERIC_SUPERSET_NBYTES*2]; + + php_iconv_err_t err = PHP_ICONV_ERR_SUCCESS; + + iconv_t cd; + + const char *in_p; + size_t in_left; + + char *out_p; + size_t out_left; + + size_t cnt; + + *pretval = (size_t)-1; + + cd = iconv_open(GENERIC_SUPERSET_NAME, enc); + + if (cd == (iconv_t)(-1)) { +#if ICONV_SUPPORTS_ERRNO + if (errno == EINVAL) { + return PHP_ICONV_ERR_WRONG_CHARSET; + } else { + return PHP_ICONV_ERR_CONVERTER; + } +#else + return PHP_ICONV_ERR_UNKNOWN; +#endif + } + + errno = 0; + out_left = 0; + + for (in_p = str, in_left = nbytes, cnt = 0; in_left > 0; cnt+=2) { + size_t prev_in_left; + out_p = buf; + out_left = sizeof(buf); + + prev_in_left = in_left; + + if (iconv(cd, (char **)&in_p, &in_left, (char **) &out_p, &out_left) == (size_t)-1) { + if (prev_in_left == in_left) { + break; + } + } + } + + if (out_left > 0) { + cnt -= out_left / GENERIC_SUPERSET_NBYTES; + } + +#if ICONV_SUPPORTS_ERRNO + switch (errno) { + case EINVAL: + err = PHP_ICONV_ERR_ILLEGAL_CHAR; + break; + + case EILSEQ: + err = PHP_ICONV_ERR_ILLEGAL_SEQ; + break; + + case E2BIG: + case 0: + *pretval = cnt; + break; + + default: + err = PHP_ICONV_ERR_UNKNOWN; + break; + } +#else + *pretval = cnt; +#endif + + iconv_close(cd); + + return err; +} + +/* }}} */ + +/* {{{ _php_iconv_substr() */ +static php_iconv_err_t _php_iconv_substr(smart_str *pretval, + const char *str, size_t nbytes, zend_long offset, zend_long len, const char *enc) +{ + char buf[GENERIC_SUPERSET_NBYTES]; + + php_iconv_err_t err = PHP_ICONV_ERR_SUCCESS; + + iconv_t cd1, cd2; + + const char *in_p; + size_t in_left; + + char *out_p; + size_t out_left; + + size_t cnt; + size_t total_len; + + err = _php_iconv_strlen(&total_len, str, nbytes, enc); + if (err != PHP_ICONV_ERR_SUCCESS) { + return err; + } + + if (len < 0) { + if ((len += (total_len - offset)) < 0) { + return PHP_ICONV_ERR_SUCCESS; + } + } + + if (offset < 0) { + if ((offset += total_len) < 0) { + return PHP_ICONV_ERR_SUCCESS; + } + } + + if((size_t)len > total_len) { + len = total_len; + } + + + if ((size_t)offset > total_len) { + return PHP_ICONV_ERR_SUCCESS; + } + + if ((size_t)(offset + len) > total_len ) { + /* trying to compute the length */ + len = total_len - offset; + } + + if (len == 0) { + smart_str_appendl(pretval, "", 0); + smart_str_0(pretval); + return PHP_ICONV_ERR_SUCCESS; + } + + cd1 = iconv_open(GENERIC_SUPERSET_NAME, enc); + + if (cd1 == (iconv_t)(-1)) { +#if ICONV_SUPPORTS_ERRNO + if (errno == EINVAL) { + return PHP_ICONV_ERR_WRONG_CHARSET; + } else { + return PHP_ICONV_ERR_CONVERTER; + } +#else + return PHP_ICONV_ERR_UNKNOWN; +#endif + } + + cd2 = (iconv_t)NULL; + errno = 0; + + for (in_p = str, in_left = nbytes, cnt = 0; in_left > 0 && len > 0; ++cnt) { + size_t prev_in_left; + out_p = buf; + out_left = sizeof(buf); + + prev_in_left = in_left; + + if (iconv(cd1, (char **)&in_p, &in_left, (char **) &out_p, &out_left) == (size_t)-1) { + if (prev_in_left == in_left) { + break; + } + } + + if ((zend_long)cnt >= offset) { + if (cd2 == (iconv_t)NULL) { + cd2 = iconv_open(enc, GENERIC_SUPERSET_NAME); + + if (cd2 == (iconv_t)(-1)) { + cd2 = (iconv_t)NULL; +#if ICONV_SUPPORTS_ERRNO + if (errno == EINVAL) { + err = PHP_ICONV_ERR_WRONG_CHARSET; + } else { + err = PHP_ICONV_ERR_CONVERTER; + } +#else + err = PHP_ICONV_ERR_UNKNOWN; +#endif + break; + } + } + + if (_php_iconv_appendl(pretval, buf, sizeof(buf), cd2) != PHP_ICONV_ERR_SUCCESS) { + break; + } + --len; + } + + } + +#if ICONV_SUPPORTS_ERRNO + switch (errno) { + case EINVAL: + err = PHP_ICONV_ERR_ILLEGAL_CHAR; + break; + + case EILSEQ: + err = PHP_ICONV_ERR_ILLEGAL_SEQ; + break; + + case E2BIG: + break; + } +#endif + if (err == PHP_ICONV_ERR_SUCCESS) { + if (cd2 != (iconv_t)NULL) { + _php_iconv_appendl(pretval, NULL, 0, cd2); + } + smart_str_0(pretval); + } + + if (cd1 != (iconv_t)NULL) { + iconv_close(cd1); + } + + if (cd2 != (iconv_t)NULL) { + iconv_close(cd2); + } + return err; +} + +/* }}} */ + +/* {{{ _php_iconv_strpos() */ +static php_iconv_err_t _php_iconv_strpos(size_t *pretval, + const char *haystk, size_t haystk_nbytes, + const char *ndl, size_t ndl_nbytes, + zend_long offset, const char *enc) +{ + char buf[GENERIC_SUPERSET_NBYTES]; + + php_iconv_err_t err = PHP_ICONV_ERR_SUCCESS; + + iconv_t cd; + + const char *in_p; + size_t in_left; + + char *out_p; + size_t out_left; + + size_t cnt; + + zend_string *ndl_buf; + const char *ndl_buf_p; + size_t ndl_buf_left; + + size_t match_ofs; + + *pretval = (size_t)-1; + + err = php_iconv_string(ndl, ndl_nbytes, &ndl_buf, GENERIC_SUPERSET_NAME, enc); + + if (err != PHP_ICONV_ERR_SUCCESS) { + if (ndl_buf != NULL) { + zend_string_free(ndl_buf); + } + return err; + } + + cd = iconv_open(GENERIC_SUPERSET_NAME, enc); + + if (cd == (iconv_t)(-1)) { + if (ndl_buf != NULL) { + zend_string_free(ndl_buf); + } +#if ICONV_SUPPORTS_ERRNO + if (errno == EINVAL) { + return PHP_ICONV_ERR_WRONG_CHARSET; + } else { + return PHP_ICONV_ERR_CONVERTER; + } +#else + return PHP_ICONV_ERR_UNKNOWN; +#endif + } + + ndl_buf_p = ZSTR_VAL(ndl_buf); + ndl_buf_left = ZSTR_LEN(ndl_buf); + match_ofs = (size_t)-1; + + for (in_p = haystk, in_left = haystk_nbytes, cnt = 0; in_left > 0; ++cnt) { + size_t prev_in_left; + out_p = buf; + out_left = sizeof(buf); + + prev_in_left = in_left; + + if (iconv(cd, (char **)&in_p, &in_left, (char **) &out_p, &out_left) == (size_t)-1) { + if (prev_in_left == in_left) { +#if ICONV_SUPPORTS_ERRNO + switch (errno) { + case EINVAL: + err = PHP_ICONV_ERR_ILLEGAL_CHAR; + break; + + case EILSEQ: + err = PHP_ICONV_ERR_ILLEGAL_SEQ; + break; + + case E2BIG: + break; + + default: + err = PHP_ICONV_ERR_UNKNOWN; + break; + } +#endif + break; + } + } + if (offset >= 0) { + if (cnt >= (size_t)offset) { + if (_php_iconv_memequal(buf, ndl_buf_p, sizeof(buf))) { + if (match_ofs == (size_t)-1) { + match_ofs = cnt; + } + ndl_buf_p += GENERIC_SUPERSET_NBYTES; + ndl_buf_left -= GENERIC_SUPERSET_NBYTES; + if (ndl_buf_left == 0) { + *pretval = match_ofs; + break; + } + } else { + size_t i, j, lim; + + i = 0; + j = GENERIC_SUPERSET_NBYTES; + lim = (size_t)(ndl_buf_p - ZSTR_VAL(ndl_buf)); + + while (j < lim) { + if (_php_iconv_memequal(&ZSTR_VAL(ndl_buf)[j], &ZSTR_VAL(ndl_buf)[i], + GENERIC_SUPERSET_NBYTES)) { + i += GENERIC_SUPERSET_NBYTES; + } else { + j -= i; + i = 0; + } + j += GENERIC_SUPERSET_NBYTES; + } + + if (_php_iconv_memequal(buf, &ZSTR_VAL(ndl_buf)[i], sizeof(buf))) { + match_ofs += (lim - i) / GENERIC_SUPERSET_NBYTES; + i += GENERIC_SUPERSET_NBYTES; + ndl_buf_p = &ZSTR_VAL(ndl_buf)[i]; + ndl_buf_left = ZSTR_LEN(ndl_buf) - i; + } else { + match_ofs = (size_t)-1; + ndl_buf_p = ZSTR_VAL(ndl_buf); + ndl_buf_left = ZSTR_LEN(ndl_buf); + } + } + } + } else { + if (_php_iconv_memequal(buf, ndl_buf_p, sizeof(buf))) { + if (match_ofs == (size_t)-1) { + match_ofs = cnt; + } + ndl_buf_p += GENERIC_SUPERSET_NBYTES; + ndl_buf_left -= GENERIC_SUPERSET_NBYTES; + if (ndl_buf_left == 0) { + *pretval = match_ofs; + ndl_buf_p = ZSTR_VAL(ndl_buf); + ndl_buf_left = ZSTR_LEN(ndl_buf); + match_ofs = -1; + } + } else { + size_t i, j, lim; + + i = 0; + j = GENERIC_SUPERSET_NBYTES; + lim = (size_t)(ndl_buf_p - ZSTR_VAL(ndl_buf)); + + while (j < lim) { + if (_php_iconv_memequal(&ZSTR_VAL(ndl_buf)[j], &ZSTR_VAL(ndl_buf)[i], + GENERIC_SUPERSET_NBYTES)) { + i += GENERIC_SUPERSET_NBYTES; + } else { + j -= i; + i = 0; + } + j += GENERIC_SUPERSET_NBYTES; + } + + if (_php_iconv_memequal(buf, &ZSTR_VAL(ndl_buf)[i], sizeof(buf))) { + match_ofs += (lim - i) / GENERIC_SUPERSET_NBYTES; + i += GENERIC_SUPERSET_NBYTES; + ndl_buf_p = &ZSTR_VAL(ndl_buf)[i]; + ndl_buf_left = ZSTR_LEN(ndl_buf) - i; + } else { + match_ofs = (size_t)-1; + ndl_buf_p = ZSTR_VAL(ndl_buf); + ndl_buf_left = ZSTR_LEN(ndl_buf); + } + } + } + } + + if (ndl_buf) { + zend_string_free(ndl_buf); + } + + iconv_close(cd); + + return err; +} +/* }}} */ + +/* {{{ _php_iconv_mime_encode() */ +static php_iconv_err_t _php_iconv_mime_encode(smart_str *pretval, const char *fname, size_t fname_nbytes, const char *fval, size_t fval_nbytes, size_t max_line_len, const char *lfchars, php_iconv_enc_scheme_t enc_scheme, const char *out_charset, const char *enc) +{ + php_iconv_err_t err = PHP_ICONV_ERR_SUCCESS; + iconv_t cd = (iconv_t)(-1), cd_pl = (iconv_t)(-1); + size_t char_cnt = 0; + size_t out_charset_len; + size_t lfchars_len; + char *buf = NULL; + const char *in_p; + size_t in_left; + char *out_p; + size_t out_left; + zend_string *encoded = NULL; + static int qp_table[256] = { + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0x00 */ + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0x10 */ + 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x20 */ + 1, 1, 1, 1, 1, 1, 1 ,1, 1, 1, 1, 1, 1, 3, 1, 3, /* 0x30 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x40 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, /* 0x50 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x60 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, /* 0x70 */ + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0x80 */ + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0x90 */ + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0xA0 */ + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0xB0 */ + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0xC0 */ + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0xD0 */ + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0xE0 */ + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 /* 0xF0 */ + }; + + out_charset_len = strlen(out_charset); + lfchars_len = strlen(lfchars); + + if ((fname_nbytes + 2) >= max_line_len + || (out_charset_len + 12) >= max_line_len) { + /* field name is too long */ + err = PHP_ICONV_ERR_TOO_BIG; + goto out; + } + + cd_pl = iconv_open(ICONV_ASCII_ENCODING, enc); + if (cd_pl == (iconv_t)(-1)) { +#if ICONV_SUPPORTS_ERRNO + if (errno == EINVAL) { + err = PHP_ICONV_ERR_WRONG_CHARSET; + } else { + err = PHP_ICONV_ERR_CONVERTER; + } +#else + err = PHP_ICONV_ERR_UNKNOWN; +#endif + goto out; + } + + cd = iconv_open(out_charset, enc); + if (cd == (iconv_t)(-1)) { +#if ICONV_SUPPORTS_ERRNO + if (errno == EINVAL) { + err = PHP_ICONV_ERR_WRONG_CHARSET; + } else { + err = PHP_ICONV_ERR_CONVERTER; + } +#else + err = PHP_ICONV_ERR_UNKNOWN; +#endif + goto out; + } + + buf = safe_emalloc(1, max_line_len, 5); + + char_cnt = max_line_len; + + _php_iconv_appendl(pretval, fname, fname_nbytes, cd_pl); + char_cnt -= fname_nbytes; + smart_str_appendl(pretval, ": ", sizeof(": ") - 1); + char_cnt -= 2; + + in_p = fval; + in_left = fval_nbytes; + + do { + size_t prev_in_left; + size_t out_size; + + if (char_cnt < (out_charset_len + 12)) { + /* lfchars must be encoded in ASCII here*/ + smart_str_appendl(pretval, lfchars, lfchars_len); + smart_str_appendc(pretval, ' '); + char_cnt = max_line_len - 1; + } + + smart_str_appendl(pretval, "=?", sizeof("=?") - 1); + char_cnt -= 2; + smart_str_appendl(pretval, out_charset, out_charset_len); + char_cnt -= out_charset_len; + smart_str_appendc(pretval, '?'); + char_cnt --; + + switch (enc_scheme) { + case PHP_ICONV_ENC_SCHEME_BASE64: { + size_t ini_in_left; + const char *ini_in_p; + size_t out_reserved = 4; + + smart_str_appendc(pretval, 'B'); + char_cnt--; + smart_str_appendc(pretval, '?'); + char_cnt--; + + prev_in_left = ini_in_left = in_left; + ini_in_p = in_p; + + out_size = (char_cnt - 2) / 4 * 3; + + for (;;) { + out_p = buf; + + if (out_size <= out_reserved) { + err = PHP_ICONV_ERR_TOO_BIG; + goto out; + } + + out_left = out_size - out_reserved; + + if (iconv(cd, (char **)&in_p, &in_left, (char **) &out_p, &out_left) == (size_t)-1) { +#if ICONV_SUPPORTS_ERRNO + switch (errno) { + case EINVAL: + err = PHP_ICONV_ERR_ILLEGAL_CHAR; + goto out; + + case EILSEQ: + err = PHP_ICONV_ERR_ILLEGAL_SEQ; + goto out; + + case E2BIG: + if (prev_in_left == in_left) { + err = PHP_ICONV_ERR_TOO_BIG; + goto out; + } + break; + + default: + err = PHP_ICONV_ERR_UNKNOWN; + goto out; + } +#else + if (prev_in_left == in_left) { + err = PHP_ICONV_ERR_UNKNOWN; + goto out; + } +#endif + } + + out_left += out_reserved; + + if (iconv(cd, NULL, NULL, (char **) &out_p, &out_left) == (size_t)-1) { +#if ICONV_SUPPORTS_ERRNO + if (errno != E2BIG) { + err = PHP_ICONV_ERR_UNKNOWN; + goto out; + } +#else + if (out_left != 0) { + err = PHP_ICONV_ERR_UNKNOWN; + goto out; + } +#endif + } else { + break; + } + + if (iconv(cd, NULL, NULL, NULL, NULL) == (size_t)-1) { + err = PHP_ICONV_ERR_UNKNOWN; + goto out; + } + + out_reserved += 4; + in_left = ini_in_left; + in_p = ini_in_p; + } + + prev_in_left = in_left; + + encoded = php_base64_encode((unsigned char *) buf, (out_size - out_left)); + + if (char_cnt < ZSTR_LEN(encoded)) { + /* something went wrong! */ + err = PHP_ICONV_ERR_UNKNOWN; + goto out; + } + + smart_str_appendl(pretval, ZSTR_VAL(encoded), ZSTR_LEN(encoded)); + char_cnt -= ZSTR_LEN(encoded); + smart_str_appendl(pretval, "?=", sizeof("?=") - 1); + char_cnt -= 2; + + zend_string_release(encoded); + encoded = NULL; + } break; /* case PHP_ICONV_ENC_SCHEME_BASE64: */ + + case PHP_ICONV_ENC_SCHEME_QPRINT: { + size_t ini_in_left; + const char *ini_in_p; + const unsigned char *p; + size_t nbytes_required; + + smart_str_appendc(pretval, 'Q'); + char_cnt--; + smart_str_appendc(pretval, '?'); + char_cnt--; + + prev_in_left = ini_in_left = in_left; + ini_in_p = in_p; + + for (out_size = (char_cnt - 2) / 3; out_size > 0;) { +#if !ICONV_SUPPORTS_ERRNO + size_t prev_out_left; +#endif + + nbytes_required = 0; + + out_p = buf; + out_left = out_size; + + if (iconv(cd, (char **)&in_p, &in_left, (char **) &out_p, &out_left) == (size_t)-1) { +#if ICONV_SUPPORTS_ERRNO + switch (errno) { + case EINVAL: + err = PHP_ICONV_ERR_ILLEGAL_CHAR; + goto out; + + case EILSEQ: + err = PHP_ICONV_ERR_ILLEGAL_SEQ; + goto out; + + case E2BIG: + if (prev_in_left == in_left) { + err = PHP_ICONV_ERR_UNKNOWN; + goto out; + } + break; + + default: + err = PHP_ICONV_ERR_UNKNOWN; + goto out; + } +#else + if (prev_in_left == in_left) { + err = PHP_ICONV_ERR_UNKNOWN; + goto out; + } +#endif + } +#if !ICONV_SUPPORTS_ERRNO + prev_out_left = out_left; +#endif + if (iconv(cd, NULL, NULL, (char **) &out_p, &out_left) == (size_t)-1) { +#if ICONV_SUPPORTS_ERRNO + if (errno != E2BIG) { + err = PHP_ICONV_ERR_UNKNOWN; + goto out; + } +#else + if (out_left == prev_out_left) { + err = PHP_ICONV_ERR_UNKNOWN; + goto out; + } +#endif + } + + for (p = (unsigned char *)buf; p < (unsigned char *)out_p; p++) { + nbytes_required += qp_table[*p]; + } + + if (nbytes_required <= char_cnt - 2) { + break; + } + + out_size -= ((nbytes_required - (char_cnt - 2)) + 1) / 3; + in_left = ini_in_left; + in_p = ini_in_p; + } + + for (p = (unsigned char *)buf; p < (unsigned char *)out_p; p++) { + if (qp_table[*p] == 1) { + smart_str_appendc(pretval, *(char *)p); + char_cnt--; + } else { + static char qp_digits[] = "0123456789ABCDEF"; + smart_str_appendc(pretval, '='); + smart_str_appendc(pretval, qp_digits[(*p >> 4) & 0x0f]); + smart_str_appendc(pretval, qp_digits[(*p & 0x0f)]); + char_cnt -= 3; + } + } + + smart_str_appendl(pretval, "?=", sizeof("?=") - 1); + char_cnt -= 2; + + if (iconv(cd, NULL, NULL, NULL, NULL) == (size_t)-1) { + err = PHP_ICONV_ERR_UNKNOWN; + goto out; + } + + } break; /* case PHP_ICONV_ENC_SCHEME_QPRINT: */ + } + } while (in_left > 0); + + smart_str_0(pretval); + +out: + if (cd != (iconv_t)(-1)) { + iconv_close(cd); + } + if (cd_pl != (iconv_t)(-1)) { + iconv_close(cd_pl); + } + if (encoded != NULL) { + zend_string_release(encoded); + } + if (buf != NULL) { + efree(buf); + } + return err; +} +/* }}} */ + +/* {{{ _php_iconv_mime_decode() */ +static php_iconv_err_t _php_iconv_mime_decode(smart_str *pretval, const char *str, size_t str_nbytes, const char *enc, const char **next_pos, int mode) +{ + php_iconv_err_t err = PHP_ICONV_ERR_SUCCESS; + + iconv_t cd = (iconv_t)(-1), cd_pl = (iconv_t)(-1); + + const char *p1; + size_t str_left; + unsigned int scan_stat = 0; + const char *csname = NULL; + size_t csname_len; + const char *encoded_text = NULL; + size_t encoded_text_len = 0; + const char *encoded_word = NULL; + const char *spaces = NULL; + + php_iconv_enc_scheme_t enc_scheme = PHP_ICONV_ENC_SCHEME_BASE64; + + if (next_pos != NULL) { + *next_pos = NULL; + } + + cd_pl = iconv_open(enc, ICONV_ASCII_ENCODING); + + if (cd_pl == (iconv_t)(-1)) { +#if ICONV_SUPPORTS_ERRNO + if (errno == EINVAL) { + err = PHP_ICONV_ERR_WRONG_CHARSET; + } else { + err = PHP_ICONV_ERR_CONVERTER; + } +#else + err = PHP_ICONV_ERR_UNKNOWN; +#endif + goto out; + } + + p1 = str; + for (str_left = str_nbytes; str_left > 0; str_left--, p1++) { + int eos = 0; + + switch (scan_stat) { + case 0: /* expecting any character */ + switch (*p1) { + case '\r': /* part of an EOL sequence? */ + scan_stat = 7; + break; + + case '\n': + scan_stat = 8; + break; + + case '=': /* first letter of an encoded chunk */ + encoded_word = p1; + scan_stat = 1; + break; + + case ' ': case '\t': /* a chunk of whitespaces */ + spaces = p1; + scan_stat = 11; + break; + + default: /* first letter of a non-encoded word */ + _php_iconv_appendc(pretval, *p1, cd_pl); + encoded_word = NULL; + if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) { + scan_stat = 12; + } + break; + } + break; + + case 1: /* expecting a delimiter */ + if (*p1 != '?') { + err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl); + if (err != PHP_ICONV_ERR_SUCCESS) { + goto out; + } + encoded_word = NULL; + if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) { + scan_stat = 12; + } else { + scan_stat = 0; + } + break; + } + csname = p1 + 1; + scan_stat = 2; + break; + + case 2: /* expecting a charset name */ + switch (*p1) { + case '?': /* normal delimiter: encoding scheme follows */ + scan_stat = 3; + break; + + case '*': /* new style delimiter: locale id follows */ + scan_stat = 10; + break; + } + if (scan_stat != 2) { + char tmpbuf[80]; + + if (csname == NULL) { + err = PHP_ICONV_ERR_MALFORMED; + goto out; + } + + csname_len = (size_t)(p1 - csname); + + if (csname_len > sizeof(tmpbuf) - 1) { + if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) { + err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl); + if (err != PHP_ICONV_ERR_SUCCESS) { + goto out; + } + encoded_word = NULL; + if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) { + scan_stat = 12; + } else { + scan_stat = 0; + } + break; + } else { + err = PHP_ICONV_ERR_MALFORMED; + goto out; + } + } + + memcpy(tmpbuf, csname, csname_len); + tmpbuf[csname_len] = '\0'; + + if (cd != (iconv_t)(-1)) { + iconv_close(cd); + } + + cd = iconv_open(enc, tmpbuf); + + if (cd == (iconv_t)(-1)) { + if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) { + /* Bad character set, but the user wants us to + * press on. In this case, we'll just insert the + * undecoded encoded word, since there isn't really + * a more sensible behaviour available; the only + * other options are to swallow the encoded word + * entirely or decode it with an arbitrarily chosen + * single byte encoding, both of which seem to have + * a higher WTF factor than leaving it undecoded. + * + * Given this approach, we need to skip ahead to + * the end of the encoded word. */ + int qmarks = 2; + while (qmarks > 0 && str_left > 1) { + if (*(++p1) == '?') { + --qmarks; + } + --str_left; + } + + /* Look ahead to check for the terminating = that + * should be there as well; if it's there, we'll + * also include that. If it's not, there isn't much + * we can do at this point. */ + if (*(p1 + 1) == '=') { + ++p1; + --str_left; + } + + err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl); + if (err != PHP_ICONV_ERR_SUCCESS) { + goto out; + } + + /* Let's go back and see if there are further + * encoded words or bare content, and hope they + * might actually have a valid character set. */ + scan_stat = 12; + break; + } else { +#if ICONV_SUPPORTS_ERRNO + if (errno == EINVAL) { + err = PHP_ICONV_ERR_WRONG_CHARSET; + } else { + err = PHP_ICONV_ERR_CONVERTER; + } +#else + err = PHP_ICONV_ERR_UNKNOWN; +#endif + goto out; + } + } + } + break; + + case 3: /* expecting a encoding scheme specifier */ + switch (*p1) { + case 'b': + case 'B': + enc_scheme = PHP_ICONV_ENC_SCHEME_BASE64; + scan_stat = 4; + break; + + case 'q': + case 'Q': + enc_scheme = PHP_ICONV_ENC_SCHEME_QPRINT; + scan_stat = 4; + break; + + default: + if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) { + err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl); + if (err != PHP_ICONV_ERR_SUCCESS) { + goto out; + } + encoded_word = NULL; + if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) { + scan_stat = 12; + } else { + scan_stat = 0; + } + break; + } else { + err = PHP_ICONV_ERR_MALFORMED; + goto out; + } + } + break; + + case 4: /* expecting a delimiter */ + if (*p1 != '?') { + if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) { + /* pass the entire chunk through the converter */ + err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl); + if (err != PHP_ICONV_ERR_SUCCESS) { + goto out; + } + encoded_word = NULL; + if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) { + scan_stat = 12; + } else { + scan_stat = 0; + } + break; + } else { + err = PHP_ICONV_ERR_MALFORMED; + goto out; + } + } + encoded_text = p1 + 1; + scan_stat = 5; + break; + + case 5: /* expecting an encoded portion */ + if (*p1 == '?') { + encoded_text_len = (size_t)(p1 - encoded_text); + scan_stat = 6; + } + break; + + case 7: /* expecting a "\n" character */ + if (*p1 == '\n') { + scan_stat = 8; + } else { + /* bare CR */ + _php_iconv_appendc(pretval, '\r', cd_pl); + _php_iconv_appendc(pretval, *p1, cd_pl); + scan_stat = 0; + } + break; + + case 8: /* checking whether the following line is part of a + folded header */ + if (*p1 != ' ' && *p1 != '\t') { + --p1; + str_left = 1; /* quit_loop */ + break; + } + if (encoded_word == NULL) { + _php_iconv_appendc(pretval, ' ', cd_pl); + } + spaces = NULL; + scan_stat = 11; + break; + + case 6: /* expecting a End-Of-Chunk character "=" */ + if (*p1 != '=') { + if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) { + /* pass the entire chunk through the converter */ + err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl); + if (err != PHP_ICONV_ERR_SUCCESS) { + goto out; + } + encoded_word = NULL; + if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) { + scan_stat = 12; + } else { + scan_stat = 0; + } + break; + } else { + err = PHP_ICONV_ERR_MALFORMED; + goto out; + } + } + scan_stat = 9; + if (str_left == 1) { + eos = 1; + } else { + break; + } + + case 9: /* choice point, seeing what to do next.*/ + switch (*p1) { + default: + /* Handle non-RFC-compliant formats + * + * RFC2047 requires the character that comes right + * after an encoded word (chunk) to be a whitespace, + * while there are lots of broken implementations that + * generate such malformed headers that don't fulfill + * that requirement. + */ + if (!eos) { + if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) { + /* pass the entire chunk through the converter */ + err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl); + if (err != PHP_ICONV_ERR_SUCCESS) { + goto out; + } + scan_stat = 12; + break; + } + } + /* break is omitted intentionally */ + + case '\r': case '\n': case ' ': case '\t': { + zend_string *decoded_text; + + switch (enc_scheme) { + case PHP_ICONV_ENC_SCHEME_BASE64: + decoded_text = php_base64_decode((unsigned char*)encoded_text, encoded_text_len); + break; + + case PHP_ICONV_ENC_SCHEME_QPRINT: + decoded_text = php_quot_print_decode((unsigned char*)encoded_text, encoded_text_len, 1); + break; + default: + decoded_text = NULL; + break; + } + + if (decoded_text == NULL) { + if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) { + /* pass the entire chunk through the converter */ + err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl); + if (err != PHP_ICONV_ERR_SUCCESS) { + goto out; + } + encoded_word = NULL; + if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) { + scan_stat = 12; + } else { + scan_stat = 0; + } + break; + } else { + err = PHP_ICONV_ERR_UNKNOWN; + goto out; + } + } + + err = _php_iconv_appendl(pretval, ZSTR_VAL(decoded_text), ZSTR_LEN(decoded_text), cd); + zend_string_release(decoded_text); + + if (err != PHP_ICONV_ERR_SUCCESS) { + if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) { + /* pass the entire chunk through the converter */ + err = _php_iconv_appendl(pretval, encoded_word, (size_t)(p1 - encoded_word), cd_pl); + encoded_word = NULL; + if (err != PHP_ICONV_ERR_SUCCESS) { + break; + } + } else { + goto out; + } + } + + if (eos) { /* reached end-of-string. done. */ + scan_stat = 0; + break; + } + + switch (*p1) { + case '\r': /* part of an EOL sequence? */ + scan_stat = 7; + break; + + case '\n': + scan_stat = 8; + break; + + case '=': /* first letter of an encoded chunk */ + scan_stat = 1; + break; + + case ' ': case '\t': /* medial whitespaces */ + spaces = p1; + scan_stat = 11; + break; + + default: /* first letter of a non-encoded word */ + _php_iconv_appendc(pretval, *p1, cd_pl); + scan_stat = 12; + break; + } + } break; + } + break; + + case 10: /* expects a language specifier. dismiss it for now */ + if (*p1 == '?') { + scan_stat = 3; + } + break; + + case 11: /* expecting a chunk of whitespaces */ + switch (*p1) { + case '\r': /* part of an EOL sequence? */ + scan_stat = 7; + break; + + case '\n': + scan_stat = 8; + break; + + case '=': /* first letter of an encoded chunk */ + if (spaces != NULL && encoded_word == NULL) { + _php_iconv_appendl(pretval, spaces, (size_t)(p1 - spaces), cd_pl); + spaces = NULL; + } + encoded_word = p1; + scan_stat = 1; + break; + + case ' ': case '\t': + break; + + default: /* first letter of a non-encoded word */ + if (spaces != NULL) { + _php_iconv_appendl(pretval, spaces, (size_t)(p1 - spaces), cd_pl); + spaces = NULL; + } + _php_iconv_appendc(pretval, *p1, cd_pl); + encoded_word = NULL; + if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) { + scan_stat = 12; + } else { + scan_stat = 0; + } + break; + } + break; + + case 12: /* expecting a non-encoded word */ + switch (*p1) { + case '\r': /* part of an EOL sequence? */ + scan_stat = 7; + break; + + case '\n': + scan_stat = 8; + break; + + case ' ': case '\t': + spaces = p1; + scan_stat = 11; + break; + + case '=': /* first letter of an encoded chunk */ + if (!(mode & PHP_ICONV_MIME_DECODE_STRICT)) { + encoded_word = p1; + scan_stat = 1; + break; + } + /* break is omitted intentionally */ + + default: + _php_iconv_appendc(pretval, *p1, cd_pl); + break; + } + break; + } + } + switch (scan_stat) { + case 0: case 8: case 11: case 12: + break; + default: + if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) { + if (scan_stat == 1) { + _php_iconv_appendc(pretval, '=', cd_pl); + } + err = PHP_ICONV_ERR_SUCCESS; + } else { + err = PHP_ICONV_ERR_MALFORMED; + goto out; + } + } + + if (next_pos != NULL) { + *next_pos = p1; + } + + smart_str_0(pretval); +out: + if (cd != (iconv_t)(-1)) { + iconv_close(cd); + } + if (cd_pl != (iconv_t)(-1)) { + iconv_close(cd_pl); + } + return err; +} +/* }}} */ + +/* {{{ php_iconv_show_error() */ +static void _php_iconv_show_error(php_iconv_err_t err, const char *out_charset, const char *in_charset) +{ + switch (err) { + case PHP_ICONV_ERR_SUCCESS: + break; + + case PHP_ICONV_ERR_CONVERTER: + php_error_docref(NULL, E_NOTICE, "Cannot open converter"); + break; + + case PHP_ICONV_ERR_WRONG_CHARSET: + php_error_docref(NULL, E_NOTICE, "Wrong charset, conversion from `%s' to `%s' is not allowed", + in_charset, out_charset); + break; + + case PHP_ICONV_ERR_ILLEGAL_CHAR: + php_error_docref(NULL, E_NOTICE, "Detected an incomplete multibyte character in input string"); + break; + + case PHP_ICONV_ERR_ILLEGAL_SEQ: + php_error_docref(NULL, E_NOTICE, "Detected an illegal character in input string"); + break; + + case PHP_ICONV_ERR_TOO_BIG: + /* should not happen */ + php_error_docref(NULL, E_WARNING, "Buffer length exceeded"); + break; + + case PHP_ICONV_ERR_MALFORMED: + php_error_docref(NULL, E_WARNING, "Malformed string"); + break; + + default: + /* other error */ + php_error_docref(NULL, E_NOTICE, "Unknown error (%d)", errno); + break; + } +} +/* }}} */ + +/* {{{ proto int iconv_strlen(string str [, string charset]) + Returns the character count of str */ +PHP_FUNCTION(iconv_strlen) +{ + char *charset = get_internal_encoding(); + size_t charset_len = 0; + zend_string *str; + + php_iconv_err_t err; + + size_t retval; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|s", + &str, &charset, &charset_len) == FAILURE) { + RETURN_FALSE; + } + + if (charset_len >= ICONV_CSNMAXLEN) { + php_error_docref(NULL, E_WARNING, "Charset parameter exceeds the maximum allowed length of %d characters", ICONV_CSNMAXLEN); + RETURN_FALSE; + } + + err = _php_iconv_strlen(&retval, ZSTR_VAL(str), ZSTR_LEN(str), charset); + _php_iconv_show_error(err, GENERIC_SUPERSET_NAME, charset); + if (err == PHP_ICONV_ERR_SUCCESS) { + RETVAL_LONG(retval); + } else { + RETVAL_FALSE; + } +} +/* }}} */ + +/* {{{ proto string iconv_substr(string str, int offset, [int length, string charset]) + Returns specified part of a string */ +PHP_FUNCTION(iconv_substr) +{ + char *charset = get_internal_encoding(); + size_t charset_len = 0; + zend_string *str; + zend_long offset, length = 0; + + php_iconv_err_t err; + + smart_str retval = {0}; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sl|ls", + &str, &offset, &length, + &charset, &charset_len) == FAILURE) { + RETURN_FALSE; + } + + if (charset_len >= ICONV_CSNMAXLEN) { + php_error_docref(NULL, E_WARNING, "Charset parameter exceeds the maximum allowed length of %d characters", ICONV_CSNMAXLEN); + RETURN_FALSE; + } + + if (ZEND_NUM_ARGS() < 3) { + length = ZSTR_LEN(str); + } + + err = _php_iconv_substr(&retval, ZSTR_VAL(str), ZSTR_LEN(str), offset, length, charset); + _php_iconv_show_error(err, GENERIC_SUPERSET_NAME, charset); + + if (err == PHP_ICONV_ERR_SUCCESS && ZSTR_LEN(str) >= 0 && retval.s != NULL) { + RETURN_NEW_STR(retval.s); + } + smart_str_free(&retval); + RETURN_FALSE; +} +/* }}} */ + +/* {{{ proto int iconv_strpos(string haystack, string needle [, int offset [, string charset]]) + Finds position of first occurrence of needle within part of haystack beginning with offset */ +PHP_FUNCTION(iconv_strpos) +{ + char *charset = get_internal_encoding(); + size_t charset_len = 0, haystk_len; + zend_string *haystk; + zend_string *ndl; + zend_long offset = 0; + + php_iconv_err_t err; + + size_t retval; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS|ls", + &haystk, &ndl, + &offset, &charset, &charset_len) == FAILURE) { + RETURN_FALSE; + } + + if (charset_len >= ICONV_CSNMAXLEN) { + php_error_docref(NULL, E_WARNING, "Charset parameter exceeds the maximum allowed length of %d characters", ICONV_CSNMAXLEN); + RETURN_FALSE; + } + + if (offset < 0) { + /* Convert negative offset (counted from the end of string) */ + err = _php_iconv_strlen(&haystk_len, ZSTR_VAL(haystk), ZSTR_LEN(haystk), charset); + if (err != PHP_ICONV_ERR_SUCCESS) { + _php_iconv_show_error(err, GENERIC_SUPERSET_NAME, charset); + RETURN_FALSE; + } + offset += haystk_len; + if (offset < 0) { /* If offset before start */ + php_error_docref(NULL, E_WARNING, "Offset not contained in string."); + RETURN_FALSE; + } + } + + if (ZSTR_LEN(ndl) < 1) { + RETURN_FALSE; + } + + err = _php_iconv_strpos(&retval, ZSTR_VAL(haystk), ZSTR_LEN(haystk), ZSTR_VAL(ndl), ZSTR_LEN(ndl), + offset, charset); + _php_iconv_show_error(err, GENERIC_SUPERSET_NAME, charset); + + if (err == PHP_ICONV_ERR_SUCCESS && retval != (size_t)-1) { + RETVAL_LONG((zend_long)retval); + } else { + RETVAL_FALSE; + } +} +/* }}} */ + +/* {{{ proto int iconv_strrpos(string haystack, string needle [, string charset]) + Finds position of last occurrence of needle within part of haystack beginning with offset */ +PHP_FUNCTION(iconv_strrpos) +{ + char *charset = get_internal_encoding(); + size_t charset_len = 0; + zend_string *haystk; + zend_string *ndl; + + php_iconv_err_t err; + + size_t retval; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS|s", + &haystk, &ndl, + &charset, &charset_len) == FAILURE) { + RETURN_FALSE; + } + + if (ZSTR_LEN(ndl) < 1) { + RETURN_FALSE; + } + + if (charset_len >= ICONV_CSNMAXLEN) { + php_error_docref(NULL, E_WARNING, "Charset parameter exceeds the maximum allowed length of %d characters", ICONV_CSNMAXLEN); + RETURN_FALSE; + } + + err = _php_iconv_strpos(&retval, ZSTR_VAL(haystk), ZSTR_LEN(haystk), ZSTR_VAL(ndl), ZSTR_LEN(ndl), + -1, charset); + _php_iconv_show_error(err, GENERIC_SUPERSET_NAME, charset); + + if (err == PHP_ICONV_ERR_SUCCESS && retval != (size_t)-1) { + RETVAL_LONG((zend_long)retval); + } else { + RETVAL_FALSE; + } +} +/* }}} */ + +/* {{{ proto string iconv_mime_encode(string field_name, string field_value [, array preference]) + Composes a mime header field with field_name and field_value in a specified scheme */ +PHP_FUNCTION(iconv_mime_encode) +{ + zend_string *field_name = NULL; + zend_string *field_value = NULL; + zend_string *tmp_str = NULL; + zval *pref = NULL; + smart_str retval = {0}; + php_iconv_err_t err; + + const char *in_charset = get_internal_encoding(); + const char *out_charset = in_charset; + zend_long line_len = 76; + const char *lfchars = "\r\n"; + php_iconv_enc_scheme_t scheme_id = PHP_ICONV_ENC_SCHEME_BASE64; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS|a", + &field_name, &field_value, + &pref) == FAILURE) { + + RETURN_FALSE; + } + + if (pref != NULL) { + zval *pzval; + + if ((pzval = zend_hash_str_find(Z_ARRVAL_P(pref), "scheme", sizeof("scheme") - 1)) != NULL) { + if (Z_TYPE_P(pzval) == IS_STRING && Z_STRLEN_P(pzval) > 0) { + switch (Z_STRVAL_P(pzval)[0]) { + case 'B': case 'b': + scheme_id = PHP_ICONV_ENC_SCHEME_BASE64; + break; + + case 'Q': case 'q': + scheme_id = PHP_ICONV_ENC_SCHEME_QPRINT; + break; + } + } + } + + if ((pzval = zend_hash_str_find(Z_ARRVAL_P(pref), "input-charset", sizeof("input-charset") - 1)) != NULL && Z_TYPE_P(pzval) == IS_STRING) { + if (Z_STRLEN_P(pzval) >= ICONV_CSNMAXLEN) { + php_error_docref(NULL, E_WARNING, "Charset parameter exceeds the maximum allowed length of %d characters", ICONV_CSNMAXLEN); + RETURN_FALSE; + } + + if (Z_STRLEN_P(pzval) > 0) { + in_charset = Z_STRVAL_P(pzval); + } + } + + + if ((pzval = zend_hash_str_find(Z_ARRVAL_P(pref), "output-charset", sizeof("output-charset") - 1)) != NULL && Z_TYPE_P(pzval) == IS_STRING) { + if (Z_STRLEN_P(pzval) >= ICONV_CSNMAXLEN) { + php_error_docref(NULL, E_WARNING, "Charset parameter exceeds the maximum allowed length of %d characters", ICONV_CSNMAXLEN); + RETURN_FALSE; + } + + if (Z_STRLEN_P(pzval) > 0) { + out_charset = Z_STRVAL_P(pzval); + } + } + + if ((pzval = zend_hash_str_find(Z_ARRVAL_P(pref), "line-length", sizeof("line-length") - 1)) != NULL) { + line_len = zval_get_long(pzval); + } + + if ((pzval = zend_hash_str_find(Z_ARRVAL_P(pref), "line-break-chars", sizeof("line-break-chars") - 1)) != NULL) { + if (Z_TYPE_P(pzval) != IS_STRING) { + tmp_str = zval_get_string(pzval); + lfchars = ZSTR_VAL(tmp_str); + } else { + lfchars = Z_STRVAL_P(pzval); + } + } + } + + err = _php_iconv_mime_encode(&retval, ZSTR_VAL(field_name), ZSTR_LEN(field_name), + ZSTR_VAL(field_value), ZSTR_LEN(field_value), line_len, lfchars, scheme_id, + out_charset, in_charset); + _php_iconv_show_error(err, out_charset, in_charset); + + if (err == PHP_ICONV_ERR_SUCCESS) { + if (retval.s != NULL) { + RETVAL_STR(retval.s); + } else { + RETVAL_EMPTY_STRING(); + } + } else { + smart_str_free(&retval); + RETVAL_FALSE; + } + + if (tmp_str) { + zend_string_release(tmp_str); + } +} +/* }}} */ + +/* {{{ proto string iconv_mime_decode(string encoded_string [, int mode, string charset]) + Decodes a mime header field */ +PHP_FUNCTION(iconv_mime_decode) +{ + zend_string *encoded_str; + char *charset = get_internal_encoding(); + size_t charset_len = 0; + zend_long mode = 0; + + smart_str retval = {0}; + + php_iconv_err_t err; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|ls", + &encoded_str, &mode, &charset, &charset_len) == FAILURE) { + + RETURN_FALSE; + } + + if (charset_len >= ICONV_CSNMAXLEN) { + php_error_docref(NULL, E_WARNING, "Charset parameter exceeds the maximum allowed length of %d characters", ICONV_CSNMAXLEN); + RETURN_FALSE; + } + + err = _php_iconv_mime_decode(&retval, ZSTR_VAL(encoded_str), ZSTR_LEN(encoded_str), charset, NULL, (int)mode); + _php_iconv_show_error(err, charset, "???"); + + if (err == PHP_ICONV_ERR_SUCCESS) { + if (retval.s != NULL) { + RETVAL_STR(retval.s); + } else { + RETVAL_EMPTY_STRING(); + } + } else { + smart_str_free(&retval); + RETVAL_FALSE; + } +} +/* }}} */ + +/* {{{ proto array iconv_mime_decode_headers(string headers [, int mode, string charset]) + Decodes multiple mime header fields */ +PHP_FUNCTION(iconv_mime_decode_headers) +{ + zend_string *encoded_str; + char *charset = get_internal_encoding(); + size_t charset_len = 0; + zend_long mode = 0; + char *enc_str_tmp; + size_t enc_str_len_tmp; + + php_iconv_err_t err = PHP_ICONV_ERR_SUCCESS; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|ls", + &encoded_str, &mode, &charset, &charset_len) == FAILURE) { + + RETURN_FALSE; + } + + if (charset_len >= ICONV_CSNMAXLEN) { + php_error_docref(NULL, E_WARNING, "Charset parameter exceeds the maximum allowed length of %d characters", ICONV_CSNMAXLEN); + RETURN_FALSE; + } + + array_init(return_value); + + enc_str_tmp = ZSTR_VAL(encoded_str); + enc_str_len_tmp = ZSTR_LEN(encoded_str); + while (enc_str_len_tmp > 0) { + smart_str decoded_header = {0}; + char *header_name = NULL; + size_t header_name_len = 0; + char *header_value = NULL; + size_t header_value_len = 0; + char *p, *limit; + const char *next_pos; + + if (PHP_ICONV_ERR_SUCCESS != (err = _php_iconv_mime_decode(&decoded_header, enc_str_tmp, enc_str_len_tmp, charset, &next_pos, (int)mode))) { + smart_str_free(&decoded_header); + break; + } + + if (decoded_header.s == NULL) { + break; + } + + limit = ZSTR_VAL(decoded_header.s) + ZSTR_LEN(decoded_header.s); + for (p = ZSTR_VAL(decoded_header.s); p < limit; p++) { + if (*p == ':') { + *p = '\0'; + header_name = ZSTR_VAL(decoded_header.s); + header_name_len = p - ZSTR_VAL(decoded_header.s); + + while (++p < limit) { + if (*p != ' ' && *p != '\t') { + break; + } + } + + header_value = p; + header_value_len = limit - p; + + break; + } + } + + if (header_name != NULL) { + zval *elem; + + if ((elem = zend_hash_str_find(Z_ARRVAL_P(return_value), header_name, header_name_len)) != NULL) { + if (Z_TYPE_P(elem) != IS_ARRAY) { + zval new_elem; + + array_init(&new_elem); + Z_ADDREF_P(elem); + add_next_index_zval(&new_elem, elem); + + elem = zend_hash_str_update(Z_ARRVAL_P(return_value), header_name, header_name_len, &new_elem); + } + add_next_index_stringl(elem, header_value, header_value_len); + } else { + add_assoc_stringl_ex(return_value, header_name, header_name_len, header_value, header_value_len); + } + } + enc_str_len_tmp -= next_pos - enc_str_tmp; + enc_str_tmp = (char *)next_pos; + + smart_str_free(&decoded_header); + } + + if (err != PHP_ICONV_ERR_SUCCESS) { + _php_iconv_show_error(err, charset, "???"); + zval_dtor(return_value); + RETVAL_FALSE; + } +} +/* }}} */ + +/* {{{ proto string iconv(string in_charset, string out_charset, string str) + Returns str converted to the out_charset character set */ +PHP_NAMED_FUNCTION(php_if_iconv) +{ + char *in_charset, *out_charset; + zend_string *in_buffer; + size_t in_charset_len = 0, out_charset_len = 0; + php_iconv_err_t err; + zend_string *out_buffer; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssS", + &in_charset, &in_charset_len, &out_charset, &out_charset_len, &in_buffer) == FAILURE) + return; + + if (in_charset_len >= ICONV_CSNMAXLEN || out_charset_len >= ICONV_CSNMAXLEN) { + php_error_docref(NULL, E_WARNING, "Charset parameter exceeds the maximum allowed length of %d characters", ICONV_CSNMAXLEN); + RETURN_FALSE; + } + + err = php_iconv_string(ZSTR_VAL(in_buffer), (size_t)ZSTR_LEN(in_buffer), &out_buffer, out_charset, in_charset); + _php_iconv_show_error(err, out_charset, in_charset); + if (err == PHP_ICONV_ERR_SUCCESS && out_buffer != NULL) { + RETVAL_STR(out_buffer); + } else { + if (out_buffer != NULL) { + zend_string_free(out_buffer); + } + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto bool iconv_set_encoding(string type, string charset) + Sets internal encoding and output encoding for ob_iconv_handler() */ +PHP_FUNCTION(iconv_set_encoding) +{ + char *type; + zend_string *charset; + size_t type_len, retval; + zend_string *name; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sS", &type, &type_len, &charset) == FAILURE) + return; + + if (ZSTR_LEN(charset) >= ICONV_CSNMAXLEN) { + php_error_docref(NULL, E_WARNING, "Charset parameter exceeds the maximum allowed length of %d characters", ICONV_CSNMAXLEN); + RETURN_FALSE; + } + + if(!strcasecmp("input_encoding", type)) { + name = zend_string_init("iconv.input_encoding", sizeof("iconv.input_encoding") - 1, 0); + } else if(!strcasecmp("output_encoding", type)) { + name = zend_string_init("iconv.output_encoding", sizeof("iconv.output_encoding") - 1, 0); + } else if(!strcasecmp("internal_encoding", type)) { + name = zend_string_init("iconv.internal_encoding", sizeof("iconv.internal_encoding") - 1, 0); + } else { + RETURN_FALSE; + } + + retval = zend_alter_ini_entry(name, charset, PHP_INI_USER, PHP_INI_STAGE_RUNTIME); + zend_string_release(name); + + if (retval == SUCCESS) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto mixed iconv_get_encoding([string type]) + Get internal encoding and output encoding for ob_iconv_handler() */ +PHP_FUNCTION(iconv_get_encoding) +{ + char *type = "all"; + size_t type_len = sizeof("all")-1; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|s", &type, &type_len) == FAILURE) + return; + + if (!strcasecmp("all", type)) { + array_init(return_value); + add_assoc_string(return_value, "input_encoding", get_input_encoding()); + add_assoc_string(return_value, "output_encoding", get_output_encoding()); + add_assoc_string(return_value, "internal_encoding", get_internal_encoding()); + } else if (!strcasecmp("input_encoding", type)) { + RETVAL_STRING(get_input_encoding()); + } else if (!strcasecmp("output_encoding", type)) { + RETVAL_STRING(get_output_encoding()); + } else if (!strcasecmp("internal_encoding", type)) { + RETVAL_STRING(get_internal_encoding()); + } else { + RETURN_FALSE; + } + +} +/* }}} */ + +/* {{{ iconv stream filter */ +typedef struct _php_iconv_stream_filter { + iconv_t cd; + int persistent; + char *to_charset; + size_t to_charset_len; + char *from_charset; + size_t from_charset_len; + char stub[128]; + size_t stub_len; +} php_iconv_stream_filter; +/* }}} iconv stream filter */ + +/* {{{ php_iconv_stream_filter_dtor */ +static void php_iconv_stream_filter_dtor(php_iconv_stream_filter *self) +{ + iconv_close(self->cd); + pefree(self->to_charset, self->persistent); + pefree(self->from_charset, self->persistent); +} +/* }}} */ + +/* {{{ php_iconv_stream_filter_ctor() */ +static php_iconv_err_t php_iconv_stream_filter_ctor(php_iconv_stream_filter *self, + const char *to_charset, size_t to_charset_len, + const char *from_charset, size_t from_charset_len, int persistent) +{ + if (NULL == (self->to_charset = pemalloc(to_charset_len + 1, persistent))) { + return PHP_ICONV_ERR_ALLOC; + } + self->to_charset_len = to_charset_len; + if (NULL == (self->from_charset = pemalloc(from_charset_len + 1, persistent))) { + pefree(self->to_charset, persistent); + return PHP_ICONV_ERR_ALLOC; + } + self->from_charset_len = from_charset_len; + + memcpy(self->to_charset, to_charset, to_charset_len); + self->to_charset[to_charset_len] = '\0'; + memcpy(self->from_charset, from_charset, from_charset_len); + self->from_charset[from_charset_len] = '\0'; + + if ((iconv_t)-1 == (self->cd = iconv_open(self->to_charset, self->from_charset))) { + pefree(self->from_charset, persistent); + pefree(self->to_charset, persistent); + return PHP_ICONV_ERR_UNKNOWN; + } + self->persistent = persistent; + self->stub_len = 0; + return PHP_ICONV_ERR_SUCCESS; +} +/* }}} */ + +/* {{{ php_iconv_stream_filter_append_bucket */ +static int php_iconv_stream_filter_append_bucket( + php_iconv_stream_filter *self, + php_stream *stream, php_stream_filter *filter, + php_stream_bucket_brigade *buckets_out, + const char *ps, size_t buf_len, size_t *consumed, + int persistent) +{ + php_stream_bucket *new_bucket; + char *out_buf = NULL; + size_t out_buf_size; + char *pd, *pt; + size_t ocnt, prev_ocnt, icnt, tcnt; + size_t initial_out_buf_size; + + if (ps == NULL) { + initial_out_buf_size = 64; + icnt = 1; + } else { + initial_out_buf_size = buf_len; + icnt = buf_len; + } + + out_buf_size = ocnt = prev_ocnt = initial_out_buf_size; + if (NULL == (out_buf = pemalloc(out_buf_size, persistent))) { + return FAILURE; + } + + pd = out_buf; + + if (self->stub_len > 0) { + pt = self->stub; + tcnt = self->stub_len; + + while (tcnt > 0) { + if (iconv(self->cd, &pt, &tcnt, &pd, &ocnt) == (size_t)-1) { +#if ICONV_SUPPORTS_ERRNO + switch (errno) { + case EILSEQ: + php_error_docref(NULL, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): invalid multibyte sequence", self->from_charset, self->to_charset); + goto out_failure; + + case EINVAL: + if (ps != NULL) { + if (icnt > 0) { + if (self->stub_len >= sizeof(self->stub)) { + php_error_docref(NULL, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): insufficient buffer", self->from_charset, self->to_charset); + goto out_failure; + } + self->stub[self->stub_len++] = *(ps++); + icnt--; + pt = self->stub; + tcnt = self->stub_len; + } else { + tcnt = 0; + break; + } + } + break; + + case E2BIG: { + char *new_out_buf; + size_t new_out_buf_size; + + new_out_buf_size = out_buf_size << 1; + + if (new_out_buf_size < out_buf_size) { + /* whoa! no bigger buckets are sold anywhere... */ + if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent))) { + goto out_failure; + } + + php_stream_bucket_append(buckets_out, new_bucket); + + out_buf_size = ocnt = initial_out_buf_size; + if (NULL == (out_buf = pemalloc(out_buf_size, persistent))) { + return FAILURE; + } + pd = out_buf; + } else { + if (NULL == (new_out_buf = perealloc(out_buf, new_out_buf_size, persistent))) { + if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent))) { + goto out_failure; + } + + php_stream_bucket_append(buckets_out, new_bucket); + return FAILURE; + } + pd = new_out_buf + (pd - out_buf); + ocnt += (new_out_buf_size - out_buf_size); + out_buf = new_out_buf; + out_buf_size = new_out_buf_size; + } + } break; + + default: + php_error_docref(NULL, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): unknown error", self->from_charset, self->to_charset); + goto out_failure; + } +#else + if (ocnt == prev_ocnt) { + php_error_docref(NULL, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): unknown error", self->from_charset, self->to_charset); + goto out_failure; + } +#endif + } + prev_ocnt = ocnt; + } + memmove(self->stub, pt, tcnt); + self->stub_len = tcnt; + } + + while (icnt > 0) { + if ((ps == NULL ? iconv(self->cd, NULL, NULL, &pd, &ocnt): + iconv(self->cd, (char **)&ps, &icnt, &pd, &ocnt)) == (size_t)-1) { +#if ICONV_SUPPORTS_ERRNO + switch (errno) { + case EILSEQ: + php_error_docref(NULL, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): invalid multibyte sequence", self->from_charset, self->to_charset); + goto out_failure; + + case EINVAL: + if (ps != NULL) { + if (icnt > sizeof(self->stub)) { + php_error_docref(NULL, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): insufficient buffer", self->from_charset, self->to_charset); + goto out_failure; + } + memcpy(self->stub, ps, icnt); + self->stub_len = icnt; + ps += icnt; + icnt = 0; + } else { + php_error_docref(NULL, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): unexpected octet values", self->from_charset, self->to_charset); + goto out_failure; + } + break; + + case E2BIG: { + char *new_out_buf; + size_t new_out_buf_size; + + new_out_buf_size = out_buf_size << 1; + + if (new_out_buf_size < out_buf_size) { + /* whoa! no bigger buckets are sold anywhere... */ + if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent))) { + goto out_failure; + } + + php_stream_bucket_append(buckets_out, new_bucket); + + out_buf_size = ocnt = initial_out_buf_size; + if (NULL == (out_buf = pemalloc(out_buf_size, persistent))) { + return FAILURE; + } + pd = out_buf; + } else { + if (NULL == (new_out_buf = perealloc(out_buf, new_out_buf_size, persistent))) { + if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent))) { + goto out_failure; + } + + php_stream_bucket_append(buckets_out, new_bucket); + return FAILURE; + } + pd = new_out_buf + (pd - out_buf); + ocnt += (new_out_buf_size - out_buf_size); + out_buf = new_out_buf; + out_buf_size = new_out_buf_size; + } + } break; + + default: + php_error_docref(NULL, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): unknown error", self->from_charset, self->to_charset); + goto out_failure; + } +#else + if (ocnt == prev_ocnt) { + php_error_docref(NULL, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): unknown error", self->from_charset, self->to_charset); + goto out_failure; + } +#endif + } else { + if (ps == NULL) { + break; + } + } + prev_ocnt = ocnt; + } + + if (out_buf_size > ocnt) { + if (NULL == (new_bucket = php_stream_bucket_new(stream, out_buf, (out_buf_size - ocnt), 1, persistent))) { + goto out_failure; + } + php_stream_bucket_append(buckets_out, new_bucket); + } else { + pefree(out_buf, persistent); + } + *consumed += buf_len - icnt; + + return SUCCESS; + +out_failure: + pefree(out_buf, persistent); + return FAILURE; +} +/* }}} php_iconv_stream_filter_append_bucket */ + +/* {{{ php_iconv_stream_filter_do_filter */ +static php_stream_filter_status_t php_iconv_stream_filter_do_filter( + php_stream *stream, php_stream_filter *filter, + php_stream_bucket_brigade *buckets_in, + php_stream_bucket_brigade *buckets_out, + size_t *bytes_consumed, int flags) +{ + php_stream_bucket *bucket = NULL; + size_t consumed = 0; + php_iconv_stream_filter *self = (php_iconv_stream_filter *)Z_PTR(filter->abstract); + + while (buckets_in->head != NULL) { + bucket = buckets_in->head; + + php_stream_bucket_unlink(bucket); + + if (php_iconv_stream_filter_append_bucket(self, stream, filter, + buckets_out, bucket->buf, bucket->buflen, &consumed, + php_stream_is_persistent(stream)) != SUCCESS) { + goto out_failure; + } + + php_stream_bucket_delref(bucket); + } + + if (flags != PSFS_FLAG_NORMAL) { + if (php_iconv_stream_filter_append_bucket(self, stream, filter, + buckets_out, NULL, 0, &consumed, + php_stream_is_persistent(stream)) != SUCCESS) { + goto out_failure; + } + } + + if (bytes_consumed != NULL) { + *bytes_consumed = consumed; + } + + return PSFS_PASS_ON; + +out_failure: + if (bucket != NULL) { + php_stream_bucket_delref(bucket); + } + return PSFS_ERR_FATAL; +} +/* }}} */ + +/* {{{ php_iconv_stream_filter_cleanup */ +static void php_iconv_stream_filter_cleanup(php_stream_filter *filter) +{ + php_iconv_stream_filter_dtor((php_iconv_stream_filter *)Z_PTR(filter->abstract)); + pefree(Z_PTR(filter->abstract), ((php_iconv_stream_filter *)Z_PTR(filter->abstract))->persistent); +} +/* }}} */ + +static php_stream_filter_ops php_iconv_stream_filter_ops = { + php_iconv_stream_filter_do_filter, + php_iconv_stream_filter_cleanup, + "convert.iconv.*" +}; + +/* {{{ php_iconv_stream_filter_create */ +static php_stream_filter *php_iconv_stream_filter_factory_create(const char *name, zval *params, int persistent) +{ + php_stream_filter *retval = NULL; + php_iconv_stream_filter *inst; + char *from_charset = NULL, *to_charset = NULL; + size_t from_charset_len, to_charset_len; + + if ((from_charset = strchr(name, '.')) == NULL) { + return NULL; + } + ++from_charset; + if ((from_charset = strchr(from_charset, '.')) == NULL) { + return NULL; + } + ++from_charset; + if ((to_charset = strpbrk(from_charset, "/.")) == NULL) { + return NULL; + } + from_charset_len = to_charset - from_charset; + ++to_charset; + to_charset_len = strlen(to_charset); + + if (from_charset_len >= ICONV_CSNMAXLEN || to_charset_len >= ICONV_CSNMAXLEN) { + return NULL; + } + + if (NULL == (inst = pemalloc(sizeof(php_iconv_stream_filter), persistent))) { + return NULL; + } + + if (php_iconv_stream_filter_ctor(inst, to_charset, to_charset_len, from_charset, from_charset_len, persistent) != PHP_ICONV_ERR_SUCCESS) { + pefree(inst, persistent); + return NULL; + } + + if (NULL == (retval = php_stream_filter_alloc(&php_iconv_stream_filter_ops, inst, persistent))) { + php_iconv_stream_filter_dtor(inst); + pefree(inst, persistent); + } + + return retval; +} +/* }}} */ + +/* {{{ php_iconv_stream_register_factory */ +static php_iconv_err_t php_iconv_stream_filter_register_factory(void) +{ + static php_stream_filter_factory filter_factory = { + php_iconv_stream_filter_factory_create + }; + + if (FAILURE == php_stream_filter_register_factory( + php_iconv_stream_filter_ops.label, + &filter_factory)) { + return PHP_ICONV_ERR_UNKNOWN; + } + return PHP_ICONV_ERR_SUCCESS; +} +/* }}} */ + +/* {{{ php_iconv_stream_unregister_factory */ +static php_iconv_err_t php_iconv_stream_filter_unregister_factory(void) +{ + if (FAILURE == php_stream_filter_unregister_factory( + php_iconv_stream_filter_ops.label)) { + return PHP_ICONV_ERR_UNKNOWN; + } + return PHP_ICONV_ERR_SUCCESS; +} +/* }}} */ +/* }}} */ +#endif + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/ext/iconv/tests/bug76249.phpt b/ext/iconv/tests/bug76249.phpt new file mode 100644 index 0000000000000..4aef7f3a0c573 --- /dev/null +++ b/ext/iconv/tests/bug76249.phpt @@ -0,0 +1,16 @@ +--TEST-- +Bug #76249 (stream filter convert.iconv leads to infinite loop on invalid sequence) +--SKIPIF-- + +--FILE-- + +DONE +--EXPECTF-- +Warning: stream_get_contents(): iconv stream filter ("ucs-2"=>"utf8//IGNORE"): invalid multibyte sequence in %sbug76249.php on line %d +æ…¢DONE diff --git a/ext/imap/php_imap.c b/ext/imap/php_imap.c index 0f6ac9a2d0d54..443cc354c9c11 100644 --- a/ext/imap/php_imap.c +++ b/ext/imap/php_imap.c @@ -562,6 +562,15 @@ static const zend_module_dep imap_deps[] = { }; /* }}} */ + +/* {{{ PHP_INI + */ +PHP_INI_BEGIN() +STD_PHP_INI_BOOLEAN("imap.enable_insecure_rsh", "0", PHP_INI_SYSTEM, OnUpdateBool, enable_rsh, zend_imap_globals, imap_globals) +PHP_INI_END() +/* }}} */ + + /* {{{ imap_module_entry */ zend_module_entry imap_module_entry = { @@ -832,6 +841,8 @@ PHP_MINIT_FUNCTION(imap) { unsigned long sa_all = SA_MESSAGES | SA_RECENT | SA_UNSEEN | SA_UIDNEXT | SA_UIDVALIDITY; + REGISTER_INI_ENTRIES(); + #ifndef PHP_WIN32 mail_link(&unixdriver); /* link in the unix driver */ mail_link(&mhdriver); /* link in the mh driver */ @@ -1049,6 +1060,12 @@ PHP_MINIT_FUNCTION(imap) GC_TEXTS texts */ + if (!IMAPG(enable_rsh)) { + /* disable SSH and RSH, see https://bugs.php.net/bug.php?id=77153 */ + mail_parameters (NIL, SET_RSHTIMEOUT, 0); + mail_parameters (NIL, SET_SSHTIMEOUT, 0); + } + le_imap = zend_register_list_destructors_ex(mail_close_it, NULL, "imap", module_number); return SUCCESS; } @@ -4109,7 +4126,6 @@ PHP_FUNCTION(imap_mail) if (!ZSTR_LEN(message)) { /* this is not really an error, so it is allowed. */ php_error_docref(NULL, E_WARNING, "No message string in mail command"); - message = NULL; } if (_php_imap_mail(ZSTR_VAL(to), ZSTR_VAL(subject), ZSTR_VAL(message), headers?ZSTR_VAL(headers):NULL, cc?ZSTR_VAL(cc):NULL, diff --git a/ext/imap/php_imap.c.orig b/ext/imap/php_imap.c.orig new file mode 100644 index 0000000000000..0f6ac9a2d0d54 --- /dev/null +++ b/ext/imap/php_imap.c.orig @@ -0,0 +1,5105 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2017 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Rex Logan | + | Mark Musone | + | Brian Wang | + | Kaj-Michael Lang | + | Antoni Pamies Olive | + | Rasmus Lerdorf | + | Chuck Hagenbuch | + | Andrew Skalski | + | Hartmut Holzgraefe | + | Jani Taskinen | + | Daniel R. Kalowsky | + | PHP 4.0 updates: Zeev Suraski | + +----------------------------------------------------------------------+ + */ +/* $Id$ */ + +#define IMAP41 + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "php_streams.h" +#include "ext/standard/php_string.h" +#include "ext/standard/info.h" +#include "ext/standard/file.h" +#include "zend_smart_str.h" +#include "ext/pcre/php_pcre.h" + +#ifdef ERROR +#undef ERROR +#endif +#include "php_imap.h" + +#include +#include +#include +#include + +#ifdef PHP_WIN32 +#include +#include +#include "win32/sendmail.h" +MAILSTREAM DEFAULTPROTO; +#endif + +#define CRLF "\015\012" +#define CRLF_LEN sizeof("\015\012") - 1 +#define PHP_EXPUNGE 32768 +#define PHP_IMAP_ADDRESS_SIZE_BUF 10 +#ifndef SENDBUFLEN +#define SENDBUFLEN 16385 +#endif + +#if defined(__GNUC__) && __GNUC__ >= 4 +# define PHP_IMAP_EXPORT __attribute__ ((visibility("default"))) +#else +# define PHP_IMAP_EXPORT +#endif + +static void _php_make_header_object(zval *myzvalue, ENVELOPE *en); +static void _php_imap_add_body(zval *arg, BODY *body); +static zend_string* _php_imap_parse_address(ADDRESS *addresslist, zval *paddress); +static zend_string* _php_rfc822_write_address(ADDRESS *addresslist); + +/* the gets we use */ +static char *php_mail_gets(readfn_t f, void *stream, unsigned long size, GETS_DATA *md); + +/* These function declarations are missing from the IMAP header files... */ +void rfc822_date(char *date); +char *cpystr(const char *str); +char *cpytxt(SIZEDTEXT *dst, char *text, unsigned long size); +#ifndef HAVE_NEW_MIME2TEXT +long utf8_mime2text(SIZEDTEXT *src, SIZEDTEXT *dst); +#else +long utf8_mime2text (SIZEDTEXT *src, SIZEDTEXT *dst, long flags); +#endif +unsigned long find_rightmost_bit(unsigned long *valptr); +void fs_give(void **block); +void *fs_get(size_t size); + +ZEND_DECLARE_MODULE_GLOBALS(imap) +static PHP_GINIT_FUNCTION(imap); + +/* {{{ arginfo */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_open, 0, 0, 3) + ZEND_ARG_INFO(0, mailbox) + ZEND_ARG_INFO(0, user) + ZEND_ARG_INFO(0, password) + ZEND_ARG_INFO(0, options) + ZEND_ARG_INFO(0, n_retries) + ZEND_ARG_INFO(0, params) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_reopen, 0, 0, 2) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, mailbox) + ZEND_ARG_INFO(0, options) + ZEND_ARG_INFO(0, n_retries) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_append, 0, 0, 3) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, folder) + ZEND_ARG_INFO(0, message) + ZEND_ARG_INFO(0, options) + ZEND_ARG_INFO(0, date) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_num_msg, 0, 0, 1) + ZEND_ARG_INFO(0, stream_id) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_ping, 0, 0, 1) + ZEND_ARG_INFO(0, stream_id) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_num_recent, 0, 0, 1) + ZEND_ARG_INFO(0, stream_id) +ZEND_END_ARG_INFO() + +#if defined(HAVE_IMAP2000) || defined(HAVE_IMAP2001) +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_get_quota, 0, 0, 2) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, qroot) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_get_quotaroot, 0, 0, 2) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, mbox) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_set_quota, 0, 0, 3) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, qroot) + ZEND_ARG_INFO(0, mailbox_size) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_setacl, 0, 0, 4) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, mailbox) + ZEND_ARG_INFO(0, id) + ZEND_ARG_INFO(0, rights) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_getacl, 0, 0, 2) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, mailbox) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_expunge, 0, 0, 1) + ZEND_ARG_INFO(0, stream_id) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_gc, 0, 0, 1) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, flags) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_close, 0, 0, 1) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_headers, 0, 0, 1) + ZEND_ARG_INFO(0, stream_id) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_body, 0, 0, 2) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, msg_no) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_mail_copy, 0, 0, 3) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, msglist) + ZEND_ARG_INFO(0, mailbox) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_mail_move, 0, 0, 3) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, sequence) + ZEND_ARG_INFO(0, mailbox) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_createmailbox, 0, 0, 2) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, mailbox) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_renamemailbox, 0, 0, 3) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, old_name) + ZEND_ARG_INFO(0, new_name) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_deletemailbox, 0, 0, 2) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, mailbox) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_list, 0, 0, 3) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, ref) + ZEND_ARG_INFO(0, pattern) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_getmailboxes, 0, 0, 3) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, ref) + ZEND_ARG_INFO(0, pattern) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_listscan, 0, 0, 4) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, ref) + ZEND_ARG_INFO(0, pattern) + ZEND_ARG_INFO(0, content) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_check, 0, 0, 1) + ZEND_ARG_INFO(0, stream_id) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_delete, 0, 0, 2) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, msg_no) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_undelete, 0, 0, 2) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, msg_no) + ZEND_ARG_INFO(0, flags) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_headerinfo, 0, 0, 2) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, msg_no) + ZEND_ARG_INFO(0, from_length) + ZEND_ARG_INFO(0, subject_length) + ZEND_ARG_INFO(0, default_host) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_rfc822_parse_headers, 0, 0, 1) + ZEND_ARG_INFO(0, headers) + ZEND_ARG_INFO(0, default_host) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_lsub, 0, 0, 3) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, ref) + ZEND_ARG_INFO(0, pattern) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_getsubscribed, 0, 0, 3) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, ref) + ZEND_ARG_INFO(0, pattern) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_subscribe, 0, 0, 2) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, mailbox) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_unsubscribe, 0, 0, 2) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, mailbox) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_fetchstructure, 0, 0, 2) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, msg_no) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_fetchbody, 0, 0, 3) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, msg_no) + ZEND_ARG_INFO(0, section) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_savebody, 0, 0, 3) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, file) + ZEND_ARG_INFO(0, msg_no) + ZEND_ARG_INFO(0, section) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_base64, 0, 0, 1) + ZEND_ARG_INFO(0, text) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_qprint, 0, 0, 1) + ZEND_ARG_INFO(0, text) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_8bit, 0, 0, 1) + ZEND_ARG_INFO(0, text) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_binary, 0, 0, 1) + ZEND_ARG_INFO(0, text) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_mailboxmsginfo, 0, 0, 1) + ZEND_ARG_INFO(0, stream_id) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_rfc822_write_address, 0, 0, 3) + ZEND_ARG_INFO(0, mailbox) + ZEND_ARG_INFO(0, host) + ZEND_ARG_INFO(0, personal) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_rfc822_parse_adrlist, 0, 0, 2) + ZEND_ARG_INFO(0, address_string) + ZEND_ARG_INFO(0, default_host) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_utf8, 0, 0, 1) + ZEND_ARG_INFO(0, mime_encoded_text) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_utf7_decode, 0, 0, 1) + ZEND_ARG_INFO(0, buf) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_utf7_encode, 0, 0, 1) + ZEND_ARG_INFO(0, buf) +ZEND_END_ARG_INFO() + +#ifdef HAVE_IMAP_MUTF7 +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_utf8_to_mutf7, 0, 0, 1) + ZEND_ARG_INFO(0, in) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_mutf7_to_utf8, 0, 0, 1) + ZEND_ARG_INFO(0, in) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_setflag_full, 0, 0, 3) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, sequence) + ZEND_ARG_INFO(0, flag) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_clearflag_full, 0, 0, 3) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, sequence) + ZEND_ARG_INFO(0, flag) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_sort, 0, 0, 3) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, criteria) + ZEND_ARG_INFO(0, reverse) + ZEND_ARG_INFO(0, options) + ZEND_ARG_INFO(0, search_criteria) + ZEND_ARG_INFO(0, charset) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_fetchheader, 0, 0, 2) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, msg_no) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_uid, 0, 0, 2) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, msg_no) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_msgno, 0, 0, 2) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, unique_msg_id) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_status, 0, 0, 3) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, mailbox) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_bodystruct, 0, 0, 3) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, msg_no) + ZEND_ARG_INFO(0, section) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_fetch_overview, 0, 0, 2) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, sequence) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_mail_compose, 0, 0, 2) + ZEND_ARG_INFO(0, envelope) + ZEND_ARG_INFO(0, body) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_mail, 0, 0, 3) + ZEND_ARG_INFO(0, to) + ZEND_ARG_INFO(0, subject) + ZEND_ARG_INFO(0, message) + ZEND_ARG_INFO(0, additional_headers) + ZEND_ARG_INFO(0, cc) + ZEND_ARG_INFO(0, bcc) + ZEND_ARG_INFO(0, rpath) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_search, 0, 0, 2) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, criteria) + ZEND_ARG_INFO(0, options) + ZEND_ARG_INFO(0, charset) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imap_alerts, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imap_errors, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imap_last_error, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_mime_header_decode, 0, 0, 1) + ZEND_ARG_INFO(0, str) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_thread, 0, 0, 1) + ZEND_ARG_INFO(0, stream_id) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_imap_timeout, 0, 0, 1) + ZEND_ARG_INFO(0, timeout_type) + ZEND_ARG_INFO(0, timeout) +ZEND_END_ARG_INFO() +/* }}} */ + +/* {{{ imap_functions[] + */ +const zend_function_entry imap_functions[] = { + PHP_FE(imap_open, arginfo_imap_open) + PHP_FE(imap_reopen, arginfo_imap_reopen) + PHP_FE(imap_close, arginfo_imap_close) + PHP_FE(imap_num_msg, arginfo_imap_num_msg) + PHP_FE(imap_num_recent, arginfo_imap_num_recent) + PHP_FE(imap_headers, arginfo_imap_headers) + PHP_FE(imap_headerinfo, arginfo_imap_headerinfo) + PHP_FE(imap_rfc822_parse_headers, arginfo_imap_rfc822_parse_headers) + PHP_FE(imap_rfc822_write_address, arginfo_imap_rfc822_write_address) + PHP_FE(imap_rfc822_parse_adrlist, arginfo_imap_rfc822_parse_adrlist) + PHP_FE(imap_body, arginfo_imap_body) + PHP_FE(imap_bodystruct, arginfo_imap_bodystruct) + PHP_FE(imap_fetchbody, arginfo_imap_fetchbody) + PHP_FE(imap_fetchmime, arginfo_imap_fetchbody) + PHP_FE(imap_savebody, arginfo_imap_savebody) + PHP_FE(imap_fetchheader, arginfo_imap_fetchheader) + PHP_FE(imap_fetchstructure, arginfo_imap_fetchstructure) + PHP_FE(imap_gc, arginfo_imap_gc) + PHP_FE(imap_expunge, arginfo_imap_expunge) + PHP_FE(imap_delete, arginfo_imap_delete) + PHP_FE(imap_undelete, arginfo_imap_undelete) + PHP_FE(imap_check, arginfo_imap_check) + PHP_FE(imap_listscan, arginfo_imap_listscan) + PHP_FE(imap_mail_copy, arginfo_imap_mail_copy) + PHP_FE(imap_mail_move, arginfo_imap_mail_move) + PHP_FE(imap_mail_compose, arginfo_imap_mail_compose) + PHP_FE(imap_createmailbox, arginfo_imap_createmailbox) + PHP_FE(imap_renamemailbox, arginfo_imap_renamemailbox) + PHP_FE(imap_deletemailbox, arginfo_imap_deletemailbox) + PHP_FE(imap_subscribe, arginfo_imap_subscribe) + PHP_FE(imap_unsubscribe, arginfo_imap_unsubscribe) + PHP_FE(imap_append, arginfo_imap_append) + PHP_FE(imap_ping, arginfo_imap_ping) + PHP_FE(imap_base64, arginfo_imap_base64) + PHP_FE(imap_qprint, arginfo_imap_qprint) + PHP_FE(imap_8bit, arginfo_imap_8bit) + PHP_FE(imap_binary, arginfo_imap_binary) + PHP_FE(imap_utf8, arginfo_imap_utf8) + PHP_FE(imap_status, arginfo_imap_status) + PHP_FE(imap_mailboxmsginfo, arginfo_imap_mailboxmsginfo) + PHP_FE(imap_setflag_full, arginfo_imap_setflag_full) + PHP_FE(imap_clearflag_full, arginfo_imap_clearflag_full) + PHP_FE(imap_sort, arginfo_imap_sort) + PHP_FE(imap_uid, arginfo_imap_uid) + PHP_FE(imap_msgno, arginfo_imap_msgno) + PHP_FE(imap_list, arginfo_imap_list) + PHP_FE(imap_lsub, arginfo_imap_lsub) + PHP_FE(imap_fetch_overview, arginfo_imap_fetch_overview) + PHP_FE(imap_alerts, arginfo_imap_alerts) + PHP_FE(imap_errors, arginfo_imap_errors) + PHP_FE(imap_last_error, arginfo_imap_last_error) + PHP_FE(imap_search, arginfo_imap_search) + PHP_FE(imap_utf7_decode, arginfo_imap_utf7_decode) + PHP_FE(imap_utf7_encode, arginfo_imap_utf7_encode) +#ifdef HAVE_IMAP_MUTF7 + PHP_FE(imap_utf8_to_mutf7, arginfo_imap_utf8_to_mutf7) + PHP_FE(imap_mutf7_to_utf8, arginfo_imap_mutf7_to_utf8) +#endif + PHP_FE(imap_mime_header_decode, arginfo_imap_mime_header_decode) + PHP_FE(imap_thread, arginfo_imap_thread) + PHP_FE(imap_timeout, arginfo_imap_timeout) + +#if defined(HAVE_IMAP2000) || defined(HAVE_IMAP2001) + PHP_FE(imap_get_quota, arginfo_imap_get_quota) + PHP_FE(imap_get_quotaroot, arginfo_imap_get_quotaroot) + PHP_FE(imap_set_quota, arginfo_imap_set_quota) + PHP_FE(imap_setacl, arginfo_imap_setacl) + PHP_FE(imap_getacl, arginfo_imap_getacl) +#endif + + PHP_FE(imap_mail, arginfo_imap_mail) + + PHP_FALIAS(imap_header, imap_headerinfo, arginfo_imap_headerinfo) + PHP_FALIAS(imap_listmailbox, imap_list, arginfo_imap_list) + PHP_FALIAS(imap_getmailboxes, imap_list_full, arginfo_imap_getmailboxes) + PHP_FALIAS(imap_scanmailbox, imap_listscan, arginfo_imap_listscan) + PHP_FALIAS(imap_listsubscribed, imap_lsub, arginfo_imap_lsub) + PHP_FALIAS(imap_getsubscribed, imap_lsub_full, arginfo_imap_getsubscribed) + PHP_FALIAS(imap_fetchtext, imap_body, arginfo_imap_body) + PHP_FALIAS(imap_scan, imap_listscan, arginfo_imap_listscan) + PHP_FALIAS(imap_create, imap_createmailbox, arginfo_imap_createmailbox) + PHP_FALIAS(imap_rename, imap_renamemailbox, arginfo_imap_renamemailbox) + PHP_FE_END +}; +/* }}} */ + +/* {{{ imap dependencies */ +static const zend_module_dep imap_deps[] = { + ZEND_MOD_REQUIRED("standard") + ZEND_MOD_END +}; +/* }}} */ + +/* {{{ imap_module_entry + */ +zend_module_entry imap_module_entry = { + STANDARD_MODULE_HEADER_EX, NULL, + imap_deps, + "imap", + imap_functions, + PHP_MINIT(imap), + NULL, + PHP_RINIT(imap), + PHP_RSHUTDOWN(imap), + PHP_MINFO(imap), + PHP_IMAP_VERSION, + PHP_MODULE_GLOBALS(imap), + PHP_GINIT(imap), + NULL, + NULL, + STANDARD_MODULE_PROPERTIES_EX +}; +/* }}} */ + +#ifdef COMPILE_DL_IMAP +ZEND_GET_MODULE(imap) +#endif + +/* True globals, no need for thread safety */ +static int le_imap; + +#define PHP_IMAP_CHECK_MSGNO(msgindex) \ + if ((msgindex < 1) || ((unsigned) msgindex > imap_le_struct->imap_stream->nmsgs)) { \ + php_error_docref(NULL, E_WARNING, "Bad message number"); \ + RETURN_FALSE; \ + } \ + +/* {{{ mail_close_it + */ +static void mail_close_it(zend_resource *rsrc) +{ + pils *imap_le_struct = (pils *)rsrc->ptr; + + /* Do not try to close prototype streams */ + if (!(imap_le_struct->flags & OP_PROTOTYPE)) { + mail_close_full(imap_le_struct->imap_stream, imap_le_struct->flags); + } + + if (IMAPG(imap_user)) { + efree(IMAPG(imap_user)); + IMAPG(imap_user) = 0; + } + if (IMAPG(imap_password)) { + efree(IMAPG(imap_password)); + IMAPG(imap_password) = 0; + } + + efree(imap_le_struct); +} +/* }}} */ + +/* {{{ add_assoc_object + */ +static zval *add_assoc_object(zval *arg, char *key, zval *tmp) +{ + HashTable *symtable; + + if (Z_TYPE_P(arg) == IS_OBJECT) { + symtable = Z_OBJPROP_P(arg); + } else { + symtable = Z_ARRVAL_P(arg); + } + return zend_hash_str_update(symtable, key, strlen(key), tmp); +} +/* }}} */ + +/* {{{ add_next_index_object + */ +static inline zval *add_next_index_object(zval *arg, zval *tmp) +{ + HashTable *symtable; + + if (Z_TYPE_P(arg) == IS_OBJECT) { + symtable = Z_OBJPROP_P(arg); + } else { + symtable = Z_ARRVAL_P(arg); + } + + return zend_hash_next_index_insert(symtable, tmp); +} +/* }}} */ + +/* {{{ mail_newfolderobjectlist + * + * Mail instantiate FOBJECTLIST + * Returns: new FOBJECTLIST list + * Author: CJH + */ +FOBJECTLIST *mail_newfolderobjectlist(void) +{ + return (FOBJECTLIST *) memset(fs_get(sizeof(FOBJECTLIST)), 0, sizeof(FOBJECTLIST)); +} +/* }}} */ + +/* {{{ mail_free_foblist + * + * Mail garbage collect FOBJECTLIST + * Accepts: pointer to FOBJECTLIST pointer + * Author: CJH + */ +void mail_free_foblist(FOBJECTLIST **foblist, FOBJECTLIST **tail) +{ + FOBJECTLIST *cur, *next; + + for (cur=*foblist, next=cur->next; cur; cur=next) { + next = cur->next; + + if(cur->text.data) + fs_give((void **)&(cur->text.data)); + + fs_give((void **)&cur); + } + + *tail = NIL; + *foblist = NIL; +} +/* }}} */ + +/* {{{ mail_newerrorlist + * + * Mail instantiate ERRORLIST + * Returns: new ERRORLIST list + * Author: CJH + */ +ERRORLIST *mail_newerrorlist(void) +{ + return (ERRORLIST *) memset(fs_get(sizeof(ERRORLIST)), 0, sizeof(ERRORLIST)); +} +/* }}} */ + +/* {{{ mail_free_errorlist + * + * Mail garbage collect FOBJECTLIST + * Accepts: pointer to FOBJECTLIST pointer + * Author: CJH + */ +void mail_free_errorlist(ERRORLIST **errlist) +{ + if (*errlist) { /* only free if exists */ + if ((*errlist)->text.data) { + fs_give((void **) &(*errlist)->text.data); + } + mail_free_errorlist (&(*errlist)->next); + fs_give((void **) errlist); /* return string to free storage */ + } +} +/* }}} */ + +/* {{{ mail_newmessagelist + * + * Mail instantiate MESSAGELIST + * Returns: new MESSAGELIST list + * Author: CJH + */ +MESSAGELIST *mail_newmessagelist(void) +{ + return (MESSAGELIST *) memset(fs_get(sizeof(MESSAGELIST)), 0, sizeof(MESSAGELIST)); +} +/* }}} */ + +/* {{{ mail_free_messagelist + * + * Mail garbage collect MESSAGELIST + * Accepts: pointer to MESSAGELIST pointer + * Author: CJH + */ +void mail_free_messagelist(MESSAGELIST **msglist, MESSAGELIST **tail) +{ + MESSAGELIST *cur, *next; + + for (cur = *msglist, next = cur->next; cur; cur = next) { + next = cur->next; + fs_give((void **)&cur); + } + + *tail = NIL; + *msglist = NIL; +} +/* }}} */ + +#if defined(HAVE_IMAP2000) || defined(HAVE_IMAP2001) +/* {{{ mail_getquota + * + * Mail GET_QUOTA callback + * Called via the mail_parameter function in c-client:src/c-client/mail.c + * Author DRK + */ + +void mail_getquota(MAILSTREAM *stream, char *qroot, QUOTALIST *qlist) +{ + zval t_map, *return_value; + + return_value = *IMAPG(quota_return); + +/* put parsing code here */ + for(; qlist; qlist = qlist->next) { + array_init(&t_map); + if (strncmp(qlist->name, "STORAGE", 7) == 0) + { + /* this is to add backwards compatibility */ + add_assoc_long_ex(return_value, "usage", sizeof("usage") - 1, qlist->usage); + add_assoc_long_ex(return_value, "limit", sizeof("limit") - 1, qlist->limit); + } + + add_assoc_long_ex(&t_map, "usage", sizeof("usage") - 1, qlist->usage); + add_assoc_long_ex(&t_map, "limit", sizeof("limit") - 1, qlist->limit); + add_assoc_zval_ex(return_value, qlist->name, strlen(qlist->name), &t_map); + } +} +/* }}} */ + +/* {{{ mail_getquota + * + * Mail GET_ACL callback + * Called via the mail_parameter function in c-client:src/c-client/mail.c + */ +void mail_getacl(MAILSTREAM *stream, char *mailbox, ACLLIST *alist) +{ + + /* walk through the ACLLIST */ + for(; alist; alist = alist->next) { + add_assoc_stringl(IMAPG(imap_acl_list), alist->identifier, alist->rights, strlen(alist->rights)); + } +} +/* }}} */ +#endif + +/* {{{ PHP_GINIT_FUNCTION + */ +static PHP_GINIT_FUNCTION(imap) +{ + imap_globals->imap_user = NIL; + imap_globals->imap_password = NIL; + + imap_globals->imap_alertstack = NIL; + imap_globals->imap_errorstack = NIL; + + imap_globals->imap_folders = NIL; + imap_globals->imap_folders_tail = NIL; + imap_globals->imap_sfolders = NIL; + imap_globals->imap_sfolders_tail = NIL; + imap_globals->imap_messages = NIL; + imap_globals->imap_messages_tail = NIL; + imap_globals->imap_folder_objects = NIL; + imap_globals->imap_folder_objects_tail = NIL; + imap_globals->imap_sfolder_objects = NIL; + imap_globals->imap_sfolder_objects_tail = NIL; + + imap_globals->folderlist_style = FLIST_ARRAY; +#if defined(HAVE_IMAP2000) || defined(HAVE_IMAP2001) + imap_globals->quota_return = NIL; + imap_globals->imap_acl_list = NIL; +#endif + imap_globals->gets_stream = NIL; +} +/* }}} */ + +/* {{{ PHP_MINIT_FUNCTION + */ +PHP_MINIT_FUNCTION(imap) +{ + unsigned long sa_all = SA_MESSAGES | SA_RECENT | SA_UNSEEN | SA_UIDNEXT | SA_UIDVALIDITY; + +#ifndef PHP_WIN32 + mail_link(&unixdriver); /* link in the unix driver */ + mail_link(&mhdriver); /* link in the mh driver */ + /* mail_link(&mxdriver); */ /* According to c-client docs (internal.txt) this shouldn't be used. */ + mail_link(&mmdfdriver); /* link in the mmdf driver */ + mail_link(&newsdriver); /* link in the news driver */ + mail_link(&philedriver); /* link in the phile driver */ +#endif + mail_link(&imapdriver); /* link in the imap driver */ + mail_link(&nntpdriver); /* link in the nntp driver */ + mail_link(&pop3driver); /* link in the pop3 driver */ + mail_link(&mbxdriver); /* link in the mbx driver */ + mail_link(&tenexdriver); /* link in the tenex driver */ + mail_link(&mtxdriver); /* link in the mtx driver */ + mail_link(&dummydriver); /* link in the dummy driver */ + +#ifndef PHP_WIN32 + auth_link(&auth_log); /* link in the log authenticator */ + auth_link(&auth_md5); /* link in the cram-md5 authenticator */ +#if HAVE_IMAP_KRB && defined(HAVE_IMAP_AUTH_GSS) + auth_link(&auth_gss); /* link in the gss authenticator */ +#endif + auth_link(&auth_pla); /* link in the plain authenticator */ +#endif + +#ifdef HAVE_IMAP_SSL + ssl_onceonlyinit (); +#endif + + /* lets allow NIL */ + REGISTER_LONG_CONSTANT("NIL", NIL, CONST_PERSISTENT | CONST_CS); + + /* plug in our gets */ + mail_parameters(NIL, SET_GETS, (void *) NIL); + + /* set default timeout values */ + mail_parameters(NIL, SET_OPENTIMEOUT, (void *) FG(default_socket_timeout)); + mail_parameters(NIL, SET_READTIMEOUT, (void *) FG(default_socket_timeout)); + mail_parameters(NIL, SET_WRITETIMEOUT, (void *) FG(default_socket_timeout)); + mail_parameters(NIL, SET_CLOSETIMEOUT, (void *) FG(default_socket_timeout)); + + /* timeout constants */ + REGISTER_LONG_CONSTANT("IMAP_OPENTIMEOUT", 1, CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("IMAP_READTIMEOUT", 2, CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("IMAP_WRITETIMEOUT", 3, CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("IMAP_CLOSETIMEOUT", 4, CONST_PERSISTENT | CONST_CS); + + /* Open Options */ + + REGISTER_LONG_CONSTANT("OP_DEBUG", OP_DEBUG, CONST_PERSISTENT | CONST_CS); + /* debug protocol negotiations */ + REGISTER_LONG_CONSTANT("OP_READONLY", OP_READONLY, CONST_PERSISTENT | CONST_CS); + /* read-only open */ + REGISTER_LONG_CONSTANT("OP_ANONYMOUS", OP_ANONYMOUS, CONST_PERSISTENT | CONST_CS); + /* anonymous open of newsgroup */ + REGISTER_LONG_CONSTANT("OP_SHORTCACHE", OP_SHORTCACHE, CONST_PERSISTENT | CONST_CS); + /* short (elt-only) caching */ + REGISTER_LONG_CONSTANT("OP_SILENT", OP_SILENT, CONST_PERSISTENT | CONST_CS); + /* don't pass up events (internal use) */ + REGISTER_LONG_CONSTANT("OP_PROTOTYPE", OP_PROTOTYPE, CONST_PERSISTENT | CONST_CS); + /* return driver prototype */ + REGISTER_LONG_CONSTANT("OP_HALFOPEN", OP_HALFOPEN, CONST_PERSISTENT | CONST_CS); + /* half-open (IMAP connect but no select) */ + REGISTER_LONG_CONSTANT("OP_EXPUNGE", OP_EXPUNGE, CONST_PERSISTENT | CONST_CS); + /* silently expunge recycle stream */ + REGISTER_LONG_CONSTANT("OP_SECURE", OP_SECURE, CONST_PERSISTENT | CONST_CS); + /* don't do non-secure authentication */ + + /* + PHP re-assigns CL_EXPUNGE a custom value that can be used as part of the imap_open() bitfield + because it seems like a good idea to be able to indicate that the mailbox should be + automatically expunged during imap_open in case the script get interrupted and it doesn't get + to the imap_close() where this option is normally placed. If the c-client library adds other + options and the value for this one conflicts, simply make PHP_EXPUNGE higher at the top of + this file + */ + REGISTER_LONG_CONSTANT("CL_EXPUNGE", PHP_EXPUNGE, CONST_PERSISTENT | CONST_CS); + /* expunge silently */ + + /* Fetch options */ + + REGISTER_LONG_CONSTANT("FT_UID", FT_UID, CONST_PERSISTENT | CONST_CS); + /* argument is a UID */ + REGISTER_LONG_CONSTANT("FT_PEEK", FT_PEEK, CONST_PERSISTENT | CONST_CS); + /* peek at data */ + REGISTER_LONG_CONSTANT("FT_NOT", FT_NOT, CONST_PERSISTENT | CONST_CS); + /* NOT flag for header lines fetch */ + REGISTER_LONG_CONSTANT("FT_INTERNAL", FT_INTERNAL, CONST_PERSISTENT | CONST_CS); + /* text can be internal strings */ + REGISTER_LONG_CONSTANT("FT_PREFETCHTEXT", FT_PREFETCHTEXT, CONST_PERSISTENT | CONST_CS); + /* IMAP prefetch text when fetching header */ + + /* Flagging options */ + + REGISTER_LONG_CONSTANT("ST_UID", ST_UID, CONST_PERSISTENT | CONST_CS); + /* argument is a UID sequence */ + REGISTER_LONG_CONSTANT("ST_SILENT", ST_SILENT, CONST_PERSISTENT | CONST_CS); + /* don't return results */ + REGISTER_LONG_CONSTANT("ST_SET", ST_SET, CONST_PERSISTENT | CONST_CS); + /* set vs. clear */ + + /* Copy options */ + + REGISTER_LONG_CONSTANT("CP_UID", CP_UID, CONST_PERSISTENT | CONST_CS); + /* argument is a UID sequence */ + REGISTER_LONG_CONSTANT("CP_MOVE", CP_MOVE, CONST_PERSISTENT | CONST_CS); + /* delete from source after copying */ + + /* Search/sort options */ + + REGISTER_LONG_CONSTANT("SE_UID", SE_UID, CONST_PERSISTENT | CONST_CS); + /* return UID */ + REGISTER_LONG_CONSTANT("SE_FREE", SE_FREE, CONST_PERSISTENT | CONST_CS); + /* free search program after finished */ + REGISTER_LONG_CONSTANT("SE_NOPREFETCH", SE_NOPREFETCH, CONST_PERSISTENT | CONST_CS); + /* no search prefetching */ + REGISTER_LONG_CONSTANT("SO_FREE", SO_FREE, CONST_PERSISTENT | CONST_CS); + /* free sort program after finished */ + REGISTER_LONG_CONSTANT("SO_NOSERVER", SO_NOSERVER, CONST_PERSISTENT | CONST_CS); + /* don't do server-based sort */ + + /* Status options */ + + REGISTER_LONG_CONSTANT("SA_MESSAGES", SA_MESSAGES , CONST_PERSISTENT | CONST_CS); + /* number of messages */ + REGISTER_LONG_CONSTANT("SA_RECENT", SA_RECENT, CONST_PERSISTENT | CONST_CS); + /* number of recent messages */ + REGISTER_LONG_CONSTANT("SA_UNSEEN", SA_UNSEEN , CONST_PERSISTENT | CONST_CS); + /* number of unseen messages */ + REGISTER_LONG_CONSTANT("SA_UIDNEXT", SA_UIDNEXT, CONST_PERSISTENT | CONST_CS); + /* next UID to be assigned */ + REGISTER_LONG_CONSTANT("SA_UIDVALIDITY", SA_UIDVALIDITY , CONST_PERSISTENT | CONST_CS); + /* UID validity value */ + REGISTER_LONG_CONSTANT("SA_ALL", sa_all, CONST_PERSISTENT | CONST_CS); + /* get all status information */ + + /* Bits for mm_list() and mm_lsub() */ + + REGISTER_LONG_CONSTANT("LATT_NOINFERIORS", LATT_NOINFERIORS , CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("LATT_NOSELECT", LATT_NOSELECT, CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("LATT_MARKED", LATT_MARKED, CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("LATT_UNMARKED", LATT_UNMARKED , CONST_PERSISTENT | CONST_CS); + +#ifdef LATT_REFERRAL + REGISTER_LONG_CONSTANT("LATT_REFERRAL", LATT_REFERRAL, CONST_PERSISTENT | CONST_CS); +#endif + +#ifdef LATT_HASCHILDREN + REGISTER_LONG_CONSTANT("LATT_HASCHILDREN", LATT_HASCHILDREN, CONST_PERSISTENT | CONST_CS); +#endif + +#ifdef LATT_HASNOCHILDREN + REGISTER_LONG_CONSTANT("LATT_HASNOCHILDREN", LATT_HASNOCHILDREN, CONST_PERSISTENT | CONST_CS); +#endif + + /* Sort functions */ + + REGISTER_LONG_CONSTANT("SORTDATE", SORTDATE , CONST_PERSISTENT | CONST_CS); + /* date */ + REGISTER_LONG_CONSTANT("SORTARRIVAL", SORTARRIVAL , CONST_PERSISTENT | CONST_CS); + /* arrival date */ + REGISTER_LONG_CONSTANT("SORTFROM", SORTFROM , CONST_PERSISTENT | CONST_CS); + /* from */ + REGISTER_LONG_CONSTANT("SORTSUBJECT", SORTSUBJECT , CONST_PERSISTENT | CONST_CS); + /* subject */ + REGISTER_LONG_CONSTANT("SORTTO", SORTTO , CONST_PERSISTENT | CONST_CS); + /* to */ + REGISTER_LONG_CONSTANT("SORTCC", SORTCC , CONST_PERSISTENT | CONST_CS); + /* cc */ + REGISTER_LONG_CONSTANT("SORTSIZE", SORTSIZE , CONST_PERSISTENT | CONST_CS); + /* size */ + + REGISTER_LONG_CONSTANT("TYPETEXT", TYPETEXT , CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("TYPEMULTIPART", TYPEMULTIPART , CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("TYPEMESSAGE", TYPEMESSAGE , CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("TYPEAPPLICATION", TYPEAPPLICATION , CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("TYPEAUDIO", TYPEAUDIO , CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("TYPEIMAGE", TYPEIMAGE , CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("TYPEVIDEO", TYPEVIDEO , CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("TYPEMODEL", TYPEMODEL , CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("TYPEOTHER", TYPEOTHER , CONST_PERSISTENT | CONST_CS); + /* + TYPETEXT unformatted text + TYPEMULTIPART multiple part + TYPEMESSAGE encapsulated message + TYPEAPPLICATION application data + TYPEAUDIO audio + TYPEIMAGE static image (GIF, JPEG, etc.) + TYPEVIDEO video + TYPEMODEL model + TYPEOTHER unknown + */ + + REGISTER_LONG_CONSTANT("ENC7BIT", ENC7BIT , CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("ENC8BIT", ENC8BIT , CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("ENCBINARY", ENCBINARY , CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("ENCBASE64", ENCBASE64, CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("ENCQUOTEDPRINTABLE", ENCQUOTEDPRINTABLE , CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("ENCOTHER", ENCOTHER , CONST_PERSISTENT | CONST_CS); + /* + ENC7BIT 7 bit SMTP semantic data + ENC8BIT 8 bit SMTP semantic data + ENCBINARY 8 bit binary data + ENCBASE64 base-64 encoded data + ENCQUOTEDPRINTABLE human-readable 8-as-7 bit data + ENCOTHER unknown + */ + + REGISTER_LONG_CONSTANT("IMAP_GC_ELT", GC_ELT , CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("IMAP_GC_ENV", GC_ENV , CONST_PERSISTENT | CONST_CS); + REGISTER_LONG_CONSTANT("IMAP_GC_TEXTS", GC_TEXTS , CONST_PERSISTENT | CONST_CS); + /* + GC_ELT message cache elements + GC_ENV ENVELOPEs and BODYs + GC_TEXTS texts + */ + + le_imap = zend_register_list_destructors_ex(mail_close_it, NULL, "imap", module_number); + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_RINIT_FUNCTION + */ +PHP_RINIT_FUNCTION(imap) +{ + IMAPG(imap_errorstack) = NIL; + IMAPG(imap_alertstack) = NIL; + IMAPG(gets_stream) = NIL; + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_RSHUTDOWN_FUNCTION + */ +PHP_RSHUTDOWN_FUNCTION(imap) +{ + ERRORLIST *ecur = NIL; + STRINGLIST *acur = NIL; + + if (IMAPG(imap_errorstack) != NIL) { + /* output any remaining errors at their original error level */ + if (EG(error_reporting) & E_NOTICE) { + ecur = IMAPG(imap_errorstack); + while (ecur != NIL) { + php_error_docref(NULL, E_NOTICE, "%s (errflg=%ld)", ecur->LTEXT, ecur->errflg); + ecur = ecur->next; + } + } + mail_free_errorlist(&IMAPG(imap_errorstack)); + } + + if (IMAPG(imap_alertstack) != NIL) { + /* output any remaining alerts at E_NOTICE level */ + if (EG(error_reporting) & E_NOTICE) { + acur = IMAPG(imap_alertstack); + while (acur != NIL) { + php_error_docref(NULL, E_NOTICE, "%s", acur->LTEXT); + acur = acur->next; + } + } + mail_free_stringlist(&IMAPG(imap_alertstack)); + IMAPG(imap_alertstack) = NIL; + } + return SUCCESS; +} +/* }}} */ + +#if !defined(CCLIENTVERSION) +#if HAVE_IMAP2007e +#define CCLIENTVERSION "2007e" +#elif HAVE_IMAP2007d +#define CCLIENTVERSION "2007d" +#elif HAVE_IMAP2007b +#define CCLIENTVERSION "2007b" +#elif HAVE_IMAP2007a +#define CCLIENTVERSION "2007a" +#elif HAVE_IMAP2004 +#define CCLIENTVERSION "2004" +#elif HAVE_IMAP2001 +#define CCLIENTVERSION "2001" +#elif HAVE_IMAP2000 +#define CCLIENTVERSION "2000" +#elif defined(IMAP41) +#define CCLIENTVERSION "4.1" +#else +#define CCLIENTVERSION "4.0" +#endif +#endif + +/* {{{ PHP_MINFO_FUNCTION + */ +PHP_MINFO_FUNCTION(imap) +{ + php_info_print_table_start(); + php_info_print_table_row(2, "IMAP c-Client Version", CCLIENTVERSION); +#if HAVE_IMAP_SSL + php_info_print_table_row(2, "SSL Support", "enabled"); +#endif +#if HAVE_IMAP_KRB && HAVE_IMAP_AUTH_GSS + php_info_print_table_row(2, "Kerberos Support", "enabled"); +#endif + php_info_print_table_end(); +} +/* }}} */ + +/* {{{ imap_do_open + */ +static void php_imap_do_open(INTERNAL_FUNCTION_PARAMETERS, int persistent) +{ + zend_string *mailbox, *user, *passwd; + zend_long retries = 0, flags = NIL, cl_flags = NIL; + MAILSTREAM *imap_stream; + pils *imap_le_struct; + zval *params = NULL; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "PSS|lla", &mailbox, &user, + &passwd, &flags, &retries, ¶ms) == FAILURE) { + return; + } + + if (argc >= 4) { + if (flags & PHP_EXPUNGE) { + cl_flags = CL_EXPUNGE; + flags ^= PHP_EXPUNGE; + } + if (flags & OP_PROTOTYPE) { + cl_flags |= OP_PROTOTYPE; + } + } + + if (params) { + zval *disabled_auth_method; + + if ((disabled_auth_method = zend_hash_str_find(Z_ARRVAL_P(params), "DISABLE_AUTHENTICATOR", sizeof("DISABLE_AUTHENTICATOR") - 1)) != NULL) { + switch (Z_TYPE_P(disabled_auth_method)) { + case IS_STRING: + if (Z_STRLEN_P(disabled_auth_method) > 1) { + mail_parameters(NIL, DISABLE_AUTHENTICATOR, (void *)Z_STRVAL_P(disabled_auth_method)); + } + break; + case IS_ARRAY: + { + zval *z_auth_method; + int i; + int nelems = zend_hash_num_elements(Z_ARRVAL_P(disabled_auth_method)); + + if (nelems == 0 ) { + break; + } + for (i = 0; i < nelems; i++) { + if ((z_auth_method = zend_hash_index_find(Z_ARRVAL_P(disabled_auth_method), i)) != NULL) { + if (Z_TYPE_P(z_auth_method) == IS_STRING) { + if (Z_STRLEN_P(z_auth_method) > 1) { + mail_parameters(NIL, DISABLE_AUTHENTICATOR, (void *)Z_STRVAL_P(z_auth_method)); + } + } else { + php_error_docref(NULL, E_WARNING, "Invalid argument, expect string or array of strings"); + } + } + } + } + break; + case IS_LONG: + default: + php_error_docref(NULL, E_WARNING, "Invalid argument, expect string or array of strings"); + break; + } + } + } + + if (IMAPG(imap_user)) { + efree(IMAPG(imap_user)); + IMAPG(imap_user) = 0; + } + + if (IMAPG(imap_password)) { + efree(IMAPG(imap_password)); + IMAPG(imap_password) = 0; + } + + /* local filename, need to perform open_basedir check */ + if (ZSTR_VAL(mailbox)[0] != '{' && php_check_open_basedir(ZSTR_VAL(mailbox))) { + RETURN_FALSE; + } + + IMAPG(imap_user) = estrndup(ZSTR_VAL(user), ZSTR_LEN(user)); + IMAPG(imap_password) = estrndup(ZSTR_VAL(passwd), ZSTR_LEN(passwd)); + +#ifdef SET_MAXLOGINTRIALS + if (argc >= 5) { + if (retries < 0) { + php_error_docref(NULL, E_WARNING ,"Retries must be greater or equal to 0"); + } else { + mail_parameters(NIL, SET_MAXLOGINTRIALS, (void *) retries); + } + } +#endif + + imap_stream = mail_open(NIL, ZSTR_VAL(mailbox), flags); + + if (imap_stream == NIL) { + php_error_docref(NULL, E_WARNING, "Couldn't open stream %s", ZSTR_VAL(mailbox)); + efree(IMAPG(imap_user)); IMAPG(imap_user) = 0; + efree(IMAPG(imap_password)); IMAPG(imap_password) = 0; + RETURN_FALSE; + } + + imap_le_struct = emalloc(sizeof(pils)); + imap_le_struct->imap_stream = imap_stream; + imap_le_struct->flags = cl_flags; + + RETURN_RES(zend_register_resource(imap_le_struct, le_imap)); +} +/* }}} */ + +/* {{{ proto resource imap_open(string mailbox, string user, string password [, int options [, int n_retries]]) + Open an IMAP stream to a mailbox */ +PHP_FUNCTION(imap_open) +{ + php_imap_do_open(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); +} +/* }}} */ + +/* {{{ proto bool imap_reopen(resource stream_id, string mailbox [, int options [, int n_retries]]) + Reopen an IMAP stream to a new mailbox */ +PHP_FUNCTION(imap_reopen) +{ + zval *streamind; + zend_string *mailbox; + zend_long options = 0, retries = 0; + pils *imap_le_struct; + long flags=NIL; + long cl_flags=NIL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rS|ll", &streamind, &mailbox, &options, &retries) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (options) { + flags = options; + if (flags & PHP_EXPUNGE) { + cl_flags = CL_EXPUNGE; + flags ^= PHP_EXPUNGE; + } + imap_le_struct->flags = cl_flags; + } +#ifdef SET_MAXLOGINTRIALS + if (retries) { + mail_parameters(NIL, SET_MAXLOGINTRIALS, (void *) retries); + } +#endif + /* local filename, need to perform open_basedir check */ + if (ZSTR_VAL(mailbox)[0] != '{' && php_check_open_basedir(ZSTR_VAL(mailbox))) { + RETURN_FALSE; + } + + imap_le_struct->imap_stream = mail_open(imap_le_struct->imap_stream, ZSTR_VAL(mailbox), flags); + if (imap_le_struct->imap_stream == NIL) { + zend_list_delete(Z_RES_P(streamind)); + php_error_docref(NULL, E_WARNING, "Couldn't re-open stream"); + RETURN_FALSE; + } + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imap_append(resource stream_id, string folder, string message [, string options [, string internal_date]]) + Append a new message to a specified mailbox */ +PHP_FUNCTION(imap_append) +{ + zval *streamind; + zend_string *folder, *message, *internal_date = NULL, *flags = NULL; + pils *imap_le_struct; + STRING st; + zend_string* regex; + pcre_cache_entry *pce; /* Compiled regex */ + zval *subpats = NULL; /* Parts (not used) */ + int global = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rSS|SS", &streamind, &folder, &message, &flags, &internal_date) == FAILURE) { + return; + } + + regex = zend_string_init("/[0-3][0-9]-((Jan)|(Feb)|(Mar)|(Apr)|(May)|(Jun)|(Jul)|(Aug)|(Sep)|(Oct)|(Nov)|(Dec))-[0-9]{4} [0-2][0-9]:[0-5][0-9]:[0-5][0-9] [+-][0-9]{4}/", sizeof("/[0-3][0-9]-((Jan)|(Feb)|(Mar)|(Apr)|(May)|(Jun)|(Jul)|(Aug)|(Sep)|(Oct)|(Nov)|(Dec))-[0-9]{4} [0-2][0-9]:[0-5][0-9]:[0-5][0-9] [+-][0-9]{4}/") - 1, 0); + + if (internal_date) { + /* Make sure the given internal_date string matches the RFC specifiedformat */ + if ((pce = pcre_get_compiled_regex_cache(regex))== NULL) { + zend_string_free(regex); + RETURN_FALSE; + } + + zend_string_free(regex); + php_pcre_match_impl(pce, ZSTR_VAL(internal_date), ZSTR_LEN(internal_date), return_value, subpats, global, + 0, Z_L(0), Z_L(0)); + + if (!Z_LVAL_P(return_value)) { + php_error_docref(NULL, E_WARNING, "internal date not correctly formatted"); + internal_date = NULL; + } + } + + zend_string_free(regex); + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + INIT (&st, mail_string, (void *) ZSTR_VAL(message), ZSTR_LEN(message)); + + if (mail_append_full(imap_le_struct->imap_stream, ZSTR_VAL(folder), (flags ? ZSTR_VAL(flags) : NIL), (internal_date ? ZSTR_VAL(internal_date) : NIL), &st)) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto int imap_num_msg(resource stream_id) + Gives the number of messages in the current mailbox */ +PHP_FUNCTION(imap_num_msg) +{ + zval *streamind; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &streamind) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(imap_le_struct->imap_stream->nmsgs); +} +/* }}} */ + +/* {{{ proto bool imap_ping(resource stream_id) + Check if the IMAP stream is still active */ +PHP_FUNCTION(imap_ping) +{ + zval *streamind; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &streamind) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + RETURN_BOOL(mail_ping(imap_le_struct->imap_stream)); +} +/* }}} */ + +/* {{{ proto int imap_num_recent(resource stream_id) + Gives the number of recent messages in current mailbox */ +PHP_FUNCTION(imap_num_recent) +{ + zval *streamind; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &streamind) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(imap_le_struct->imap_stream->recent); +} +/* }}} */ + +#if defined(HAVE_IMAP2000) || defined(HAVE_IMAP2001) +/* {{{ proto array imap_get_quota(resource stream_id, string qroot) + Returns the quota set to the mailbox account qroot */ +PHP_FUNCTION(imap_get_quota) +{ + zval *streamind; + zend_string *qroot; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rS", &streamind, &qroot) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + array_init(return_value); + IMAPG(quota_return) = &return_value; + + /* set the callback for the GET_QUOTA function */ + mail_parameters(NIL, SET_QUOTA, (void *) mail_getquota); + if (!imap_getquota(imap_le_struct->imap_stream, ZSTR_VAL(qroot))) { + php_error_docref(NULL, E_WARNING, "c-client imap_getquota failed"); + zval_dtor(return_value); + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto array imap_get_quotaroot(resource stream_id, string mbox) + Returns the quota set to the mailbox account mbox */ +PHP_FUNCTION(imap_get_quotaroot) +{ + zval *streamind; + zend_string *mbox; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rS", &streamind, &mbox) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + array_init(return_value); + IMAPG(quota_return) = &return_value; + + /* set the callback for the GET_QUOTAROOT function */ + mail_parameters(NIL, SET_QUOTA, (void *) mail_getquota); + if (!imap_getquotaroot(imap_le_struct->imap_stream, ZSTR_VAL(mbox))) { + php_error_docref(NULL, E_WARNING, "c-client imap_getquotaroot failed"); + zval_dtor(return_value); + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto bool imap_set_quota(resource stream_id, string qroot, int mailbox_size) + Will set the quota for qroot mailbox */ +PHP_FUNCTION(imap_set_quota) +{ + zval *streamind; + zend_string *qroot; + zend_long mailbox_size; + pils *imap_le_struct; + STRINGLIST limits; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rSl", &streamind, &qroot, &mailbox_size) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + limits.text.data = (unsigned char*)"STORAGE"; + limits.text.size = mailbox_size; + limits.next = NIL; + + RETURN_BOOL(imap_setquota(imap_le_struct->imap_stream, ZSTR_VAL(qroot), &limits)); +} +/* }}} */ + +/* {{{ proto bool imap_setacl(resource stream_id, string mailbox, string id, string rights) + Sets the ACL for a given mailbox */ +PHP_FUNCTION(imap_setacl) +{ + zval *streamind; + zend_string *mailbox, *id, *rights; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rSSS", &streamind, &mailbox, &id, &rights) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + RETURN_BOOL(imap_setacl(imap_le_struct->imap_stream, ZSTR_VAL(mailbox), ZSTR_VAL(id), ZSTR_VAL(rights))); +} +/* }}} */ + +/* {{{ proto array imap_getacl(resource stream_id, string mailbox) + Gets the ACL for a given mailbox */ +PHP_FUNCTION(imap_getacl) +{ + zval *streamind; + zend_string *mailbox; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rS", &streamind, &mailbox) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + /* initializing the special array for the return values */ + array_init(return_value); + + IMAPG(imap_acl_list) = return_value; + + /* set the callback for the GET_ACL function */ + mail_parameters(NIL, SET_ACL, (void *) mail_getacl); + if (!imap_getacl(imap_le_struct->imap_stream, ZSTR_VAL(mailbox))) { + php_error(E_WARNING, "c-client imap_getacl failed"); + zval_dtor(return_value); + RETURN_FALSE; + } + + IMAPG(imap_acl_list) = NIL; +} +/* }}} */ +#endif /* HAVE_IMAP2000 || HAVE_IMAP2001 */ + +/* {{{ proto bool imap_expunge(resource stream_id) + Permanently delete all messages marked for deletion */ +PHP_FUNCTION(imap_expunge) +{ + zval *streamind; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &streamind) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + mail_expunge (imap_le_struct->imap_stream); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imap_gc(resource stream_id, int flags) + This function garbage collects (purges) the cache of entries of a specific type. */ +PHP_FUNCTION(imap_gc) +{ + zval *streamind; + pils *imap_le_struct; + zend_long flags; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rl", &streamind, &flags) == FAILURE) { + return; + } + + if (flags && ((flags & ~(GC_TEXTS | GC_ELT | GC_ENV)) != 0)) { + php_error_docref(NULL, E_WARNING, "invalid value for the flags parameter"); + RETURN_FALSE; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + mail_gc(imap_le_struct->imap_stream, flags); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imap_close(resource stream_id [, int options]) + Close an IMAP stream */ +PHP_FUNCTION(imap_close) +{ + zval *streamind; + pils *imap_le_struct=NULL; + zend_long options = 0, flags = NIL; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "r|l", &streamind, &options) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (argc == 2) { + flags = options; + + /* Check that flags is exactly equal to PHP_EXPUNGE or zero */ + if (flags && ((flags & ~PHP_EXPUNGE) != 0)) { + php_error_docref(NULL, E_WARNING, "invalid value for the flags parameter"); + RETURN_FALSE; + } + + /* Do the translation from PHP's internal PHP_EXPUNGE define to c-client's CL_EXPUNGE */ + if (flags & PHP_EXPUNGE) { + flags ^= PHP_EXPUNGE; + flags |= CL_EXPUNGE; + } + imap_le_struct->flags = flags; + } + + zend_list_close(Z_RES_P(streamind)); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto array imap_headers(resource stream_id) + Returns headers for all messages in a mailbox */ +PHP_FUNCTION(imap_headers) +{ + zval *streamind; + pils *imap_le_struct; + unsigned long i; + char *t; + unsigned int msgno; + char tmp[MAILTMPLEN]; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &streamind) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + /* Initialize return array */ + array_init(return_value); + + for (msgno = 1; msgno <= imap_le_struct->imap_stream->nmsgs; msgno++) { + MESSAGECACHE * cache = mail_elt (imap_le_struct->imap_stream, msgno); + mail_fetchstructure(imap_le_struct->imap_stream, msgno, NIL); + tmp[0] = cache->recent ? (cache->seen ? 'R': 'N') : ' '; + tmp[1] = (cache->recent | cache->seen) ? ' ' : 'U'; + tmp[2] = cache->flagged ? 'F' : ' '; + tmp[3] = cache->answered ? 'A' : ' '; + tmp[4] = cache->deleted ? 'D' : ' '; + tmp[5] = cache->draft ? 'X' : ' '; + snprintf(tmp + 6, sizeof(tmp) - 6, "%4ld) ", cache->msgno); + mail_date(tmp+11, cache); + tmp[22] = ' '; + tmp[23] = '\0'; + mail_fetchfrom(tmp+23, imap_le_struct->imap_stream, msgno, (long)20); + strcat(tmp, " "); + if ((i = cache->user_flags)) { + strcat(tmp, "{"); + while (i) { + strlcat(tmp, imap_le_struct->imap_stream->user_flags[find_rightmost_bit (&i)], sizeof(tmp)); + if (i) strlcat(tmp, " ", sizeof(tmp)); + } + strlcat(tmp, "} ", sizeof(tmp)); + } + mail_fetchsubject(t = tmp + strlen(tmp), imap_le_struct->imap_stream, msgno, (long)25); + snprintf(t += strlen(t), sizeof(tmp) - strlen(tmp), " (%ld chars)", cache->rfc822_size); + add_next_index_string(return_value, tmp); + } +} +/* }}} */ + +/* {{{ proto string imap_body(resource stream_id, int msg_no [, int options]) + Read the message body */ +PHP_FUNCTION(imap_body) +{ + zval *streamind; + zend_long msgno, flags = 0; + pils *imap_le_struct; + int msgindex, argc = ZEND_NUM_ARGS(); + char *body; + unsigned long body_len = 0; + + if (zend_parse_parameters(argc, "rl|l", &streamind, &msgno, &flags) == FAILURE) { + return; + } + + if (flags && ((flags & ~(FT_UID|FT_PEEK|FT_INTERNAL)) != 0)) { + php_error_docref(NULL, E_WARNING, "invalid value for the options parameter"); + RETURN_FALSE; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if ((argc == 3) && (flags & FT_UID)) { + /* This should be cached; if it causes an extra RTT to the + IMAP server, then that's the price we pay for making + sure we don't crash. */ + msgindex = mail_msgno(imap_le_struct->imap_stream, msgno); + } else { + msgindex = msgno; + } + if ((msgindex < 1) || ((unsigned) msgindex > imap_le_struct->imap_stream->nmsgs)) { + php_error_docref(NULL, E_WARNING, "Bad message number"); + RETURN_FALSE; + } + + body = mail_fetchtext_full (imap_le_struct->imap_stream, msgno, &body_len, (argc == 3 ? flags : NIL)); + if (body_len == 0) { + RETVAL_EMPTY_STRING(); + } else { + RETVAL_STRINGL(body, body_len); + } +} +/* }}} */ + +/* {{{ proto bool imap_mail_copy(resource stream_id, string msglist, string mailbox [, int options]) + Copy specified message to a mailbox */ +PHP_FUNCTION(imap_mail_copy) +{ + zval *streamind; + zend_long options = 0; + zend_string *seq, *folder; + int argc = ZEND_NUM_ARGS(); + pils *imap_le_struct; + + if (zend_parse_parameters(argc, "rSS|l", &streamind, &seq, &folder, &options) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (mail_copy_full(imap_le_struct->imap_stream, ZSTR_VAL(seq), ZSTR_VAL(folder), (argc == 4 ? options : NIL)) == T) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto bool imap_mail_move(resource stream_id, string sequence, string mailbox [, int options]) + Move specified message to a mailbox */ +PHP_FUNCTION(imap_mail_move) +{ + zval *streamind; + zend_string *seq, *folder; + zend_long options = 0; + pils *imap_le_struct; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "rSS|l", &streamind, &seq, &folder, &options) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (mail_copy_full(imap_le_struct->imap_stream, ZSTR_VAL(seq), ZSTR_VAL(folder), (argc == 4 ? (options | CP_MOVE) : CP_MOVE)) == T) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto bool imap_createmailbox(resource stream_id, string mailbox) + Create a new mailbox */ +PHP_FUNCTION(imap_createmailbox) +{ + zval *streamind; + zend_string *folder; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rS", &streamind, &folder) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (mail_create(imap_le_struct->imap_stream, ZSTR_VAL(folder)) == T) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto bool imap_renamemailbox(resource stream_id, string old_name, string new_name) + Rename a mailbox */ +PHP_FUNCTION(imap_renamemailbox) +{ + zval *streamind; + zend_string *old_mailbox, *new_mailbox; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rSS", &streamind, &old_mailbox, &new_mailbox) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (mail_rename(imap_le_struct->imap_stream, ZSTR_VAL(old_mailbox), ZSTR_VAL(new_mailbox)) == T) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto bool imap_deletemailbox(resource stream_id, string mailbox) + Delete a mailbox */ +PHP_FUNCTION(imap_deletemailbox) +{ + zval *streamind; + zend_string *folder; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rS", &streamind, &folder) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (mail_delete(imap_le_struct->imap_stream, ZSTR_VAL(folder)) == T) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto array imap_list(resource stream_id, string ref, string pattern) + Read the list of mailboxes */ +PHP_FUNCTION(imap_list) +{ + zval *streamind; + zend_string *ref, *pat; + pils *imap_le_struct; + STRINGLIST *cur=NIL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rSS", &streamind, &ref, &pat) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + /* set flag for normal, old mailbox list */ + IMAPG(folderlist_style) = FLIST_ARRAY; + + IMAPG(imap_folders) = IMAPG(imap_folders_tail) = NIL; + mail_list(imap_le_struct->imap_stream, ZSTR_VAL(ref), ZSTR_VAL(pat)); + if (IMAPG(imap_folders) == NIL) { + RETURN_FALSE; + } + + array_init(return_value); + cur=IMAPG(imap_folders); + while (cur != NIL) { + add_next_index_string(return_value, (char*)cur->LTEXT); + cur=cur->next; + } + mail_free_stringlist (&IMAPG(imap_folders)); + IMAPG(imap_folders) = IMAPG(imap_folders_tail) = NIL; +} + +/* }}} */ + +/* {{{ proto array imap_getmailboxes(resource stream_id, string ref, string pattern) + Reads the list of mailboxes and returns a full array of objects containing name, attributes, and delimiter */ +/* Author: CJH */ +PHP_FUNCTION(imap_list_full) +{ + zval *streamind, mboxob; + zend_string *ref, *pat; + pils *imap_le_struct; + FOBJECTLIST *cur=NIL; + char *delim=NIL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rSS", &streamind, &ref, &pat) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + /* set flag for new, improved array of objects mailbox list */ + IMAPG(folderlist_style) = FLIST_OBJECT; + + IMAPG(imap_folder_objects) = IMAPG(imap_folder_objects_tail) = NIL; + mail_list(imap_le_struct->imap_stream, ZSTR_VAL(ref), ZSTR_VAL(pat)); + if (IMAPG(imap_folder_objects) == NIL) { + RETURN_FALSE; + } + + array_init(return_value); + delim = safe_emalloc(2, sizeof(char), 0); + cur=IMAPG(imap_folder_objects); + while (cur != NIL) { + object_init(&mboxob); + add_property_string(&mboxob, "name", (char*)cur->LTEXT); + add_property_long(&mboxob, "attributes", cur->attributes); +#ifdef IMAP41 + delim[0] = (char)cur->delimiter; + delim[1] = 0; + add_property_string(&mboxob, "delimiter", delim); +#else + add_property_string(&mboxob, "delimiter", cur->delimiter); +#endif + add_next_index_object(return_value, &mboxob); + cur=cur->next; + } + mail_free_foblist(&IMAPG(imap_folder_objects), &IMAPG(imap_folder_objects_tail)); + efree(delim); + IMAPG(folderlist_style) = FLIST_ARRAY; /* reset to default */ +} +/* }}} */ + +/* {{{ proto array imap_listscan(resource stream_id, string ref, string pattern, string content) + Read list of mailboxes containing a certain string */ +PHP_FUNCTION(imap_listscan) +{ + zval *streamind; + zend_string *ref, *pat, *content; + pils *imap_le_struct; + STRINGLIST *cur=NIL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rSSS", &streamind, &ref, &pat, &content) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + IMAPG(imap_folders) = NIL; + mail_scan(imap_le_struct->imap_stream, ZSTR_VAL(ref), ZSTR_VAL(pat), ZSTR_VAL(content)); + if (IMAPG(imap_folders) == NIL) { + RETURN_FALSE; + } + + array_init(return_value); + cur=IMAPG(imap_folders); + while (cur != NIL) { + add_next_index_string(return_value, (char*)cur->LTEXT); + cur=cur->next; + } + mail_free_stringlist (&IMAPG(imap_folders)); + IMAPG(imap_folders) = IMAPG(imap_folders_tail) = NIL; +} + +/* }}} */ + +/* {{{ proto object imap_check(resource stream_id) + Get mailbox properties */ +PHP_FUNCTION(imap_check) +{ + zval *streamind; + pils *imap_le_struct; + char date[100]; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &streamind) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (mail_ping (imap_le_struct->imap_stream) == NIL) { + RETURN_FALSE; + } + + if (imap_le_struct->imap_stream && imap_le_struct->imap_stream->mailbox) { + rfc822_date(date); + object_init(return_value); + add_property_string(return_value, "Date", date); + add_property_string(return_value, "Driver", imap_le_struct->imap_stream->dtb->name); + add_property_string(return_value, "Mailbox", imap_le_struct->imap_stream->mailbox); + add_property_long(return_value, "Nmsgs", imap_le_struct->imap_stream->nmsgs); + add_property_long(return_value, "Recent", imap_le_struct->imap_stream->recent); + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto bool imap_delete(resource stream_id, int msg_no [, int options]) + Mark a message for deletion */ +PHP_FUNCTION(imap_delete) +{ + zval *streamind, *sequence; + pils *imap_le_struct; + zend_long flags = 0; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "rz|l", &streamind, &sequence, &flags) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + convert_to_string_ex(sequence); + + mail_setflag_full(imap_le_struct->imap_stream, Z_STRVAL_P(sequence), "\\DELETED", (argc == 3 ? flags : NIL)); + RETVAL_TRUE; +} +/* }}} */ + +/* {{{ proto bool imap_undelete(resource stream_id, int msg_no [, int flags]) + Remove the delete flag from a message */ +PHP_FUNCTION(imap_undelete) +{ + zval *streamind, *sequence; + zend_long flags = 0; + pils *imap_le_struct; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "rz|l", &streamind, &sequence, &flags) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + convert_to_string_ex(sequence); + + mail_clearflag_full(imap_le_struct->imap_stream, Z_STRVAL_P(sequence), "\\DELETED", (argc == 3 ? flags : NIL)); + RETVAL_TRUE; +} +/* }}} */ + +/* {{{ proto object imap_headerinfo(resource stream_id, int msg_no [, int from_length [, int subject_length [, string default_host]]]) + Read the headers of the message */ +PHP_FUNCTION(imap_headerinfo) +{ + zval *streamind; + zend_string *defaulthost = NULL; + int argc = ZEND_NUM_ARGS(); + zend_long msgno, fromlength, subjectlength; + pils *imap_le_struct; + MESSAGECACHE *cache; + ENVELOPE *en; + char dummy[2000], fulladdress[MAILTMPLEN + 1]; + + if (zend_parse_parameters(argc, "rl|llS", &streamind, &msgno, &fromlength, &subjectlength, &defaulthost) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (argc >= 3) { + if (fromlength < 0 || fromlength > MAILTMPLEN) { + php_error_docref(NULL, E_WARNING, "From length has to be between 0 and %d", MAILTMPLEN); + RETURN_FALSE; + } + } else { + fromlength = 0x00; + } + if (argc >= 4) { + if (subjectlength < 0 || subjectlength > MAILTMPLEN) { + php_error_docref(NULL, E_WARNING, "Subject length has to be between 0 and %d", MAILTMPLEN); + RETURN_FALSE; + } + } else { + subjectlength = 0x00; + } + + PHP_IMAP_CHECK_MSGNO(msgno); + + if (mail_fetchstructure(imap_le_struct->imap_stream, msgno, NIL)) { + cache = mail_elt(imap_le_struct->imap_stream, msgno); + } else { + RETURN_FALSE; + } + + en = mail_fetchenvelope(imap_le_struct->imap_stream, msgno); + + /* call a function to parse all the text, so that we can use the + same function to parse text from other sources */ + _php_make_header_object(return_value, en); + + /* now run through properties that are only going to be returned + from a server, not text headers */ + add_property_string(return_value, "Recent", cache->recent ? (cache->seen ? "R": "N") : " "); + add_property_string(return_value, "Unseen", (cache->recent | cache->seen) ? " " : "U"); + add_property_string(return_value, "Flagged", cache->flagged ? "F" : " "); + add_property_string(return_value, "Answered", cache->answered ? "A" : " "); + add_property_string(return_value, "Deleted", cache->deleted ? "D" : " "); + add_property_string(return_value, "Draft", cache->draft ? "X" : " "); + + snprintf(dummy, sizeof(dummy), "%4ld", cache->msgno); + add_property_string(return_value, "Msgno", dummy); + + mail_date(dummy, cache); + add_property_string(return_value, "MailDate", dummy); + + snprintf(dummy, sizeof(dummy), "%ld", cache->rfc822_size); + add_property_string(return_value, "Size", dummy); + + add_property_long(return_value, "udate", mail_longdate(cache)); + + if (en->from && fromlength) { + fulladdress[0] = 0x00; + mail_fetchfrom(fulladdress, imap_le_struct->imap_stream, msgno, fromlength); + add_property_string(return_value, "fetchfrom", fulladdress); + } + if (en->subject && subjectlength) { + fulladdress[0] = 0x00; + mail_fetchsubject(fulladdress, imap_le_struct->imap_stream, msgno, subjectlength); + add_property_string(return_value, "fetchsubject", fulladdress); + } +} +/* }}} */ + +/* {{{ proto object imap_rfc822_parse_headers(string headers [, string default_host]) + Parse a set of mail headers contained in a string, and return an object similar to imap_headerinfo() */ +PHP_FUNCTION(imap_rfc822_parse_headers) +{ + zend_string *headers, *defaulthost = NULL; + ENVELOPE *en; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "S|S", &headers, &defaulthost) == FAILURE) { + return; + } + + if (argc == 2) { + rfc822_parse_msg(&en, NULL, ZSTR_VAL(headers), ZSTR_LEN(headers), NULL, ZSTR_VAL(defaulthost), NIL); + } else { + rfc822_parse_msg(&en, NULL, ZSTR_VAL(headers), ZSTR_LEN(headers), NULL, "UNKNOWN", NIL); + } + + /* call a function to parse all the text, so that we can use the + same function no matter where the headers are from */ + _php_make_header_object(return_value, en); + mail_free_envelope(&en); +} +/* }}} */ + +/* KMLANG */ +/* {{{ proto array imap_lsub(resource stream_id, string ref, string pattern) + Return a list of subscribed mailboxes */ +PHP_FUNCTION(imap_lsub) +{ + zval *streamind; + zend_string *ref, *pat; + pils *imap_le_struct; + STRINGLIST *cur=NIL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rSS", &streamind, &ref, &pat) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + /* set flag for normal, old mailbox list */ + IMAPG(folderlist_style) = FLIST_ARRAY; + + IMAPG(imap_sfolders) = NIL; + mail_lsub(imap_le_struct->imap_stream, ZSTR_VAL(ref), ZSTR_VAL(pat)); + if (IMAPG(imap_sfolders) == NIL) { + RETURN_FALSE; + } + + array_init(return_value); + cur=IMAPG(imap_sfolders); + while (cur != NIL) { + add_next_index_string(return_value, (char*)cur->LTEXT); + cur=cur->next; + } + mail_free_stringlist (&IMAPG(imap_sfolders)); + IMAPG(imap_sfolders) = IMAPG(imap_sfolders_tail) = NIL; +} +/* }}} */ + +/* {{{ proto array imap_getsubscribed(resource stream_id, string ref, string pattern) + Return a list of subscribed mailboxes, in the same format as imap_getmailboxes() */ +/* Author: CJH */ +PHP_FUNCTION(imap_lsub_full) +{ + zval *streamind, mboxob; + zend_string *ref, *pat; + pils *imap_le_struct; + FOBJECTLIST *cur=NIL; + char *delim=NIL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rSS", &streamind, &ref, &pat) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + /* set flag for new, improved array of objects list */ + IMAPG(folderlist_style) = FLIST_OBJECT; + + IMAPG(imap_sfolder_objects) = IMAPG(imap_sfolder_objects_tail) = NIL; + mail_lsub(imap_le_struct->imap_stream, ZSTR_VAL(ref), ZSTR_VAL(pat)); + if (IMAPG(imap_sfolder_objects) == NIL) { + RETURN_FALSE; + } + + array_init(return_value); + delim = safe_emalloc(2, sizeof(char), 0); + cur=IMAPG(imap_sfolder_objects); + while (cur != NIL) { + object_init(&mboxob); + add_property_string(&mboxob, "name", (char*)cur->LTEXT); + add_property_long(&mboxob, "attributes", cur->attributes); +#ifdef IMAP41 + delim[0] = (char)cur->delimiter; + delim[1] = 0; + add_property_string(&mboxob, "delimiter", delim); +#else + add_property_string(&mboxob, "delimiter", cur->delimiter); +#endif + add_next_index_object(return_value, &mboxob); + cur=cur->next; + } + mail_free_foblist (&IMAPG(imap_sfolder_objects), &IMAPG(imap_sfolder_objects_tail)); + efree(delim); + IMAPG(folderlist_style) = FLIST_ARRAY; /* reset to default */ +} +/* }}} */ + +/* {{{ proto bool imap_subscribe(resource stream_id, string mailbox) + Subscribe to a mailbox */ +PHP_FUNCTION(imap_subscribe) +{ + zval *streamind; + zend_string *folder; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rS", &streamind, &folder) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (mail_subscribe(imap_le_struct->imap_stream, ZSTR_VAL(folder)) == T) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto bool imap_unsubscribe(resource stream_id, string mailbox) + Unsubscribe from a mailbox */ +PHP_FUNCTION(imap_unsubscribe) +{ + zval *streamind; + zend_string *folder; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rS", &streamind, &folder) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (mail_unsubscribe(imap_le_struct->imap_stream, ZSTR_VAL(folder)) == T) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto object imap_fetchstructure(resource stream_id, int msg_no [, int options]) + Read the full structure of a message */ +PHP_FUNCTION(imap_fetchstructure) +{ + zval *streamind; + zend_long msgno, flags = 0; + pils *imap_le_struct; + BODY *body; + int msgindex, argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "rl|l", &streamind, &msgno, &flags) == FAILURE) { + return; + } + + if (flags && ((flags & ~FT_UID) != 0)) { + php_error_docref(NULL, E_WARNING, "invalid value for the options parameter"); + RETURN_FALSE; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (msgno < 1) { + RETURN_FALSE; + } + + object_init(return_value); + + if ((argc == 3) && (flags & FT_UID)) { + /* This should be cached; if it causes an extra RTT to the + IMAP server, then that's the price we pay for making + sure we don't crash. */ + msgindex = mail_msgno(imap_le_struct->imap_stream, msgno); + } else { + msgindex = msgno; + } + PHP_IMAP_CHECK_MSGNO(msgindex); + + mail_fetchstructure_full(imap_le_struct->imap_stream, msgno, &body , (argc == 3 ? flags : NIL)); + + if (!body) { + php_error_docref(NULL, E_WARNING, "No body information available"); + RETURN_FALSE; + } + + _php_imap_add_body(return_value, body); +} +/* }}} */ + +/* {{{ proto string imap_fetchbody(resource stream_id, int msg_no, string section [, int options]) + Get a specific body section */ +PHP_FUNCTION(imap_fetchbody) +{ + zval *streamind; + zend_long msgno, flags = 0; + pils *imap_le_struct; + char *body; + zend_string *sec; + unsigned long len; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "rlS|l", &streamind, &msgno, &sec, &flags) == FAILURE) { + return; + } + + if (flags && ((flags & ~(FT_UID|FT_PEEK|FT_INTERNAL)) != 0)) { + php_error_docref(NULL, E_WARNING, "invalid value for the options parameter"); + RETURN_FALSE; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (argc < 4 || !(flags & FT_UID)) { + /* only perform the check if the msgno is a message number and not a UID */ + PHP_IMAP_CHECK_MSGNO(msgno); + } + + body = mail_fetchbody_full(imap_le_struct->imap_stream, msgno, ZSTR_VAL(sec), &len, (argc == 4 ? flags : NIL)); + + if (!body) { + php_error_docref(NULL, E_WARNING, "No body information available"); + RETURN_FALSE; + } + RETVAL_STRINGL(body, len); +} + +/* }}} */ + + +/* {{{ proto string imap_fetchmime(resource stream_id, int msg_no, string section [, int options]) + Get a specific body section's MIME headers */ +PHP_FUNCTION(imap_fetchmime) +{ + zval *streamind; + zend_long msgno, flags = 0; + pils *imap_le_struct; + char *body; + zend_string *sec; + unsigned long len; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "rlS|l", &streamind, &msgno, &sec, &flags) == FAILURE) { + return; + } + + if (flags && ((flags & ~(FT_UID|FT_PEEK|FT_INTERNAL)) != 0)) { + php_error_docref(NULL, E_WARNING, "invalid value for the options parameter"); + RETURN_FALSE; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (argc < 4 || !(flags & FT_UID)) { + /* only perform the check if the msgno is a message number and not a UID */ + PHP_IMAP_CHECK_MSGNO(msgno); + } + + body = mail_fetch_mime(imap_le_struct->imap_stream, msgno, ZSTR_VAL(sec), &len, (argc == 4 ? flags : NIL)); + + if (!body) { + php_error_docref(NULL, E_WARNING, "No body MIME information available"); + RETURN_FALSE; + } + RETVAL_STRINGL(body, len); +} + +/* }}} */ + +/* {{{ proto bool imap_savebody(resource stream_id, string|resource file, int msg_no[, string section = ""[, int options = 0]]) + Save a specific body section to a file */ +PHP_FUNCTION(imap_savebody) +{ + zval *stream, *out; + pils *imap_ptr = NULL; + php_stream *writer = NULL; + zend_string *section = NULL; + int close_stream = 1; + zend_long msgno, flags = 0; + + if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS(), "rzl|Sl", &stream, &out, &msgno, §ion, &flags)) { + RETURN_FALSE; + } + + if ((imap_ptr = (pils *)zend_fetch_resource(Z_RES_P(stream), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (!imap_ptr) { + RETURN_FALSE; + } + + switch (Z_TYPE_P(out)) + { + case IS_LONG: + case IS_RESOURCE: + close_stream = 0; + php_stream_from_zval(writer, out); + break; + + default: + convert_to_string_ex(out); + writer = php_stream_open_wrapper(Z_STRVAL_P(out), "wb", REPORT_ERRORS, NULL); + break; + } + + if (!writer) { + RETURN_FALSE; + } + + IMAPG(gets_stream) = writer; + mail_parameters(NIL, SET_GETS, (void *) php_mail_gets); + mail_fetchbody_full(imap_ptr->imap_stream, msgno, section?ZSTR_VAL(section):"", NULL, flags); + mail_parameters(NIL, SET_GETS, (void *) NULL); + IMAPG(gets_stream) = NULL; + + if (close_stream) { + php_stream_close(writer); + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto string imap_base64(string text) + Decode BASE64 encoded text */ +PHP_FUNCTION(imap_base64) +{ + zend_string *text; + char *decode; + unsigned long newlength; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &text) == FAILURE) { + return; + } + + decode = (char *) rfc822_base64((unsigned char *) ZSTR_VAL(text), ZSTR_LEN(text), &newlength); + + if (decode == NULL) { + RETURN_FALSE; + } + + RETVAL_STRINGL(decode, newlength); + fs_give((void**) &decode); +} +/* }}} */ + +/* {{{ proto string imap_qprint(string text) + Convert a quoted-printable string to an 8-bit string */ +PHP_FUNCTION(imap_qprint) +{ + zend_string *text; + char *decode; + unsigned long newlength; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &text) == FAILURE) { + return; + } + + decode = (char *) rfc822_qprint((unsigned char *) ZSTR_VAL(text), ZSTR_LEN(text), &newlength); + + if (decode == NULL) { + RETURN_FALSE; + } + + RETVAL_STRINGL(decode, newlength); + fs_give((void**) &decode); +} +/* }}} */ + +/* {{{ proto string imap_8bit(string text) + Convert an 8-bit string to a quoted-printable string */ +PHP_FUNCTION(imap_8bit) +{ + zend_string *text; + char *decode; + unsigned long newlength; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &text) == FAILURE) { + return; + } + + decode = (char *) rfc822_8bit((unsigned char *) ZSTR_VAL(text), ZSTR_LEN(text), &newlength); + + if (decode == NULL) { + RETURN_FALSE; + } + + RETVAL_STRINGL(decode, newlength); + fs_give((void**) &decode); +} +/* }}} */ + +/* {{{ proto string imap_binary(string text) + Convert an 8bit string to a base64 string */ +PHP_FUNCTION(imap_binary) +{ + zend_string *text; + char *decode; + unsigned long newlength; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &text) == FAILURE) { + return; + } + + decode = (char*)rfc822_binary(ZSTR_VAL(text), ZSTR_LEN(text), &newlength); + + if (decode == NULL) { + RETURN_FALSE; + } + + RETVAL_STRINGL(decode, newlength); + fs_give((void**) &decode); +} +/* }}} */ + +/* {{{ proto object imap_mailboxmsginfo(resource stream_id) + Returns info about the current mailbox */ +PHP_FUNCTION(imap_mailboxmsginfo) +{ + zval *streamind; + pils *imap_le_struct; + char date[100]; + unsigned int msgno, unreadmsg, deletedmsg, msize; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &streamind) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + /* Initialize return object */ + object_init(return_value); + + unreadmsg = 0; + deletedmsg = 0; + msize = 0; + + for (msgno = 1; msgno <= imap_le_struct->imap_stream->nmsgs; msgno++) { + MESSAGECACHE * cache = mail_elt (imap_le_struct->imap_stream, msgno); + mail_fetchstructure (imap_le_struct->imap_stream, msgno, NIL); + + if (!cache->seen || cache->recent) { + unreadmsg++; + } + + if (cache->deleted) { + deletedmsg++; + } + msize = msize + cache->rfc822_size; + } + add_property_long(return_value, "Unread", unreadmsg); + add_property_long(return_value, "Deleted", deletedmsg); + add_property_long(return_value, "Nmsgs", imap_le_struct->imap_stream->nmsgs); + add_property_long(return_value, "Size", msize); + rfc822_date(date); + add_property_string(return_value, "Date", date); + add_property_string(return_value, "Driver", imap_le_struct->imap_stream->dtb->name); + add_property_string(return_value, "Mailbox", imap_le_struct->imap_stream->mailbox); + add_property_long(return_value, "Recent", imap_le_struct->imap_stream->recent); +} +/* }}} */ + +/* {{{ proto string imap_rfc822_write_address(string mailbox, string host, string personal) + Returns a properly formatted email address given the mailbox, host, and personal info */ +PHP_FUNCTION(imap_rfc822_write_address) +{ + zend_string *mailbox, *host, *personal; + ADDRESS *addr; + zend_string *string; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "SSS", &mailbox, &host, &personal) == FAILURE) { + return; + } + + addr=mail_newaddr(); + + if (mailbox) { + addr->mailbox = cpystr(ZSTR_VAL(mailbox)); + } + + if (host) { + addr->host = cpystr(ZSTR_VAL(host)); + } + + if (personal) { + addr->personal = cpystr(ZSTR_VAL(personal)); + } + + addr->next=NIL; + addr->error=NIL; + addr->adl=NIL; + + string = _php_rfc822_write_address(addr); + if (string) { + RETVAL_STR(string); + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto array imap_rfc822_parse_adrlist(string address_string, string default_host) + Parses an address string */ +PHP_FUNCTION(imap_rfc822_parse_adrlist) +{ + zval tovals; + zend_string *str, *defaulthost; + char *str_copy; + ADDRESS *addresstmp; + ENVELOPE *env; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS", &str, &defaulthost) == FAILURE) { + return; + } + + env = mail_newenvelope(); + + /* rfc822_parse_adrlist() modifies passed string. Copy it. */ + str_copy = estrndup(ZSTR_VAL(str), ZSTR_LEN(str)); + rfc822_parse_adrlist(&env->to, str_copy, ZSTR_VAL(defaulthost)); + efree(str_copy); + + array_init(return_value); + + addresstmp = env->to; + + if (addresstmp) do { + object_init(&tovals); + if (addresstmp->mailbox) { + add_property_string(&tovals, "mailbox", addresstmp->mailbox); + } + if (addresstmp->host) { + add_property_string(&tovals, "host", addresstmp->host); + } + if (addresstmp->personal) { + add_property_string(&tovals, "personal", addresstmp->personal); + } + if (addresstmp->adl) { + add_property_string(&tovals, "adl", addresstmp->adl); + } + add_next_index_object(return_value, &tovals); + } while ((addresstmp = addresstmp->next)); + + mail_free_envelope(&env); +} +/* }}} */ + +/* {{{ proto string imap_utf8(string mime_encoded_text) + Convert a mime-encoded text to UTF-8 */ +PHP_FUNCTION(imap_utf8) +{ + zend_string *str; + SIZEDTEXT src, dest; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &str) == FAILURE) { + return; + } + + src.data = NULL; + src.size = 0; + dest.data = NULL; + dest.size = 0; + + cpytxt(&src, ZSTR_VAL(str), ZSTR_LEN(str)); + +#ifndef HAVE_NEW_MIME2TEXT + utf8_mime2text(&src, &dest); +#else + utf8_mime2text(&src, &dest, U8T_DECOMPOSE); +#endif + RETVAL_STRINGL((char*)dest.data, dest.size); + if (dest.data) { + free(dest.data); + } + if (src.data && src.data != dest.data) { + free(src.data); + } +} +/* }}} */ + +/* {{{ macros for the modified utf7 conversion functions + * + * author: Andrew Skalski + */ + +/* tests `c' and returns true if it is a special character */ +#define SPECIAL(c) ((c) <= 0x1f || (c) >= 0x7f) + +/* validate a modified-base64 character */ +#define B64CHAR(c) (isalnum(c) || (c) == '+' || (c) == ',') + +/* map the low 64 bits of `n' to the modified-base64 characters */ +#define B64(n) ("ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ + "abcdefghijklmnopqrstuvwxyz0123456789+,"[(n) & 0x3f]) + +/* map the modified-base64 character `c' to its 64 bit value */ +#define UNB64(c) ((c) == '+' ? 62 : (c) == ',' ? 63 : (c) >= 'a' ? \ + (c) - 71 : (c) >= 'A' ? (c) - 65 : (c) + 4) +/* }}} */ + +/* {{{ proto string imap_utf7_decode(string buf) + Decode a modified UTF-7 string */ +PHP_FUNCTION(imap_utf7_decode) +{ + /* author: Andrew Skalski */ + zend_string *arg; + const unsigned char *in, *inp, *endp; + unsigned char *out, *outp; + unsigned char c; + int inlen, outlen; + enum { + ST_NORMAL, /* printable text */ + ST_DECODE0, /* encoded text rotation... */ + ST_DECODE1, + ST_DECODE2, + ST_DECODE3 + } state; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &arg) == FAILURE) { + return; + } + + in = (const unsigned char *) ZSTR_VAL(arg); + inlen = ZSTR_LEN(arg); + + /* validate and compute length of output string */ + outlen = 0; + state = ST_NORMAL; + for (endp = (inp = in) + inlen; inp < endp; inp++) { + if (state == ST_NORMAL) { + /* process printable character */ + if (SPECIAL(*inp)) { + php_error_docref(NULL, E_WARNING, "Invalid modified UTF-7 character: `%c'", *inp); + RETURN_FALSE; + } else if (*inp != '&') { + outlen++; + } else if (inp + 1 == endp) { + php_error_docref(NULL, E_WARNING, "Unexpected end of string"); + RETURN_FALSE; + } else if (inp[1] != '-') { + state = ST_DECODE0; + } else { + outlen++; + inp++; + } + } else if (*inp == '-') { + /* return to NORMAL mode */ + if (state == ST_DECODE1) { + php_error_docref(NULL, E_WARNING, "Stray modified base64 character: `%c'", *--inp); + RETURN_FALSE; + } + state = ST_NORMAL; + } else if (!B64CHAR(*inp)) { + php_error_docref(NULL, E_WARNING, "Invalid modified base64 character: `%c'", *inp); + RETURN_FALSE; + } else { + switch (state) { + case ST_DECODE3: + outlen++; + state = ST_DECODE0; + break; + case ST_DECODE2: + case ST_DECODE1: + outlen++; + case ST_DECODE0: + state++; + case ST_NORMAL: + break; + } + } + } + + /* enforce end state */ + if (state != ST_NORMAL) { + php_error_docref(NULL, E_WARNING, "Unexpected end of string"); + RETURN_FALSE; + } + + /* allocate output buffer */ + out = emalloc(outlen + 1); + + /* decode input string */ + outp = out; + state = ST_NORMAL; + for (endp = (inp = in) + inlen; inp < endp; inp++) { + if (state == ST_NORMAL) { + if (*inp == '&' && inp[1] != '-') { + state = ST_DECODE0; + } + else if ((*outp++ = *inp) == '&') { + inp++; + } + } + else if (*inp == '-') { + state = ST_NORMAL; + } + else { + /* decode input character */ + switch (state) { + case ST_DECODE0: + *outp = UNB64(*inp) << 2; + state = ST_DECODE1; + break; + case ST_DECODE1: + outp[1] = UNB64(*inp); + c = outp[1] >> 4; + *outp++ |= c; + *outp <<= 4; + state = ST_DECODE2; + break; + case ST_DECODE2: + outp[1] = UNB64(*inp); + c = outp[1] >> 2; + *outp++ |= c; + *outp <<= 6; + state = ST_DECODE3; + break; + case ST_DECODE3: + *outp++ |= UNB64(*inp); + state = ST_DECODE0; + case ST_NORMAL: + break; + } + } + } + + *outp = 0; + +#if PHP_DEBUG + /* warn if we computed outlen incorrectly */ + if (outp - out != outlen) { + php_error_docref(NULL, E_WARNING, "outp - out [%zd] != outlen [%d]", outp - out, outlen); + } +#endif + + RETURN_STRINGL((char*)out, outlen); +} +/* }}} */ + +/* {{{ proto string imap_utf7_encode(string buf) + Encode a string in modified UTF-7 */ +PHP_FUNCTION(imap_utf7_encode) +{ + /* author: Andrew Skalski */ + zend_string *arg; + const unsigned char *in, *inp, *endp; + zend_string *out; + unsigned char *outp; + unsigned char c; + int inlen, outlen; + enum { + ST_NORMAL, /* printable text */ + ST_ENCODE0, /* encoded text rotation... */ + ST_ENCODE1, + ST_ENCODE2 + } state; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &arg) == FAILURE) { + return; + } + + in = (const unsigned char *) ZSTR_VAL(arg); + inlen = ZSTR_LEN(arg); + + /* compute the length of the result string */ + outlen = 0; + state = ST_NORMAL; + endp = (inp = in) + inlen; + while (inp < endp) { + if (state == ST_NORMAL) { + if (SPECIAL(*inp)) { + state = ST_ENCODE0; + outlen++; + } else if (*inp++ == '&') { + outlen++; + } + outlen++; + } else if (!SPECIAL(*inp)) { + state = ST_NORMAL; + } else { + /* ST_ENCODE0 -> ST_ENCODE1 - two chars + * ST_ENCODE1 -> ST_ENCODE2 - one char + * ST_ENCODE2 -> ST_ENCODE0 - one char + */ + if (state == ST_ENCODE2) { + state = ST_ENCODE0; + } + else if (state++ == ST_ENCODE0) { + outlen++; + } + outlen++; + inp++; + } + } + + /* allocate output buffer */ + out = zend_string_safe_alloc(1, outlen, 0, 0); + + /* encode input string */ + outp = (unsigned char*)ZSTR_VAL(out); + state = ST_NORMAL; + endp = (inp = in) + inlen; + while (inp < endp || state != ST_NORMAL) { + if (state == ST_NORMAL) { + if (SPECIAL(*inp)) { + /* begin encoding */ + *outp++ = '&'; + state = ST_ENCODE0; + } else if ((*outp++ = *inp++) == '&') { + *outp++ = '-'; + } + } else if (inp == endp || !SPECIAL(*inp)) { + /* flush overflow and terminate region */ + if (state != ST_ENCODE0) { + c = B64(*outp); + *outp++ = c; + } + *outp++ = '-'; + state = ST_NORMAL; + } else { + /* encode input character */ + switch (state) { + case ST_ENCODE0: + *outp++ = B64(*inp >> 2); + *outp = *inp++ << 4; + state = ST_ENCODE1; + break; + case ST_ENCODE1: + c = B64(*outp | *inp >> 4); + *outp++ = c; + *outp = *inp++ << 2; + state = ST_ENCODE2; + break; + case ST_ENCODE2: + c = B64(*outp | *inp >> 6); + *outp++ = c; + *outp++ = B64(*inp++); + state = ST_ENCODE0; + case ST_NORMAL: + break; + } + } + } + + *outp = 0; + + RETURN_STR(out); +} +/* }}} */ + +#undef SPECIAL +#undef B64CHAR +#undef B64 +#undef UNB64 + +#ifdef HAVE_IMAP_MUTF7 +static void php_imap_mutf7(INTERNAL_FUNCTION_PARAMETERS, int mode) /* {{{ */ +{ + zend_string *in; + unsigned char *out; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &in) == FAILURE) { + return; + } + + if (ZSTR_LEN(in) < 1) { + RETURN_EMPTY_STRING(); + } + + if (mode == 0) { + out = utf8_to_mutf7((unsigned char *) ZSTR_VAL(in)); + } else { + out = utf8_from_mutf7((unsigned char *) ZSTR_VAL(in)); + } + + if (out == NIL) { + RETURN_FALSE; + } else { + RETURN_STRING((char *)out); + } +} +/* }}} */ + +/* {{{ proto string imap_utf8_to_mutf7(string in) + Encode a UTF-8 string to modified UTF-7 */ +PHP_FUNCTION(imap_utf8_to_mutf7) +{ + php_imap_mutf7(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); +} +/* }}} */ + +/* {{{ proto string imap_mutf7_to_utf8(string in) + Decode a modified UTF-7 string to UTF-8 */ +PHP_FUNCTION(imap_mutf7_to_utf8) +{ + php_imap_mutf7(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); +} +/* }}} */ +#endif + +/* {{{ proto bool imap_setflag_full(resource stream_id, string sequence, string flag [, int options]) + Sets flags on messages */ +PHP_FUNCTION(imap_setflag_full) +{ + zval *streamind; + zend_string *sequence, *flag; + zend_long flags = 0; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rSS|l", &streamind, &sequence, &flag, &flags) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + mail_setflag_full(imap_le_struct->imap_stream, ZSTR_VAL(sequence), ZSTR_VAL(flag), (flags ? flags : NIL)); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool imap_clearflag_full(resource stream_id, string sequence, string flag [, int options]) + Clears flags on messages */ +PHP_FUNCTION(imap_clearflag_full) +{ + zval *streamind; + zend_string *sequence, *flag; + zend_long flags = 0; + pils *imap_le_struct; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "rSS|l", &streamind, &sequence, &flag, &flags) ==FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + mail_clearflag_full(imap_le_struct->imap_stream, ZSTR_VAL(sequence), ZSTR_VAL(flag), (argc == 4 ? flags : NIL)); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto array imap_sort(resource stream_id, int criteria, int reverse [, int options [, string search_criteria [, string charset]]]) + Sort an array of message headers, optionally including only messages that meet specified criteria. */ +PHP_FUNCTION(imap_sort) +{ + zval *streamind; + zend_string *criteria = NULL, *charset = NULL; + zend_long pgm, rev, flags = 0; + pils *imap_le_struct; + unsigned long *slst, *sl; + char *search_criteria; + SORTPGM *mypgm=NIL; + SEARCHPGM *spg=NIL; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "rll|lSS", &streamind, &pgm, &rev, &flags, &criteria, &charset) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (pgm > SORTSIZE) { + php_error_docref(NULL, E_WARNING, "Unrecognized sort criteria"); + RETURN_FALSE; + } + if (argc >= 4) { + if (flags < 0) { + php_error_docref(NULL, E_WARNING, "Search options parameter has to be greater than or equal to 0"); + RETURN_FALSE; + } + } + if (argc >= 5) { + search_criteria = estrndup(ZSTR_VAL(criteria), ZSTR_LEN(criteria)); + spg = mail_criteria(search_criteria); + efree(search_criteria); + } else { + spg = mail_newsearchpgm(); + } + + mypgm = mail_newsortpgm(); + mypgm->reverse = rev; + mypgm->function = (short) pgm; + mypgm->next = NIL; + + slst = mail_sort(imap_le_struct->imap_stream, (argc == 6 ? ZSTR_VAL(charset) : NIL), spg, mypgm, (argc >= 4 ? flags : NIL)); + + if (spg && !(flags & SE_FREE)) { + mail_free_searchpgm(&spg); + } + + array_init(return_value); + if (slst != NIL && slst != 0) { + for (sl = slst; *sl; sl++) { + add_next_index_long(return_value, *sl); + } + fs_give ((void **) &slst); + } +} +/* }}} */ + +/* {{{ proto string imap_fetchheader(resource stream_id, int msg_no [, int options]) + Get the full unfiltered header for a message */ +PHP_FUNCTION(imap_fetchheader) +{ + zval *streamind; + zend_long msgno, flags = 0L; + pils *imap_le_struct; + int msgindex, argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "rl|l", &streamind, &msgno, &flags) == FAILURE) { + return; + } + + if (flags && ((flags & ~(FT_UID|FT_INTERNAL|FT_PREFETCHTEXT)) != 0)) { + php_error_docref(NULL, E_WARNING, "invalid value for the options parameter"); + RETURN_FALSE; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if ((argc == 3) && (flags & FT_UID)) { + /* This should be cached; if it causes an extra RTT to the + IMAP server, then that's the price we pay for making sure + we don't crash. */ + msgindex = mail_msgno(imap_le_struct->imap_stream, msgno); + } else { + msgindex = msgno; + } + + PHP_IMAP_CHECK_MSGNO(msgindex); + + RETVAL_STRING(mail_fetchheader_full(imap_le_struct->imap_stream, msgno, NIL, NIL, (argc == 3 ? flags : NIL))); +} +/* }}} */ + +/* {{{ proto int imap_uid(resource stream_id, int msg_no) + Get the unique message id associated with a standard sequential message number */ +PHP_FUNCTION(imap_uid) +{ + zval *streamind; + zend_long msgno; + pils *imap_le_struct; + int msgindex; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rl", &streamind, &msgno) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + msgindex = msgno; + if ((msgindex < 1) || ((unsigned) msgindex > imap_le_struct->imap_stream->nmsgs)) { + php_error_docref(NULL, E_WARNING, "Bad message number"); + RETURN_FALSE; + } + + RETURN_LONG(mail_uid(imap_le_struct->imap_stream, msgno)); +} +/* }}} */ + +/* {{{ proto int imap_msgno(resource stream_id, int unique_msg_id) + Get the sequence number associated with a UID */ +PHP_FUNCTION(imap_msgno) +{ + zval *streamind; + zend_long msgno; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rl", &streamind, &msgno) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(mail_msgno(imap_le_struct->imap_stream, msgno)); +} +/* }}} */ + +/* {{{ proto object imap_status(resource stream_id, string mailbox, int options) + Get status info from a mailbox */ +PHP_FUNCTION(imap_status) +{ + zval *streamind; + zend_string *mbx; + zend_long flags; + pils *imap_le_struct; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rSl", &streamind, &mbx, &flags) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + object_init(return_value); + + if (mail_status(imap_le_struct->imap_stream, ZSTR_VAL(mbx), flags)) { + add_property_long(return_value, "flags", IMAPG(status_flags)); + if (IMAPG(status_flags) & SA_MESSAGES) { + add_property_long(return_value, "messages", IMAPG(status_messages)); + } + if (IMAPG(status_flags) & SA_RECENT) { + add_property_long(return_value, "recent", IMAPG(status_recent)); + } + if (IMAPG(status_flags) & SA_UNSEEN) { + add_property_long(return_value, "unseen", IMAPG(status_unseen)); + } + if (IMAPG(status_flags) & SA_UIDNEXT) { + add_property_long(return_value, "uidnext", IMAPG(status_uidnext)); + } + if (IMAPG(status_flags) & SA_UIDVALIDITY) { + add_property_long(return_value, "uidvalidity", IMAPG(status_uidvalidity)); + } + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto object imap_bodystruct(resource stream_id, int msg_no, string section) + Read the structure of a specified body section of a specific message */ +PHP_FUNCTION(imap_bodystruct) +{ + zval *streamind; + zend_long msg; + zend_string *section; + pils *imap_le_struct; + zval parametres, param, dparametres, dparam; + PARAMETER *par, *dpar; + BODY *body; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlS", &streamind, &msg, §ion) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + if (!msg || msg < 1 || (unsigned) msg > imap_le_struct->imap_stream->nmsgs) { + php_error_docref(NULL, E_WARNING, "Bad message number"); + RETURN_FALSE; + } + + object_init(return_value); + + body=mail_body(imap_le_struct->imap_stream, msg, (unsigned char*)ZSTR_VAL(section)); + if (body == NULL) { + zval_dtor(return_value); + RETURN_FALSE; + } + if (body->type <= TYPEMAX) { + add_property_long(return_value, "type", body->type); + } + if (body->encoding <= ENCMAX) { + add_property_long(return_value, "encoding", body->encoding); + } + + if (body->subtype) { + add_property_long(return_value, "ifsubtype", 1); + add_property_string(return_value, "subtype", body->subtype); + } else { + add_property_long(return_value, "ifsubtype", 0); + } + + if (body->description) { + add_property_long(return_value, "ifdescription", 1); + add_property_string(return_value, "description", body->description); + } else { + add_property_long(return_value, "ifdescription", 0); + } + if (body->id) { + add_property_long(return_value, "ifid", 1); + add_property_string(return_value, "id", body->id); + } else { + add_property_long(return_value, "ifid", 0); + } + + if (body->size.lines) { + add_property_long(return_value, "lines", body->size.lines); + } + if (body->size.bytes) { + add_property_long(return_value, "bytes", body->size.bytes); + } +#ifdef IMAP41 + if (body->disposition.type) { + add_property_long(return_value, "ifdisposition", 1); + add_property_string(return_value, "disposition", body->disposition.type); + } else { + add_property_long(return_value, "ifdisposition", 0); + } + + if (body->disposition.parameter) { + dpar = body->disposition.parameter; + add_property_long(return_value, "ifdparameters", 1); + array_init(&dparametres); + do { + object_init(&dparam); + add_property_string(&dparam, "attribute", dpar->attribute); + add_property_string(&dparam, "value", dpar->value); + add_next_index_object(&dparametres, &dparam); + } while ((dpar = dpar->next)); + add_assoc_object(return_value, "dparameters", &dparametres); + } else { + add_property_long(return_value, "ifdparameters", 0); + } +#endif + + if ((par = body->parameter)) { + add_property_long(return_value, "ifparameters", 1); + + array_init(¶metres); + do { + object_init(¶m); + if (par->attribute) { + add_property_string(¶m, "attribute", par->attribute); + } + if (par->value) { + add_property_string(¶m, "value", par->value); + } + + add_next_index_object(¶metres, ¶m); + } while ((par = par->next)); + } else { + object_init(¶metres); + add_property_long(return_value, "ifparameters", 0); + } + add_assoc_object(return_value, "parameters", ¶metres); +} + +/* }}} */ + +/* {{{ proto array imap_fetch_overview(resource stream_id, string sequence [, int options]) + Read an overview of the information in the headers of the given message sequence */ +PHP_FUNCTION(imap_fetch_overview) +{ + zval *streamind; + zend_string *sequence; + pils *imap_le_struct; + zval myoverview; + zend_string *address; + zend_long status, flags = 0L; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "rS|l", &streamind, &sequence, &flags) == FAILURE) { + return; + } + + if (flags && ((flags & ~FT_UID) != 0)) { + php_error_docref(NULL, E_WARNING, "invalid value for the options parameter"); + RETURN_FALSE; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + array_init(return_value); + + status = (flags & FT_UID) + ? mail_uid_sequence(imap_le_struct->imap_stream, (unsigned char*)ZSTR_VAL(sequence)) + : mail_sequence(imap_le_struct->imap_stream, (unsigned char*)ZSTR_VAL(sequence)); + + if (status) { + MESSAGECACHE *elt; + ENVELOPE *env; + unsigned long i; + + for (i = 1; i <= imap_le_struct->imap_stream->nmsgs; i++) { + if (((elt = mail_elt (imap_le_struct->imap_stream, i))->sequence) && + (env = mail_fetch_structure (imap_le_struct->imap_stream, i, NIL, NIL))) { + object_init(&myoverview); + if (env->subject) { + add_property_string(&myoverview, "subject", env->subject); + } + if (env->from) { + env->from->next=NULL; + address =_php_rfc822_write_address(env->from); + if (address) { + add_property_str(&myoverview, "from", address); + } + } + if (env->to) { + env->to->next = NULL; + address = _php_rfc822_write_address(env->to); + if (address) { + add_property_str(&myoverview, "to", address); + } + } + if (env->date) { + add_property_string(&myoverview, "date", (char*)env->date); + } + if (env->message_id) { + add_property_string(&myoverview, "message_id", env->message_id); + } + if (env->references) { + add_property_string(&myoverview, "references", env->references); + } + if (env->in_reply_to) { + add_property_string(&myoverview, "in_reply_to", env->in_reply_to); + } + add_property_long(&myoverview, "size", elt->rfc822_size); + add_property_long(&myoverview, "uid", mail_uid(imap_le_struct->imap_stream, i)); + add_property_long(&myoverview, "msgno", i); + add_property_long(&myoverview, "recent", elt->recent); + add_property_long(&myoverview, "flagged", elt->flagged); + add_property_long(&myoverview, "answered", elt->answered); + add_property_long(&myoverview, "deleted", elt->deleted); + add_property_long(&myoverview, "seen", elt->seen); + add_property_long(&myoverview, "draft", elt->draft); + add_property_long(&myoverview, "udate", mail_longdate(elt)); + add_next_index_object(return_value, &myoverview); + } + } + } +} +/* }}} */ + +/* {{{ proto string imap_mail_compose(array envelope, array body) + Create a MIME message based on given envelope and body sections */ +PHP_FUNCTION(imap_mail_compose) +{ + zval *envelope, *body; + zend_string *key; + zval *data, *pvalue, *disp_data, *env_data; + char *cookie = NIL; + ENVELOPE *env; + BODY *bod=NULL, *topbod=NULL; + PART *mypart=NULL, *part; + PARAMETER *param, *disp_param = NULL, *custom_headers_param = NULL, *tmp_param = NULL; + char *tmp=NULL, *mystring=NULL, *t=NULL, *tempstring=NULL, *str_copy = NULL; + int toppart = 0; + int first; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "aa", &envelope, &body) == FAILURE) { + return; + } + +#define PHP_RFC822_PARSE_ADRLIST(target, value) \ + str_copy = estrndup(Z_STRVAL_P(value), Z_STRLEN_P(value)); \ + rfc822_parse_adrlist(target, str_copy, "NO HOST"); \ + efree(str_copy); + + env = mail_newenvelope(); + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "remail", sizeof("remail") - 1)) != NULL) { + convert_to_string_ex(pvalue); + env->remail = cpystr(Z_STRVAL_P(pvalue)); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "return_path", sizeof("return_path") - 1)) != NULL) { + convert_to_string_ex(pvalue); + PHP_RFC822_PARSE_ADRLIST(&env->return_path, pvalue); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "date", sizeof("date") - 1)) != NULL) { + convert_to_string_ex(pvalue); + env->date = (unsigned char*)cpystr(Z_STRVAL_P(pvalue)); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "from", sizeof("from") - 1)) != NULL) { + convert_to_string_ex(pvalue); + PHP_RFC822_PARSE_ADRLIST(&env->from, pvalue); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "reply_to", sizeof("reply_to") - 1)) != NULL) { + convert_to_string_ex(pvalue); + PHP_RFC822_PARSE_ADRLIST(&env->reply_to, pvalue); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "in_reply_to", sizeof("in_reply_to") - 1)) != NULL) { + convert_to_string_ex(pvalue); + env->in_reply_to = cpystr(Z_STRVAL_P(pvalue)); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "subject", sizeof("subject") - 1)) != NULL) { + convert_to_string_ex(pvalue); + env->subject = cpystr(Z_STRVAL_P(pvalue)); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "to", sizeof("to") - 1)) != NULL) { + convert_to_string_ex(pvalue); + PHP_RFC822_PARSE_ADRLIST(&env->to, pvalue); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "cc", sizeof("cc") - 1)) != NULL) { + convert_to_string_ex(pvalue); + PHP_RFC822_PARSE_ADRLIST(&env->cc, pvalue); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "bcc", sizeof("bcc") - 1)) != NULL) { + convert_to_string_ex(pvalue); + PHP_RFC822_PARSE_ADRLIST(&env->bcc, pvalue); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "message_id", sizeof("message_id") - 1)) != NULL) { + convert_to_string_ex(pvalue); + env->message_id=cpystr(Z_STRVAL_P(pvalue)); + } + + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(envelope), "custom_headers", sizeof("custom_headers") - 1)) != NULL) { + if (Z_TYPE_P(pvalue) == IS_ARRAY) { + custom_headers_param = tmp_param = NULL; + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pvalue), env_data) { + custom_headers_param = mail_newbody_parameter(); + convert_to_string_ex(env_data); + custom_headers_param->value = (char *) fs_get(Z_STRLEN_P(env_data) + 1); + custom_headers_param->attribute = NULL; + memcpy(custom_headers_param->value, Z_STRVAL_P(env_data), Z_STRLEN_P(env_data) + 1); + custom_headers_param->next = tmp_param; + tmp_param = custom_headers_param; + } ZEND_HASH_FOREACH_END(); + } + } + + first = 1; + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(body), data) { + if (first) { + first = 0; + + if (Z_TYPE_P(data) != IS_ARRAY) { + php_error_docref(NULL, E_WARNING, "body parameter must be a non-empty array"); + RETURN_FALSE; + } + + bod = mail_newbody(); + topbod = bod; + + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "type", sizeof("type") - 1)) != NULL) { + convert_to_long_ex(pvalue); + bod->type = (short) Z_LVAL_P(pvalue); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "encoding", sizeof("encoding") - 1)) != NULL) { + convert_to_long_ex(pvalue); + bod->encoding = (short) Z_LVAL_P(pvalue); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "charset", sizeof("charset") - 1)) != NULL) { + convert_to_string_ex(pvalue); + tmp_param = mail_newbody_parameter(); + tmp_param->value = cpystr(Z_STRVAL_P(pvalue)); + tmp_param->attribute = cpystr("CHARSET"); + tmp_param->next = bod->parameter; + bod->parameter = tmp_param; + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "type.parameters", sizeof("type.parameters") - 1)) != NULL) { + if(Z_TYPE_P(pvalue) == IS_ARRAY) { + disp_param = tmp_param = NULL; + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(pvalue), key, disp_data) { + disp_param = mail_newbody_parameter(); + disp_param->attribute = cpystr(ZSTR_VAL(key)); + convert_to_string_ex(disp_data); + disp_param->value = (char *) fs_get(Z_STRLEN_P(disp_data) + 1); + memcpy(disp_param->value, Z_STRVAL_P(disp_data), Z_STRLEN_P(disp_data) + 1); + disp_param->next = tmp_param; + tmp_param = disp_param; + } ZEND_HASH_FOREACH_END(); + bod->parameter = disp_param; + } + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "subtype", sizeof("subtype") - 1)) != NULL) { + convert_to_string_ex(pvalue); + bod->subtype = cpystr(Z_STRVAL_P(pvalue)); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "id", sizeof("id") - 1)) != NULL) { + convert_to_string_ex(pvalue); + bod->id = cpystr(Z_STRVAL_P(pvalue)); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "description", sizeof("description") - 1)) != NULL) { + convert_to_string_ex(pvalue); + bod->description = cpystr(Z_STRVAL_P(pvalue)); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "disposition.type", sizeof("disposition.type") - 1)) != NULL) { + convert_to_string_ex(pvalue); + bod->disposition.type = (char *) fs_get(Z_STRLEN_P(pvalue) + 1); + memcpy(bod->disposition.type, Z_STRVAL_P(pvalue), Z_STRLEN_P(pvalue)+1); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "disposition", sizeof("disposition") - 1)) != NULL) { + if (Z_TYPE_P(pvalue) == IS_ARRAY) { + disp_param = tmp_param = NULL; + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(pvalue), key, disp_data) { + disp_param = mail_newbody_parameter(); + disp_param->attribute = cpystr(ZSTR_VAL(key)); + convert_to_string_ex(disp_data); + disp_param->value = (char *) fs_get(Z_STRLEN_P(disp_data) + 1); + memcpy(disp_param->value, Z_STRVAL_P(disp_data), Z_STRLEN_P(disp_data) + 1); + disp_param->next = tmp_param; + tmp_param = disp_param; + } ZEND_HASH_FOREACH_END(); + bod->disposition.parameter = disp_param; + } + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "contents.data", sizeof("contents.data") - 1)) != NULL) { + convert_to_string_ex(pvalue); + bod->contents.text.data = fs_get(Z_STRLEN_P(pvalue) + 1); + memcpy(bod->contents.text.data, Z_STRVAL_P(pvalue), Z_STRLEN_P(pvalue)+1); + bod->contents.text.size = Z_STRLEN_P(pvalue); + } else { + bod->contents.text.data = fs_get(1); + memcpy(bod->contents.text.data, "", 1); + bod->contents.text.size = 0; + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "lines", sizeof("lines") - 1)) != NULL) { + convert_to_long_ex(pvalue); + bod->size.lines = Z_LVAL_P(pvalue); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "bytes", sizeof("bytes") - 1)) != NULL) { + convert_to_long_ex(pvalue); + bod->size.bytes = Z_LVAL_P(pvalue); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "md5", sizeof("md5") - 1)) != NULL) { + convert_to_string_ex(pvalue); + bod->md5 = cpystr(Z_STRVAL_P(pvalue)); + } + } else if (Z_TYPE_P(data) == IS_ARRAY) { + short type = -1; + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "type", sizeof("type") - 1)) != NULL) { + convert_to_long_ex(pvalue); + type = (short) Z_LVAL_P(pvalue); + } + + if (!toppart) { + bod->nested.part = mail_newbody_part(); + mypart = bod->nested.part; + toppart = 1; + } else { + mypart->next = mail_newbody_part(); + mypart = mypart->next; + } + + bod = &mypart->body; + + if (type != TYPEMULTIPART) { + bod->type = type; + } + + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "encoding", sizeof("encoding") - 1)) != NULL) { + convert_to_long_ex(pvalue); + bod->encoding = (short) Z_LVAL_P(pvalue); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "charset", sizeof("charset") - 1)) != NULL) { + convert_to_string_ex(pvalue); + tmp_param = mail_newbody_parameter(); + tmp_param->value = (char *) fs_get(Z_STRLEN_P(pvalue) + 1); + memcpy(tmp_param->value, Z_STRVAL_P(pvalue), Z_STRLEN_P(pvalue) + 1); + tmp_param->attribute = cpystr("CHARSET"); + tmp_param->next = bod->parameter; + bod->parameter = tmp_param; + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "type.parameters", sizeof("type.parameters") - 1)) != NULL) { + if (Z_TYPE_P(pvalue) == IS_ARRAY) { + disp_param = tmp_param = NULL; + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(pvalue), key, disp_data) { + disp_param = mail_newbody_parameter(); + disp_param->attribute = cpystr(ZSTR_VAL(key)); + convert_to_string_ex(disp_data); + disp_param->value = (char *)fs_get(Z_STRLEN_P(disp_data) + 1); + memcpy(disp_param->value, Z_STRVAL_P(disp_data), Z_STRLEN_P(disp_data) + 1); + disp_param->next = tmp_param; + tmp_param = disp_param; + } ZEND_HASH_FOREACH_END(); + bod->parameter = disp_param; + } + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "subtype", sizeof("subtype") - 1)) != NULL) { + convert_to_string_ex(pvalue); + bod->subtype = cpystr(Z_STRVAL_P(pvalue)); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "id", sizeof("id") - 1)) != NULL) { + convert_to_string_ex(pvalue); + bod->id = cpystr(Z_STRVAL_P(pvalue)); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "description", sizeof("description") - 1)) != NULL) { + convert_to_string_ex(pvalue); + bod->description = cpystr(Z_STRVAL_P(pvalue)); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "disposition.type", sizeof("disposition.type") - 1)) != NULL) { + convert_to_string_ex(pvalue); + bod->disposition.type = (char *) fs_get(Z_STRLEN_P(pvalue) + 1); + memcpy(bod->disposition.type, Z_STRVAL_P(pvalue), Z_STRLEN_P(pvalue)+1); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "disposition", sizeof("disposition") - 1)) != NULL) { + if (Z_TYPE_P(pvalue) == IS_ARRAY) { + disp_param = tmp_param = NULL; + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(pvalue), key, disp_data) { + disp_param = mail_newbody_parameter(); + disp_param->attribute = cpystr(ZSTR_VAL(key)); + convert_to_string_ex(disp_data); + disp_param->value = (char *) fs_get(Z_STRLEN_P(disp_data) + 1); + memcpy(disp_param->value, Z_STRVAL_P(disp_data), Z_STRLEN_P(disp_data) + 1); + disp_param->next = tmp_param; + tmp_param = disp_param; + } ZEND_HASH_FOREACH_END(); + bod->disposition.parameter = disp_param; + } + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "contents.data", sizeof("contents.data") - 1)) != NULL) { + convert_to_string_ex(pvalue); + bod->contents.text.data = fs_get(Z_STRLEN_P(pvalue) + 1); + memcpy(bod->contents.text.data, Z_STRVAL_P(pvalue), Z_STRLEN_P(pvalue) + 1); + bod->contents.text.size = Z_STRLEN_P(pvalue); + } else { + bod->contents.text.data = fs_get(1); + memcpy(bod->contents.text.data, "", 1); + bod->contents.text.size = 0; + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "lines", sizeof("lines") - 1)) != NULL) { + convert_to_long_ex(pvalue); + bod->size.lines = Z_LVAL_P(pvalue); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "bytes", sizeof("bytes") - 1)) != NULL) { + convert_to_long_ex(pvalue); + bod->size.bytes = Z_LVAL_P(pvalue); + } + if ((pvalue = zend_hash_str_find(Z_ARRVAL_P(data), "md5", sizeof("md5") - 1)) != NULL) { + convert_to_string_ex(pvalue); + bod->md5 = cpystr(Z_STRVAL_P(pvalue)); + } + } + } ZEND_HASH_FOREACH_END(); + + if (first) { + php_error_docref(NULL, E_WARNING, "body parameter must be a non-empty array"); + RETURN_FALSE; + } + + if (bod && bod->type == TYPEMULTIPART && (!bod->nested.part || !bod->nested.part->next)) { + php_error_docref(NULL, E_WARNING, "cannot generate multipart e-mail without components."); + RETVAL_FALSE; + goto done; + } + + rfc822_encode_body_7bit(env, topbod); + + tmp = emalloc(SENDBUFLEN + 1); + + rfc822_header(tmp, env, topbod); + + /* add custom envelope headers */ + if (custom_headers_param) { + int l = strlen(tmp) - 2, l2; + PARAMETER *tp = custom_headers_param; + + /* remove last CRLF from tmp */ + tmp[l] = '\0'; + tempstring = emalloc(l); + memcpy(tempstring, tmp, l); + + do { + l2 = strlen(custom_headers_param->value); + tempstring = erealloc(tempstring, l + l2 + CRLF_LEN + 1); + memcpy(tempstring + l, custom_headers_param->value, l2); + memcpy(tempstring + l + l2, CRLF, CRLF_LEN); + l += l2 + CRLF_LEN; + } while ((custom_headers_param = custom_headers_param->next)); + + mail_free_body_parameter(&tp); + + mystring = emalloc(l + CRLF_LEN + 1); + memcpy(mystring, tempstring, l); + memcpy(mystring + l , CRLF, CRLF_LEN); + mystring[l + CRLF_LEN] = '\0'; + + efree(tempstring); + } else { + mystring = estrdup(tmp); + } + + bod = topbod; + + if (bod && bod->type == TYPEMULTIPART) { + + /* first body part */ + part = bod->nested.part; + + /* find cookie */ + for (param = bod->parameter; param && !cookie; param = param->next) { + if (!strcmp (param->attribute, "BOUNDARY")) { + cookie = param->value; + } + } + + /* yucky default */ + if (!cookie) { + cookie = "-"; + } else if (strlen(cookie) > (SENDBUFLEN - 2 - 2 - 2)) { /* validate cookie length -- + CRLF * 2 */ + php_error_docref(NULL, E_WARNING, "The boundary should be no longer than 4kb"); + RETVAL_FALSE; + goto done; + } + + /* for each part */ + do { + t = tmp; + + /* append mini-header */ + *t = '\0'; + rfc822_write_body_header(&t, &part->body); + + /* output cookie, mini-header, and contents */ + spprintf(&tempstring, 0, "%s--%s%s%s%s", mystring, cookie, CRLF, tmp, CRLF); + efree(mystring); + mystring=tempstring; + + bod=&part->body; + + spprintf(&tempstring, 0, "%s%s%s", mystring, bod->contents.text.data, CRLF); + efree(mystring); + mystring=tempstring; + } while ((part = part->next)); /* until done */ + + /* output trailing cookie */ + spprintf(&tempstring, 0, "%s--%s--%s", mystring, cookie, CRLF); + efree(mystring); + mystring=tempstring; + } else if (bod) { + spprintf(&tempstring, 0, "%s%s%s", mystring, bod->contents.text.data, CRLF); + efree(mystring); + mystring=tempstring; + } else { + efree(mystring); + RETVAL_FALSE; + goto done; + } + + RETVAL_STRING(tempstring); + efree(tempstring); +done: + if (tmp) { + efree(tmp); + } + mail_free_body(&topbod); + mail_free_envelope(&env); +} +/* }}} */ + +/* {{{ _php_imap_mail + */ +int _php_imap_mail(char *to, char *subject, char *message, char *headers, char *cc, char *bcc, char* rpath) +{ +#ifdef PHP_WIN32 + int tsm_err; +#else + FILE *sendmail; + int ret; +#endif + +#ifdef PHP_WIN32 + char *tempMailTo; + char *tsm_errmsg = NULL; + ADDRESS *addr; + char *bufferTo = NULL, *bufferCc = NULL, *bufferBcc = NULL, *bufferHeader = NULL; + size_t offset, bufferLen = 0; + size_t bt_len; + + if (headers) { + bufferLen += strlen(headers); + } + if (to) { + bufferLen += strlen(to) + 6; + } + if (cc) { + bufferLen += strlen(cc) + 6; + } + +#define PHP_IMAP_CLEAN if (bufferTo) efree(bufferTo); if (bufferCc) efree(bufferCc); if (bufferBcc) efree(bufferBcc); if (bufferHeader) efree(bufferHeader); +#define PHP_IMAP_BAD_DEST PHP_IMAP_CLEAN; efree(tempMailTo); return (BAD_MSG_DESTINATION); + + bufferHeader = (char *)safe_emalloc(bufferLen, 1, 1); + memset(bufferHeader, 0, bufferLen); + if (to && *to) { + strlcat(bufferHeader, "To: ", bufferLen + 1); + strlcat(bufferHeader, to, bufferLen + 1); + strlcat(bufferHeader, "\r\n", bufferLen + 1); + tempMailTo = estrdup(to); + bt_len = strlen(to); + bufferTo = (char *)safe_emalloc(bt_len, 1, 1); + bt_len++; + offset = 0; + addr = NULL; + rfc822_parse_adrlist(&addr, tempMailTo, "NO HOST"); + while (addr) { + if (addr->host == NULL || strcmp(addr->host, ERRHOST) == 0) { + PHP_IMAP_BAD_DEST; + } else { + bufferTo = safe_erealloc(bufferTo, bt_len, 1, strlen(addr->mailbox)); + bt_len += strlen(addr->mailbox); + bufferTo = safe_erealloc(bufferTo, bt_len, 1, strlen(addr->host)); + bt_len += strlen(addr->host); + offset += slprintf(bufferTo + offset, bt_len - offset, "%s@%s,", addr->mailbox, addr->host); + } + addr = addr->next; + } + efree(tempMailTo); + if (offset>0) { + bufferTo[offset-1] = 0; + } + } + + if (cc && *cc) { + strlcat(bufferHeader, "Cc: ", bufferLen + 1); + strlcat(bufferHeader, cc, bufferLen + 1); + strlcat(bufferHeader, "\r\n", bufferLen + 1); + tempMailTo = estrdup(cc); + bt_len = strlen(cc); + bufferCc = (char *)safe_emalloc(bt_len, 1, 1); + bt_len++; + offset = 0; + addr = NULL; + rfc822_parse_adrlist(&addr, tempMailTo, "NO HOST"); + while (addr) { + if (addr->host == NULL || strcmp(addr->host, ERRHOST) == 0) { + PHP_IMAP_BAD_DEST; + } else { + bufferCc = safe_erealloc(bufferCc, bt_len, 1, strlen(addr->mailbox)); + bt_len += strlen(addr->mailbox); + bufferCc = safe_erealloc(bufferCc, bt_len, 1, strlen(addr->host)); + bt_len += strlen(addr->host); + offset += slprintf(bufferCc + offset, bt_len - offset, "%s@%s,", addr->mailbox, addr->host); + } + addr = addr->next; + } + efree(tempMailTo); + if (offset>0) { + bufferCc[offset-1] = 0; + } + } + + if (bcc && *bcc) { + tempMailTo = estrdup(bcc); + bt_len = strlen(bcc); + bufferBcc = (char *)safe_emalloc(bt_len, 1, 1); + bt_len++; + offset = 0; + addr = NULL; + rfc822_parse_adrlist(&addr, tempMailTo, "NO HOST"); + while (addr) { + if (addr->host == NULL || strcmp(addr->host, ERRHOST) == 0) { + PHP_IMAP_BAD_DEST; + } else { + bufferBcc = safe_erealloc(bufferBcc, bt_len, 1, strlen(addr->mailbox)); + bt_len += strlen(addr->mailbox); + bufferBcc = safe_erealloc(bufferBcc, bt_len, 1, strlen(addr->host)); + bt_len += strlen(addr->host); + offset += slprintf(bufferBcc + offset, bt_len - offset, "%s@%s,", addr->mailbox, addr->host); + } + addr = addr->next; + } + efree(tempMailTo); + if (offset>0) { + bufferBcc[offset-1] = 0; + } + } + + if (headers && *headers) { + strlcat(bufferHeader, headers, bufferLen + 1); + } + + if (TSendMail(INI_STR("SMTP"), &tsm_err, &tsm_errmsg, bufferHeader, subject, bufferTo, message, bufferCc, bufferBcc, rpath) != SUCCESS) { + if (tsm_errmsg) { + php_error_docref(NULL, E_WARNING, "%s", tsm_errmsg); + efree(tsm_errmsg); + } else { + php_error_docref(NULL, E_WARNING, "%s", GetSMErrorText(tsm_err)); + } + PHP_IMAP_CLEAN; + return 0; + } + PHP_IMAP_CLEAN; +#else + if (!INI_STR("sendmail_path")) { + return 0; + } + sendmail = popen(INI_STR("sendmail_path"), "w"); + if (sendmail) { + if (rpath && rpath[0]) fprintf(sendmail, "From: %s\n", rpath); + fprintf(sendmail, "To: %s\n", to); + if (cc && cc[0]) fprintf(sendmail, "Cc: %s\n", cc); + if (bcc && bcc[0]) fprintf(sendmail, "Bcc: %s\n", bcc); + fprintf(sendmail, "Subject: %s\n", subject); + if (headers != NULL) { + fprintf(sendmail, "%s\n", headers); + } + fprintf(sendmail, "\n%s\n", message); + ret = pclose(sendmail); + if (ret == -1) { + return 0; + } else { + return 1; + } + } else { + php_error_docref(NULL, E_WARNING, "Could not execute mail delivery program"); + return 0; + } +#endif + return 1; +} +/* }}} */ + +/* {{{ proto bool imap_mail(string to, string subject, string message [, string additional_headers [, string cc [, string bcc [, string rpath]]]]) + Send an email message */ +PHP_FUNCTION(imap_mail) +{ + zend_string *to=NULL, *message=NULL, *headers=NULL, *subject=NULL, *cc=NULL, *bcc=NULL, *rpath=NULL; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "SSS|SSSS", &to, &subject, &message, + &headers, &cc, &bcc, &rpath) == FAILURE) { + return; + } + + /* To: */ + if (!ZSTR_LEN(to)) { + php_error_docref(NULL, E_WARNING, "No to field in mail command"); + RETURN_FALSE; + } + + /* Subject: */ + if (!ZSTR_LEN(subject)) { + php_error_docref(NULL, E_WARNING, "No subject field in mail command"); + RETURN_FALSE; + } + + /* message body */ + if (!ZSTR_LEN(message)) { + /* this is not really an error, so it is allowed. */ + php_error_docref(NULL, E_WARNING, "No message string in mail command"); + message = NULL; + } + + if (_php_imap_mail(ZSTR_VAL(to), ZSTR_VAL(subject), ZSTR_VAL(message), headers?ZSTR_VAL(headers):NULL, cc?ZSTR_VAL(cc):NULL, + bcc?ZSTR_VAL(bcc):NULL, rpath?ZSTR_VAL(rpath):NULL)) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto array imap_search(resource stream_id, string criteria [, int options [, string charset]]) + Return a list of messages matching the given criteria */ +PHP_FUNCTION(imap_search) +{ + zval *streamind; + zend_string *criteria, *charset = NULL; + zend_long flags = SE_FREE; + pils *imap_le_struct; + char *search_criteria; + MESSAGELIST *cur; + int argc = ZEND_NUM_ARGS(); + SEARCHPGM *pgm = NIL; + + if (zend_parse_parameters(argc, "rS|lS", &streamind, &criteria, &flags, &charset) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + search_criteria = estrndup(ZSTR_VAL(criteria), ZSTR_LEN(criteria)); + + IMAPG(imap_messages) = IMAPG(imap_messages_tail) = NIL; + pgm = mail_criteria(search_criteria); + + mail_search_full(imap_le_struct->imap_stream, (argc == 4 ? ZSTR_VAL(charset) : NIL), pgm, flags); + + if (pgm && !(flags & SE_FREE)) { + mail_free_searchpgm(&pgm); + } + + if (IMAPG(imap_messages) == NIL) { + efree(search_criteria); + RETURN_FALSE; + } + + array_init(return_value); + + cur = IMAPG(imap_messages); + while (cur != NIL) { + add_next_index_long(return_value, cur->msgid); + cur = cur->next; + } + mail_free_messagelist(&IMAPG(imap_messages), &IMAPG(imap_messages_tail)); + efree(search_criteria); +} +/* }}} */ + +/* {{{ proto array imap_alerts(void) + Returns an array of all IMAP alerts that have been generated since the last page load or since the last imap_alerts() call, whichever came last. The alert stack is cleared after imap_alerts() is called. */ +/* Author: CJH */ +PHP_FUNCTION(imap_alerts) +{ + STRINGLIST *cur=NIL; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (IMAPG(imap_alertstack) == NIL) { + RETURN_FALSE; + } + + array_init(return_value); + + cur = IMAPG(imap_alertstack); + while (cur != NIL) { + add_next_index_string(return_value, (char*)cur->LTEXT); + cur = cur->next; + } + mail_free_stringlist(&IMAPG(imap_alertstack)); + IMAPG(imap_alertstack) = NIL; +} +/* }}} */ + +/* {{{ proto array imap_errors(void) + Returns an array of all IMAP errors generated since the last page load, or since the last imap_errors() call, whichever came last. The error stack is cleared after imap_errors() is called. */ +/* Author: CJH */ +PHP_FUNCTION(imap_errors) +{ + ERRORLIST *cur=NIL; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (IMAPG(imap_errorstack) == NIL) { + RETURN_FALSE; + } + + array_init(return_value); + + cur = IMAPG(imap_errorstack); + while (cur != NIL) { + add_next_index_string(return_value, (char*)cur->LTEXT); + cur = cur->next; + } + mail_free_errorlist(&IMAPG(imap_errorstack)); + IMAPG(imap_errorstack) = NIL; +} +/* }}} */ + +/* {{{ proto string imap_last_error(void) + Returns the last error that was generated by an IMAP function. The error stack is NOT cleared after this call. */ +/* Author: CJH */ +PHP_FUNCTION(imap_last_error) +{ + ERRORLIST *cur=NIL; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (IMAPG(imap_errorstack) == NIL) { + RETURN_FALSE; + } + + cur = IMAPG(imap_errorstack); + while (cur != NIL) { + if (cur->next == NIL) { + RETURN_STRING((char*)cur->LTEXT); + } + cur = cur->next; + } +} +/* }}} */ + +/* {{{ proto array imap_mime_header_decode(string str) + Decode mime header element in accordance with RFC 2047 and return array of objects containing 'charset' encoding and decoded 'text' */ +PHP_FUNCTION(imap_mime_header_decode) +{ + /* Author: Ted Parnefors */ + zval myobject; + zend_string *str; + char *string, *charset, encoding, *text, *decode; + zend_long charset_token, encoding_token, end_token, end, offset=0, i; + unsigned long newlength; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &str) == FAILURE) { + return; + } + + array_init(return_value); + + string = ZSTR_VAL(str); + end = ZSTR_LEN(str); + + charset = (char *) safe_emalloc((end + 1), 2, 0); + text = &charset[end + 1]; + while (offset < end) { /* Reached end of the string? */ + if ((charset_token = (zend_long)php_memnstr(&string[offset], "=?", 2, string + end))) { /* Is there anything encoded in the string? */ + charset_token -= (zend_long)string; + if (offset != charset_token) { /* Is there anything before the encoded data? */ + /* Retrieve unencoded data that is found before encoded data */ + memcpy(text, &string[offset], charset_token-offset); + text[charset_token - offset] = 0x00; + object_init(&myobject); + add_property_string(&myobject, "charset", "default"); + add_property_string(&myobject, "text", text); + zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &myobject); + } + if ((encoding_token = (zend_long)php_memnstr(&string[charset_token+2], "?", 1, string+end))) { /* Find token for encoding */ + encoding_token -= (zend_long)string; + if ((end_token = (zend_long)php_memnstr(&string[encoding_token+3], "?=", 2, string+end))) { /* Find token for end of encoded data */ + end_token -= (zend_long)string; + memcpy(charset, &string[charset_token + 2], encoding_token - (charset_token + 2)); /* Extract charset encoding */ + charset[encoding_token-(charset_token + 2)] = 0x00; + encoding=string[encoding_token + 1]; /* Extract encoding from string */ + memcpy(text, &string[encoding_token + 3], end_token - (encoding_token + 3)); /* Extract text */ + text[end_token - (encoding_token + 3)] = 0x00; + decode = text; + if (encoding == 'q' || encoding == 'Q') { /* Decode 'q' encoded data */ + for(i=0; text[i] != 0x00; i++) if (text[i] == '_') text[i] = ' '; /* Replace all *_' with space. */ + decode = (char *)rfc822_qprint((unsigned char *) text, strlen(text), &newlength); + } else if (encoding == 'b' || encoding == 'B') { + decode = (char *)rfc822_base64((unsigned char *) text, strlen(text), &newlength); /* Decode 'B' encoded data */ + } + if (decode == NULL) { + efree(charset); + zval_dtor(return_value); + RETURN_FALSE; + } + object_init(&myobject); + add_property_string(&myobject, "charset", charset); + add_property_string(&myobject, "text", decode); + zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &myobject); + + /* only free decode if it was allocated by rfc822_qprint or rfc822_base64 */ + if (decode != text) { + fs_give((void**)&decode); + } + + offset = end_token+2; + for (i = 0; (string[offset + i] == ' ') || (string[offset + i] == 0x0a) || (string[offset + i] == 0x0d) || (string[offset + i] == '\t'); i++); + if ((string[offset + i] == '=') && (string[offset + i + 1] == '?') && (offset + i < end)) { + offset += i; + } + continue; /*/ Iterate the loop again please. */ + } + } + } else { + /* Just some tweaking to optimize the code, and get the end statements work in a general manner. + * If we end up here we didn't find a position for "charset_token", + * so we need to set it to the start of the yet unextracted data. + */ + charset_token = offset; + } + /* Return the rest of the data as unencoded, as it was either unencoded or was missing separators + which rendered the remainder of the string impossible for us to decode. */ + memcpy(text, &string[charset_token], end - charset_token); /* Extract unencoded text from string */ + text[end - charset_token] = 0x00; + object_init(&myobject); + add_property_string(&myobject, "charset", "default"); + add_property_string(&myobject, "text", text); + zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &myobject); + + offset = end; /* We have reached the end of the string. */ + } + efree(charset); +} +/* }}} */ + +/* Support Functions */ + +#ifdef HAVE_RFC822_OUTPUT_ADDRESS_LIST +/* {{{ _php_rfc822_soutr + */ +static long _php_rfc822_soutr (void *stream, char *string) +{ + smart_str *ret = (smart_str*)stream; + int len = strlen(string); + + smart_str_appendl(ret, string, len); + return LONGT; +} +/* }}} */ + +/* {{{ _php_rfc822_write_address + */ +static zend_string* _php_rfc822_write_address(ADDRESS *addresslist) +{ + char address[MAILTMPLEN]; + smart_str ret = {0}; + RFC822BUFFER buf; + + buf.beg = address; + buf.cur = buf.beg; + buf.end = buf.beg + sizeof(address) - 1; + buf.s = &ret; + buf.f = _php_rfc822_soutr; + rfc822_output_address_list(&buf, addresslist, 0, NULL); + rfc822_output_flush(&buf); + smart_str_0(&ret); + return ret.s; +} +/* }}} */ + +#else + +/* {{{ _php_rfc822_len + * Calculate string length based on imap's rfc822_cat function. + */ +static int _php_rfc822_len(char *str) +{ + int len; + char *p; + + if (!str || !*str) { + return 0; + } + + /* strings with special characters will need to be quoted, as a safety measure we + * add 2 bytes for the quotes just in case. + */ + len = strlen(str) + 2; + p = str; + /* rfc822_cat() will escape all " and \ characters, therefor we need to increase + * our buffer length to account for these characters. + */ + while ((p = strpbrk(p, "\\\""))) { + p++; + len++; + } + + return len; +} +/* }}} */ + +/* {{{ _php_imap_get_address_size + */ +static int _php_imap_address_size (ADDRESS *addresslist) +{ + ADDRESS *tmp; + int ret=0, num_ent=0; + + tmp = addresslist; + + if (tmp) do { + ret += _php_rfc822_len(tmp->personal); + ret += _php_rfc822_len(tmp->adl); + ret += _php_rfc822_len(tmp->mailbox); + ret += _php_rfc822_len(tmp->host); + num_ent++; + } while ((tmp = tmp->next)); + + /* + * rfc822_write_address_full() needs some extra space for '<>,', etc. + * for this perpouse we allocate additional PHP_IMAP_ADDRESS_SIZE_BUF bytes + * by default this buffer is 10 bytes long + */ + ret += (ret) ? num_ent*PHP_IMAP_ADDRESS_SIZE_BUF : 0; + + return ret; +} + +/* }}} */ + +/* {{{ _php_rfc822_write_address + */ +static zend_string* _php_rfc822_write_address(ADDRESS *addresslist) +{ + char address[SENDBUFLEN]; + + if (_php_imap_address_size(addresslist) >= SENDBUFLEN) { + zend_throw_error(NULL, "Address buffer overflow"); + return NULL; + } + address[0] = 0; + rfc822_write_address(address, addresslist); + return zend_string_init(address, strlen(address), 0); +} +/* }}} */ +#endif +/* {{{ _php_imap_parse_address + */ +static zend_string* _php_imap_parse_address (ADDRESS *addresslist, zval *paddress) +{ + zend_string *fulladdress; + ADDRESS *addresstmp; + zval tmpvals; + + addresstmp = addresslist; + + fulladdress = _php_rfc822_write_address(addresstmp); + + addresstmp = addresslist; + do { + object_init(&tmpvals); + if (addresstmp->personal) add_property_string(&tmpvals, "personal", addresstmp->personal); + if (addresstmp->adl) add_property_string(&tmpvals, "adl", addresstmp->adl); + if (addresstmp->mailbox) add_property_string(&tmpvals, "mailbox", addresstmp->mailbox); + if (addresstmp->host) add_property_string(&tmpvals, "host", addresstmp->host); + add_next_index_object(paddress, &tmpvals); + } while ((addresstmp = addresstmp->next)); + return fulladdress; +} +/* }}} */ + +/* {{{ _php_make_header_object + */ +static void _php_make_header_object(zval *myzvalue, ENVELOPE *en) +{ + zval paddress; + zend_string *fulladdress=NULL; + + object_init(myzvalue); + + if (en->remail) add_property_string(myzvalue, "remail", en->remail); + if (en->date) add_property_string(myzvalue, "date", (char*)en->date); + if (en->date) add_property_string(myzvalue, "Date", (char*)en->date); + if (en->subject) add_property_string(myzvalue, "subject", en->subject); + if (en->subject) add_property_string(myzvalue, "Subject", en->subject); + if (en->in_reply_to) add_property_string(myzvalue, "in_reply_to", en->in_reply_to); + if (en->message_id) add_property_string(myzvalue, "message_id", en->message_id); + if (en->newsgroups) add_property_string(myzvalue, "newsgroups", en->newsgroups); + if (en->followup_to) add_property_string(myzvalue, "followup_to", en->followup_to); + if (en->references) add_property_string(myzvalue, "references", en->references); + + if (en->to) { + array_init(&paddress); + fulladdress = _php_imap_parse_address(en->to, &paddress); + if (fulladdress) { + add_property_str(myzvalue, "toaddress", fulladdress); + } + add_assoc_object(myzvalue, "to", &paddress); + } + + if (en->from) { + array_init(&paddress); + fulladdress = _php_imap_parse_address(en->from, &paddress); + if (fulladdress) { + add_property_str(myzvalue, "fromaddress", fulladdress); + } + add_assoc_object(myzvalue, "from", &paddress); + } + + if (en->cc) { + array_init(&paddress); + fulladdress = _php_imap_parse_address(en->cc, &paddress); + if (fulladdress) { + add_property_str(myzvalue, "ccaddress", fulladdress); + } + add_assoc_object(myzvalue, "cc", &paddress); + } + + if (en->bcc) { + array_init(&paddress); + fulladdress = _php_imap_parse_address(en->bcc, &paddress); + if (fulladdress) { + add_property_str(myzvalue, "bccaddress", fulladdress); + } + add_assoc_object(myzvalue, "bcc", &paddress); + } + + if (en->reply_to) { + array_init(&paddress); + fulladdress = _php_imap_parse_address(en->reply_to, &paddress); + if (fulladdress) { + add_property_str(myzvalue, "reply_toaddress", fulladdress); + } + add_assoc_object(myzvalue, "reply_to", &paddress); + } + + if (en->sender) { + array_init(&paddress); + fulladdress = _php_imap_parse_address(en->sender, &paddress); + if (fulladdress) { + add_property_str(myzvalue, "senderaddress", fulladdress); + } + add_assoc_object(myzvalue, "sender", &paddress); + } + + if (en->return_path) { + array_init(&paddress); + fulladdress = _php_imap_parse_address(en->return_path, &paddress); + if (fulladdress) { + add_property_str(myzvalue, "return_pathaddress", fulladdress); + } + add_assoc_object(myzvalue, "return_path", &paddress); + } +} +/* }}} */ + +/* {{{ _php_imap_add_body + */ +void _php_imap_add_body(zval *arg, BODY *body) +{ + zval parametres, param, dparametres, dparam; + PARAMETER *par, *dpar; + PART *part; + + if (body->type <= TYPEMAX) { + add_property_long(arg, "type", body->type); + } + + if (body->encoding <= ENCMAX) { + add_property_long(arg, "encoding", body->encoding); + } + + if (body->subtype) { + add_property_long(arg, "ifsubtype", 1); + add_property_string(arg, "subtype", body->subtype); + } else { + add_property_long(arg, "ifsubtype", 0); + } + + if (body->description) { + add_property_long(arg, "ifdescription", 1); + add_property_string(arg, "description", body->description); + } else { + add_property_long(arg, "ifdescription", 0); + } + + if (body->id) { + add_property_long(arg, "ifid", 1); + add_property_string(arg, "id", body->id); + } else { + add_property_long(arg, "ifid", 0); + } + + if (body->size.lines) { + add_property_long(arg, "lines", body->size.lines); + } + + if (body->size.bytes) { + add_property_long(arg, "bytes", body->size.bytes); + } + +#ifdef IMAP41 + if (body->disposition.type) { + add_property_long(arg, "ifdisposition", 1); + add_property_string(arg, "disposition", body->disposition.type); + } else { + add_property_long(arg, "ifdisposition", 0); + } + + if (body->disposition.parameter) { + dpar = body->disposition.parameter; + add_property_long(arg, "ifdparameters", 1); + array_init(&dparametres); + do { + object_init(&dparam); + add_property_string(&dparam, "attribute", dpar->attribute); + add_property_string(&dparam, "value", dpar->value); + add_next_index_object(&dparametres, &dparam); + } while ((dpar = dpar->next)); + add_assoc_object(arg, "dparameters", &dparametres); + } else { + add_property_long(arg, "ifdparameters", 0); + } +#endif + + if ((par = body->parameter)) { + add_property_long(arg, "ifparameters", 1); + + array_init(¶metres); + do { + object_init(¶m); + if (par->attribute) { + add_property_string(¶m, "attribute", par->attribute); + } + if (par->value) { + add_property_string(¶m, "value", par->value); + } + + add_next_index_object(¶metres, ¶m); + } while ((par = par->next)); + } else { + object_init(¶metres); + add_property_long(arg, "ifparameters", 0); + } + add_assoc_object(arg, "parameters", ¶metres); + + /* multipart message ? */ + if (body->type == TYPEMULTIPART) { + array_init(¶metres); + for (part = body->CONTENT_PART; part; part = part->next) { + object_init(¶m); + _php_imap_add_body(¶m, &part->body); + add_next_index_object(¶metres, ¶m); + } + add_assoc_object(arg, "parts", ¶metres); + } + + /* encapsulated message ? */ + if ((body->type == TYPEMESSAGE) && (!strcasecmp(body->subtype, "rfc822"))) { + body = body->CONTENT_MSG_BODY; + array_init(¶metres); + object_init(¶m); + _php_imap_add_body(¶m, body); + add_next_index_object(¶metres, ¶m); + add_assoc_object(arg, "parts", ¶metres); + } +} +/* }}} */ + +/* imap_thread, stealing this from header cclient -rjs3 */ +/* {{{ build_thread_tree_helper + */ +static void build_thread_tree_helper(THREADNODE *cur, zval *tree, long *numNodes, char *buf) +{ + unsigned long thisNode = *numNodes; + + /* define "#.num" */ + snprintf(buf, 25, "%ld.num", thisNode); + + add_assoc_long(tree, buf, cur->num); + + snprintf(buf, 25, "%ld.next", thisNode); + if(cur->next) { + (*numNodes)++; + add_assoc_long(tree, buf, *numNodes); + build_thread_tree_helper(cur->next, tree, numNodes, buf); + } else { /* "null pointer" */ + add_assoc_long(tree, buf, 0); + } + + snprintf(buf, 25, "%ld.branch", thisNode); + if(cur->branch) { + (*numNodes)++; + add_assoc_long(tree, buf, *numNodes); + build_thread_tree_helper(cur->branch, tree, numNodes, buf); + } else { /* "null pointer" */ + add_assoc_long(tree, buf, 0); + } +} +/* }}} */ + +/* {{{ build_thread_tree + */ +static int build_thread_tree(THREADNODE *top, zval **tree) +{ + long numNodes = 0; + char buf[25]; + + array_init(*tree); + + build_thread_tree_helper(top, *tree, &numNodes, buf); + + return SUCCESS; +} +/* }}} */ + +/* {{{ proto array imap_thread(resource stream_id [, int options]) + Return threaded by REFERENCES tree */ +PHP_FUNCTION(imap_thread) +{ + zval *streamind; + pils *imap_le_struct; + zend_long flags = SE_FREE; + char criteria[] = "ALL"; + THREADNODE *top; + int argc = ZEND_NUM_ARGS(); + SEARCHPGM *pgm = NIL; + + if (zend_parse_parameters(argc, "r|l", &streamind, &flags) == FAILURE) { + return; + } + + if ((imap_le_struct = (pils *)zend_fetch_resource(Z_RES_P(streamind), "imap", le_imap)) == NULL) { + RETURN_FALSE; + } + + pgm = mail_criteria(criteria); + top = mail_thread(imap_le_struct->imap_stream, "REFERENCES", NIL, pgm, flags); + if (pgm && !(flags & SE_FREE)) { + mail_free_searchpgm(&pgm); + } + + if(top == NIL) { + php_error_docref(NULL, E_WARNING, "Function returned an empty tree"); + RETURN_FALSE; + } + + /* Populate our return value data structure here. */ + if(build_thread_tree(top, &return_value) == FAILURE) { + mail_free_threadnode(&top); + RETURN_FALSE; + } + mail_free_threadnode(&top); +} +/* }}} */ + +/* {{{ proto mixed imap_timeout(int timeout_type [, int timeout]) + Set or fetch imap timeout */ +PHP_FUNCTION(imap_timeout) +{ + zend_long ttype, timeout=-1; + int timeout_type; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l|l", &ttype, &timeout) == FAILURE) { + RETURN_FALSE; + } + + if (timeout == -1) { + switch (ttype) { + case 1: + timeout_type = GET_OPENTIMEOUT; + break; + case 2: + timeout_type = GET_READTIMEOUT; + break; + case 3: + timeout_type = GET_WRITETIMEOUT; + break; + case 4: + timeout_type = GET_CLOSETIMEOUT; + break; + default: + RETURN_FALSE; + break; + } + + timeout = (zend_long) mail_parameters(NIL, timeout_type, NIL); + RETURN_LONG(timeout); + } else if (timeout >= 0) { + switch (ttype) { + case 1: + timeout_type = SET_OPENTIMEOUT; + break; + case 2: + timeout_type = SET_READTIMEOUT; + break; + case 3: + timeout_type = SET_WRITETIMEOUT; + break; + case 4: + timeout_type = SET_CLOSETIMEOUT; + break; + default: + RETURN_FALSE; + break; + } + + timeout = (zend_long) mail_parameters(NIL, timeout_type, (void *) timeout); + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +#define GETS_FETCH_SIZE 8196LU +static char *php_mail_gets(readfn_t f, void *stream, unsigned long size, GETS_DATA *md) /* {{{ */ +{ + + /* write to the gets stream if it is set, + otherwise forward to c-clients gets */ + if (IMAPG(gets_stream)) { + char buf[GETS_FETCH_SIZE]; + + while (size) { + unsigned long read; + + if (size > GETS_FETCH_SIZE) { + read = GETS_FETCH_SIZE; + size -=GETS_FETCH_SIZE; + } else { + read = size; + size = 0; + } + + if (!f(stream, read, buf)) { + php_error_docref(NULL, E_WARNING, "Failed to read from socket"); + break; + } else if (read != php_stream_write(IMAPG(gets_stream), buf, read)) { + php_error_docref(NULL, E_WARNING, "Failed to write to stream"); + break; + } + } + return NULL; + } else { + char *buf = pemalloc(size + 1, 1); + + if (f(stream, size, buf)) { + buf[size] = '\0'; + } else { + php_error_docref(NULL, E_WARNING, "Failed to read from socket"); + free(buf); + buf = NULL; + } + return buf; + } +} +/* }}} */ + +/* {{{ Interfaces to C-client + */ +PHP_IMAP_EXPORT void mm_searched(MAILSTREAM *stream, unsigned long number) +{ + MESSAGELIST *cur = NIL; + + if (IMAPG(imap_messages) == NIL) { + IMAPG(imap_messages) = mail_newmessagelist(); + IMAPG(imap_messages)->msgid = number; + IMAPG(imap_messages)->next = NIL; + IMAPG(imap_messages_tail) = IMAPG(imap_messages); + } else { + cur = IMAPG(imap_messages_tail); + cur->next = mail_newmessagelist(); + cur = cur->next; + cur->msgid = number; + cur->next = NIL; + IMAPG(imap_messages_tail) = cur; + } +} + +PHP_IMAP_EXPORT void mm_exists(MAILSTREAM *stream, unsigned long number) +{ +} + +PHP_IMAP_EXPORT void mm_expunged(MAILSTREAM *stream, unsigned long number) +{ +} + +PHP_IMAP_EXPORT void mm_flags(MAILSTREAM *stream, unsigned long number) +{ +} + +/* Author: CJH */ +PHP_IMAP_EXPORT void mm_notify(MAILSTREAM *stream, char *str, long errflg) +{ + STRINGLIST *cur = NIL; + + if (strncmp(str, "[ALERT] ", 8) == 0) { + if (IMAPG(imap_alertstack) == NIL) { + IMAPG(imap_alertstack) = mail_newstringlist(); + IMAPG(imap_alertstack)->LSIZE = strlen((char*)(IMAPG(imap_alertstack)->LTEXT = (unsigned char*)cpystr(str))); + IMAPG(imap_alertstack)->next = NIL; + } else { + cur = IMAPG(imap_alertstack); + while (cur->next != NIL) { + cur = cur->next; + } + cur->next = mail_newstringlist (); + cur = cur->next; + cur->LSIZE = strlen((char*)(cur->LTEXT = (unsigned char*)cpystr(str))); + cur->next = NIL; + } + } +} + +PHP_IMAP_EXPORT void mm_list(MAILSTREAM *stream, DTYPE delimiter, char *mailbox, long attributes) +{ + STRINGLIST *cur=NIL; + FOBJECTLIST *ocur=NIL; + + if (IMAPG(folderlist_style) == FLIST_OBJECT) { + /* build up a the new array of objects */ + /* Author: CJH */ + if (IMAPG(imap_folder_objects) == NIL) { + IMAPG(imap_folder_objects) = mail_newfolderobjectlist(); + IMAPG(imap_folder_objects)->LSIZE=strlen((char*)(IMAPG(imap_folder_objects)->LTEXT = (unsigned char*)cpystr(mailbox))); + IMAPG(imap_folder_objects)->delimiter = delimiter; + IMAPG(imap_folder_objects)->attributes = attributes; + IMAPG(imap_folder_objects)->next = NIL; + IMAPG(imap_folder_objects_tail) = IMAPG(imap_folder_objects); + } else { + ocur=IMAPG(imap_folder_objects_tail); + ocur->next=mail_newfolderobjectlist(); + ocur=ocur->next; + ocur->LSIZE = strlen((char*)(ocur->LTEXT = (unsigned char*)cpystr(mailbox))); + ocur->delimiter = delimiter; + ocur->attributes = attributes; + ocur->next = NIL; + IMAPG(imap_folder_objects_tail) = ocur; + } + + } else { + /* build the old IMAPG(imap_folders) variable to allow old imap_listmailbox() to work */ + if (!(attributes & LATT_NOSELECT)) { + if (IMAPG(imap_folders) == NIL) { + IMAPG(imap_folders)=mail_newstringlist(); + IMAPG(imap_folders)->LSIZE=strlen((char*)(IMAPG(imap_folders)->LTEXT = (unsigned char*)cpystr(mailbox))); + IMAPG(imap_folders)->next=NIL; + IMAPG(imap_folders_tail) = IMAPG(imap_folders); + } else { + cur=IMAPG(imap_folders_tail); + cur->next=mail_newstringlist (); + cur=cur->next; + cur->LSIZE = strlen((char*)(cur->LTEXT = (unsigned char*)cpystr(mailbox))); + cur->next = NIL; + IMAPG(imap_folders_tail) = cur; + } + } + } +} + +PHP_IMAP_EXPORT void mm_lsub(MAILSTREAM *stream, DTYPE delimiter, char *mailbox, long attributes) +{ + STRINGLIST *cur=NIL; + FOBJECTLIST *ocur=NIL; + + if (IMAPG(folderlist_style) == FLIST_OBJECT) { + /* build the array of objects */ + /* Author: CJH */ + if (IMAPG(imap_sfolder_objects) == NIL) { + IMAPG(imap_sfolder_objects) = mail_newfolderobjectlist(); + IMAPG(imap_sfolder_objects)->LSIZE = strlen((char*)(IMAPG(imap_sfolder_objects)->LTEXT = (unsigned char*)cpystr(mailbox))); + IMAPG(imap_sfolder_objects)->delimiter = delimiter; + IMAPG(imap_sfolder_objects)->attributes = attributes; + IMAPG(imap_sfolder_objects)->next = NIL; + IMAPG(imap_sfolder_objects_tail) = IMAPG(imap_sfolder_objects); + } else { + ocur=IMAPG(imap_sfolder_objects_tail); + ocur->next=mail_newfolderobjectlist(); + ocur=ocur->next; + ocur->LSIZE=strlen((char*)(ocur->LTEXT = (unsigned char*)cpystr(mailbox))); + ocur->delimiter = delimiter; + ocur->attributes = attributes; + ocur->next = NIL; + IMAPG(imap_sfolder_objects_tail) = ocur; + } + } else { + /* build the old simple array for imap_listsubscribed() */ + if (IMAPG(imap_sfolders) == NIL) { + IMAPG(imap_sfolders)=mail_newstringlist(); + IMAPG(imap_sfolders)->LSIZE=strlen((char*)(IMAPG(imap_sfolders)->LTEXT = (unsigned char*)cpystr(mailbox))); + IMAPG(imap_sfolders)->next=NIL; + IMAPG(imap_sfolders_tail) = IMAPG(imap_sfolders); + } else { + cur=IMAPG(imap_sfolders_tail); + cur->next=mail_newstringlist (); + cur=cur->next; + cur->LSIZE = strlen((char*)(cur->LTEXT = (unsigned char*)cpystr(mailbox))); + cur->next = NIL; + IMAPG(imap_sfolders_tail) = cur; + } + } +} + +PHP_IMAP_EXPORT void mm_status(MAILSTREAM *stream, char *mailbox, MAILSTATUS *status) +{ + + IMAPG(status_flags)=status->flags; + if (IMAPG(status_flags) & SA_MESSAGES) { + IMAPG(status_messages)=status->messages; + } + if (IMAPG(status_flags) & SA_RECENT) { + IMAPG(status_recent)=status->recent; + } + if (IMAPG(status_flags) & SA_UNSEEN) { + IMAPG(status_unseen)=status->unseen; + } + if (IMAPG(status_flags) & SA_UIDNEXT) { + IMAPG(status_uidnext)=status->uidnext; + } + if (IMAPG(status_flags) & SA_UIDVALIDITY) { + IMAPG(status_uidvalidity)=status->uidvalidity; + } +} + +PHP_IMAP_EXPORT void mm_log(char *str, long errflg) +{ + ERRORLIST *cur = NIL; + + /* Author: CJH */ + if (errflg != NIL) { /* CJH: maybe put these into a more comprehensive log for debugging purposes? */ + if (IMAPG(imap_errorstack) == NIL) { + IMAPG(imap_errorstack) = mail_newerrorlist(); + IMAPG(imap_errorstack)->LSIZE = strlen((char*)(IMAPG(imap_errorstack)->LTEXT = (unsigned char*)cpystr(str))); + IMAPG(imap_errorstack)->errflg = errflg; + IMAPG(imap_errorstack)->next = NIL; + } else { + cur = IMAPG(imap_errorstack); + while (cur->next != NIL) { + cur = cur->next; + } + cur->next = mail_newerrorlist(); + cur = cur->next; + cur->LSIZE = strlen((char*)(cur->LTEXT = (unsigned char*)cpystr(str))); + cur->errflg = errflg; + cur->next = NIL; + } + } +} + +PHP_IMAP_EXPORT void mm_dlog(char *str) +{ + /* CJH: this is for debugging; it might be useful to allow setting + the stream to debug mode and capturing this somewhere - syslog? + php debugger? */ +} + +PHP_IMAP_EXPORT void mm_login(NETMBX *mb, char *user, char *pwd, long trial) +{ + + if (*mb->user) { + strlcpy (user, mb->user, MAILTMPLEN); + } else { + strlcpy (user, IMAPG(imap_user), MAILTMPLEN); + } + strlcpy (pwd, IMAPG(imap_password), MAILTMPLEN); +} + +PHP_IMAP_EXPORT void mm_critical(MAILSTREAM *stream) +{ +} + +PHP_IMAP_EXPORT void mm_nocritical(MAILSTREAM *stream) +{ +} + +PHP_IMAP_EXPORT long mm_diskerror(MAILSTREAM *stream, long errcode, long serious) +{ + return 1; +} + +PHP_IMAP_EXPORT void mm_fatal(char *str) +{ +} +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/ext/imap/php_imap.h b/ext/imap/php_imap.h index b4b4360860ee9..f7083d13f9e57 100644 --- a/ext/imap/php_imap.h +++ b/ext/imap/php_imap.h @@ -216,6 +216,7 @@ ZEND_BEGIN_MODULE_GLOBALS(imap) #endif /* php_stream for php_mail_gets() */ php_stream *gets_stream; + zend_bool enable_rsh; ZEND_END_MODULE_GLOBALS(imap) #ifdef ZTS diff --git a/ext/imap/php_imap.h.orig b/ext/imap/php_imap.h.orig new file mode 100644 index 0000000000000..b4b4360860ee9 --- /dev/null +++ b/ext/imap/php_imap.h.orig @@ -0,0 +1,235 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2017 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Rex Logan | + | Mark Musone | + | Brian Wang | + | Kaj-Michael Lang | + | Antoni Pamies Olive | + | Rasmus Lerdorf | + | Chuck Hagenbuch | + | Andrew Skalski | + | Hartmut Holzgraefe | + | Jani Taskinen | + | Daniel R. Kalowsky | + | PHP 4.0 updates: Zeev Suraski | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#ifndef PHP_IMAP_H +#define PHP_IMAP_H + +#if HAVE_IMAP + +#if defined(HAVE_IMAP2000) || defined(HAVE_IMAP2001) + /* these are used for quota support */ +# include "c-client.h" /* includes mail.h and rfc822.h */ +# include "imap4r1.h" /* location of c-client quota functions */ +#else +# include "mail.h" +# include "rfc822.h" +#endif + +extern zend_module_entry imap_module_entry; +#define imap_module_ptr &imap_module_entry + +#include "php_version.h" +#define PHP_IMAP_VERSION PHP_VERSION + +/* Data types */ + +#ifdef IMAP41 +#define LSIZE text.size +#define LTEXT text.data +#define DTYPE int +#define CONTENT_PART nested.part +#define CONTENT_MSG_BODY nested.msg->body +#define IMAPVER "Imap 4R1" +#else +#define LSIZE size +#define LTEXT text +#define DTYPE char +#define CONTENT_PART contents.part +#define CONTENT_MSG_BODY contents.msg.body +#define IMAPVER "Imap 4" +#endif + + +/* Determines how mm_list() and mm_lsub() are to return their results. */ +typedef enum { + FLIST_ARRAY, + FLIST_OBJECT +} folderlist_style_t; + +typedef struct php_imap_le_struct { + MAILSTREAM *imap_stream; + long flags; +} pils; + +typedef struct php_imap_mailbox_struct { + SIZEDTEXT text; + DTYPE delimiter; + long attributes; + struct php_imap_mailbox_struct *next; +} FOBJECTLIST; + +typedef struct php_imap_error_struct { + SIZEDTEXT text; + long errflg; + struct php_imap_error_struct *next; +} ERRORLIST; + +typedef struct _php_imap_message_struct { + unsigned long msgid; + struct _php_imap_message_struct *next; +} MESSAGELIST; + + +/* Functions */ + +PHP_MINIT_FUNCTION(imap); +PHP_RINIT_FUNCTION(imap); +PHP_RSHUTDOWN_FUNCTION(imap); +PHP_MINFO_FUNCTION(imap); + +PHP_FUNCTION(imap_open); +PHP_FUNCTION(imap_popen); +PHP_FUNCTION(imap_reopen); +PHP_FUNCTION(imap_num_msg); +PHP_FUNCTION(imap_num_recent); +PHP_FUNCTION(imap_headers); +PHP_FUNCTION(imap_headerinfo); +PHP_FUNCTION(imap_rfc822_parse_headers); +PHP_FUNCTION(imap_body); +PHP_FUNCTION(imap_fetchstructure); +PHP_FUNCTION(imap_fetchbody); +PHP_FUNCTION(imap_fetchmime); +PHP_FUNCTION(imap_savebody); +PHP_FUNCTION(imap_gc); +PHP_FUNCTION(imap_expunge); +PHP_FUNCTION(imap_delete); +PHP_FUNCTION(imap_undelete); +PHP_FUNCTION(imap_check); +PHP_FUNCTION(imap_close); +PHP_FUNCTION(imap_mail_copy); +PHP_FUNCTION(imap_mail_move); +PHP_FUNCTION(imap_createmailbox); +PHP_FUNCTION(imap_renamemailbox); +PHP_FUNCTION(imap_deletemailbox); +PHP_FUNCTION(imap_listmailbox); +PHP_FUNCTION(imap_scanmailbox); +PHP_FUNCTION(imap_subscribe); +PHP_FUNCTION(imap_unsubscribe); +PHP_FUNCTION(imap_append); +PHP_FUNCTION(imap_ping); +PHP_FUNCTION(imap_base64); +PHP_FUNCTION(imap_qprint); +PHP_FUNCTION(imap_8bit); +PHP_FUNCTION(imap_binary); +PHP_FUNCTION(imap_mailboxmsginfo); +PHP_FUNCTION(imap_rfc822_write_address); +PHP_FUNCTION(imap_rfc822_parse_adrlist); +PHP_FUNCTION(imap_setflag_full); +PHP_FUNCTION(imap_clearflag_full); +PHP_FUNCTION(imap_sort); +PHP_FUNCTION(imap_fetchheader); +PHP_FUNCTION(imap_fetchtext); +PHP_FUNCTION(imap_uid); +PHP_FUNCTION(imap_msgno); +PHP_FUNCTION(imap_list); +PHP_FUNCTION(imap_list_full); +PHP_FUNCTION(imap_listscan); +PHP_FUNCTION(imap_lsub); +PHP_FUNCTION(imap_lsub_full); +PHP_FUNCTION(imap_create); +PHP_FUNCTION(imap_rename); +PHP_FUNCTION(imap_status); +PHP_FUNCTION(imap_bodystruct); +PHP_FUNCTION(imap_fetch_overview); +PHP_FUNCTION(imap_mail_compose); +PHP_FUNCTION(imap_alerts); +PHP_FUNCTION(imap_errors); +PHP_FUNCTION(imap_last_error); +PHP_FUNCTION(imap_mail); +PHP_FUNCTION(imap_search); +PHP_FUNCTION(imap_utf8); +PHP_FUNCTION(imap_utf7_decode); +PHP_FUNCTION(imap_utf7_encode); +#ifdef HAVE_IMAP_MUTF7 +PHP_FUNCTION(imap_utf8_to_mutf7); +PHP_FUNCTION(imap_mutf7_to_utf8); +#endif +PHP_FUNCTION(imap_mime_header_decode); +PHP_FUNCTION(imap_thread); +PHP_FUNCTION(imap_timeout); + +#if defined(HAVE_IMAP2000) || defined(HAVE_IMAP2001) +PHP_FUNCTION(imap_get_quota); +PHP_FUNCTION(imap_get_quotaroot); +PHP_FUNCTION(imap_set_quota); +PHP_FUNCTION(imap_setacl); +PHP_FUNCTION(imap_getacl); +#endif + + +ZEND_BEGIN_MODULE_GLOBALS(imap) + char *imap_user; + char *imap_password; + + STRINGLIST *imap_alertstack; + ERRORLIST *imap_errorstack; + + STRINGLIST *imap_folders; + STRINGLIST *imap_folders_tail; + STRINGLIST *imap_sfolders; + STRINGLIST *imap_sfolders_tail; + MESSAGELIST *imap_messages; + MESSAGELIST *imap_messages_tail; + FOBJECTLIST *imap_folder_objects; + FOBJECTLIST *imap_folder_objects_tail; + FOBJECTLIST *imap_sfolder_objects; + FOBJECTLIST *imap_sfolder_objects_tail; + + folderlist_style_t folderlist_style; + long status_flags; + unsigned long status_messages; + unsigned long status_recent; + unsigned long status_unseen; + unsigned long status_uidnext; + unsigned long status_uidvalidity; +#if defined(HAVE_IMAP2000) || defined(HAVE_IMAP2001) + zval **quota_return; + zval *imap_acl_list; +#endif + /* php_stream for php_mail_gets() */ + php_stream *gets_stream; +ZEND_END_MODULE_GLOBALS(imap) + +#ifdef ZTS +# define IMAPG(v) TSRMG(imap_globals_id, zend_imap_globals *, v) +#else +# define IMAPG(v) (imap_globals.v) +#endif + +#else + +#define imap_module_ptr NULL + +#endif + +#define phpext_imap_ptr imap_module_ptr + +#endif /* PHP_IMAP_H */ diff --git a/ext/imap/tests/bug77020.phpt b/ext/imap/tests/bug77020.phpt new file mode 100644 index 0000000000000..8a65232eec6d3 --- /dev/null +++ b/ext/imap/tests/bug77020.phpt @@ -0,0 +1,15 @@ +--TEST-- +Bug #77020 (null pointer dereference in imap_mail) +--SKIPIF-- + +--FILE-- + +===DONE=== +--EXPECTF-- +Warning: imap_mail(): No message string in mail command in %s on line %d +%s +===DONE=== diff --git a/ext/imap/tests/bug77153.phpt b/ext/imap/tests/bug77153.phpt new file mode 100644 index 0000000000000..63590aee1dde4 --- /dev/null +++ b/ext/imap/tests/bug77153.phpt @@ -0,0 +1,24 @@ +--TEST-- +Bug #77153 (imap_open allows to run arbitrary shell commands via mailbox parameter) +--SKIPIF-- + +--FILE-- + " . __DIR__ . '/__bug'; +$payloadb64 = base64_encode($payload); +$server = "x -oProxyCommand=echo\t$payloadb64|base64\t-d|sh}"; +@imap_open('{'.$server.':143/imap}INBOX', '', ''); +// clean +imap_errors(); +var_dump(file_exists(__DIR__ . '/__bug')); +?> +--EXPECT-- +bool(false) +--CLEAN-- + \ No newline at end of file diff --git a/ext/libxml/libxml.c b/ext/libxml/libxml.c index a029b9f477a17..d95349dc9dd5e 100644 --- a/ext/libxml/libxml.c +++ b/ext/libxml/libxml.c @@ -308,6 +308,10 @@ static void *php_libxml_streams_IO_open_wrapper(const char *filename, const char int isescaped=0; xmlURI *uri; + if (strstr(filename, "%00")) { + php_error_docref(NULL, E_WARNING, "URI must not contain percent-encoded NUL bytes"); + return NULL; + } uri = xmlParseURI(filename); if (uri && (uri->scheme == NULL || @@ -438,6 +442,11 @@ php_libxml_output_buffer_create_filename(const char *URI, if (URI == NULL) return(NULL); + if (strstr(URI, "%00")) { + php_error_docref(NULL, E_WARNING, "URI must not contain percent-encoded NUL bytes"); + return NULL; + } + puri = xmlParseURI(URI); if (puri != NULL) { if (puri->scheme != NULL) diff --git a/ext/libxml/libxml.c.orig b/ext/libxml/libxml.c.orig new file mode 100644 index 0000000000000..a029b9f477a17 --- /dev/null +++ b/ext/libxml/libxml.c.orig @@ -0,0 +1,1365 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2017 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Shane Caraveo | + | Wez Furlong | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#define IS_EXT_MODULE + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "SAPI.h" + +#define PHP_XML_INTERNAL +#include "zend_variables.h" +#include "ext/standard/php_string.h" +#include "ext/standard/info.h" +#include "ext/standard/file.h" + +#if HAVE_LIBXML + +#include +#include +#include +#include +#include +#include +#ifdef LIBXML_SCHEMAS_ENABLED +#include +#include +#endif + +#include "php_libxml.h" + +#define PHP_LIBXML_ERROR 0 +#define PHP_LIBXML_CTX_ERROR 1 +#define PHP_LIBXML_CTX_WARNING 2 + +/* a true global for initialization */ +static int _php_libxml_initialized = 0; +static int _php_libxml_per_request_initialization = 1; +static xmlExternalEntityLoader _php_libxml_default_entity_loader; + +typedef struct _php_libxml_func_handler { + php_libxml_export_node export_func; +} php_libxml_func_handler; + +static HashTable php_libxml_exports; + +static ZEND_DECLARE_MODULE_GLOBALS(libxml) +static PHP_GINIT_FUNCTION(libxml); + +static PHP_FUNCTION(libxml_set_streams_context); +static PHP_FUNCTION(libxml_use_internal_errors); +static PHP_FUNCTION(libxml_get_last_error); +static PHP_FUNCTION(libxml_clear_errors); +static PHP_FUNCTION(libxml_get_errors); +static PHP_FUNCTION(libxml_set_external_entity_loader); +static PHP_FUNCTION(libxml_disable_entity_loader); + +static zend_class_entry *libxmlerror_class_entry; + +/* {{{ dynamically loadable module stuff */ +#ifdef COMPILE_DL_LIBXML +#ifdef ZTS +ZEND_TSRMLS_CACHE_DEFINE() +#endif +ZEND_GET_MODULE(libxml) +#endif /* COMPILE_DL_LIBXML */ +/* }}} */ + +/* {{{ function prototypes */ +static PHP_MINIT_FUNCTION(libxml); +static PHP_RINIT_FUNCTION(libxml); +static PHP_RSHUTDOWN_FUNCTION(libxml); +static PHP_MSHUTDOWN_FUNCTION(libxml); +static PHP_MINFO_FUNCTION(libxml); +static int php_libxml_post_deactivate(void); + +/* }}} */ + +/* {{{ arginfo */ +ZEND_BEGIN_ARG_INFO(arginfo_libxml_set_streams_context, 0) + ZEND_ARG_INFO(0, context) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_libxml_use_internal_errors, 0, 0, 0) + ZEND_ARG_INFO(0, use_errors) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_libxml_get_last_error, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_libxml_get_errors, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_libxml_clear_errors, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_libxml_disable_entity_loader, 0, 0, 0) + ZEND_ARG_INFO(0, disable) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_libxml_set_external_entity_loader, 0, 0, 1) + ZEND_ARG_INFO(0, resolver_function) +ZEND_END_ARG_INFO() +/* }}} */ + +/* {{{ extension definition structures */ +static const zend_function_entry libxml_functions[] = { + PHP_FE(libxml_set_streams_context, arginfo_libxml_set_streams_context) + PHP_FE(libxml_use_internal_errors, arginfo_libxml_use_internal_errors) + PHP_FE(libxml_get_last_error, arginfo_libxml_get_last_error) + PHP_FE(libxml_clear_errors, arginfo_libxml_clear_errors) + PHP_FE(libxml_get_errors, arginfo_libxml_get_errors) + PHP_FE(libxml_disable_entity_loader, arginfo_libxml_disable_entity_loader) + PHP_FE(libxml_set_external_entity_loader, arginfo_libxml_set_external_entity_loader) + PHP_FE_END +}; + +zend_module_entry libxml_module_entry = { + STANDARD_MODULE_HEADER, + "libxml", /* extension name */ + libxml_functions, /* extension function list */ + PHP_MINIT(libxml), /* extension-wide startup function */ + PHP_MSHUTDOWN(libxml), /* extension-wide shutdown function */ + PHP_RINIT(libxml), /* per-request startup function */ + PHP_RSHUTDOWN(libxml), /* per-request shutdown function */ + PHP_MINFO(libxml), /* information function */ + PHP_LIBXML_VERSION, + PHP_MODULE_GLOBALS(libxml), /* globals descriptor */ + PHP_GINIT(libxml), /* globals ctor */ + NULL, /* globals dtor */ + php_libxml_post_deactivate, /* post deactivate */ + STANDARD_MODULE_PROPERTIES_EX +}; + +/* }}} */ + +/* {{{ internal functions for interoperability */ +static int php_libxml_clear_object(php_libxml_node_object *object) +{ + if (object->properties) { + object->properties = NULL; + } + php_libxml_decrement_node_ptr(object); + return php_libxml_decrement_doc_ref(object); +} + +static int php_libxml_unregister_node(xmlNodePtr nodep) +{ + php_libxml_node_object *wrapper; + + php_libxml_node_ptr *nodeptr = nodep->_private; + + if (nodeptr != NULL) { + wrapper = nodeptr->_private; + if (wrapper) { + php_libxml_clear_object(wrapper); + } else { + if (nodeptr->node != NULL && nodeptr->node->type != XML_DOCUMENT_NODE) { + nodeptr->node->_private = NULL; + } + nodeptr->node = NULL; + } + } + + return -1; +} + +static void php_libxml_node_free(xmlNodePtr node) +{ + if(node) { + if (node->_private != NULL) { + ((php_libxml_node_ptr *) node->_private)->node = NULL; + } + switch (node->type) { + case XML_ATTRIBUTE_NODE: + xmlFreeProp((xmlAttrPtr) node); + break; + case XML_ENTITY_DECL: + case XML_ELEMENT_DECL: + case XML_ATTRIBUTE_DECL: + break; + case XML_NOTATION_NODE: + /* These require special handling */ + if (node->name != NULL) { + xmlFree((char *) node->name); + } + if (((xmlEntityPtr) node)->ExternalID != NULL) { + xmlFree((char *) ((xmlEntityPtr) node)->ExternalID); + } + if (((xmlEntityPtr) node)->SystemID != NULL) { + xmlFree((char *) ((xmlEntityPtr) node)->SystemID); + } + xmlFree(node); + break; + case XML_NAMESPACE_DECL: + if (node->ns) { + xmlFreeNs(node->ns); + node->ns = NULL; + } + node->type = XML_ELEMENT_NODE; + default: + xmlFreeNode(node); + } + } +} + +PHP_LIBXML_API void php_libxml_node_free_list(xmlNodePtr node) +{ + xmlNodePtr curnode; + + if (node != NULL) { + curnode = node; + while (curnode != NULL) { + node = curnode; + switch (node->type) { + /* Skip property freeing for the following types */ + case XML_NOTATION_NODE: + case XML_ENTITY_DECL: + break; + case XML_ENTITY_REF_NODE: + php_libxml_node_free_list((xmlNodePtr) node->properties); + break; + case XML_ATTRIBUTE_NODE: + if ((node->doc != NULL) && (((xmlAttrPtr) node)->atype == XML_ATTRIBUTE_ID)) { + xmlRemoveID(node->doc, (xmlAttrPtr) node); + } + case XML_ATTRIBUTE_DECL: + case XML_DTD_NODE: + case XML_DOCUMENT_TYPE_NODE: + case XML_NAMESPACE_DECL: + case XML_TEXT_NODE: + php_libxml_node_free_list(node->children); + break; + default: + php_libxml_node_free_list(node->children); + php_libxml_node_free_list((xmlNodePtr) node->properties); + } + + curnode = node->next; + xmlUnlinkNode(node); + if (php_libxml_unregister_node(node) == 0) { + node->doc = NULL; + } + php_libxml_node_free(node); + } + } +} + +/* }}} */ + +/* {{{ startup, shutdown and info functions */ +static PHP_GINIT_FUNCTION(libxml) +{ +#if defined(COMPILE_DL_LIBXML) && defined(ZTS) + ZEND_TSRMLS_CACHE_UPDATE(); +#endif + ZVAL_UNDEF(&libxml_globals->stream_context); + libxml_globals->error_buffer.s = NULL; + libxml_globals->error_list = NULL; + ZVAL_UNDEF(&libxml_globals->entity_loader.object); + libxml_globals->entity_loader.fci.size = 0; + libxml_globals->entity_loader_disabled = 0; +} + +static void _php_libxml_destroy_fci(zend_fcall_info *fci, zval *object) +{ + if (fci->size > 0) { + zval_ptr_dtor(&fci->function_name); + fci->size = 0; + } + if (!Z_ISUNDEF_P(object)) { + zval_ptr_dtor(object); + ZVAL_UNDEF(object); + } +} + +/* Channel libxml file io layer through the PHP streams subsystem. + * This allows use of ftps:// and https:// urls */ + +static void *php_libxml_streams_IO_open_wrapper(const char *filename, const char *mode, const int read_only) +{ + php_stream_statbuf ssbuf; + php_stream_context *context = NULL; + php_stream_wrapper *wrapper = NULL; + char *resolved_path; + const char *path_to_open = NULL; + void *ret_val = NULL; + int isescaped=0; + xmlURI *uri; + + + uri = xmlParseURI(filename); + if (uri && (uri->scheme == NULL || + (xmlStrncmp(BAD_CAST uri->scheme, BAD_CAST "file", 4) == 0))) { + resolved_path = xmlURIUnescapeString(filename, 0, NULL); + isescaped = 1; +#if LIBXML_VERSION >= 20902 && defined(PHP_WIN32) + /* Libxml 2.9.2 prefixes local paths with file:/ instead of file://, + thus the php stream wrapper will fail on a valid case. For this + reason the prefix is rather better cut off. */ + { + size_t pre_len = sizeof("file:/") - 1; + + if (strncasecmp(resolved_path, "file:/", pre_len) == 0 + && '/' != resolved_path[pre_len]) { + xmlChar *tmp = xmlStrdup(resolved_path + pre_len); + xmlFree(resolved_path); + resolved_path = tmp; + } + } +#endif + } else { + resolved_path = (char *)filename; + } + + if (uri) { + xmlFreeURI(uri); + } + + if (resolved_path == NULL) { + return NULL; + } + + /* logic copied from _php_stream_stat, but we only want to fail + if the wrapper supports stat, otherwise, figure it out from + the open. This logic is only to support hiding warnings + that the streams layer puts out at times, but for libxml we + may try to open files that don't exist, but it is not a failure + in xml processing (eg. DTD files) */ + wrapper = php_stream_locate_url_wrapper(resolved_path, &path_to_open, 0); + if (wrapper && read_only && wrapper->wops->url_stat) { + if (wrapper->wops->url_stat(wrapper, path_to_open, PHP_STREAM_URL_STAT_QUIET, &ssbuf, NULL) == -1) { + if (isescaped) { + xmlFree(resolved_path); + } + return NULL; + } + } + + context = php_stream_context_from_zval(Z_ISUNDEF(LIBXML(stream_context))? NULL : &LIBXML(stream_context), 0); + + ret_val = php_stream_open_wrapper_ex(path_to_open, (char *)mode, REPORT_ERRORS, NULL, context); + if (isescaped) { + xmlFree(resolved_path); + } + return ret_val; +} + +static void *php_libxml_streams_IO_open_read_wrapper(const char *filename) +{ + return php_libxml_streams_IO_open_wrapper(filename, "rb", 1); +} + +static void *php_libxml_streams_IO_open_write_wrapper(const char *filename) +{ + return php_libxml_streams_IO_open_wrapper(filename, "wb", 0); +} + +static int php_libxml_streams_IO_read(void *context, char *buffer, int len) +{ + return php_stream_read((php_stream*)context, buffer, len); +} + +static int php_libxml_streams_IO_write(void *context, const char *buffer, int len) +{ + if (CG(unclean_shutdown)) { + return -1; + } + return php_stream_write((php_stream*)context, buffer, len); +} + +static int php_libxml_streams_IO_close(void *context) +{ + return php_stream_close((php_stream*)context); +} + +static xmlParserInputBufferPtr +php_libxml_input_buffer_create_filename(const char *URI, xmlCharEncoding enc) +{ + xmlParserInputBufferPtr ret; + void *context = NULL; + + if (LIBXML(entity_loader_disabled)) { + return NULL; + } + + if (URI == NULL) + return(NULL); + + context = php_libxml_streams_IO_open_read_wrapper(URI); + + if (context == NULL) { + return(NULL); + } + + /* Allocate the Input buffer front-end. */ + ret = xmlAllocParserInputBuffer(enc); + if (ret != NULL) { + ret->context = context; + ret->readcallback = php_libxml_streams_IO_read; + ret->closecallback = php_libxml_streams_IO_close; + } else + php_libxml_streams_IO_close(context); + + return(ret); +} + +static xmlOutputBufferPtr +php_libxml_output_buffer_create_filename(const char *URI, + xmlCharEncodingHandlerPtr encoder, + int compression ATTRIBUTE_UNUSED) +{ + xmlOutputBufferPtr ret; + xmlURIPtr puri; + void *context = NULL; + char *unescaped = NULL; + + if (URI == NULL) + return(NULL); + + puri = xmlParseURI(URI); + if (puri != NULL) { + if (puri->scheme != NULL) + unescaped = xmlURIUnescapeString(URI, 0, NULL); + xmlFreeURI(puri); + } + + if (unescaped != NULL) { + context = php_libxml_streams_IO_open_write_wrapper(unescaped); + xmlFree(unescaped); + } + + /* try with a non-escaped URI this may be a strange filename */ + if (context == NULL) { + context = php_libxml_streams_IO_open_write_wrapper(URI); + } + + if (context == NULL) { + return(NULL); + } + + /* Allocate the Output buffer front-end. */ + ret = xmlAllocOutputBuffer(encoder); + if (ret != NULL) { + ret->context = context; + ret->writecallback = php_libxml_streams_IO_write; + ret->closecallback = php_libxml_streams_IO_close; + } + + return(ret); +} + +static int _php_libxml_free_error(xmlErrorPtr error) +{ + /* This will free the libxml alloc'd memory */ + xmlResetError(error); + return 1; +} + +static void _php_list_set_error_structure(xmlErrorPtr error, const char *msg) +{ + xmlError error_copy; + int ret; + + + memset(&error_copy, 0, sizeof(xmlError)); + + if (error) { + ret = xmlCopyError(error, &error_copy); + } else { + error_copy.domain = 0; + error_copy.code = XML_ERR_INTERNAL_ERROR; + error_copy.level = XML_ERR_ERROR; + error_copy.line = 0; + error_copy.node = NULL; + error_copy.int1 = 0; + error_copy.int2 = 0; + error_copy.ctxt = NULL; + error_copy.message = (char*)xmlStrdup((xmlChar*)msg); + error_copy.file = NULL; + error_copy.str1 = NULL; + error_copy.str2 = NULL; + error_copy.str3 = NULL; + ret = 0; + } + + if (ret == 0) { + zend_llist_add_element(LIBXML(error_list), &error_copy); + } +} + +static void php_libxml_ctx_error_level(int level, void *ctx, const char *msg) +{ + xmlParserCtxtPtr parser; + + parser = (xmlParserCtxtPtr) ctx; + + if (parser != NULL && parser->input != NULL) { + if (parser->input->filename) { + php_error_docref(NULL, level, "%s in %s, line: %d", msg, parser->input->filename, parser->input->line); + } else { + php_error_docref(NULL, level, "%s in Entity, line: %d", msg, parser->input->line); + } + } +} + +void php_libxml_issue_error(int level, const char *msg) +{ + if (LIBXML(error_list)) { + _php_list_set_error_structure(NULL, msg); + } else { + php_error_docref(NULL, level, "%s", msg); + } +} + +static void php_libxml_internal_error_handler(int error_type, void *ctx, const char **msg, va_list ap) +{ + char *buf; + int len, len_iter, output = 0; + + + len = vspprintf(&buf, 0, *msg, ap); + len_iter = len; + + /* remove any trailing \n */ + while (len_iter && buf[--len_iter] == '\n') { + buf[len_iter] = '\0'; + output = 1; + } + + smart_str_appendl(&LIBXML(error_buffer), buf, len); + + efree(buf); + + if (output == 1) { + if (LIBXML(error_list)) { + _php_list_set_error_structure(NULL, ZSTR_VAL(LIBXML(error_buffer).s)); + } else { + switch (error_type) { + case PHP_LIBXML_CTX_ERROR: + php_libxml_ctx_error_level(E_WARNING, ctx, ZSTR_VAL(LIBXML(error_buffer).s)); + break; + case PHP_LIBXML_CTX_WARNING: + php_libxml_ctx_error_level(E_NOTICE, ctx, ZSTR_VAL(LIBXML(error_buffer).s)); + break; + default: + php_error_docref(NULL, E_WARNING, "%s", ZSTR_VAL(LIBXML(error_buffer).s)); + } + } + smart_str_free(&LIBXML(error_buffer)); + } +} + +static xmlParserInputPtr _php_libxml_external_entity_loader(const char *URL, + const char *ID, xmlParserCtxtPtr context) +{ + xmlParserInputPtr ret = NULL; + const char *resource = NULL; + zval *ctxzv, retval; + zval params[3]; + int status; + zend_fcall_info *fci; + + fci = &LIBXML(entity_loader).fci; + + if (fci->size == 0) { + /* no custom user-land callback set up; delegate to original loader */ + return _php_libxml_default_entity_loader(URL, ID, context); + } + + if (ID != NULL) { + ZVAL_STRING(¶ms[0], ID); + } else { + ZVAL_UNDEF(¶ms[0]); + } + if (URL != NULL) { + ZVAL_STRING(¶ms[1], URL); + } else { + ZVAL_UNDEF(¶ms[1]); + } + ctxzv = ¶ms[2]; + array_init_size(ctxzv, 4); + +#define ADD_NULL_OR_STRING_KEY(memb) \ + if (context->memb == NULL) { \ + add_assoc_null_ex(ctxzv, #memb, sizeof(#memb) - 1); \ + } else { \ + add_assoc_string_ex(ctxzv, #memb, sizeof(#memb) - 1, \ + (char *)context->memb); \ + } + + ADD_NULL_OR_STRING_KEY(directory) + ADD_NULL_OR_STRING_KEY(intSubName) + ADD_NULL_OR_STRING_KEY(extSubURI) + ADD_NULL_OR_STRING_KEY(extSubSystem) + +#undef ADD_NULL_OR_STRING_KEY + + fci->retval = &retval; + fci->params = params; + fci->param_count = sizeof(params)/sizeof(*params); + fci->no_separation = 1; + + status = zend_call_function(fci, &LIBXML(entity_loader).fcc); + if (status != SUCCESS || Z_ISUNDEF(retval)) { + php_libxml_ctx_error(context, + "Call to user entity loader callback '%s' has failed", + Z_STRVAL(fci->function_name)); + } else { + /* + retval_ptr = *fci->retval_ptr_ptr; + if (retval_ptr == NULL) { + php_libxml_ctx_error(context, + "Call to user entity loader callback '%s' has failed; " + "probably it has thrown an exception", + fci->function_name); + } else */ if (Z_TYPE(retval) == IS_STRING) { +is_string: + resource = Z_STRVAL(retval); + } else if (Z_TYPE(retval) == IS_RESOURCE) { + php_stream *stream; + php_stream_from_zval_no_verify(stream, &retval); + if (stream == NULL) { + php_libxml_ctx_error(context, + "The user entity loader callback '%s' has returned a " + "resource, but it is not a stream", + Z_STRVAL(fci->function_name)); + } else { + /* TODO: allow storing the encoding in the stream context? */ + xmlCharEncoding enc = XML_CHAR_ENCODING_NONE; + xmlParserInputBufferPtr pib = xmlAllocParserInputBuffer(enc); + if (pib == NULL) { + php_libxml_ctx_error(context, "Could not allocate parser " + "input buffer"); + } else { + /* make stream not being closed when the zval is freed */ + ++GC_REFCOUNT(stream->res); + pib->context = stream; + pib->readcallback = php_libxml_streams_IO_read; + pib->closecallback = php_libxml_streams_IO_close; + + ret = xmlNewIOInputStream(context, pib, enc); + if (ret == NULL) { + xmlFreeParserInputBuffer(pib); + } + } + } + } else if (Z_TYPE(retval) != IS_NULL) { + /* retval not string nor resource nor null; convert to string */ + convert_to_string(&retval); + goto is_string; + } /* else is null; don't try anything */ + } + + if (ret == NULL) { + if (resource == NULL) { + if (ID == NULL) { + ID = "NULL"; + } + php_libxml_ctx_error(context, + "Failed to load external entity \"%s\"\n", ID); + } else { + /* we got the resource in the form of a string; open it */ + ret = xmlNewInputFromFile(context, resource); + } + } + + zval_ptr_dtor(¶ms[0]); + zval_ptr_dtor(¶ms[1]); + zval_ptr_dtor(¶ms[2]); + zval_ptr_dtor(&retval); + return ret; +} + +static xmlParserInputPtr _php_libxml_pre_ext_ent_loader(const char *URL, + const char *ID, xmlParserCtxtPtr context) +{ + + /* Check whether we're running in a PHP context, since the entity loader + * we've defined is an application level (true global) setting. + * If we are, we also want to check whether we've finished activating + * the modules (RINIT phase). Using our external entity loader during a + * RINIT should not be problem per se (though during MINIT it is, because + * we don't even have a resource list by then), but then whether one + * extension would be using the custom external entity loader or not + * could depend on extension loading order + * (if _php_libxml_per_request_initialization */ + if (xmlGenericError == php_libxml_error_handler && PG(modules_activated)) { + return _php_libxml_external_entity_loader(URL, ID, context); + } else { + return _php_libxml_default_entity_loader(URL, ID, context); + } +} + +PHP_LIBXML_API void php_libxml_ctx_error(void *ctx, const char *msg, ...) +{ + va_list args; + va_start(args, msg); + php_libxml_internal_error_handler(PHP_LIBXML_CTX_ERROR, ctx, &msg, args); + va_end(args); +} + +PHP_LIBXML_API void php_libxml_ctx_warning(void *ctx, const char *msg, ...) +{ + va_list args; + va_start(args, msg); + php_libxml_internal_error_handler(PHP_LIBXML_CTX_WARNING, ctx, &msg, args); + va_end(args); +} + +PHP_LIBXML_API void php_libxml_structured_error_handler(void *userData, xmlErrorPtr error) +{ + _php_list_set_error_structure(error, NULL); + + return; +} + +PHP_LIBXML_API void php_libxml_error_handler(void *ctx, const char *msg, ...) +{ + va_list args; + va_start(args, msg); + php_libxml_internal_error_handler(PHP_LIBXML_ERROR, ctx, &msg, args); + va_end(args); +} + +static void php_libxml_exports_dtor(zval *zv) +{ + free(Z_PTR_P(zv)); +} + +PHP_LIBXML_API void php_libxml_initialize(void) +{ + if (!_php_libxml_initialized) { + /* we should be the only one's to ever init!! */ + xmlInitParser(); + + _php_libxml_default_entity_loader = xmlGetExternalEntityLoader(); + xmlSetExternalEntityLoader(_php_libxml_pre_ext_ent_loader); + + zend_hash_init(&php_libxml_exports, 0, NULL, php_libxml_exports_dtor, 1); + + _php_libxml_initialized = 1; + } +} + +PHP_LIBXML_API void php_libxml_shutdown(void) +{ + if (_php_libxml_initialized) { +#if defined(LIBXML_SCHEMAS_ENABLED) + xmlRelaxNGCleanupTypes(); +#endif + /* xmlCleanupParser(); */ + zend_hash_destroy(&php_libxml_exports); + + xmlSetExternalEntityLoader(_php_libxml_default_entity_loader); + _php_libxml_initialized = 0; + } +} + +PHP_LIBXML_API void php_libxml_switch_context(zval *context, zval *oldcontext) +{ + if (oldcontext) { + ZVAL_COPY_VALUE(oldcontext, &LIBXML(stream_context)); + } + if (context) { + ZVAL_COPY_VALUE(&LIBXML(stream_context), context); + } +} + +static PHP_MINIT_FUNCTION(libxml) +{ + zend_class_entry ce; + + php_libxml_initialize(); + + REGISTER_LONG_CONSTANT("LIBXML_VERSION", LIBXML_VERSION, CONST_CS | CONST_PERSISTENT); + REGISTER_STRING_CONSTANT("LIBXML_DOTTED_VERSION", LIBXML_DOTTED_VERSION, CONST_CS | CONST_PERSISTENT); + REGISTER_STRING_CONSTANT("LIBXML_LOADED_VERSION", (char *)xmlParserVersion, CONST_CS | CONST_PERSISTENT); + + /* For use with loading xml */ + REGISTER_LONG_CONSTANT("LIBXML_NOENT", XML_PARSE_NOENT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_DTDLOAD", XML_PARSE_DTDLOAD, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_DTDATTR", XML_PARSE_DTDATTR, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_DTDVALID", XML_PARSE_DTDVALID, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_NOERROR", XML_PARSE_NOERROR, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_NOWARNING", XML_PARSE_NOWARNING, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_NOBLANKS", XML_PARSE_NOBLANKS, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_XINCLUDE", XML_PARSE_XINCLUDE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_NSCLEAN", XML_PARSE_NSCLEAN, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_NOCDATA", XML_PARSE_NOCDATA, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_NONET", XML_PARSE_NONET, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_PEDANTIC", XML_PARSE_PEDANTIC, CONST_CS | CONST_PERSISTENT); +#if LIBXML_VERSION >= 20621 + REGISTER_LONG_CONSTANT("LIBXML_COMPACT", XML_PARSE_COMPACT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_NOXMLDECL", XML_SAVE_NO_DECL, CONST_CS | CONST_PERSISTENT); +#endif +#if LIBXML_VERSION >= 20703 + REGISTER_LONG_CONSTANT("LIBXML_PARSEHUGE", XML_PARSE_HUGE, CONST_CS | CONST_PERSISTENT); +#endif +#if LIBXML_VERSION >= 20900 + REGISTER_LONG_CONSTANT("LIBXML_BIGLINES", XML_PARSE_BIG_LINES, CONST_CS | CONST_PERSISTENT); +#endif + REGISTER_LONG_CONSTANT("LIBXML_NOEMPTYTAG", LIBXML_SAVE_NOEMPTYTAG, CONST_CS | CONST_PERSISTENT); + + /* Schema validation options */ +#if defined(LIBXML_SCHEMAS_ENABLED) && LIBXML_VERSION >= 20614 + REGISTER_LONG_CONSTANT("LIBXML_SCHEMA_CREATE", XML_SCHEMA_VAL_VC_I_CREATE, CONST_CS | CONST_PERSISTENT); +#endif + + /* Additional constants for use with loading html */ +#if LIBXML_VERSION >= 20707 + REGISTER_LONG_CONSTANT("LIBXML_HTML_NOIMPLIED", HTML_PARSE_NOIMPLIED, CONST_CS | CONST_PERSISTENT); +#endif + +#if LIBXML_VERSION >= 20708 + REGISTER_LONG_CONSTANT("LIBXML_HTML_NODEFDTD", HTML_PARSE_NODEFDTD, CONST_CS | CONST_PERSISTENT); +#endif + + /* Error levels */ + REGISTER_LONG_CONSTANT("LIBXML_ERR_NONE", XML_ERR_NONE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_ERR_WARNING", XML_ERR_WARNING, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_ERR_ERROR", XML_ERR_ERROR, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LIBXML_ERR_FATAL", XML_ERR_FATAL, CONST_CS | CONST_PERSISTENT); + + INIT_CLASS_ENTRY(ce, "LibXMLError", NULL); + libxmlerror_class_entry = zend_register_internal_class(&ce); + + if (sapi_module.name) { + static const char * const supported_sapis[] = { + "cgi-fcgi", + "fpm-fcgi", + "litespeed", + NULL + }; + const char * const *sapi_name; + + for (sapi_name = supported_sapis; *sapi_name; sapi_name++) { + if (strcmp(sapi_module.name, *sapi_name) == 0) { + _php_libxml_per_request_initialization = 0; + break; + } + } + } + + if (!_php_libxml_per_request_initialization) { + /* report errors via handler rather than stderr */ + xmlSetGenericErrorFunc(NULL, php_libxml_error_handler); + xmlParserInputBufferCreateFilenameDefault(php_libxml_input_buffer_create_filename); + xmlOutputBufferCreateFilenameDefault(php_libxml_output_buffer_create_filename); + } + + return SUCCESS; +} + + +static PHP_RINIT_FUNCTION(libxml) +{ + if (_php_libxml_per_request_initialization) { + /* report errors via handler rather than stderr */ + xmlSetGenericErrorFunc(NULL, php_libxml_error_handler); + xmlParserInputBufferCreateFilenameDefault(php_libxml_input_buffer_create_filename); + xmlOutputBufferCreateFilenameDefault(php_libxml_output_buffer_create_filename); + + /* Enable the entity loader by default. This ensures that + * other threads/requests that might have disabled the loader + * do not affect the current request. + */ + LIBXML(entity_loader_disabled) = 0; + } + return SUCCESS; +} + +static PHP_RSHUTDOWN_FUNCTION(libxml) +{ + _php_libxml_destroy_fci(&LIBXML(entity_loader).fci, &LIBXML(entity_loader).object); + + return SUCCESS; +} + +static PHP_MSHUTDOWN_FUNCTION(libxml) +{ + if (!_php_libxml_per_request_initialization) { + xmlSetGenericErrorFunc(NULL, NULL); + + xmlParserInputBufferCreateFilenameDefault(NULL); + xmlOutputBufferCreateFilenameDefault(NULL); + } + php_libxml_shutdown(); + + return SUCCESS; +} + +static int php_libxml_post_deactivate(void) +{ + /* reset libxml generic error handling */ + if (_php_libxml_per_request_initialization) { + xmlSetGenericErrorFunc(NULL, NULL); + + xmlParserInputBufferCreateFilenameDefault(NULL); + xmlOutputBufferCreateFilenameDefault(NULL); + } + xmlSetStructuredErrorFunc(NULL, NULL); + + /* the steam_context resource will be released by resource list destructor */ + ZVAL_UNDEF(&LIBXML(stream_context)); + smart_str_free(&LIBXML(error_buffer)); + if (LIBXML(error_list)) { + zend_llist_destroy(LIBXML(error_list)); + efree(LIBXML(error_list)); + LIBXML(error_list) = NULL; + } + xmlResetLastError(); + + return SUCCESS; +} + + +static PHP_MINFO_FUNCTION(libxml) +{ + php_info_print_table_start(); + php_info_print_table_row(2, "libXML support", "active"); + php_info_print_table_row(2, "libXML Compiled Version", LIBXML_DOTTED_VERSION); + php_info_print_table_row(2, "libXML Loaded Version", (char *)xmlParserVersion); + php_info_print_table_row(2, "libXML streams", "enabled"); + php_info_print_table_end(); +} +/* }}} */ + +/* {{{ proto void libxml_set_streams_context(resource streams_context) + Set the streams context for the next libxml document load or write */ +static PHP_FUNCTION(libxml_set_streams_context) +{ + zval *arg; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &arg) == FAILURE) { + return; + } + if (!Z_ISUNDEF(LIBXML(stream_context))) { + zval_ptr_dtor(&LIBXML(stream_context)); + ZVAL_UNDEF(&LIBXML(stream_context)); + } + ZVAL_COPY(&LIBXML(stream_context), arg); +} +/* }}} */ + +/* {{{ proto bool libxml_use_internal_errors([boolean use_errors]) + Disable libxml errors and allow user to fetch error information as needed */ +static PHP_FUNCTION(libxml_use_internal_errors) +{ + xmlStructuredErrorFunc current_handler; + zend_bool use_errors=0, retval; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &use_errors) == FAILURE) { + return; + } + + current_handler = xmlStructuredError; + if (current_handler && current_handler == php_libxml_structured_error_handler) { + retval = 1; + } else { + retval = 0; + } + + if (ZEND_NUM_ARGS() == 0) { + RETURN_BOOL(retval); + } + + if (use_errors == 0) { + xmlSetStructuredErrorFunc(NULL, NULL); + if (LIBXML(error_list)) { + zend_llist_destroy(LIBXML(error_list)); + efree(LIBXML(error_list)); + LIBXML(error_list) = NULL; + } + } else { + xmlSetStructuredErrorFunc(NULL, php_libxml_structured_error_handler); + if (LIBXML(error_list) == NULL) { + LIBXML(error_list) = (zend_llist *) emalloc(sizeof(zend_llist)); + zend_llist_init(LIBXML(error_list), sizeof(xmlError), (llist_dtor_func_t) _php_libxml_free_error, 0); + } + } + RETURN_BOOL(retval); +} +/* }}} */ + +/* {{{ proto object libxml_get_last_error() + Retrieve last error from libxml */ +static PHP_FUNCTION(libxml_get_last_error) +{ + xmlErrorPtr error; + + error = xmlGetLastError(); + + if (error) { + object_init_ex(return_value, libxmlerror_class_entry); + add_property_long(return_value, "level", error->level); + add_property_long(return_value, "code", error->code); + add_property_long(return_value, "column", error->int2); + if (error->message) { + add_property_string(return_value, "message", error->message); + } else { + add_property_stringl(return_value, "message", "", 0); + } + if (error->file) { + add_property_string(return_value, "file", error->file); + } else { + add_property_stringl(return_value, "file", "", 0); + } + add_property_long(return_value, "line", error->line); + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto object libxml_get_errors() + Retrieve array of errors */ +static PHP_FUNCTION(libxml_get_errors) +{ + + xmlErrorPtr error; + + if (array_init(return_value) == FAILURE) { + RETURN_FALSE; + } + + if (LIBXML(error_list)) { + + error = zend_llist_get_first(LIBXML(error_list)); + + while (error != NULL) { + zval z_error; + + object_init_ex(&z_error, libxmlerror_class_entry); + add_property_long_ex(&z_error, "level", sizeof("level") - 1, error->level); + add_property_long_ex(&z_error, "code", sizeof("code") - 1, error->code); + add_property_long_ex(&z_error, "column", sizeof("column") - 1, error->int2 ); + if (error->message) { + add_property_string_ex(&z_error, "message", sizeof("message") - 1, error->message); + } else { + add_property_stringl_ex(&z_error, "message", sizeof("message") - 1, "", 0); + } + if (error->file) { + add_property_string_ex(&z_error, "file", sizeof("file") - 1, error->file); + } else { + add_property_stringl_ex(&z_error, "file", sizeof("file") - 1, "", 0); + } + add_property_long_ex(&z_error, "line", sizeof("line") - 1, error->line); + add_next_index_zval(return_value, &z_error); + + error = zend_llist_get_next(LIBXML(error_list)); + } + } +} +/* }}} */ + +/* {{{ proto void libxml_clear_errors() + Clear last error from libxml */ +static PHP_FUNCTION(libxml_clear_errors) +{ + xmlResetLastError(); + if (LIBXML(error_list)) { + zend_llist_clean(LIBXML(error_list)); + } +} +/* }}} */ + +PHP_LIBXML_API zend_bool php_libxml_disable_entity_loader(zend_bool disable) /* {{{ */ +{ + zend_bool old = LIBXML(entity_loader_disabled); + + LIBXML(entity_loader_disabled) = disable; + return old; +} /* }}} */ + +/* {{{ proto bool libxml_disable_entity_loader([boolean disable]) + Disable/Enable ability to load external entities */ +static PHP_FUNCTION(libxml_disable_entity_loader) +{ + zend_bool disable = 1; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &disable) == FAILURE) { + return; + } + + RETURN_BOOL(php_libxml_disable_entity_loader(disable)); +} +/* }}} */ + +/* {{{ proto void libxml_set_external_entity_loader(callback resolver_function) + Changes the default external entity loader */ +static PHP_FUNCTION(libxml_set_external_entity_loader) +{ + zend_fcall_info fci; + zend_fcall_info_cache fcc; + if (zend_parse_parameters(ZEND_NUM_ARGS(), "f!", &fci, &fcc) + == FAILURE) { + return; + } + + _php_libxml_destroy_fci(&LIBXML(entity_loader).fci, &LIBXML(entity_loader).object); + + if (fci.size > 0) { /* argument not null */ + LIBXML(entity_loader).fci = fci; + Z_ADDREF(fci.function_name); + if (fci.object != NULL) { + ZVAL_OBJ(&LIBXML(entity_loader).object, fci.object); + Z_ADDREF(LIBXML(entity_loader).object); + } + LIBXML(entity_loader).fcc = fcc; + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ Common functions shared by extensions */ +int php_libxml_xmlCheckUTF8(const unsigned char *s) +{ + int i; + unsigned char c; + + for (i = 0; (c = s[i++]);) { + if ((c & 0x80) == 0) { + } else if ((c & 0xe0) == 0xc0) { + if ((s[i++] & 0xc0) != 0x80) { + return 0; + } + } else if ((c & 0xf0) == 0xe0) { + if ((s[i++] & 0xc0) != 0x80 || (s[i++] & 0xc0) != 0x80) { + return 0; + } + } else if ((c & 0xf8) == 0xf0) { + if ((s[i++] & 0xc0) != 0x80 || (s[i++] & 0xc0) != 0x80 || (s[i++] & 0xc0) != 0x80) { + return 0; + } + } else { + return 0; + } + } + return 1; +} + +zval *php_libxml_register_export(zend_class_entry *ce, php_libxml_export_node export_function) +{ + php_libxml_func_handler export_hnd; + + /* Initialize in case this module hasn't been loaded yet */ + php_libxml_initialize(); + export_hnd.export_func = export_function; + + return zend_hash_add_mem(&php_libxml_exports, ce->name, &export_hnd, sizeof(export_hnd)); +} + +PHP_LIBXML_API xmlNodePtr php_libxml_import_node(zval *object) +{ + zend_class_entry *ce = NULL; + xmlNodePtr node = NULL; + php_libxml_func_handler *export_hnd; + + if (Z_TYPE_P(object) == IS_OBJECT) { + ce = Z_OBJCE_P(object); + while (ce->parent != NULL) { + ce = ce->parent; + } + if ((export_hnd = zend_hash_find_ptr(&php_libxml_exports, ce->name))) { + node = export_hnd->export_func(object); + } + } + return node; +} + +PHP_LIBXML_API int php_libxml_increment_node_ptr(php_libxml_node_object *object, xmlNodePtr node, void *private_data) +{ + int ret_refcount = -1; + + if (object != NULL && node != NULL) { + if (object->node != NULL) { + if (object->node->node == node) { + return object->node->refcount; + } else { + php_libxml_decrement_node_ptr(object); + } + } + if (node->_private != NULL) { + object->node = node->_private; + ret_refcount = ++object->node->refcount; + /* Only dom uses _private */ + if (object->node->_private == NULL) { + object->node->_private = private_data; + } + } else { + ret_refcount = 1; + object->node = emalloc(sizeof(php_libxml_node_ptr)); + object->node->node = node; + object->node->refcount = 1; + object->node->_private = private_data; + node->_private = object->node; + } + } + + return ret_refcount; +} + +PHP_LIBXML_API int php_libxml_decrement_node_ptr(php_libxml_node_object *object) +{ + int ret_refcount = -1; + php_libxml_node_ptr *obj_node; + + if (object != NULL && object->node != NULL) { + obj_node = (php_libxml_node_ptr *) object->node; + ret_refcount = --obj_node->refcount; + if (ret_refcount == 0) { + if (obj_node->node != NULL) { + obj_node->node->_private = NULL; + } + efree(obj_node); + } + object->node = NULL; + } + + return ret_refcount; +} + +PHP_LIBXML_API int php_libxml_increment_doc_ref(php_libxml_node_object *object, xmlDocPtr docp) +{ + int ret_refcount = -1; + + if (object->document != NULL) { + object->document->refcount++; + ret_refcount = object->document->refcount; + } else if (docp != NULL) { + ret_refcount = 1; + object->document = emalloc(sizeof(php_libxml_ref_obj)); + object->document->ptr = docp; + object->document->refcount = ret_refcount; + object->document->doc_props = NULL; + } + + return ret_refcount; +} + +PHP_LIBXML_API int php_libxml_decrement_doc_ref(php_libxml_node_object *object) +{ + int ret_refcount = -1; + + if (object != NULL && object->document != NULL) { + ret_refcount = --object->document->refcount; + if (ret_refcount == 0) { + if (object->document->ptr != NULL) { + xmlFreeDoc((xmlDoc *) object->document->ptr); + } + if (object->document->doc_props != NULL) { + if (object->document->doc_props->classmap) { + zend_hash_destroy(object->document->doc_props->classmap); + FREE_HASHTABLE(object->document->doc_props->classmap); + } + efree(object->document->doc_props); + } + efree(object->document); + } + object->document = NULL; + } + + return ret_refcount; +} + +PHP_LIBXML_API void php_libxml_node_free_resource(xmlNodePtr node) +{ + if (!node) { + return; + } + + switch (node->type) { + case XML_DOCUMENT_NODE: + case XML_HTML_DOCUMENT_NODE: + break; + default: + if (node->parent == NULL || node->type == XML_NAMESPACE_DECL) { + php_libxml_node_free_list((xmlNodePtr) node->children); + switch (node->type) { + /* Skip property freeing for the following types */ + case XML_ATTRIBUTE_DECL: + case XML_DTD_NODE: + case XML_DOCUMENT_TYPE_NODE: + case XML_ENTITY_DECL: + case XML_ATTRIBUTE_NODE: + case XML_NAMESPACE_DECL: + case XML_TEXT_NODE: + break; + default: + php_libxml_node_free_list((xmlNodePtr) node->properties); + } + if (php_libxml_unregister_node(node) == 0) { + node->doc = NULL; + } + php_libxml_node_free(node); + } else { + php_libxml_unregister_node(node); + } + } +} + +PHP_LIBXML_API void php_libxml_node_decrement_resource(php_libxml_node_object *object) +{ + int ret_refcount = -1; + xmlNodePtr nodep; + php_libxml_node_ptr *obj_node; + + if (object != NULL && object->node != NULL) { + obj_node = (php_libxml_node_ptr *) object->node; + nodep = object->node->node; + ret_refcount = php_libxml_decrement_node_ptr(object); + if (ret_refcount == 0) { + php_libxml_node_free_resource(nodep); + } else { + if (obj_node && object == obj_node->_private) { + obj_node->_private = NULL; + } + } + } + if (object != NULL && object->document != NULL) { + /* Safe to call as if the resource were freed then doc pointer is NULL */ + php_libxml_decrement_doc_ref(object); + } +} +/* }}} */ + +#if defined(PHP_WIN32) && defined(COMPILE_DL_LIBXML) +PHP_LIBXML_API BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) +{ + return xmlDllMain(hinstDLL, fdwReason, lpvReserved); +} +#endif + +#endif + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/ext/mbstring/libmbfl/filters/mbfilter_big5.c b/ext/mbstring/libmbfl/filters/mbfilter_big5.c index 122ff4c7780ef..657eb98aa58e7 100644 --- a/ext/mbstring/libmbfl/filters/mbfilter_big5.c +++ b/ext/mbstring/libmbfl/filters/mbfilter_big5.c @@ -138,6 +138,17 @@ static unsigned short cp950_pua_tbl[][4] = { {0xf70f,0xf848,0xc740,0xc8fe}, }; +static inline int is_in_cp950_pua(int c1, int c) { + if ((c1 >= 0xfa && c1 <= 0xfe) || (c1 >= 0x8e && c1 <= 0xa0) || + (c1 >= 0x81 && c1 <= 0x8d) || (c1 >= 0xc7 && c1 <= 0xc8)) { + return (c >=0x40 && c <= 0x7e) || (c >= 0xa1 && c <= 0xfe); + } + if (c1 == 0xc6) { + return c >= 0xa1 && c <= 0xfe; + } + return 0; +} + /* * Big5 => wchar */ @@ -186,11 +197,7 @@ mbfl_filt_conv_big5_wchar(int c, mbfl_convert_filter *filter) if (filter->from->no_encoding == mbfl_no_encoding_cp950) { /* PUA for CP950 */ - if (w <= 0 && - (((c1 >= 0xfa && c1 <= 0xfe) || (c1 >= 0x8e && c1 <= 0xa0) || - (c1 >= 0x81 && c1 <= 0x8d) ||(c1 >= 0xc7 && c1 <= 0xc8)) - && ((c > 0x39 && c < 0x7f) || (c > 0xa0 && c < 0xff))) || - ((c1 == 0xc6) && (c > 0xa0 && c < 0xff))) { + if (w <= 0 && is_in_cp950_pua(c1, c)) { c2 = c1 << 8 | c; for (k = 0; k < sizeof(cp950_pua_tbl)/(sizeof(unsigned short)*4); k++) { if (c2 >= cp950_pua_tbl[k][2] && c2 <= cp950_pua_tbl[k][3]) { diff --git a/ext/mbstring/oniguruma/enc/unicode.c b/ext/mbstring/oniguruma/enc/unicode.c index e13429f51e9c4..9f86095896b6c 100644 --- a/ext/mbstring/oniguruma/enc/unicode.c +++ b/ext/mbstring/oniguruma/enc/unicode.c @@ -10989,6 +10989,7 @@ onigenc_unicode_mbc_case_fold(OnigEncoding enc, code = ONIGENC_MBC_TO_CODE(enc, p, end); len = enclen(enc, p); + if (*pp + len > end) len = end - *pp; *pp += len; #ifdef USE_UNICODE_CASE_FOLD_TURKISH_AZERI diff --git a/ext/mbstring/oniguruma/regcomp.c b/ext/mbstring/oniguruma/regcomp.c index b93ca948a773e..820257341f54c 100644 --- a/ext/mbstring/oniguruma/regcomp.c +++ b/ext/mbstring/oniguruma/regcomp.c @@ -469,13 +469,13 @@ compile_length_string_node(Node* node, regex_t* reg) ambig = NSTRING_IS_AMBIG(node); p = prev = sn->s; - prev_len = enclen(enc, p); + SAFE_ENC_LEN(enc, p, sn->end, prev_len); p += prev_len; slen = 1; rlen = 0; for (; p < sn->end; ) { - len = enclen(enc, p); + SAFE_ENC_LEN(enc, p, sn->end, len); if (len == prev_len) { slen++; } @@ -518,12 +518,12 @@ compile_string_node(Node* node, regex_t* reg) ambig = NSTRING_IS_AMBIG(node); p = prev = sn->s; - prev_len = enclen(enc, p); + SAFE_ENC_LEN(enc, p, end, prev_len); p += prev_len; slen = 1; for (; p < end; ) { - len = enclen(enc, p); + SAFE_ENC_LEN(enc, p, end, len); if (len == prev_len) { slen++; } @@ -3390,7 +3390,7 @@ expand_case_fold_string(Node* node, regex_t* reg) goto err; } - len = enclen(reg->enc, p); + SAFE_ENC_LEN(reg->enc, p, end, len); if (n == 0) { if (IS_NULL(snode)) { diff --git a/ext/mbstring/oniguruma/regparse.c b/ext/mbstring/oniguruma/regparse.c index d2925f1e81b0b..fcfaf4378c061 100644 --- a/ext/mbstring/oniguruma/regparse.c +++ b/ext/mbstring/oniguruma/regparse.c @@ -260,14 +260,17 @@ strdup_with_null(OnigEncoding enc, UChar* s, UChar* end) c = ONIGENC_MBC_TO_CODE(enc, p, end); \ pfetch_prev = p; \ p += ONIGENC_MBC_ENC_LEN(enc, p); \ + if(UNEXPECTED(p > end)) p = end; \ } while (0) #define PINC_S do { \ p += ONIGENC_MBC_ENC_LEN(enc, p); \ + if(UNEXPECTED(p > end)) p = end; \ } while (0) #define PFETCH_S(c) do { \ c = ONIGENC_MBC_TO_CODE(enc, p, end); \ p += ONIGENC_MBC_ENC_LEN(enc, p); \ + if(UNEXPECTED(p > end)) p = end; \ } while (0) #define PPEEK (p < end ? ONIGENC_MBC_TO_CODE(enc, p, end) : PEND_VALUE) @@ -3580,7 +3583,9 @@ fetch_token(OnigToken* tok, UChar** src, UChar* end, ScanEnv* env) tok->u.code = (OnigCodePoint )num; } else { /* string */ - p = tok->backp + enclen(enc, tok->backp); + int len; + SAFE_ENC_LEN(enc, tok->backp, end, len); + p = tok->backp + len; } break; } diff --git a/ext/mbstring/oniguruma/regparse.h b/ext/mbstring/oniguruma/regparse.h index 0c5c2c936c044..bcab03ed58921 100644 --- a/ext/mbstring/oniguruma/regparse.h +++ b/ext/mbstring/oniguruma/regparse.h @@ -348,4 +348,16 @@ extern int onig_print_names(FILE*, regex_t*); #endif #endif +#if (defined (__GNUC__) && __GNUC__ > 2 ) && !defined(DARWIN) && !defined(__hpux) && !defined(_AIX) +# define UNEXPECTED(condition) __builtin_expect(condition, 0) +#else +# define UNEXPECTED(condition) (condition) +#endif + +#define SAFE_ENC_LEN(enc, p, end, res) do { \ + int __res = enclen(enc, p); \ + if (UNEXPECTED(p + __res > end)) __res = end - p; \ + res = __res; \ +} while(0); + #endif /* REGPARSE_H */ diff --git a/ext/mbstring/tests/bug77370.phpt b/ext/mbstring/tests/bug77370.phpt new file mode 100644 index 0000000000000..c4d25582fe3bd --- /dev/null +++ b/ext/mbstring/tests/bug77370.phpt @@ -0,0 +1,13 @@ +--TEST-- +Bug #77370 (Buffer overflow on mb regex functions - fetch_token) +--SKIPIF-- + +--FILE-- + +--EXPECT-- +array(1) { + [0]=> + string(0) "" +} diff --git a/ext/mbstring/tests/bug77371.phpt b/ext/mbstring/tests/bug77371.phpt new file mode 100644 index 0000000000000..33e5fc115c966 --- /dev/null +++ b/ext/mbstring/tests/bug77371.phpt @@ -0,0 +1,10 @@ +--TEST-- +Bug #77371 (heap buffer overflow in mb regex functions - compile_string_node) +--SKIPIF-- + +--FILE-- + +--EXPECT-- +bool(false) \ No newline at end of file diff --git a/ext/mbstring/tests/bug77381.phpt b/ext/mbstring/tests/bug77381.phpt new file mode 100644 index 0000000000000..cb83759fc09b6 --- /dev/null +++ b/ext/mbstring/tests/bug77381.phpt @@ -0,0 +1,16 @@ +--TEST-- +Bug #77381 (heap buffer overflow in multibyte match_at) +--SKIPIF-- + +--FILE-- + +--EXPECT-- +int(1) +bool(false) +bool(false) +bool(false) diff --git a/ext/mbstring/tests/bug79037.phpt b/ext/mbstring/tests/bug79037.phpt new file mode 100644 index 0000000000000..94ff01a4a1da9 --- /dev/null +++ b/ext/mbstring/tests/bug79037.phpt @@ -0,0 +1,10 @@ +--TEST-- +Bug #79037: global buffer-overflow in `mbfl_filt_conv_big5_wchar` +--FILE-- + +--EXPECT-- +string(1) "?" diff --git a/ext/phar/phar.c b/ext/phar/phar.c index 59c11f4e29768..83446e1f69628 100644 --- a/ext/phar/phar.c +++ b/ext/phar/phar.c @@ -1999,7 +1999,7 @@ int phar_detect_phar_fname_ext(const char *filename, int filename_len, const cha } while (pos != filename && (*(pos - 1) == '/' || *(pos - 1) == '\0')) { - pos = memchr(pos + 1, '.', filename_len - (pos - filename) + 1); + pos = memchr(pos + 1, '.', filename_len - (pos - filename) - 1); if (!pos) { return FAILURE; } diff --git a/ext/phar/phar.c.orig b/ext/phar/phar.c.orig new file mode 100644 index 0000000000000..7f1d6ff5e39d4 --- /dev/null +++ b/ext/phar/phar.c.orig @@ -0,0 +1,3623 @@ +/* + +----------------------------------------------------------------------+ + | phar php single-file executable PHP extension | + +----------------------------------------------------------------------+ + | Copyright (c) 2005-2017 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt. | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Gregory Beaver | + | Marcus Boerger | + +----------------------------------------------------------------------+ +*/ + +/* $Id: 59c11f4e29768bfbbf6f41cb469abd81d8655850 $ */ + +#define PHAR_MAIN 1 +#include "phar_internal.h" +#include "SAPI.h" +#include "func_interceptors.h" + +static void destroy_phar_data(zval *zv); + +ZEND_DECLARE_MODULE_GLOBALS(phar) +zend_string *(*phar_save_resolve_path)(const char *filename, int filename_len); + +/** + * set's phar->is_writeable based on the current INI value + */ +static int phar_set_writeable_bit(zval *zv, void *argument) /* {{{ */ +{ + zend_bool keep = *(zend_bool *)argument; + phar_archive_data *phar = (phar_archive_data *)Z_PTR_P(zv); + + if (!phar->is_data) { + phar->is_writeable = !keep; + } + + return ZEND_HASH_APPLY_KEEP; +} +/* }}} */ + +/* if the original value is 0 (disabled), then allow setting/unsetting at will. Otherwise only allow 1 (enabled), and error on disabling */ +ZEND_INI_MH(phar_ini_modify_handler) /* {{{ */ +{ + zend_bool old, ini; + + if (ZSTR_LEN(entry->name) == sizeof("phar.readonly")-1) { + old = PHAR_G(readonly_orig); + } else { + old = PHAR_G(require_hash_orig); + } + + if (ZSTR_LEN(new_value) == 2 && !strcasecmp("on", ZSTR_VAL(new_value))) { + ini = (zend_bool) 1; + } + else if (ZSTR_LEN(new_value) == 3 && !strcasecmp("yes", ZSTR_VAL(new_value))) { + ini = (zend_bool) 1; + } + else if (ZSTR_LEN(new_value) == 4 && !strcasecmp("true", ZSTR_VAL(new_value))) { + ini = (zend_bool) 1; + } + else { + ini = (zend_bool) atoi(ZSTR_VAL(new_value)); + } + + /* do not allow unsetting in runtime */ + if (stage == ZEND_INI_STAGE_STARTUP) { + if (ZSTR_LEN(entry->name) == sizeof("phar.readonly")-1) { + PHAR_G(readonly_orig) = ini; + } else { + PHAR_G(require_hash_orig) = ini; + } + } else if (old && !ini) { + return FAILURE; + } + + if (ZSTR_LEN(entry->name) == sizeof("phar.readonly")-1) { + PHAR_G(readonly) = ini; + if (PHAR_G(request_init) && PHAR_G(phar_fname_map.u.flags)) { + zend_hash_apply_with_argument(&(PHAR_G(phar_fname_map)), phar_set_writeable_bit, (void *)&ini); + } + } else { + PHAR_G(require_hash) = ini; + } + + return SUCCESS; +} +/* }}}*/ + +/* this global stores the global cached pre-parsed manifests */ +HashTable cached_phars; +HashTable cached_alias; + +static void phar_split_cache_list(void) /* {{{ */ +{ + char *tmp; + char *key, *lasts, *end; + char ds[2]; + phar_archive_data *phar; + uint i = 0; + + if (!PHAR_G(cache_list) || !(PHAR_G(cache_list)[0])) { + return; + } + + ds[0] = DEFAULT_DIR_SEPARATOR; + ds[1] = '\0'; + tmp = estrdup(PHAR_G(cache_list)); + + /* fake request startup */ + PHAR_G(request_init) = 1; + zend_hash_init(&EG(regular_list), 0, NULL, NULL, 0); + EG(regular_list).nNextFreeElement=1; /* we don't want resource id 0 */ + + PHAR_G(has_bz2) = zend_hash_str_exists(&module_registry, "bz2", sizeof("bz2")-1); + PHAR_G(has_zlib) = zend_hash_str_exists(&module_registry, "zlib", sizeof("zlib")-1); + /* these two are dummies and will be destroyed later */ + zend_hash_init(&cached_phars, sizeof(phar_archive_data*), zend_get_hash_value, destroy_phar_data, 1); + zend_hash_init(&cached_alias, sizeof(phar_archive_data*), zend_get_hash_value, NULL, 1); + /* these two are real and will be copied over cached_phars/cached_alias later */ + zend_hash_init(&(PHAR_G(phar_fname_map)), sizeof(phar_archive_data*), zend_get_hash_value, destroy_phar_data, 1); + zend_hash_init(&(PHAR_G(phar_alias_map)), sizeof(phar_archive_data*), zend_get_hash_value, NULL, 1); + PHAR_G(manifest_cached) = 1; + PHAR_G(persist) = 1; + + for (key = php_strtok_r(tmp, ds, &lasts); + key; + key = php_strtok_r(NULL, ds, &lasts)) { + end = strchr(key, DEFAULT_DIR_SEPARATOR); + + if (end) { + if (SUCCESS == phar_open_from_filename(key, end - key, NULL, 0, 0, &phar, NULL)) { +finish_up: + phar->phar_pos = i++; + php_stream_close(phar->fp); + phar->fp = NULL; + } else { +finish_error: + PHAR_G(persist) = 0; + PHAR_G(manifest_cached) = 0; + efree(tmp); + zend_hash_destroy(&(PHAR_G(phar_fname_map))); + PHAR_G(phar_fname_map.u.flags) = 0; + zend_hash_destroy(&(PHAR_G(phar_alias_map))); + PHAR_G(phar_alias_map.u.flags) = 0; + zend_hash_destroy(&cached_phars); + zend_hash_destroy(&cached_alias); + zend_hash_graceful_reverse_destroy(&EG(regular_list)); + memset(&EG(regular_list), 0, sizeof(HashTable)); + /* free cached manifests */ + PHAR_G(request_init) = 0; + return; + } + } else { + if (SUCCESS == phar_open_from_filename(key, strlen(key), NULL, 0, 0, &phar, NULL)) { + goto finish_up; + } else { + goto finish_error; + } + } + } + + PHAR_G(persist) = 0; + PHAR_G(request_init) = 0; + /* destroy dummy values from before */ + zend_hash_destroy(&cached_phars); + zend_hash_destroy(&cached_alias); + cached_phars = PHAR_G(phar_fname_map); + cached_alias = PHAR_G(phar_alias_map); + PHAR_G(phar_fname_map.u.flags) = 0; + PHAR_G(phar_alias_map.u.flags) = 0; + zend_hash_graceful_reverse_destroy(&EG(regular_list)); + memset(&EG(regular_list), 0, sizeof(HashTable)); + efree(tmp); +} +/* }}} */ + +ZEND_INI_MH(phar_ini_cache_list) /* {{{ */ +{ + PHAR_G(cache_list) = ZSTR_VAL(new_value); + + if (stage == ZEND_INI_STAGE_STARTUP) { + phar_split_cache_list(); + } + + return SUCCESS; +} +/* }}} */ + +PHP_INI_BEGIN() + STD_PHP_INI_BOOLEAN("phar.readonly", "1", PHP_INI_ALL, phar_ini_modify_handler, readonly, zend_phar_globals, phar_globals) + STD_PHP_INI_BOOLEAN("phar.require_hash", "1", PHP_INI_ALL, phar_ini_modify_handler, require_hash, zend_phar_globals, phar_globals) + STD_PHP_INI_ENTRY("phar.cache_list", "", PHP_INI_SYSTEM, phar_ini_cache_list, cache_list, zend_phar_globals, phar_globals) +PHP_INI_END() + +/** + * When all uses of a phar have been concluded, this frees the manifest + * and the phar slot + */ +void phar_destroy_phar_data(phar_archive_data *phar) /* {{{ */ +{ + if (phar->alias && phar->alias != phar->fname) { + pefree(phar->alias, phar->is_persistent); + phar->alias = NULL; + } + + if (phar->fname) { + pefree(phar->fname, phar->is_persistent); + phar->fname = NULL; + } + + if (phar->signature) { + pefree(phar->signature, phar->is_persistent); + phar->signature = NULL; + } + + if (phar->manifest.u.flags) { + zend_hash_destroy(&phar->manifest); + phar->manifest.u.flags = 0; + } + + if (phar->mounted_dirs.u.flags) { + zend_hash_destroy(&phar->mounted_dirs); + phar->mounted_dirs.u.flags = 0; + } + + if (phar->virtual_dirs.u.flags) { + zend_hash_destroy(&phar->virtual_dirs); + phar->virtual_dirs.u.flags = 0; + } + + if (Z_TYPE(phar->metadata) != IS_UNDEF) { + if (phar->is_persistent) { + if (phar->metadata_len) { + /* for zip comments that are strings */ + free(Z_PTR(phar->metadata)); + } else { + zval_internal_ptr_dtor(&phar->metadata); + } + } else { + zval_ptr_dtor(&phar->metadata); + } + phar->metadata_len = 0; + ZVAL_UNDEF(&phar->metadata); + } + + if (phar->fp) { + php_stream_close(phar->fp); + phar->fp = 0; + } + + if (phar->ufp) { + php_stream_close(phar->ufp); + phar->ufp = 0; + } + + pefree(phar, phar->is_persistent); +} +/* }}}*/ + +/** + * Delete refcount and destruct if needed. On destruct return 1 else 0. + */ +int phar_archive_delref(phar_archive_data *phar) /* {{{ */ +{ + if (phar->is_persistent) { + return 0; + } + + if (--phar->refcount < 0) { + if (PHAR_G(request_done) + || zend_hash_str_del(&(PHAR_G(phar_fname_map)), phar->fname, phar->fname_len) != SUCCESS) { + phar_destroy_phar_data(phar); + } + return 1; + } else if (!phar->refcount) { + /* invalidate phar cache */ + PHAR_G(last_phar) = NULL; + PHAR_G(last_phar_name) = PHAR_G(last_alias) = NULL; + + if (phar->fp && (!(phar->flags & PHAR_FILE_COMPRESSION_MASK) || !phar->alias)) { + /* close open file handle - allows removal or rename of + the file on windows, which has greedy locking + only close if the archive was not already compressed. If it + was compressed, then the fp does not refer to the original file. + We're also closing compressed files to save resources, + but only if the archive isn't aliased. */ + php_stream_close(phar->fp); + phar->fp = NULL; + } + + if (!zend_hash_num_elements(&phar->manifest)) { + /* this is a new phar that has perhaps had an alias/metadata set, but has never + been flushed */ + if (zend_hash_str_del(&(PHAR_G(phar_fname_map)), phar->fname, phar->fname_len) != SUCCESS) { + phar_destroy_phar_data(phar); + } + return 1; + } + } + return 0; +} +/* }}}*/ + +/** + * Destroy phar's in shutdown, here we don't care about aliases + */ +static void destroy_phar_data_only(zval *zv) /* {{{ */ +{ + phar_archive_data *phar_data = (phar_archive_data *) Z_PTR_P(zv); + + if (EG(exception) || --phar_data->refcount < 0) { + phar_destroy_phar_data(phar_data); + } +} +/* }}}*/ + +/** + * Delete aliases to phar's that got kicked out of the global table + */ +static int phar_unalias_apply(zval *zv, void *argument) /* {{{ */ +{ + return Z_PTR_P(zv) == argument ? ZEND_HASH_APPLY_REMOVE : ZEND_HASH_APPLY_KEEP; +} +/* }}} */ + +/** + * Delete aliases to phar's that got kicked out of the global table + */ +static int phar_tmpclose_apply(zval *zv) /* {{{ */ +{ + phar_entry_info *entry = (phar_entry_info *) Z_PTR_P(zv); + + if (entry->fp_type != PHAR_TMP) { + return ZEND_HASH_APPLY_KEEP; + } + + if (entry->fp && !entry->fp_refcount) { + php_stream_close(entry->fp); + entry->fp = NULL; + } + + return ZEND_HASH_APPLY_KEEP; +} +/* }}} */ + +/** + * Filename map destructor + */ +static void destroy_phar_data(zval *zv) /* {{{ */ +{ + phar_archive_data *phar_data = (phar_archive_data *)Z_PTR_P(zv); + + if (PHAR_G(request_ends)) { + /* first, iterate over the manifest and close all PHAR_TMP entry fp handles, + this prevents unnecessary unfreed stream resources */ + zend_hash_apply(&(phar_data->manifest), phar_tmpclose_apply); + destroy_phar_data_only(zv); + return; + } + + zend_hash_apply_with_argument(&(PHAR_G(phar_alias_map)), phar_unalias_apply, phar_data); + + if (--phar_data->refcount < 0) { + phar_destroy_phar_data(phar_data); + } +} +/* }}}*/ + +/** + * destructor for the manifest hash, frees each file's entry + */ +void destroy_phar_manifest_entry_int(phar_entry_info *entry) /* {{{ */ +{ + + if (entry->cfp) { + php_stream_close(entry->cfp); + entry->cfp = 0; + } + + if (entry->fp) { + php_stream_close(entry->fp); + entry->fp = 0; + } + + if (Z_TYPE(entry->metadata) != IS_UNDEF) { + if (entry->is_persistent) { + if (entry->metadata_len) { + /* for zip comments that are strings */ + free(Z_PTR(entry->metadata)); + } else { + zval_internal_ptr_dtor(&entry->metadata); + } + } else { + zval_ptr_dtor(&entry->metadata); + } + entry->metadata_len = 0; + ZVAL_UNDEF(&entry->metadata); + } + + if (entry->metadata_str.s) { + smart_str_free(&entry->metadata_str); + entry->metadata_str.s = NULL; + } + + pefree(entry->filename, entry->is_persistent); + + if (entry->link) { + pefree(entry->link, entry->is_persistent); + entry->link = 0; + } + + if (entry->tmp) { + pefree(entry->tmp, entry->is_persistent); + entry->tmp = 0; + } +} +/* }}} */ + +void destroy_phar_manifest_entry(zval *zv) /* {{{ */ +{ + phar_entry_info *entry = Z_PTR_P(zv); + destroy_phar_manifest_entry_int(entry); + pefree(entry, entry->is_persistent); +} +/* }}} */ + +int phar_entry_delref(phar_entry_data *idata) /* {{{ */ +{ + int ret = 0; + + if (idata->internal_file && !idata->internal_file->is_persistent) { + if (--idata->internal_file->fp_refcount < 0) { + idata->internal_file->fp_refcount = 0; + } + + if (idata->fp && idata->fp != idata->phar->fp && idata->fp != idata->phar->ufp && idata->fp != idata->internal_file->fp) { + php_stream_close(idata->fp); + } + /* if phar_get_or_create_entry_data returns a sub-directory, we have to free it */ + if (idata->internal_file->is_temp_dir) { + destroy_phar_manifest_entry_int(idata->internal_file); + efree(idata->internal_file); + } + } + + phar_archive_delref(idata->phar); + efree(idata); + return ret; +} +/* }}} */ + +/** + * Removes an entry, either by actually removing it or by marking it. + */ +void phar_entry_remove(phar_entry_data *idata, char **error) /* {{{ */ +{ + phar_archive_data *phar; + + phar = idata->phar; + + if (idata->internal_file->fp_refcount < 2) { + if (idata->fp && idata->fp != idata->phar->fp && idata->fp != idata->phar->ufp && idata->fp != idata->internal_file->fp) { + php_stream_close(idata->fp); + } + zend_hash_str_del(&idata->phar->manifest, idata->internal_file->filename, idata->internal_file->filename_len); + idata->phar->refcount--; + efree(idata); + } else { + idata->internal_file->is_deleted = 1; + phar_entry_delref(idata); + } + + if (!phar->donotflush) { + phar_flush(phar, 0, 0, 0, error); + } +} +/* }}} */ + +#define MAPPHAR_ALLOC_FAIL(msg) \ + if (fp) {\ + php_stream_close(fp);\ + }\ + if (error) {\ + spprintf(error, 0, msg, fname);\ + }\ + return FAILURE; + +#define MAPPHAR_FAIL(msg) \ + efree(savebuf);\ + if (mydata) {\ + phar_destroy_phar_data(mydata);\ + }\ + if (signature) {\ + pefree(signature, PHAR_G(persist));\ + }\ + MAPPHAR_ALLOC_FAIL(msg) + +#ifdef WORDS_BIGENDIAN +# define PHAR_GET_32(buffer, var) \ + var = ((((unsigned char*)(buffer))[3]) << 24) \ + | ((((unsigned char*)(buffer))[2]) << 16) \ + | ((((unsigned char*)(buffer))[1]) << 8) \ + | (((unsigned char*)(buffer))[0]); \ + (buffer) += 4 +# define PHAR_GET_16(buffer, var) \ + var = ((((unsigned char*)(buffer))[1]) << 8) \ + | (((unsigned char*)(buffer))[0]); \ + (buffer) += 2 +#else +# define PHAR_GET_32(buffer, var) \ + memcpy(&var, buffer, sizeof(var)); \ + buffer += 4 +# define PHAR_GET_16(buffer, var) \ + var = *(uint16_t*)(buffer); \ + buffer += 2 +#endif +#define PHAR_ZIP_16(var) ((uint16_t)((((uint16_t)var[0]) & 0xff) | \ + (((uint16_t)var[1]) & 0xff) << 8)) +#define PHAR_ZIP_32(var) ((uint32_t)((((uint32_t)var[0]) & 0xff) | \ + (((uint32_t)var[1]) & 0xff) << 8 | \ + (((uint32_t)var[2]) & 0xff) << 16 | \ + (((uint32_t)var[3]) & 0xff) << 24)) + +/** + * Open an already loaded phar + */ +int phar_open_parsed_phar(char *fname, int fname_len, char *alias, int alias_len, int is_data, int options, phar_archive_data** pphar, char **error) /* {{{ */ +{ + phar_archive_data *phar; +#ifdef PHP_WIN32 + char *unixfname; +#endif + + if (error) { + *error = NULL; + } +#ifdef PHP_WIN32 + unixfname = estrndup(fname, fname_len); + phar_unixify_path_separators(unixfname, fname_len); + + if (SUCCESS == phar_get_archive(&phar, unixfname, fname_len, alias, alias_len, error) + && ((alias && fname_len == phar->fname_len + && !strncmp(unixfname, phar->fname, fname_len)) || !alias) + ) { + phar_entry_info *stub; + efree(unixfname); +#else + if (SUCCESS == phar_get_archive(&phar, fname, fname_len, alias, alias_len, error) + && ((alias && fname_len == phar->fname_len + && !strncmp(fname, phar->fname, fname_len)) || !alias) + ) { + phar_entry_info *stub; +#endif + /* logic above is as follows: + If an explicit alias was requested, ensure the filename passed in + matches the phar's filename. + If no alias was passed in, then it can match either and be valid + */ + + if (!is_data) { + /* prevent any ".phar" without a stub getting through */ + if (!phar->halt_offset && !phar->is_brandnew && (phar->is_tar || phar->is_zip)) { + if (PHAR_G(readonly) && NULL == (stub = zend_hash_str_find_ptr(&(phar->manifest), ".phar/stub.php", sizeof(".phar/stub.php")-1))) { + if (error) { + spprintf(error, 0, "'%s' is not a phar archive. Use PharData::__construct() for a standard zip or tar archive", fname); + } + return FAILURE; + } + } + } + + if (pphar) { + *pphar = phar; + } + + return SUCCESS; + } else { +#ifdef PHP_WIN32 + efree(unixfname); +#endif + if (pphar) { + *pphar = NULL; + } + + if (phar && error && !(options & REPORT_ERRORS)) { + efree(error); + } + + return FAILURE; + } +} +/* }}}*/ + +/** + * Parse out metadata from the manifest for a single file + * + * Meta-data is in this format: + * [len32][data...] + * + * data is the serialized zval + */ +int phar_parse_metadata(char **buffer, zval *metadata, uint32_t zip_metadata_len) /* {{{ */ +{ + php_unserialize_data_t var_hash; + + if (zip_metadata_len) { + const unsigned char *p; + unsigned char *p_buff = (unsigned char *)estrndup(*buffer, zip_metadata_len); + p = p_buff; + ZVAL_NULL(metadata); + PHP_VAR_UNSERIALIZE_INIT(var_hash); + + if (!php_var_unserialize(metadata, &p, p + zip_metadata_len, &var_hash)) { + efree(p_buff); + PHP_VAR_UNSERIALIZE_DESTROY(var_hash); + zval_ptr_dtor(metadata); + ZVAL_UNDEF(metadata); + return FAILURE; + } + efree(p_buff); + PHP_VAR_UNSERIALIZE_DESTROY(var_hash); + + if (PHAR_G(persist)) { + /* lazy init metadata */ + zval_ptr_dtor(metadata); + Z_PTR_P(metadata) = pemalloc(zip_metadata_len, 1); + memcpy(Z_PTR_P(metadata), *buffer, zip_metadata_len); + return SUCCESS; + } + } else { + ZVAL_UNDEF(metadata); + } + + return SUCCESS; +} +/* }}}*/ + +/** + * Does not check for a previously opened phar in the cache. + * + * Parse a new one and add it to the cache, returning either SUCCESS or + * FAILURE, and setting pphar to the pointer to the manifest entry + * + * This is used by phar_open_from_filename to process the manifest, but can be called + * directly. + */ +static int phar_parse_pharfile(php_stream *fp, char *fname, int fname_len, char *alias, int alias_len, zend_long halt_offset, phar_archive_data** pphar, uint32_t compression, char **error) /* {{{ */ +{ + char b32[4], *buffer, *endbuffer, *savebuf; + phar_archive_data *mydata = NULL; + phar_entry_info entry; + uint32_t manifest_len, manifest_count, manifest_flags, manifest_index, tmp_len, sig_flags; + uint16_t manifest_ver; + uint32_t len; + zend_long offset; + int sig_len, register_alias = 0, temp_alias = 0; + char *signature = NULL; + + if (pphar) { + *pphar = NULL; + } + + if (error) { + *error = NULL; + } + + /* check for ?>\n and increment accordingly */ + if (-1 == php_stream_seek(fp, halt_offset, SEEK_SET)) { + MAPPHAR_ALLOC_FAIL("cannot seek to __HALT_COMPILER(); location in phar \"%s\"") + } + + buffer = b32; + + if (3 != php_stream_read(fp, buffer, 3)) { + MAPPHAR_ALLOC_FAIL("internal corruption of phar \"%s\" (truncated manifest at stub end)") + } + + if ((*buffer == ' ' || *buffer == '\n') && *(buffer + 1) == '?' && *(buffer + 2) == '>') { + int nextchar; + halt_offset += 3; + if (EOF == (nextchar = php_stream_getc(fp))) { + MAPPHAR_ALLOC_FAIL("internal corruption of phar \"%s\" (truncated manifest at stub end)") + } + + if ((char) nextchar == '\r') { + /* if we have an \r we require an \n as well */ + if (EOF == (nextchar = php_stream_getc(fp)) || (char)nextchar != '\n') { + MAPPHAR_ALLOC_FAIL("internal corruption of phar \"%s\" (truncated manifest at stub end)") + } + ++halt_offset; + } + + if ((char) nextchar == '\n') { + ++halt_offset; + } + } + + /* make sure we are at the right location to read the manifest */ + if (-1 == php_stream_seek(fp, halt_offset, SEEK_SET)) { + MAPPHAR_ALLOC_FAIL("cannot seek to __HALT_COMPILER(); location in phar \"%s\"") + } + + /* read in manifest */ + buffer = b32; + + if (4 != php_stream_read(fp, buffer, 4)) { + MAPPHAR_ALLOC_FAIL("internal corruption of phar \"%s\" (truncated manifest at manifest length)") + } + + PHAR_GET_32(buffer, manifest_len); + + if (manifest_len > 1048576 * 100) { + /* prevent serious memory issues by limiting manifest to at most 100 MB in length */ + MAPPHAR_ALLOC_FAIL("manifest cannot be larger than 100 MB in phar \"%s\"") + } + + buffer = (char *)emalloc(manifest_len); + savebuf = buffer; + endbuffer = buffer + manifest_len; + + if (manifest_len < 10 || manifest_len != php_stream_read(fp, buffer, manifest_len)) { + MAPPHAR_FAIL("internal corruption of phar \"%s\" (truncated manifest header)") + } + + /* extract the number of entries */ + PHAR_GET_32(buffer, manifest_count); + + if (manifest_count == 0) { + MAPPHAR_FAIL("in phar \"%s\", manifest claims to have zero entries. Phars must have at least 1 entry"); + } + + /* extract API version, lowest nibble currently unused */ + manifest_ver = (((unsigned char)buffer[0]) << 8) + + ((unsigned char)buffer[1]); + buffer += 2; + + if ((manifest_ver & PHAR_API_VER_MASK) < PHAR_API_MIN_READ) { + efree(savebuf); + php_stream_close(fp); + if (error) { + spprintf(error, 0, "phar \"%s\" is API version %1.u.%1.u.%1.u, and cannot be processed", fname, manifest_ver >> 12, (manifest_ver >> 8) & 0xF, (manifest_ver >> 4) & 0x0F); + } + return FAILURE; + } + + PHAR_GET_32(buffer, manifest_flags); + + manifest_flags &= ~PHAR_HDR_COMPRESSION_MASK; + manifest_flags &= ~PHAR_FILE_COMPRESSION_MASK; + /* remember whether this entire phar was compressed with gz/bzip2 */ + manifest_flags |= compression; + + /* The lowest nibble contains the phar wide flags. The compression flags can */ + /* be ignored on reading because it is being generated anyways. */ + if (manifest_flags & PHAR_HDR_SIGNATURE) { + char sig_buf[8], *sig_ptr = sig_buf; + zend_off_t read_len; + size_t end_of_phar; + + if (-1 == php_stream_seek(fp, -8, SEEK_END) + || (read_len = php_stream_tell(fp)) < 20 + || 8 != php_stream_read(fp, sig_buf, 8) + || memcmp(sig_buf+4, "GBMB", 4)) { + efree(savebuf); + php_stream_close(fp); + if (error) { + spprintf(error, 0, "phar \"%s\" has a broken signature", fname); + } + return FAILURE; + } + + PHAR_GET_32(sig_ptr, sig_flags); + + switch(sig_flags) { + case PHAR_SIG_OPENSSL: { + uint32_t signature_len; + char *sig; + zend_off_t whence; + + /* we store the signature followed by the signature length */ + if (-1 == php_stream_seek(fp, -12, SEEK_CUR) + || 4 != php_stream_read(fp, sig_buf, 4)) { + efree(savebuf); + php_stream_close(fp); + if (error) { + spprintf(error, 0, "phar \"%s\" openssl signature length could not be read", fname); + } + return FAILURE; + } + + sig_ptr = sig_buf; + PHAR_GET_32(sig_ptr, signature_len); + sig = (char *) emalloc(signature_len); + whence = signature_len + 4; + whence = -whence; + + if (-1 == php_stream_seek(fp, whence, SEEK_CUR) + || !(end_of_phar = php_stream_tell(fp)) + || signature_len != php_stream_read(fp, sig, signature_len)) { + efree(savebuf); + efree(sig); + php_stream_close(fp); + if (error) { + spprintf(error, 0, "phar \"%s\" openssl signature could not be read", fname); + } + return FAILURE; + } + + if (FAILURE == phar_verify_signature(fp, end_of_phar, PHAR_SIG_OPENSSL, sig, signature_len, fname, &signature, &sig_len, error)) { + efree(savebuf); + efree(sig); + php_stream_close(fp); + if (error) { + char *save = *error; + spprintf(error, 0, "phar \"%s\" openssl signature could not be verified: %s", fname, *error); + efree(save); + } + return FAILURE; + } + efree(sig); + } + break; +#if PHAR_HASH_OK + case PHAR_SIG_SHA512: { + unsigned char digest[64]; + + php_stream_seek(fp, -(8 + 64), SEEK_END); + read_len = php_stream_tell(fp); + + if (php_stream_read(fp, (char*)digest, sizeof(digest)) != sizeof(digest)) { + efree(savebuf); + php_stream_close(fp); + if (error) { + spprintf(error, 0, "phar \"%s\" has a broken signature", fname); + } + return FAILURE; + } + + if (FAILURE == phar_verify_signature(fp, read_len, PHAR_SIG_SHA512, (char *)digest, 64, fname, &signature, &sig_len, error)) { + efree(savebuf); + php_stream_close(fp); + if (error) { + char *save = *error; + spprintf(error, 0, "phar \"%s\" SHA512 signature could not be verified: %s", fname, *error); + efree(save); + } + return FAILURE; + } + break; + } + case PHAR_SIG_SHA256: { + unsigned char digest[32]; + + php_stream_seek(fp, -(8 + 32), SEEK_END); + read_len = php_stream_tell(fp); + + if (php_stream_read(fp, (char*)digest, sizeof(digest)) != sizeof(digest)) { + efree(savebuf); + php_stream_close(fp); + if (error) { + spprintf(error, 0, "phar \"%s\" has a broken signature", fname); + } + return FAILURE; + } + + if (FAILURE == phar_verify_signature(fp, read_len, PHAR_SIG_SHA256, (char *)digest, 32, fname, &signature, &sig_len, error)) { + efree(savebuf); + php_stream_close(fp); + if (error) { + char *save = *error; + spprintf(error, 0, "phar \"%s\" SHA256 signature could not be verified: %s", fname, *error); + efree(save); + } + return FAILURE; + } + break; + } +#else + case PHAR_SIG_SHA512: + case PHAR_SIG_SHA256: + efree(savebuf); + php_stream_close(fp); + + if (error) { + spprintf(error, 0, "phar \"%s\" has a unsupported signature", fname); + } + return FAILURE; +#endif + case PHAR_SIG_SHA1: { + unsigned char digest[20]; + + php_stream_seek(fp, -(8 + 20), SEEK_END); + read_len = php_stream_tell(fp); + + if (php_stream_read(fp, (char*)digest, sizeof(digest)) != sizeof(digest)) { + efree(savebuf); + php_stream_close(fp); + if (error) { + spprintf(error, 0, "phar \"%s\" has a broken signature", fname); + } + return FAILURE; + } + + if (FAILURE == phar_verify_signature(fp, read_len, PHAR_SIG_SHA1, (char *)digest, 20, fname, &signature, &sig_len, error)) { + efree(savebuf); + php_stream_close(fp); + if (error) { + char *save = *error; + spprintf(error, 0, "phar \"%s\" SHA1 signature could not be verified: %s", fname, *error); + efree(save); + } + return FAILURE; + } + break; + } + case PHAR_SIG_MD5: { + unsigned char digest[16]; + + php_stream_seek(fp, -(8 + 16), SEEK_END); + read_len = php_stream_tell(fp); + + if (php_stream_read(fp, (char*)digest, sizeof(digest)) != sizeof(digest)) { + efree(savebuf); + php_stream_close(fp); + if (error) { + spprintf(error, 0, "phar \"%s\" has a broken signature", fname); + } + return FAILURE; + } + + if (FAILURE == phar_verify_signature(fp, read_len, PHAR_SIG_MD5, (char *)digest, 16, fname, &signature, &sig_len, error)) { + efree(savebuf); + php_stream_close(fp); + if (error) { + char *save = *error; + spprintf(error, 0, "phar \"%s\" MD5 signature could not be verified: %s", fname, *error); + efree(save); + } + return FAILURE; + } + break; + } + default: + efree(savebuf); + php_stream_close(fp); + + if (error) { + spprintf(error, 0, "phar \"%s\" has a broken or unsupported signature", fname); + } + return FAILURE; + } + } else if (PHAR_G(require_hash)) { + efree(savebuf); + php_stream_close(fp); + + if (error) { + spprintf(error, 0, "phar \"%s\" does not have a signature", fname); + } + return FAILURE; + } else { + sig_flags = 0; + sig_len = 0; + } + + /* extract alias */ + PHAR_GET_32(buffer, tmp_len); + + if (buffer + tmp_len > endbuffer) { + MAPPHAR_FAIL("internal corruption of phar \"%s\" (buffer overrun)"); + } + + if (manifest_len < 10 + tmp_len) { + MAPPHAR_FAIL("internal corruption of phar \"%s\" (truncated manifest header)") + } + + /* tmp_len = 0 says alias length is 0, which means the alias is not stored in the phar */ + if (tmp_len) { + /* if the alias is stored we enforce it (implicit overrides explicit) */ + if (alias && alias_len && (alias_len != (int)tmp_len || strncmp(alias, buffer, tmp_len))) + { + php_stream_close(fp); + + if (signature) { + efree(signature); + } + + if (error) { + spprintf(error, 0, "cannot load phar \"%s\" with implicit alias \"%.*s\" under different alias \"%s\"", fname, tmp_len, buffer, alias); + } + + efree(savebuf); + return FAILURE; + } + + alias_len = tmp_len; + alias = buffer; + buffer += tmp_len; + register_alias = 1; + } else if (!alias_len || !alias) { + /* if we neither have an explicit nor an implicit alias, we use the filename */ + alias = NULL; + alias_len = 0; + register_alias = 0; + } else if (alias_len) { + register_alias = 1; + temp_alias = 1; + } + + /* we have 5 32-bit items plus 1 byte at least */ + if (manifest_count > ((manifest_len - 10 - tmp_len) / (5 * 4 + 1))) { + /* prevent serious memory issues */ + MAPPHAR_FAIL("internal corruption of phar \"%s\" (too many manifest entries for size of manifest)") + } + + mydata = pecalloc(1, sizeof(phar_archive_data), PHAR_G(persist)); + mydata->is_persistent = PHAR_G(persist); + + /* check whether we have meta data, zero check works regardless of byte order */ + PHAR_GET_32(buffer, len); + if (mydata->is_persistent) { + mydata->metadata_len = len; + if(!len) { + /* FIXME: not sure why this is needed but removing it breaks tests */ + PHAR_GET_32(buffer, len); + } + } + if(len > (size_t)(endbuffer - buffer)) { + MAPPHAR_FAIL("internal corruption of phar \"%s\" (trying to read past buffer end)"); + } + if (phar_parse_metadata(&buffer, &mydata->metadata, len) == FAILURE) { + MAPPHAR_FAIL("unable to read phar metadata in .phar file \"%s\""); + } + buffer += len; + + /* set up our manifest */ + zend_hash_init(&mydata->manifest, manifest_count, + zend_get_hash_value, destroy_phar_manifest_entry, (zend_bool)mydata->is_persistent); + zend_hash_init(&mydata->mounted_dirs, 5, + zend_get_hash_value, NULL, (zend_bool)mydata->is_persistent); + zend_hash_init(&mydata->virtual_dirs, manifest_count * 2, + zend_get_hash_value, NULL, (zend_bool)mydata->is_persistent); + mydata->fname = pestrndup(fname, fname_len, mydata->is_persistent); +#ifdef PHP_WIN32 + phar_unixify_path_separators(mydata->fname, fname_len); +#endif + mydata->fname_len = fname_len; + offset = halt_offset + manifest_len + 4; + memset(&entry, 0, sizeof(phar_entry_info)); + entry.phar = mydata; + entry.fp_type = PHAR_FP; + entry.is_persistent = mydata->is_persistent; + + for (manifest_index = 0; manifest_index < manifest_count; ++manifest_index) { + if (buffer + 28 > endbuffer) { + MAPPHAR_FAIL("internal corruption of phar \"%s\" (truncated manifest entry)") + } + + PHAR_GET_32(buffer, entry.filename_len); + + if (entry.filename_len == 0) { + MAPPHAR_FAIL("zero-length filename encountered in phar \"%s\""); + } + + if (entry.is_persistent) { + entry.manifest_pos = manifest_index; + } + + if (entry.filename_len > (size_t)(endbuffer - buffer - 24)) { + MAPPHAR_FAIL("internal corruption of phar \"%s\" (truncated manifest entry)"); + } + + if ((manifest_ver & PHAR_API_VER_MASK) >= PHAR_API_MIN_DIR && buffer[entry.filename_len - 1] == '/') { + entry.is_dir = 1; + } else { + entry.is_dir = 0; + } + + phar_add_virtual_dirs(mydata, buffer, entry.filename_len); + entry.filename = pestrndup(buffer, entry.filename_len, entry.is_persistent); + buffer += entry.filename_len; + PHAR_GET_32(buffer, entry.uncompressed_filesize); + PHAR_GET_32(buffer, entry.timestamp); + + if (offset == halt_offset + (int)manifest_len + 4) { + mydata->min_timestamp = entry.timestamp; + mydata->max_timestamp = entry.timestamp; + } else { + if (mydata->min_timestamp > entry.timestamp) { + mydata->min_timestamp = entry.timestamp; + } else if (mydata->max_timestamp < entry.timestamp) { + mydata->max_timestamp = entry.timestamp; + } + } + + PHAR_GET_32(buffer, entry.compressed_filesize); + PHAR_GET_32(buffer, entry.crc32); + PHAR_GET_32(buffer, entry.flags); + + if (entry.is_dir) { + entry.filename_len--; + entry.flags |= PHAR_ENT_PERM_DEF_DIR; + } + + PHAR_GET_32(buffer, len); + if (entry.is_persistent) { + entry.metadata_len = len; + } else { + entry.metadata_len = 0; + } + if (len > (size_t)(endbuffer - buffer)) { + pefree(entry.filename, entry.is_persistent); + MAPPHAR_FAIL("internal corruption of phar \"%s\" (truncated manifest entry)"); + } + if (phar_parse_metadata(&buffer, &entry.metadata, len) == FAILURE) { + pefree(entry.filename, entry.is_persistent); + MAPPHAR_FAIL("unable to read file metadata in .phar file \"%s\""); + } + buffer += len; + + entry.offset = entry.offset_abs = offset; + offset += entry.compressed_filesize; + + switch (entry.flags & PHAR_ENT_COMPRESSION_MASK) { + case PHAR_ENT_COMPRESSED_GZ: + if (!PHAR_G(has_zlib)) { + if (Z_TYPE(entry.metadata) != IS_UNDEF) { + if (entry.is_persistent) { + free(Z_PTR(entry.metadata)); + } else { + zval_ptr_dtor(&entry.metadata); + } + } + pefree(entry.filename, entry.is_persistent); + MAPPHAR_FAIL("zlib extension is required for gz compressed .phar file \"%s\""); + } + break; + case PHAR_ENT_COMPRESSED_BZ2: + if (!PHAR_G(has_bz2)) { + if (Z_TYPE(entry.metadata) != IS_UNDEF) { + if (entry.is_persistent) { + free(Z_PTR(entry.metadata)); + } else { + zval_ptr_dtor(&entry.metadata); + } + } + pefree(entry.filename, entry.is_persistent); + MAPPHAR_FAIL("bz2 extension is required for bzip2 compressed .phar file \"%s\""); + } + break; + default: + if (entry.uncompressed_filesize != entry.compressed_filesize) { + if (Z_TYPE(entry.metadata) != IS_UNDEF) { + if (entry.is_persistent) { + free(Z_PTR(entry.metadata)); + } else { + zval_ptr_dtor(&entry.metadata); + } + } + pefree(entry.filename, entry.is_persistent); + MAPPHAR_FAIL("internal corruption of phar \"%s\" (compressed and uncompressed size does not match for uncompressed entry)"); + } + break; + } + + manifest_flags |= (entry.flags & PHAR_ENT_COMPRESSION_MASK); + /* if signature matched, no need to check CRC32 for each file */ + entry.is_crc_checked = (manifest_flags & PHAR_HDR_SIGNATURE ? 1 : 0); + phar_set_inode(&entry); + zend_hash_str_add_mem(&mydata->manifest, entry.filename, entry.filename_len, (void*)&entry, sizeof(phar_entry_info)); + } + + snprintf(mydata->version, sizeof(mydata->version), "%u.%u.%u", manifest_ver >> 12, (manifest_ver >> 8) & 0xF, (manifest_ver >> 4) & 0xF); + mydata->internal_file_start = halt_offset + manifest_len + 4; + mydata->halt_offset = halt_offset; + mydata->flags = manifest_flags; + endbuffer = strrchr(mydata->fname, '/'); + + if (endbuffer) { + mydata->ext = memchr(endbuffer, '.', (mydata->fname + fname_len) - endbuffer); + if (mydata->ext == endbuffer) { + mydata->ext = memchr(endbuffer + 1, '.', (mydata->fname + fname_len) - endbuffer - 1); + } + if (mydata->ext) { + mydata->ext_len = (mydata->fname + mydata->fname_len) - mydata->ext; + } + } + + mydata->alias = alias ? + pestrndup(alias, alias_len, mydata->is_persistent) : + pestrndup(mydata->fname, fname_len, mydata->is_persistent); + mydata->alias_len = alias ? alias_len : fname_len; + mydata->sig_flags = sig_flags; + mydata->fp = fp; + mydata->sig_len = sig_len; + mydata->signature = signature; + phar_request_initialize(); + + if (register_alias) { + phar_archive_data *fd_ptr; + + mydata->is_temporary_alias = temp_alias; + + if (!phar_validate_alias(mydata->alias, mydata->alias_len)) { + signature = NULL; + fp = NULL; + MAPPHAR_FAIL("Cannot open archive \"%s\", invalid alias"); + } + + if (NULL != (fd_ptr = zend_hash_str_find_ptr(&(PHAR_G(phar_alias_map)), alias, alias_len))) { + if (SUCCESS != phar_free_alias(fd_ptr, alias, alias_len)) { + signature = NULL; + fp = NULL; + MAPPHAR_FAIL("Cannot open archive \"%s\", alias is already in use by existing archive"); + } + } + + zend_hash_str_add_ptr(&(PHAR_G(phar_alias_map)), alias, alias_len, mydata); + } else { + mydata->is_temporary_alias = 1; + } + + zend_hash_str_add_ptr(&(PHAR_G(phar_fname_map)), mydata->fname, fname_len, mydata); + efree(savebuf); + + if (pphar) { + *pphar = mydata; + } + + return SUCCESS; +} +/* }}} */ + +/** + * Create or open a phar for writing + */ +int phar_open_or_create_filename(char *fname, int fname_len, char *alias, int alias_len, int is_data, int options, phar_archive_data** pphar, char **error) /* {{{ */ +{ + const char *ext_str, *z; + char *my_error; + int ext_len; + phar_archive_data **test, *unused = NULL; + + test = &unused; + + if (error) { + *error = NULL; + } + + /* first try to open an existing file */ + if (phar_detect_phar_fname_ext(fname, fname_len, &ext_str, &ext_len, !is_data, 0, 1) == SUCCESS) { + goto check_file; + } + + /* next try to create a new file */ + if (FAILURE == phar_detect_phar_fname_ext(fname, fname_len, &ext_str, &ext_len, !is_data, 1, 1)) { + if (error) { + if (ext_len == -2) { + spprintf(error, 0, "Cannot create a phar archive from a URL like \"%s\". Phar objects can only be created from local files", fname); + } else { + spprintf(error, 0, "Cannot create phar '%s', file extension (or combination) not recognised or the directory does not exist", fname); + } + } + return FAILURE; + } +check_file: + if (phar_open_parsed_phar(fname, fname_len, alias, alias_len, is_data, options, test, &my_error) == SUCCESS) { + if (pphar) { + *pphar = *test; + } + + if ((*test)->is_data && !(*test)->is_tar && !(*test)->is_zip) { + if (error) { + spprintf(error, 0, "Cannot open '%s' as a PharData object. Use Phar::__construct() for executable archives", fname); + } + return FAILURE; + } + + if (PHAR_G(readonly) && !(*test)->is_data && ((*test)->is_tar || (*test)->is_zip)) { + phar_entry_info *stub; + if (NULL == (stub = zend_hash_str_find_ptr(&((*test)->manifest), ".phar/stub.php", sizeof(".phar/stub.php")-1))) { + spprintf(error, 0, "'%s' is not a phar archive. Use PharData::__construct() for a standard zip or tar archive", fname); + return FAILURE; + } + } + + if (!PHAR_G(readonly) || (*test)->is_data) { + (*test)->is_writeable = 1; + } + return SUCCESS; + } else if (my_error) { + if (error) { + *error = my_error; + } else { + efree(my_error); + } + return FAILURE; + } + + if (ext_len > 3 && (z = memchr(ext_str, 'z', ext_len)) && ((ext_str + ext_len) - z >= 2) && !memcmp(z + 1, "ip", 2)) { + /* assume zip-based phar */ + return phar_open_or_create_zip(fname, fname_len, alias, alias_len, is_data, options, pphar, error); + } + + if (ext_len > 3 && (z = memchr(ext_str, 't', ext_len)) && ((ext_str + ext_len) - z >= 2) && !memcmp(z + 1, "ar", 2)) { + /* assume tar-based phar */ + return phar_open_or_create_tar(fname, fname_len, alias, alias_len, is_data, options, pphar, error); + } + + return phar_create_or_parse_filename(fname, fname_len, alias, alias_len, is_data, options, pphar, error); +} +/* }}} */ + +int phar_create_or_parse_filename(char *fname, int fname_len, char *alias, int alias_len, int is_data, int options, phar_archive_data** pphar, char **error) /* {{{ */ +{ + phar_archive_data *mydata; + php_stream *fp; + zend_string *actual = NULL; + char *p; + + if (!pphar) { + pphar = &mydata; + } + if (php_check_open_basedir(fname)) { + return FAILURE; + } + + /* first open readonly so it won't be created if not present */ + fp = php_stream_open_wrapper(fname, "rb", IGNORE_URL|STREAM_MUST_SEEK|0, &actual); + + if (actual) { + fname = ZSTR_VAL(actual); + fname_len = ZSTR_LEN(actual); + } + + if (fp) { + if (phar_open_from_fp(fp, fname, fname_len, alias, alias_len, options, pphar, is_data, error) == SUCCESS) { + if ((*pphar)->is_data || !PHAR_G(readonly)) { + (*pphar)->is_writeable = 1; + } + if (actual) { + zend_string_release(actual); + } + return SUCCESS; + } else { + /* file exists, but is either corrupt or not a phar archive */ + if (actual) { + zend_string_release(actual); + } + return FAILURE; + } + } + + if (actual) { + zend_string_release(actual); + } + + if (PHAR_G(readonly) && !is_data) { + if (options & REPORT_ERRORS) { + if (error) { + spprintf(error, 0, "creating archive \"%s\" disabled by the php.ini setting phar.readonly", fname); + } + } + return FAILURE; + } + + /* set up our manifest */ + mydata = ecalloc(1, sizeof(phar_archive_data)); + mydata->fname = expand_filepath(fname, NULL); + fname_len = strlen(mydata->fname); +#ifdef PHP_WIN32 + phar_unixify_path_separators(mydata->fname, fname_len); +#endif + p = strrchr(mydata->fname, '/'); + + if (p) { + mydata->ext = memchr(p, '.', (mydata->fname + fname_len) - p); + if (mydata->ext == p) { + mydata->ext = memchr(p + 1, '.', (mydata->fname + fname_len) - p - 1); + } + if (mydata->ext) { + mydata->ext_len = (mydata->fname + fname_len) - mydata->ext; + } + } + + if (pphar) { + *pphar = mydata; + } + + zend_hash_init(&mydata->manifest, sizeof(phar_entry_info), + zend_get_hash_value, destroy_phar_manifest_entry, 0); + zend_hash_init(&mydata->mounted_dirs, sizeof(char *), + zend_get_hash_value, NULL, 0); + zend_hash_init(&mydata->virtual_dirs, sizeof(char *), + zend_get_hash_value, NULL, (zend_bool)mydata->is_persistent); + mydata->fname_len = fname_len; + snprintf(mydata->version, sizeof(mydata->version), "%s", PHP_PHAR_API_VERSION); + mydata->is_temporary_alias = alias ? 0 : 1; + mydata->internal_file_start = -1; + mydata->fp = NULL; + mydata->is_writeable = 1; + mydata->is_brandnew = 1; + phar_request_initialize(); + zend_hash_str_add_ptr(&(PHAR_G(phar_fname_map)), mydata->fname, fname_len, mydata); + + if (is_data) { + alias = NULL; + alias_len = 0; + mydata->is_data = 1; + /* assume tar format, PharData can specify other */ + mydata->is_tar = 1; + } else { + phar_archive_data *fd_ptr; + + if (alias && NULL != (fd_ptr = zend_hash_str_find_ptr(&(PHAR_G(phar_alias_map)), alias, alias_len))) { + if (SUCCESS != phar_free_alias(fd_ptr, alias, alias_len)) { + if (error) { + spprintf(error, 4096, "phar error: phar \"%s\" cannot set alias \"%s\", already in use by another phar archive", mydata->fname, alias); + } + + zend_hash_str_del(&(PHAR_G(phar_fname_map)), mydata->fname, fname_len); + + if (pphar) { + *pphar = NULL; + } + + return FAILURE; + } + } + + mydata->alias = alias ? estrndup(alias, alias_len) : estrndup(mydata->fname, fname_len); + mydata->alias_len = alias ? alias_len : fname_len; + } + + if (alias_len && alias) { + if (NULL == zend_hash_str_add_ptr(&(PHAR_G(phar_alias_map)), alias, alias_len, mydata)) { + if (options & REPORT_ERRORS) { + if (error) { + spprintf(error, 0, "archive \"%s\" cannot be associated with alias \"%s\", already in use", fname, alias); + } + } + + zend_hash_str_del(&(PHAR_G(phar_fname_map)), mydata->fname, fname_len); + + if (pphar) { + *pphar = NULL; + } + + return FAILURE; + } + } + + return SUCCESS; +} +/* }}}*/ + +/** + * Return an already opened filename. + * + * Or scan a phar file for the required __HALT_COMPILER(); ?> token and verify + * that the manifest is proper, then pass it to phar_parse_pharfile(). SUCCESS + * or FAILURE is returned and pphar is set to a pointer to the phar's manifest + */ +int phar_open_from_filename(char *fname, int fname_len, char *alias, int alias_len, int options, phar_archive_data** pphar, char **error) /* {{{ */ +{ + php_stream *fp; + zend_string *actual; + int ret, is_data = 0; + + if (error) { + *error = NULL; + } + + if (!strstr(fname, ".phar")) { + is_data = 1; + } + + if (phar_open_parsed_phar(fname, fname_len, alias, alias_len, is_data, options, pphar, error) == SUCCESS) { + return SUCCESS; + } else if (error && *error) { + return FAILURE; + } + if (php_check_open_basedir(fname)) { + return FAILURE; + } + + fp = php_stream_open_wrapper(fname, "rb", IGNORE_URL|STREAM_MUST_SEEK, &actual); + + if (!fp) { + if (options & REPORT_ERRORS) { + if (error) { + spprintf(error, 0, "unable to open phar for reading \"%s\"", fname); + } + } + if (actual) { + zend_string_release(actual); + } + return FAILURE; + } + + if (actual) { + fname = ZSTR_VAL(actual); + fname_len = ZSTR_LEN(actual); + } + + ret = phar_open_from_fp(fp, fname, fname_len, alias, alias_len, options, pphar, is_data, error); + + if (actual) { + zend_string_release(actual); + } + + return ret; +} +/* }}}*/ + +static inline char *phar_strnstr(const char *buf, int buf_len, const char *search, int search_len) /* {{{ */ +{ + const char *c; + ptrdiff_t so_far = 0; + + if (buf_len < search_len) { + return NULL; + } + + c = buf - 1; + + do { + if (!(c = memchr(c + 1, search[0], buf_len - search_len - so_far))) { + return (char *) NULL; + } + + so_far = c - buf; + + if (so_far >= (buf_len - search_len)) { + return (char *) NULL; + } + + if (!memcmp(c, search, search_len)) { + return (char *) c; + } + } while (1); +} +/* }}} */ + +/** + * Scan an open fp for the required __HALT_COMPILER(); ?> token and verify + * that the manifest is proper, then pass it to phar_parse_pharfile(). SUCCESS + * or FAILURE is returned and pphar is set to a pointer to the phar's manifest + */ +static int phar_open_from_fp(php_stream* fp, char *fname, int fname_len, char *alias, int alias_len, int options, phar_archive_data** pphar, int is_data, char **error) /* {{{ */ +{ + const char token[] = "__HALT_COMPILER();"; + const char zip_magic[] = "PK\x03\x04"; + const char gz_magic[] = "\x1f\x8b\x08"; + const char bz_magic[] = "BZh"; + char *pos, test = '\0'; + const int window_size = 1024; + char buffer[1024 + sizeof(token)]; /* a 1024 byte window + the size of the halt_compiler token (moving window) */ + const zend_long readsize = sizeof(buffer) - sizeof(token); + const zend_long tokenlen = sizeof(token) - 1; + zend_long halt_offset; + size_t got; + uint32_t compression = PHAR_FILE_COMPRESSED_NONE; + + if (error) { + *error = NULL; + } + + if (-1 == php_stream_rewind(fp)) { + MAPPHAR_ALLOC_FAIL("cannot rewind phar \"%s\"") + } + + buffer[sizeof(buffer)-1] = '\0'; + memset(buffer, 32, sizeof(token)); + halt_offset = 0; + + /* Maybe it's better to compile the file instead of just searching, */ + /* but we only want the offset. So we want a .re scanner to find it. */ + while(!php_stream_eof(fp)) { + if ((got = php_stream_read(fp, buffer+tokenlen, readsize)) < (size_t) tokenlen) { + MAPPHAR_ALLOC_FAIL("internal corruption of phar \"%s\" (truncated entry)") + } + + if (!test) { + test = '\1'; + pos = buffer+tokenlen; + if (!memcmp(pos, gz_magic, 3)) { + char err = 0; + php_stream_filter *filter; + php_stream *temp; + /* to properly decompress, we have to tell zlib to look for a zlib or gzip header */ + zval filterparams; + + if (!PHAR_G(has_zlib)) { + MAPPHAR_ALLOC_FAIL("unable to decompress gzipped phar archive \"%s\" to temporary file, enable zlib extension in php.ini") + } + array_init(&filterparams); +/* this is defined in zlib's zconf.h */ +#ifndef MAX_WBITS +#define MAX_WBITS 15 +#endif + add_assoc_long_ex(&filterparams, "window", sizeof("window") - 1, MAX_WBITS + 32); + + /* entire file is gzip-compressed, uncompress to temporary file */ + if (!(temp = php_stream_fopen_tmpfile())) { + MAPPHAR_ALLOC_FAIL("unable to create temporary file for decompression of gzipped phar archive \"%s\"") + } + + php_stream_rewind(fp); + filter = php_stream_filter_create("zlib.inflate", &filterparams, php_stream_is_persistent(fp)); + + if (!filter) { + err = 1; + add_assoc_long_ex(&filterparams, "window", sizeof("window") - 1, MAX_WBITS); + filter = php_stream_filter_create("zlib.inflate", &filterparams, php_stream_is_persistent(fp)); + zval_dtor(&filterparams); + + if (!filter) { + php_stream_close(temp); + MAPPHAR_ALLOC_FAIL("unable to decompress gzipped phar archive \"%s\", ext/zlib is buggy in PHP versions older than 5.2.6") + } + } else { + zval_dtor(&filterparams); + } + + php_stream_filter_append(&temp->writefilters, filter); + + if (SUCCESS != php_stream_copy_to_stream_ex(fp, temp, PHP_STREAM_COPY_ALL, NULL)) { + if (err) { + php_stream_close(temp); + MAPPHAR_ALLOC_FAIL("unable to decompress gzipped phar archive \"%s\", ext/zlib is buggy in PHP versions older than 5.2.6") + } + php_stream_close(temp); + MAPPHAR_ALLOC_FAIL("unable to decompress gzipped phar archive \"%s\" to temporary file") + } + + php_stream_filter_flush(filter, 1); + php_stream_filter_remove(filter, 1); + php_stream_close(fp); + fp = temp; + php_stream_rewind(fp); + compression = PHAR_FILE_COMPRESSED_GZ; + + /* now, start over */ + test = '\0'; + continue; + } else if (!memcmp(pos, bz_magic, 3)) { + php_stream_filter *filter; + php_stream *temp; + + if (!PHAR_G(has_bz2)) { + MAPPHAR_ALLOC_FAIL("unable to decompress bzipped phar archive \"%s\" to temporary file, enable bz2 extension in php.ini") + } + + /* entire file is bzip-compressed, uncompress to temporary file */ + if (!(temp = php_stream_fopen_tmpfile())) { + MAPPHAR_ALLOC_FAIL("unable to create temporary file for decompression of bzipped phar archive \"%s\"") + } + + php_stream_rewind(fp); + filter = php_stream_filter_create("bzip2.decompress", NULL, php_stream_is_persistent(fp)); + + if (!filter) { + php_stream_close(temp); + MAPPHAR_ALLOC_FAIL("unable to decompress bzipped phar archive \"%s\", filter creation failed") + } + + php_stream_filter_append(&temp->writefilters, filter); + + if (SUCCESS != php_stream_copy_to_stream_ex(fp, temp, PHP_STREAM_COPY_ALL, NULL)) { + php_stream_close(temp); + MAPPHAR_ALLOC_FAIL("unable to decompress bzipped phar archive \"%s\" to temporary file") + } + + php_stream_filter_flush(filter, 1); + php_stream_filter_remove(filter, 1); + php_stream_close(fp); + fp = temp; + php_stream_rewind(fp); + compression = PHAR_FILE_COMPRESSED_BZ2; + + /* now, start over */ + test = '\0'; + continue; + } + + if (!memcmp(pos, zip_magic, 4)) { + php_stream_seek(fp, 0, SEEK_END); + return phar_parse_zipfile(fp, fname, fname_len, alias, alias_len, pphar, error); + } + + if (got > 512) { + if (phar_is_tar(pos, fname)) { + php_stream_rewind(fp); + return phar_parse_tarfile(fp, fname, fname_len, alias, alias_len, pphar, is_data, compression, error); + } + } + } + + if (got > 0 && (pos = phar_strnstr(buffer, got + sizeof(token), token, sizeof(token)-1)) != NULL) { + halt_offset += (pos - buffer); /* no -tokenlen+tokenlen here */ + return phar_parse_pharfile(fp, fname, fname_len, alias, alias_len, halt_offset, pphar, compression, error); + } + + halt_offset += got; + memmove(buffer, buffer + window_size, tokenlen); /* move the memory buffer by the size of the window */ + } + + MAPPHAR_ALLOC_FAIL("internal corruption of phar \"%s\" (__HALT_COMPILER(); not found)") +} +/* }}} */ + +/* + * given the location of the file extension and the start of the file path, + * determine the end of the portion of the path (i.e. /path/to/file.ext/blah + * grabs "/path/to/file.ext" as does the straight /path/to/file.ext), + * stat it to determine if it exists. + * if so, check to see if it is a directory and fail if so + * if not, check to see if its dirname() exists (i.e. "/path/to") and is a directory + * succeed if we are creating the file, otherwise fail. + */ +static int phar_analyze_path(const char *fname, const char *ext, int ext_len, int for_create) /* {{{ */ +{ + php_stream_statbuf ssb; + char *realpath; + char *filename = estrndup(fname, (ext - fname) + ext_len); + + if ((realpath = expand_filepath(filename, NULL))) { +#ifdef PHP_WIN32 + phar_unixify_path_separators(realpath, strlen(realpath)); +#endif + if (zend_hash_str_exists(&(PHAR_G(phar_fname_map)), realpath, strlen(realpath))) { + efree(realpath); + efree(filename); + return SUCCESS; + } + + if (PHAR_G(manifest_cached) && zend_hash_str_exists(&cached_phars, realpath, strlen(realpath))) { + efree(realpath); + efree(filename); + return SUCCESS; + } + efree(realpath); + } + + if (SUCCESS == php_stream_stat_path((char *) filename, &ssb)) { + + efree(filename); + + if (ssb.sb.st_mode & S_IFDIR) { + return FAILURE; + } + + if (for_create == 1) { + return FAILURE; + } + + return SUCCESS; + } else { + char *slash; + + if (!for_create) { + efree(filename); + return FAILURE; + } + + slash = (char *) strrchr(filename, '/'); + + if (slash) { + *slash = '\0'; + } + + if (SUCCESS != php_stream_stat_path((char *) filename, &ssb)) { + if (!slash) { + if (!(realpath = expand_filepath(filename, NULL))) { + efree(filename); + return FAILURE; + } +#ifdef PHP_WIN32 + phar_unixify_path_separators(realpath, strlen(realpath)); +#endif + slash = strstr(realpath, filename); + if (slash) { + slash += ((ext - fname) + ext_len); + *slash = '\0'; + } + slash = strrchr(realpath, '/'); + + if (slash) { + *slash = '\0'; + } else { + efree(realpath); + efree(filename); + return FAILURE; + } + + if (SUCCESS != php_stream_stat_path(realpath, &ssb)) { + efree(realpath); + efree(filename); + return FAILURE; + } + + efree(realpath); + + if (ssb.sb.st_mode & S_IFDIR) { + efree(filename); + return SUCCESS; + } + } + + efree(filename); + return FAILURE; + } + + efree(filename); + + if (ssb.sb.st_mode & S_IFDIR) { + return SUCCESS; + } + + return FAILURE; + } +} +/* }}} */ + +/* check for ".phar" in extension */ +static int phar_check_str(const char *fname, const char *ext_str, int ext_len, int executable, int for_create) /* {{{ */ +{ + char test[51]; + const char *pos; + + if (ext_len >= 50) { + return FAILURE; + } + + if (executable == 1) { + /* copy "." as well */ + memcpy(test, ext_str - 1, ext_len + 1); + test[ext_len + 1] = '\0'; + /* executable phars must contain ".phar" as a valid extension (phar://.pharmy/oops is invalid) */ + /* (phar://hi/there/.phar/oops is also invalid) */ + pos = strstr(test, ".phar"); + + if (pos && (*(pos - 1) != '/') + && (pos += 5) && (*pos == '\0' || *pos == '/' || *pos == '.')) { + return phar_analyze_path(fname, ext_str, ext_len, for_create); + } else { + return FAILURE; + } + } + + /* data phars need only contain a single non-"." to be valid */ + if (!executable) { + pos = strstr(ext_str, ".phar"); + if (!(pos && (*(pos - 1) != '/') + && (pos += 5) && (*pos == '\0' || *pos == '/' || *pos == '.')) && *(ext_str + 1) != '.' && *(ext_str + 1) != '/' && *(ext_str + 1) != '\0') { + return phar_analyze_path(fname, ext_str, ext_len, for_create); + } + } else { + if (*(ext_str + 1) != '.' && *(ext_str + 1) != '/' && *(ext_str + 1) != '\0') { + return phar_analyze_path(fname, ext_str, ext_len, for_create); + } + } + + return FAILURE; +} +/* }}} */ + +/* + * if executable is 1, only returns SUCCESS if the extension is one of the tar/zip .phar extensions + * if executable is 0, it returns SUCCESS only if the filename does *not* contain ".phar" anywhere, and treats + * the first extension as the filename extension + * + * if an extension is found, it sets ext_str to the location of the file extension in filename, + * and ext_len to the length of the extension. + * for urls like "phar://alias/oops" it instead sets ext_len to -1 and returns FAILURE, which tells + * the calling function to use "alias" as the phar alias + * + * the last parameter should be set to tell the thing to assume that filename is the full path, and only to check the + * extension rules, not to iterate. + */ +int phar_detect_phar_fname_ext(const char *filename, int filename_len, const char **ext_str, int *ext_len, int executable, int for_create, int is_complete) /* {{{ */ +{ + const char *pos, *slash; + + *ext_str = NULL; + *ext_len = 0; + + if (!filename_len || filename_len == 1) { + return FAILURE; + } + + phar_request_initialize(); + /* first check for alias in first segment */ + pos = memchr(filename, '/', filename_len); + + if (pos && pos != filename) { + /* check for url like http:// or phar:// */ + if (*(pos - 1) == ':' && (pos - filename) < filename_len - 1 && *(pos + 1) == '/') { + *ext_len = -2; + *ext_str = NULL; + return FAILURE; + } + if (zend_hash_str_exists(&(PHAR_G(phar_alias_map)), (char *) filename, pos - filename)) { + *ext_str = pos; + *ext_len = -1; + return FAILURE; + } + + if (PHAR_G(manifest_cached) && zend_hash_str_exists(&cached_alias, (char *) filename, pos - filename)) { + *ext_str = pos; + *ext_len = -1; + return FAILURE; + } + } + + if (zend_hash_num_elements(&(PHAR_G(phar_fname_map))) || PHAR_G(manifest_cached)) { + phar_archive_data *pphar; + + if (is_complete) { + if (NULL != (pphar = zend_hash_str_find_ptr(&(PHAR_G(phar_fname_map)), (char *) filename, filename_len))) { + *ext_str = filename + (filename_len - pphar->ext_len); +woohoo: + *ext_len = pphar->ext_len; + + if (executable == 2) { + return SUCCESS; + } + + if (executable == 1 && !pphar->is_data) { + return SUCCESS; + } + + if (!executable && pphar->is_data) { + return SUCCESS; + } + + return FAILURE; + } + + if (PHAR_G(manifest_cached) && NULL != (pphar = zend_hash_str_find_ptr(&cached_phars, (char *) filename, filename_len))) { + *ext_str = filename + (filename_len - pphar->ext_len); + goto woohoo; + } + } else { + zend_string *str_key; + zend_ulong unused; + + for (zend_hash_internal_pointer_reset(&(PHAR_G(phar_fname_map))); + HASH_KEY_NON_EXISTENT != zend_hash_get_current_key(&(PHAR_G(phar_fname_map)), &str_key, &unused); + zend_hash_move_forward(&(PHAR_G(phar_fname_map))) + ) { + if (ZSTR_LEN(str_key) > (uint) filename_len) { + continue; + } + + if (!memcmp(filename, ZSTR_VAL(str_key), ZSTR_LEN(str_key)) && ((uint)filename_len == ZSTR_LEN(str_key) + || filename[ZSTR_LEN(str_key)] == '/' || filename[ZSTR_LEN(str_key)] == '\0')) { + if (NULL == (pphar = zend_hash_get_current_data_ptr(&(PHAR_G(phar_fname_map))))) { + break; + } + *ext_str = filename + (ZSTR_LEN(str_key) - pphar->ext_len); + goto woohoo; + } + } + + if (PHAR_G(manifest_cached)) { + for (zend_hash_internal_pointer_reset(&cached_phars); + HASH_KEY_NON_EXISTENT != zend_hash_get_current_key(&cached_phars, &str_key, &unused); + zend_hash_move_forward(&cached_phars) + ) { + if (ZSTR_LEN(str_key) > (uint) filename_len) { + continue; + } + + if (!memcmp(filename, ZSTR_VAL(str_key), ZSTR_LEN(str_key)) && ((uint)filename_len == ZSTR_LEN(str_key) + || filename[ZSTR_LEN(str_key)] == '/' || filename[ZSTR_LEN(str_key)] == '\0')) { + if (NULL == (pphar = zend_hash_get_current_data_ptr(&cached_phars))) { + break; + } + *ext_str = filename + (ZSTR_LEN(str_key) - pphar->ext_len); + goto woohoo; + } + } + } + } + } + + pos = memchr(filename + 1, '.', filename_len); +next_extension: + if (!pos) { + return FAILURE; + } + + while (pos != filename && (*(pos - 1) == '/' || *(pos - 1) == '\0')) { + pos = memchr(pos + 1, '.', filename_len - (pos - filename) + 1); + if (!pos) { + return FAILURE; + } + } + + slash = memchr(pos, '/', filename_len - (pos - filename)); + + if (!slash) { + /* this is a url like "phar://blah.phar" with no directory */ + *ext_str = pos; + *ext_len = strlen(pos); + + /* file extension must contain "phar" */ + switch (phar_check_str(filename, *ext_str, *ext_len, executable, for_create)) { + case SUCCESS: + return SUCCESS; + case FAILURE: + /* we are at the end of the string, so we fail */ + return FAILURE; + } + } + + /* we've found an extension that ends at a directory separator */ + *ext_str = pos; + *ext_len = slash - pos; + + switch (phar_check_str(filename, *ext_str, *ext_len, executable, for_create)) { + case SUCCESS: + return SUCCESS; + case FAILURE: + /* look for more extensions */ + pos = strchr(pos + 1, '.'); + if (pos) { + *ext_str = NULL; + *ext_len = 0; + } + goto next_extension; + } + + return FAILURE; +} +/* }}} */ + +static int php_check_dots(const char *element, int n) /* {{{ */ +{ + for(n--; n >= 0; --n) { + if (element[n] != '.') { + return 1; + } + } + return 0; +} +/* }}} */ + +#define IS_DIRECTORY_UP(element, len) \ + (len >= 2 && !php_check_dots(element, len)) + +#define IS_DIRECTORY_CURRENT(element, len) \ + (len == 1 && element[0] == '.') + +#define IS_BACKSLASH(c) ((c) == '/') + +/** + * Remove .. and . references within a phar filename + */ +char *phar_fix_filepath(char *path, int *new_len, int use_cwd) /* {{{ */ +{ + char *newpath; + int newpath_len; + char *ptr; + char *tok; + int ptr_length, path_length = *new_len; + + if (PHAR_G(cwd_len) && use_cwd && path_length > 2 && path[0] == '.' && path[1] == '/') { + newpath_len = PHAR_G(cwd_len); + newpath = emalloc(strlen(path) + newpath_len + 1); + memcpy(newpath, PHAR_G(cwd), newpath_len); + } else { + newpath = emalloc(strlen(path) + 2); + newpath[0] = '/'; + newpath_len = 1; + } + + ptr = path; + + if (*ptr == '/') { + ++ptr; + } + + tok = ptr; + + do { + ptr = memchr(ptr, '/', path_length - (ptr - path)); + } while (ptr && ptr - tok == 0 && *ptr == '/' && ++ptr && ++tok); + + if (!ptr && (path_length - (tok - path))) { + switch (path_length - (tok - path)) { + case 1: + if (*tok == '.') { + efree(path); + *new_len = 1; + efree(newpath); + return estrndup("/", 1); + } + break; + case 2: + if (tok[0] == '.' && tok[1] == '.') { + efree(path); + *new_len = 1; + efree(newpath); + return estrndup("/", 1); + } + } + efree(newpath); + return path; + } + + while (ptr) { + ptr_length = ptr - tok; +last_time: + if (IS_DIRECTORY_UP(tok, ptr_length)) { +#define PREVIOUS newpath[newpath_len - 1] + + while (newpath_len > 1 && !IS_BACKSLASH(PREVIOUS)) { + newpath_len--; + } + + if (newpath[0] != '/') { + newpath[newpath_len] = '\0'; + } else if (newpath_len > 1) { + --newpath_len; + } + } else if (!IS_DIRECTORY_CURRENT(tok, ptr_length)) { + if (newpath_len > 1) { + newpath[newpath_len++] = '/'; + memcpy(newpath + newpath_len, tok, ptr_length+1); + } else { + memcpy(newpath + newpath_len, tok, ptr_length+1); + } + + newpath_len += ptr_length; + } + + if (ptr == path + path_length) { + break; + } + + tok = ++ptr; + + do { + ptr = memchr(ptr, '/', path_length - (ptr - path)); + } while (ptr && ptr - tok == 0 && *ptr == '/' && ++ptr && ++tok); + + if (!ptr && (path_length - (tok - path))) { + ptr_length = path_length - (tok - path); + ptr = path + path_length; + goto last_time; + } + } + + efree(path); + *new_len = newpath_len; + newpath[newpath_len] = '\0'; + return erealloc(newpath, newpath_len + 1); +} +/* }}} */ + +/** + * Process a phar stream name, ensuring we can handle any of: + * + * - whatever.phar + * - whatever.phar.gz + * - whatever.phar.bz2 + * - whatever.phar.php + * + * Optionally the name might start with 'phar://' + * + * This is used by phar_parse_url() + */ +int phar_split_fname(const char *filename, int filename_len, char **arch, int *arch_len, char **entry, int *entry_len, int executable, int for_create) /* {{{ */ +{ + const char *ext_str; +#ifdef PHP_WIN32 + char *save; +#endif + int ext_len; + + if (CHECK_NULL_PATH(filename, filename_len)) { + return FAILURE; + } + + if (CHECK_NULL_PATH(filename, filename_len)) { + return FAILURE; + } + + if (!strncasecmp(filename, "phar://", 7)) { + filename += 7; + filename_len -= 7; + } + + ext_len = 0; +#ifdef PHP_WIN32 + save = filename; + filename = estrndup(filename, filename_len); + phar_unixify_path_separators(filename, filename_len); +#endif + if (phar_detect_phar_fname_ext(filename, filename_len, &ext_str, &ext_len, executable, for_create, 0) == FAILURE) { + if (ext_len != -1) { + if (!ext_str) { + /* no / detected, restore arch for error message */ +#ifdef PHP_WIN32 + *arch = save; +#else + *arch = (char*)filename; +#endif + } + +#ifdef PHP_WIN32 + efree(filename); +#endif + return FAILURE; + } + + ext_len = 0; + /* no extension detected - instead we are dealing with an alias */ + } + + *arch_len = ext_str - filename + ext_len; + *arch = estrndup(filename, *arch_len); + + if (ext_str[ext_len]) { + *entry_len = filename_len - *arch_len; + *entry = estrndup(ext_str+ext_len, *entry_len); +#ifdef PHP_WIN32 + phar_unixify_path_separators(*entry, *entry_len); +#endif + *entry = phar_fix_filepath(*entry, entry_len, 0); + } else { + *entry_len = 1; + *entry = estrndup("/", 1); + } + +#ifdef PHP_WIN32 + efree(filename); +#endif + + return SUCCESS; +} +/* }}} */ + +/** + * Invoked when a user calls Phar::mapPhar() from within an executing .phar + * to set up its manifest directly + */ +int phar_open_executed_filename(char *alias, int alias_len, char **error) /* {{{ */ +{ + char *fname; + php_stream *fp; + int fname_len; + zend_string *actual = NULL; + int ret; + + if (error) { + *error = NULL; + } + + fname = (char*)zend_get_executed_filename(); + fname_len = strlen(fname); + + if (phar_open_parsed_phar(fname, fname_len, alias, alias_len, 0, REPORT_ERRORS, NULL, 0) == SUCCESS) { + return SUCCESS; + } + + if (!strcmp(fname, "[no active file]")) { + if (error) { + spprintf(error, 0, "cannot initialize a phar outside of PHP execution"); + } + return FAILURE; + } + + if (0 == zend_get_constant_str("__COMPILER_HALT_OFFSET__", sizeof("__COMPILER_HALT_OFFSET__")-1)) { + if (error) { + spprintf(error, 0, "__HALT_COMPILER(); must be declared in a phar"); + } + return FAILURE; + } + + if (php_check_open_basedir(fname)) { + return FAILURE; + } + + fp = php_stream_open_wrapper(fname, "rb", IGNORE_URL|STREAM_MUST_SEEK|REPORT_ERRORS, &actual); + + if (!fp) { + if (error) { + spprintf(error, 0, "unable to open phar for reading \"%s\"", fname); + } + if (actual) { + zend_string_release(actual); + } + return FAILURE; + } + + if (actual) { + fname = ZSTR_VAL(actual); + fname_len = ZSTR_LEN(actual); + } + + ret = phar_open_from_fp(fp, fname, fname_len, alias, alias_len, REPORT_ERRORS, NULL, 0, error); + + if (actual) { + zend_string_release(actual); + } + + return ret; +} +/* }}} */ + +/** + * Validate the CRC32 of a file opened from within the phar + */ +int phar_postprocess_file(phar_entry_data *idata, uint32_t crc32, char **error, int process_zip) /* {{{ */ +{ + uint32_t crc = ~0; + int len = idata->internal_file->uncompressed_filesize; + php_stream *fp = idata->fp; + phar_entry_info *entry = idata->internal_file; + + if (error) { + *error = NULL; + } + + if (entry->is_zip && process_zip > 0) { + /* verify local file header */ + phar_zip_file_header local; + phar_zip_data_desc desc; + + if (SUCCESS != phar_open_archive_fp(idata->phar)) { + spprintf(error, 0, "phar error: unable to open zip-based phar archive \"%s\" to verify local file header for file \"%s\"", idata->phar->fname, entry->filename); + return FAILURE; + } + php_stream_seek(phar_get_entrypfp(idata->internal_file), entry->header_offset, SEEK_SET); + + if (sizeof(local) != php_stream_read(phar_get_entrypfp(idata->internal_file), (char *) &local, sizeof(local))) { + + spprintf(error, 0, "phar error: internal corruption of zip-based phar \"%s\" (cannot read local file header for file \"%s\")", idata->phar->fname, entry->filename); + return FAILURE; + } + + /* check for data descriptor */ + if (((PHAR_ZIP_16(local.flags)) & 0x8) == 0x8) { + php_stream_seek(phar_get_entrypfp(idata->internal_file), + entry->header_offset + sizeof(local) + + PHAR_ZIP_16(local.filename_len) + + PHAR_ZIP_16(local.extra_len) + + entry->compressed_filesize, SEEK_SET); + if (sizeof(desc) != php_stream_read(phar_get_entrypfp(idata->internal_file), + (char *) &desc, sizeof(desc))) { + spprintf(error, 0, "phar error: internal corruption of zip-based phar \"%s\" (cannot read local data descriptor for file \"%s\")", idata->phar->fname, entry->filename); + return FAILURE; + } + if (desc.signature[0] == 'P' && desc.signature[1] == 'K') { + memcpy(&(local.crc32), &(desc.crc32), 12); + } else { + /* old data descriptors have no signature */ + memcpy(&(local.crc32), &desc, 12); + } + } + /* verify local header */ + if (entry->filename_len != PHAR_ZIP_16(local.filename_len) || entry->crc32 != PHAR_ZIP_32(local.crc32) || entry->uncompressed_filesize != PHAR_ZIP_32(local.uncompsize) || entry->compressed_filesize != PHAR_ZIP_32(local.compsize)) { + spprintf(error, 0, "phar error: internal corruption of zip-based phar \"%s\" (local header of file \"%s\" does not match central directory)", idata->phar->fname, entry->filename); + return FAILURE; + } + + /* construct actual offset to file start - local extra_len can be different from central extra_len */ + entry->offset = entry->offset_abs = + sizeof(local) + entry->header_offset + PHAR_ZIP_16(local.filename_len) + PHAR_ZIP_16(local.extra_len); + + if (idata->zero && idata->zero != entry->offset_abs) { + idata->zero = entry->offset_abs; + } + } + + if (process_zip == 1) { + return SUCCESS; + } + + php_stream_seek(fp, idata->zero, SEEK_SET); + + while (len--) { + CRC32(crc, php_stream_getc(fp)); + } + + php_stream_seek(fp, idata->zero, SEEK_SET); + + if (~crc == crc32) { + entry->is_crc_checked = 1; + return SUCCESS; + } else { + spprintf(error, 0, "phar error: internal corruption of phar \"%s\" (crc32 mismatch on file \"%s\")", idata->phar->fname, entry->filename); + return FAILURE; + } +} +/* }}} */ + +static inline void phar_set_32(char *buffer, int var) /* {{{ */ +{ +#ifdef WORDS_BIGENDIAN + *((buffer) + 3) = (unsigned char) (((var) >> 24) & 0xFF); + *((buffer) + 2) = (unsigned char) (((var) >> 16) & 0xFF); + *((buffer) + 1) = (unsigned char) (((var) >> 8) & 0xFF); + *((buffer) + 0) = (unsigned char) ((var) & 0xFF); +#else + memcpy(buffer, &var, sizeof(var)); +#endif +} /* }}} */ + +static int phar_flush_clean_deleted_apply(zval *zv) /* {{{ */ +{ + phar_entry_info *entry = (phar_entry_info *)Z_PTR_P(zv); + + if (entry->fp_refcount <= 0 && entry->is_deleted) { + return ZEND_HASH_APPLY_REMOVE; + } else { + return ZEND_HASH_APPLY_KEEP; + } +} +/* }}} */ + +#include "stub.h" + +zend_string *phar_create_default_stub(const char *index_php, const char *web_index, char **error) /* {{{ */ +{ + int index_len, web_len; + + if (error) { + *error = NULL; + } + + if (!index_php) { + index_php = "index.php"; + } + + if (!web_index) { + web_index = "index.php"; + } + + index_len = strlen(index_php); + web_len = strlen(web_index); + + if (index_len > 400) { + /* ridiculous size not allowed for index.php startup filename */ + if (error) { + spprintf(error, 0, "Illegal filename passed in for stub creation, was %d characters long, and only 400 or less is allowed", index_len); + return NULL; + } + } + + if (web_len > 400) { + /* ridiculous size not allowed for index.php startup filename */ + if (error) { + spprintf(error, 0, "Illegal web filename passed in for stub creation, was %d characters long, and only 400 or less is allowed", web_len); + return NULL; + } + } + + return phar_get_stub(index_php, web_index, index_len+1, web_len+1); +} +/* }}} */ + +/** + * Save phar contents to disk + * + * user_stub contains either a string, or a resource pointer, if len is a negative length. + * user_stub and len should be both 0 if the default or existing stub should be used + */ +int phar_flush(phar_archive_data *phar, char *user_stub, zend_long len, int convert, char **error) /* {{{ */ +{ + char halt_stub[] = "__HALT_COMPILER();"; + zend_string *newstub; + char *tmp; + phar_entry_info *entry, *newentry; + int halt_offset, restore_alias_len, global_flags = 0, closeoldfile; + char *pos, has_dirs = 0; + char manifest[18], entry_buffer[24]; + zend_off_t manifest_ftell; + zend_long offset; + size_t wrote; + uint32_t manifest_len, mytime, loc, new_manifest_count; + uint32_t newcrc32; + php_stream *file, *oldfile, *newfile, *stubfile; + php_stream_filter *filter; + php_serialize_data_t metadata_hash; + smart_str main_metadata_str = {0}; + int free_user_stub, free_fp = 1, free_ufp = 1; + int manifest_hack = 0; + + if (phar->is_persistent) { + if (error) { + spprintf(error, 0, "internal error: attempt to flush cached zip-based phar \"%s\"", phar->fname); + } + return EOF; + } + + if (error) { + *error = NULL; + } + + if (!zend_hash_num_elements(&phar->manifest) && !user_stub) { + return EOF; + } + + zend_hash_clean(&phar->virtual_dirs); + + if (phar->is_zip) { + return phar_zip_flush(phar, user_stub, len, convert, error); + } + + if (phar->is_tar) { + return phar_tar_flush(phar, user_stub, len, convert, error); + } + + if (PHAR_G(readonly)) { + return EOF; + } + + if (phar->fp && !phar->is_brandnew) { + oldfile = phar->fp; + closeoldfile = 0; + php_stream_rewind(oldfile); + } else { + oldfile = php_stream_open_wrapper(phar->fname, "rb", 0, NULL); + closeoldfile = oldfile != NULL; + } + newfile = php_stream_fopen_tmpfile(); + if (!newfile) { + if (error) { + spprintf(error, 0, "unable to create temporary file"); + } + if (closeoldfile) { + php_stream_close(oldfile); + } + return EOF; + } + + if (user_stub) { + zend_string *suser_stub; + if (len < 0) { + /* resource passed in */ + if (!(php_stream_from_zval_no_verify(stubfile, (zval *)user_stub))) { + if (closeoldfile) { + php_stream_close(oldfile); + } + php_stream_close(newfile); + if (error) { + spprintf(error, 0, "unable to access resource to copy stub to new phar \"%s\"", phar->fname); + } + return EOF; + } + if (len == -1) { + len = PHP_STREAM_COPY_ALL; + } else { + len = -len; + } + user_stub = 0; + + if (!(suser_stub = php_stream_copy_to_mem(stubfile, len, 0))) { + if (closeoldfile) { + php_stream_close(oldfile); + } + php_stream_close(newfile); + if (error) { + spprintf(error, 0, "unable to read resource to copy stub to new phar \"%s\"", phar->fname); + } + return EOF; + } + free_user_stub = 1; + user_stub = ZSTR_VAL(suser_stub); + len = ZSTR_LEN(suser_stub); + } else { + free_user_stub = 0; + } + tmp = estrndup(user_stub, len); + if ((pos = php_stristr(tmp, halt_stub, len, sizeof(halt_stub) - 1)) == NULL) { + efree(tmp); + if (closeoldfile) { + php_stream_close(oldfile); + } + php_stream_close(newfile); + if (error) { + spprintf(error, 0, "illegal stub for phar \"%s\"", phar->fname); + } + if (free_user_stub) { + zend_string_free(suser_stub); + } + return EOF; + } + pos = user_stub + (pos - tmp); + efree(tmp); + len = pos - user_stub + 18; + if ((size_t)len != php_stream_write(newfile, user_stub, len) + || 5 != php_stream_write(newfile, " ?>\r\n", 5)) { + if (closeoldfile) { + php_stream_close(oldfile); + } + php_stream_close(newfile); + if (error) { + spprintf(error, 0, "unable to create stub from string in new phar \"%s\"", phar->fname); + } + if (free_user_stub) { + zend_string_free(suser_stub); + } + return EOF; + } + phar->halt_offset = len + 5; + if (free_user_stub) { + zend_string_free(suser_stub); + } + } else { + size_t written; + + if (!user_stub && phar->halt_offset && oldfile && !phar->is_brandnew) { + php_stream_copy_to_stream_ex(oldfile, newfile, phar->halt_offset, &written); + newstub = NULL; + } else { + /* this is either a brand new phar or a default stub overwrite */ + newstub = phar_create_default_stub(NULL, NULL, NULL); + phar->halt_offset = ZSTR_LEN(newstub); + written = php_stream_write(newfile, ZSTR_VAL(newstub), phar->halt_offset); + } + if (phar->halt_offset != written) { + if (closeoldfile) { + php_stream_close(oldfile); + } + php_stream_close(newfile); + if (error) { + if (newstub) { + spprintf(error, 0, "unable to create stub in new phar \"%s\"", phar->fname); + } else { + spprintf(error, 0, "unable to copy stub of old phar to new phar \"%s\"", phar->fname); + } + } + if (newstub) { + zend_string_free(newstub); + } + return EOF; + } + if (newstub) { + zend_string_free(newstub); + } + } + manifest_ftell = php_stream_tell(newfile); + halt_offset = manifest_ftell; + + /* Check whether we can get rid of some of the deleted entries which are + * unused. However some might still be in use so even after this clean-up + * we need to skip entries marked is_deleted. */ + zend_hash_apply(&phar->manifest, phar_flush_clean_deleted_apply); + + /* compress as necessary, calculate crcs, serialize meta-data, manifest size, and file sizes */ + main_metadata_str.s = NULL; + if (Z_TYPE(phar->metadata) != IS_UNDEF) { + PHP_VAR_SERIALIZE_INIT(metadata_hash); + php_var_serialize(&main_metadata_str, &phar->metadata, &metadata_hash); + PHP_VAR_SERIALIZE_DESTROY(metadata_hash); + } + new_manifest_count = 0; + offset = 0; + for (zend_hash_internal_pointer_reset(&phar->manifest); + zend_hash_has_more_elements(&phar->manifest) == SUCCESS; + zend_hash_move_forward(&phar->manifest)) { + if ((entry = zend_hash_get_current_data_ptr(&phar->manifest)) == NULL) { + continue; + } + if (entry->cfp) { + /* did we forget to get rid of cfp last time? */ + php_stream_close(entry->cfp); + entry->cfp = 0; + } + if (entry->is_deleted || entry->is_mounted) { + /* remove this from the new phar */ + continue; + } + if (!entry->is_modified && entry->fp_refcount) { + /* open file pointers refer to this fp, do not free the stream */ + switch (entry->fp_type) { + case PHAR_FP: + free_fp = 0; + break; + case PHAR_UFP: + free_ufp = 0; + default: + break; + } + } + /* after excluding deleted files, calculate manifest size in bytes and number of entries */ + ++new_manifest_count; + phar_add_virtual_dirs(phar, entry->filename, entry->filename_len); + + if (entry->is_dir) { + /* we use this to calculate API version, 1.1.1 is used for phars with directories */ + has_dirs = 1; + } + if (Z_TYPE(entry->metadata) != IS_UNDEF) { + if (entry->metadata_str.s) { + smart_str_free(&entry->metadata_str); + } + entry->metadata_str.s = NULL; + PHP_VAR_SERIALIZE_INIT(metadata_hash); + php_var_serialize(&entry->metadata_str, &entry->metadata, &metadata_hash); + PHP_VAR_SERIALIZE_DESTROY(metadata_hash); + } else { + if (entry->metadata_str.s) { + smart_str_free(&entry->metadata_str); + } + entry->metadata_str.s = NULL; + } + + /* 32 bits for filename length, length of filename, manifest + metadata, and add 1 for trailing / if a directory */ + offset += 4 + entry->filename_len + sizeof(entry_buffer) + (entry->metadata_str.s ? ZSTR_LEN(entry->metadata_str.s) : 0) + (entry->is_dir ? 1 : 0); + + /* compress and rehash as necessary */ + if ((oldfile && !entry->is_modified) || entry->is_dir) { + if (entry->fp_type == PHAR_UFP) { + /* reset so we can copy the compressed data over */ + entry->fp_type = PHAR_FP; + } + continue; + } + if (!phar_get_efp(entry, 0)) { + /* re-open internal file pointer just-in-time */ + newentry = phar_open_jit(phar, entry, error); + if (!newentry) { + /* major problem re-opening, so we ignore this file and the error */ + efree(*error); + *error = NULL; + continue; + } + entry = newentry; + } + file = phar_get_efp(entry, 0); + if (-1 == phar_seek_efp(entry, 0, SEEK_SET, 0, 1)) { + if (closeoldfile) { + php_stream_close(oldfile); + } + php_stream_close(newfile); + if (error) { + spprintf(error, 0, "unable to seek to start of file \"%s\" while creating new phar \"%s\"", entry->filename, phar->fname); + } + return EOF; + } + newcrc32 = ~0; + mytime = entry->uncompressed_filesize; + for (loc = 0;loc < mytime; ++loc) { + CRC32(newcrc32, php_stream_getc(file)); + } + entry->crc32 = ~newcrc32; + entry->is_crc_checked = 1; + if (!(entry->flags & PHAR_ENT_COMPRESSION_MASK)) { + /* not compressed */ + entry->compressed_filesize = entry->uncompressed_filesize; + continue; + } + filter = php_stream_filter_create(phar_compress_filter(entry, 0), NULL, 0); + if (!filter) { + if (closeoldfile) { + php_stream_close(oldfile); + } + php_stream_close(newfile); + if (entry->flags & PHAR_ENT_COMPRESSED_GZ) { + if (error) { + spprintf(error, 0, "unable to gzip compress file \"%s\" to new phar \"%s\"", entry->filename, phar->fname); + } + } else { + if (error) { + spprintf(error, 0, "unable to bzip2 compress file \"%s\" to new phar \"%s\"", entry->filename, phar->fname); + } + } + return EOF; + } + + /* create new file that holds the compressed version */ + /* work around inability to specify freedom in write and strictness + in read count */ + entry->cfp = php_stream_fopen_tmpfile(); + if (!entry->cfp) { + if (error) { + spprintf(error, 0, "unable to create temporary file"); + } + if (closeoldfile) { + php_stream_close(oldfile); + } + php_stream_close(newfile); + return EOF; + } + php_stream_flush(file); + if (-1 == phar_seek_efp(entry, 0, SEEK_SET, 0, 0)) { + if (closeoldfile) { + php_stream_close(oldfile); + } + php_stream_close(newfile); + if (error) { + spprintf(error, 0, "unable to seek to start of file \"%s\" while creating new phar \"%s\"", entry->filename, phar->fname); + } + return EOF; + } + php_stream_filter_append((&entry->cfp->writefilters), filter); + if (SUCCESS != php_stream_copy_to_stream_ex(file, entry->cfp, entry->uncompressed_filesize, NULL)) { + if (closeoldfile) { + php_stream_close(oldfile); + } + php_stream_close(newfile); + if (error) { + spprintf(error, 0, "unable to copy compressed file contents of file \"%s\" while creating new phar \"%s\"", entry->filename, phar->fname); + } + return EOF; + } + php_stream_filter_flush(filter, 1); + php_stream_flush(entry->cfp); + php_stream_filter_remove(filter, 1); + php_stream_seek(entry->cfp, 0, SEEK_END); + entry->compressed_filesize = (uint32_t) php_stream_tell(entry->cfp); + /* generate crc on compressed file */ + php_stream_rewind(entry->cfp); + entry->old_flags = entry->flags; + entry->is_modified = 1; + global_flags |= (entry->flags & PHAR_ENT_COMPRESSION_MASK); + } + global_flags |= PHAR_HDR_SIGNATURE; + + /* write out manifest pre-header */ + /* 4: manifest length + * 4: manifest entry count + * 2: phar version + * 4: phar global flags + * 4: alias length + * ?: the alias itself + * 4: phar metadata length + * ?: phar metadata + */ + restore_alias_len = phar->alias_len; + if (phar->is_temporary_alias) { + phar->alias_len = 0; + } + + manifest_len = offset + phar->alias_len + sizeof(manifest) + (main_metadata_str.s ? ZSTR_LEN(main_metadata_str.s) : 0); + phar_set_32(manifest, manifest_len); + /* Hack - see bug #65028, add padding byte to the end of the manifest */ + if(manifest[0] == '\r' || manifest[0] == '\n') { + manifest_len++; + phar_set_32(manifest, manifest_len); + manifest_hack = 1; + } + phar_set_32(manifest+4, new_manifest_count); + if (has_dirs) { + *(manifest + 8) = (unsigned char) (((PHAR_API_VERSION) >> 8) & 0xFF); + *(manifest + 9) = (unsigned char) (((PHAR_API_VERSION) & 0xF0)); + } else { + *(manifest + 8) = (unsigned char) (((PHAR_API_VERSION_NODIR) >> 8) & 0xFF); + *(manifest + 9) = (unsigned char) (((PHAR_API_VERSION_NODIR) & 0xF0)); + } + phar_set_32(manifest+10, global_flags); + phar_set_32(manifest+14, phar->alias_len); + + /* write the manifest header */ + if (sizeof(manifest) != php_stream_write(newfile, manifest, sizeof(manifest)) + || (size_t)phar->alias_len != php_stream_write(newfile, phar->alias, phar->alias_len)) { + + if (closeoldfile) { + php_stream_close(oldfile); + } + + php_stream_close(newfile); + phar->alias_len = restore_alias_len; + + if (error) { + spprintf(error, 0, "unable to write manifest header of new phar \"%s\"", phar->fname); + } + + return EOF; + } + + phar->alias_len = restore_alias_len; + + phar_set_32(manifest, main_metadata_str.s ? ZSTR_LEN(main_metadata_str.s) : 0); + if (4 != php_stream_write(newfile, manifest, 4) || ((main_metadata_str.s ? ZSTR_LEN(main_metadata_str.s) : 0) + && ZSTR_LEN(main_metadata_str.s) != php_stream_write(newfile, ZSTR_VAL(main_metadata_str.s), ZSTR_LEN(main_metadata_str.s)))) { + smart_str_free(&main_metadata_str); + + if (closeoldfile) { + php_stream_close(oldfile); + } + + php_stream_close(newfile); + phar->alias_len = restore_alias_len; + + if (error) { + spprintf(error, 0, "unable to write manifest meta-data of new phar \"%s\"", phar->fname); + } + + return EOF; + } + smart_str_free(&main_metadata_str); + + /* re-calculate the manifest location to simplify later code */ + manifest_ftell = php_stream_tell(newfile); + + /* now write the manifest */ + for (zend_hash_internal_pointer_reset(&phar->manifest); + zend_hash_has_more_elements(&phar->manifest) == SUCCESS; + zend_hash_move_forward(&phar->manifest)) { + + if ((entry = zend_hash_get_current_data_ptr(&phar->manifest)) == NULL) { + continue; + } + + if (entry->is_deleted || entry->is_mounted) { + /* remove this from the new phar if deleted, ignore if mounted */ + continue; + } + + if (entry->is_dir) { + /* add 1 for trailing slash */ + phar_set_32(entry_buffer, entry->filename_len + 1); + } else { + phar_set_32(entry_buffer, entry->filename_len); + } + + if (4 != php_stream_write(newfile, entry_buffer, 4) + || entry->filename_len != php_stream_write(newfile, entry->filename, entry->filename_len) + || (entry->is_dir && 1 != php_stream_write(newfile, "/", 1))) { + if (closeoldfile) { + php_stream_close(oldfile); + } + php_stream_close(newfile); + if (error) { + if (entry->is_dir) { + spprintf(error, 0, "unable to write filename of directory \"%s\" to manifest of new phar \"%s\"", entry->filename, phar->fname); + } else { + spprintf(error, 0, "unable to write filename of file \"%s\" to manifest of new phar \"%s\"", entry->filename, phar->fname); + } + } + return EOF; + } + + /* set the manifest meta-data: + 4: uncompressed filesize + 4: creation timestamp + 4: compressed filesize + 4: crc32 + 4: flags + 4: metadata-len + +: metadata + */ + mytime = time(NULL); + phar_set_32(entry_buffer, entry->uncompressed_filesize); + phar_set_32(entry_buffer+4, mytime); + phar_set_32(entry_buffer+8, entry->compressed_filesize); + phar_set_32(entry_buffer+12, entry->crc32); + phar_set_32(entry_buffer+16, entry->flags); + phar_set_32(entry_buffer+20, entry->metadata_str.s ? ZSTR_LEN(entry->metadata_str.s) : 0); + + if (sizeof(entry_buffer) != php_stream_write(newfile, entry_buffer, sizeof(entry_buffer)) + || (entry->metadata_str.s && + ZSTR_LEN(entry->metadata_str.s) != php_stream_write(newfile, ZSTR_VAL(entry->metadata_str.s), ZSTR_LEN(entry->metadata_str.s)))) { + if (closeoldfile) { + php_stream_close(oldfile); + } + + php_stream_close(newfile); + + if (error) { + spprintf(error, 0, "unable to write temporary manifest of file \"%s\" to manifest of new phar \"%s\"", entry->filename, phar->fname); + } + + return EOF; + } + } + /* Hack - see bug #65028, add padding byte to the end of the manifest */ + if(manifest_hack) { + if(1 != php_stream_write(newfile, manifest, 1)) { + if (closeoldfile) { + php_stream_close(oldfile); + } + + php_stream_close(newfile); + + if (error) { + spprintf(error, 0, "unable to write manifest padding byte"); + } + + return EOF; + } + } + + /* now copy the actual file data to the new phar */ + offset = php_stream_tell(newfile); + for (zend_hash_internal_pointer_reset(&phar->manifest); + zend_hash_has_more_elements(&phar->manifest) == SUCCESS; + zend_hash_move_forward(&phar->manifest)) { + + if ((entry = zend_hash_get_current_data_ptr(&phar->manifest)) == NULL) { + continue; + } + + if (entry->is_deleted || entry->is_dir || entry->is_mounted) { + continue; + } + + if (entry->cfp) { + file = entry->cfp; + php_stream_rewind(file); + } else { + file = phar_get_efp(entry, 0); + if (-1 == phar_seek_efp(entry, 0, SEEK_SET, 0, 0)) { + if (closeoldfile) { + php_stream_close(oldfile); + } + php_stream_close(newfile); + if (error) { + spprintf(error, 0, "unable to seek to start of file \"%s\" while creating new phar \"%s\"", entry->filename, phar->fname); + } + return EOF; + } + } + + if (!file) { + if (closeoldfile) { + php_stream_close(oldfile); + } + php_stream_close(newfile); + if (error) { + spprintf(error, 0, "unable to seek to start of file \"%s\" while creating new phar \"%s\"", entry->filename, phar->fname); + } + return EOF; + } + + /* this will have changed for all files that have either changed compression or been modified */ + entry->offset = entry->offset_abs = offset; + offset += entry->compressed_filesize; + if (php_stream_copy_to_stream_ex(file, newfile, entry->compressed_filesize, &wrote) == FAILURE) { + if (closeoldfile) { + php_stream_close(oldfile); + } + + php_stream_close(newfile); + + if (error) { + spprintf(error, 0, "unable to write contents of file \"%s\" to new phar \"%s\"", entry->filename, phar->fname); + } + + return EOF; + } + + entry->is_modified = 0; + + if (entry->cfp) { + php_stream_close(entry->cfp); + entry->cfp = NULL; + } + + if (entry->fp_type == PHAR_MOD) { + /* this fp is in use by a phar_entry_data returned by phar_get_entry_data, it will be closed when the phar_entry_data is phar_entry_delref'ed */ + if (entry->fp_refcount == 0 && entry->fp != phar->fp && entry->fp != phar->ufp) { + php_stream_close(entry->fp); + } + + entry->fp = NULL; + entry->fp_type = PHAR_FP; + } else if (entry->fp_type == PHAR_UFP) { + entry->fp_type = PHAR_FP; + } + } + + /* append signature */ + if (global_flags & PHAR_HDR_SIGNATURE) { + char sig_buf[4]; + + php_stream_rewind(newfile); + + if (phar->signature) { + efree(phar->signature); + phar->signature = NULL; + } + + switch(phar->sig_flags) { +#ifndef PHAR_HASH_OK + case PHAR_SIG_SHA512: + case PHAR_SIG_SHA256: + if (closeoldfile) { + php_stream_close(oldfile); + } + php_stream_close(newfile); + if (error) { + spprintf(error, 0, "unable to write contents of file \"%s\" to new phar \"%s\" with requested hash type", entry->filename, phar->fname); + } + return EOF; +#endif + default: { + char *digest = NULL; + int digest_len; + + if (FAILURE == phar_create_signature(phar, newfile, &digest, &digest_len, error)) { + if (error) { + char *save = *error; + spprintf(error, 0, "phar error: unable to write signature: %s", save); + efree(save); + } + if (digest) { + efree(digest); + } + if (closeoldfile) { + php_stream_close(oldfile); + } + php_stream_close(newfile); + return EOF; + } + + php_stream_write(newfile, digest, digest_len); + efree(digest); + if (phar->sig_flags == PHAR_SIG_OPENSSL) { + phar_set_32(sig_buf, digest_len); + php_stream_write(newfile, sig_buf, 4); + } + break; + } + } + phar_set_32(sig_buf, phar->sig_flags); + php_stream_write(newfile, sig_buf, 4); + php_stream_write(newfile, "GBMB", 4); + } + + /* finally, close the temp file, rename the original phar, + move the temp to the old phar, unlink the old phar, and reload it into memory + */ + if (phar->fp && free_fp) { + php_stream_close(phar->fp); + } + + if (phar->ufp) { + if (free_ufp) { + php_stream_close(phar->ufp); + } + phar->ufp = NULL; + } + + if (closeoldfile) { + php_stream_close(oldfile); + } + + phar->internal_file_start = halt_offset + manifest_len + 4; + phar->halt_offset = halt_offset; + phar->is_brandnew = 0; + + php_stream_rewind(newfile); + + if (phar->donotflush) { + /* deferred flush */ + phar->fp = newfile; + } else { + phar->fp = php_stream_open_wrapper(phar->fname, "w+b", IGNORE_URL|STREAM_MUST_SEEK|REPORT_ERRORS, NULL); + if (!phar->fp) { + phar->fp = newfile; + if (error) { + spprintf(error, 4096, "unable to open new phar \"%s\" for writing", phar->fname); + } + return EOF; + } + + if (phar->flags & PHAR_FILE_COMPRESSED_GZ) { + /* to properly compress, we have to tell zlib to add a zlib header */ + zval filterparams; + + array_init(&filterparams); + add_assoc_long(&filterparams, "window", MAX_WBITS+16); + filter = php_stream_filter_create("zlib.deflate", &filterparams, php_stream_is_persistent(phar->fp)); + zval_dtor(&filterparams); + + if (!filter) { + if (error) { + spprintf(error, 4096, "unable to compress all contents of phar \"%s\" using zlib, PHP versions older than 5.2.6 have a buggy zlib", phar->fname); + } + return EOF; + } + + php_stream_filter_append(&phar->fp->writefilters, filter); + php_stream_copy_to_stream_ex(newfile, phar->fp, PHP_STREAM_COPY_ALL, NULL); + php_stream_filter_flush(filter, 1); + php_stream_filter_remove(filter, 1); + php_stream_close(phar->fp); + /* use the temp stream as our base */ + phar->fp = newfile; + } else if (phar->flags & PHAR_FILE_COMPRESSED_BZ2) { + filter = php_stream_filter_create("bzip2.compress", NULL, php_stream_is_persistent(phar->fp)); + php_stream_filter_append(&phar->fp->writefilters, filter); + php_stream_copy_to_stream_ex(newfile, phar->fp, PHP_STREAM_COPY_ALL, NULL); + php_stream_filter_flush(filter, 1); + php_stream_filter_remove(filter, 1); + php_stream_close(phar->fp); + /* use the temp stream as our base */ + phar->fp = newfile; + } else { + php_stream_copy_to_stream_ex(newfile, phar->fp, PHP_STREAM_COPY_ALL, NULL); + /* we could also reopen the file in "rb" mode but there is no need for that */ + php_stream_close(newfile); + } + } + + if (-1 == php_stream_seek(phar->fp, phar->halt_offset, SEEK_SET)) { + if (error) { + spprintf(error, 0, "unable to seek to __HALT_COMPILER(); in new phar \"%s\"", phar->fname); + } + return EOF; + } + + return EOF; +} +/* }}} */ + +#ifdef COMPILE_DL_PHAR +#ifdef ZTS +ZEND_TSRMLS_CACHE_DEFINE() +#endif +ZEND_GET_MODULE(phar) +#endif + +/* {{{ phar_functions[] + * + * Every user visible function must have an entry in phar_functions[]. + */ +zend_function_entry phar_functions[] = { + PHP_FE_END +}; +/* }}}*/ + +static size_t phar_zend_stream_reader(void *handle, char *buf, size_t len) /* {{{ */ +{ + return php_stream_read(phar_get_pharfp((phar_archive_data*)handle), buf, len); +} +/* }}} */ + +static size_t phar_zend_stream_fsizer(void *handle) /* {{{ */ +{ + return ((phar_archive_data*)handle)->halt_offset + 32; +} /* }}} */ + +zend_op_array *(*phar_orig_compile_file)(zend_file_handle *file_handle, int type); +#define phar_orig_zend_open zend_stream_open_function + +static zend_string *phar_resolve_path(const char *filename, int filename_len) +{ + return phar_find_in_include_path((char *) filename, filename_len, NULL); +} + +static zend_op_array *phar_compile_file(zend_file_handle *file_handle, int type) /* {{{ */ +{ + zend_op_array *res; + char *name = NULL; + int failed; + phar_archive_data *phar; + + if (!file_handle || !file_handle->filename) { + return phar_orig_compile_file(file_handle, type); + } + if (strstr(file_handle->filename, ".phar") && !strstr(file_handle->filename, "://")) { + if (SUCCESS == phar_open_from_filename((char*)file_handle->filename, strlen(file_handle->filename), NULL, 0, 0, &phar, NULL)) { + if (phar->is_zip || phar->is_tar) { + zend_file_handle f = *file_handle; + + /* zip or tar-based phar */ + spprintf(&name, 4096, "phar://%s/%s", file_handle->filename, ".phar/stub.php"); + if (SUCCESS == phar_orig_zend_open((const char *)name, &f)) { + + efree(name); + name = NULL; + + f.filename = file_handle->filename; + if (f.opened_path) { + efree(f.opened_path); + } + f.opened_path = file_handle->opened_path; + f.free_filename = file_handle->free_filename; + + switch (file_handle->type) { + case ZEND_HANDLE_STREAM: + case ZEND_HANDLE_MAPPED: + if (file_handle->handle.stream.closer && file_handle->handle.stream.handle) { + file_handle->handle.stream.closer(file_handle->handle.stream.handle); + } + file_handle->handle.stream.handle = NULL; + break; + default: + break; + } + *file_handle = f; + } + } else if (phar->flags & PHAR_FILE_COMPRESSION_MASK) { + zend_file_handle_dtor(file_handle); + /* compressed phar */ + file_handle->type = ZEND_HANDLE_STREAM; + /* we do our own reading directly from the phar, don't change the next line */ + file_handle->handle.stream.handle = phar; + file_handle->handle.stream.reader = phar_zend_stream_reader; + file_handle->handle.stream.closer = NULL; + file_handle->handle.stream.fsizer = phar_zend_stream_fsizer; + file_handle->handle.stream.isatty = 0; + phar->is_persistent ? + php_stream_rewind(PHAR_G(cached_fp)[phar->phar_pos].fp) : + php_stream_rewind(phar->fp); + memset(&file_handle->handle.stream.mmap, 0, sizeof(file_handle->handle.stream.mmap)); + } + } + } + + zend_try { + failed = 0; + CG(zend_lineno) = 0; + res = phar_orig_compile_file(file_handle, type); + } zend_catch { + failed = 1; + res = NULL; + } zend_end_try(); + + if (name) { + efree(name); + } + + if (failed) { + zend_bailout(); + } + + return res; +} +/* }}} */ + +typedef zend_op_array* (zend_compile_t)(zend_file_handle*, int); +typedef zend_compile_t* (compile_hook)(zend_compile_t *ptr); + +static void mime_type_dtor(zval *zv) +{ + free(Z_PTR_P(zv)); +} + +PHP_GINIT_FUNCTION(phar) /* {{{ */ +{ +#if defined(COMPILE_DL_PHAR) && defined(ZTS) + ZEND_TSRMLS_CACHE_UPDATE(); +#endif + phar_mime_type mime; + + memset(phar_globals, 0, sizeof(zend_phar_globals)); + phar_globals->readonly = 1; + + zend_hash_init(&phar_globals->mime_types, 0, NULL, mime_type_dtor, 1); + +#define PHAR_SET_MIME(mimetype, ret, fileext) \ + mime.mime = mimetype; \ + mime.len = sizeof((mimetype))+1; \ + mime.type = ret; \ + zend_hash_str_add_mem(&phar_globals->mime_types, fileext, sizeof(fileext)-1, (void *)&mime, sizeof(phar_mime_type)); \ + + PHAR_SET_MIME("text/html", PHAR_MIME_PHPS, "phps") + PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "c") + PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "cc") + PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "cpp") + PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "c++") + PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "dtd") + PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "h") + PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "log") + PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "rng") + PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "txt") + PHAR_SET_MIME("text/plain", PHAR_MIME_OTHER, "xsd") + PHAR_SET_MIME("", PHAR_MIME_PHP, "php") + PHAR_SET_MIME("", PHAR_MIME_PHP, "inc") + PHAR_SET_MIME("video/avi", PHAR_MIME_OTHER, "avi") + PHAR_SET_MIME("image/bmp", PHAR_MIME_OTHER, "bmp") + PHAR_SET_MIME("text/css", PHAR_MIME_OTHER, "css") + PHAR_SET_MIME("image/gif", PHAR_MIME_OTHER, "gif") + PHAR_SET_MIME("text/html", PHAR_MIME_OTHER, "htm") + PHAR_SET_MIME("text/html", PHAR_MIME_OTHER, "html") + PHAR_SET_MIME("text/html", PHAR_MIME_OTHER, "htmls") + PHAR_SET_MIME("image/x-ico", PHAR_MIME_OTHER, "ico") + PHAR_SET_MIME("image/jpeg", PHAR_MIME_OTHER, "jpe") + PHAR_SET_MIME("image/jpeg", PHAR_MIME_OTHER, "jpg") + PHAR_SET_MIME("image/jpeg", PHAR_MIME_OTHER, "jpeg") + PHAR_SET_MIME("application/x-javascript", PHAR_MIME_OTHER, "js") + PHAR_SET_MIME("audio/midi", PHAR_MIME_OTHER, "midi") + PHAR_SET_MIME("audio/midi", PHAR_MIME_OTHER, "mid") + PHAR_SET_MIME("audio/mod", PHAR_MIME_OTHER, "mod") + PHAR_SET_MIME("movie/quicktime", PHAR_MIME_OTHER, "mov") + PHAR_SET_MIME("audio/mp3", PHAR_MIME_OTHER, "mp3") + PHAR_SET_MIME("video/mpeg", PHAR_MIME_OTHER, "mpg") + PHAR_SET_MIME("video/mpeg", PHAR_MIME_OTHER, "mpeg") + PHAR_SET_MIME("application/pdf", PHAR_MIME_OTHER, "pdf") + PHAR_SET_MIME("image/png", PHAR_MIME_OTHER, "png") + PHAR_SET_MIME("application/shockwave-flash", PHAR_MIME_OTHER, "swf") + PHAR_SET_MIME("image/tiff", PHAR_MIME_OTHER, "tif") + PHAR_SET_MIME("image/tiff", PHAR_MIME_OTHER, "tiff") + PHAR_SET_MIME("audio/wav", PHAR_MIME_OTHER, "wav") + PHAR_SET_MIME("image/xbm", PHAR_MIME_OTHER, "xbm") + PHAR_SET_MIME("text/xml", PHAR_MIME_OTHER, "xml") + + phar_restore_orig_functions(); +} +/* }}} */ + +PHP_GSHUTDOWN_FUNCTION(phar) /* {{{ */ +{ + zend_hash_destroy(&phar_globals->mime_types); +} +/* }}} */ + +PHP_MINIT_FUNCTION(phar) /* {{{ */ +{ + REGISTER_INI_ENTRIES(); + + phar_orig_compile_file = zend_compile_file; + zend_compile_file = phar_compile_file; + + phar_save_resolve_path = zend_resolve_path; + zend_resolve_path = phar_resolve_path; + + phar_object_init(); + + phar_intercept_functions_init(); + phar_save_orig_functions(); + + return php_register_url_stream_wrapper("phar", &php_stream_phar_wrapper); +} +/* }}} */ + +PHP_MSHUTDOWN_FUNCTION(phar) /* {{{ */ +{ + php_unregister_url_stream_wrapper("phar"); + + phar_intercept_functions_shutdown(); + + if (zend_compile_file == phar_compile_file) { + zend_compile_file = phar_orig_compile_file; + } + + if (PHAR_G(manifest_cached)) { + zend_hash_destroy(&(cached_phars)); + zend_hash_destroy(&(cached_alias)); + } + + return SUCCESS; +} +/* }}} */ + +void phar_request_initialize(void) /* {{{ */ +{ + if (!PHAR_G(request_init)) + { + PHAR_G(last_phar) = NULL; + PHAR_G(last_phar_name) = PHAR_G(last_alias) = NULL; + PHAR_G(has_bz2) = zend_hash_str_exists(&module_registry, "bz2", sizeof("bz2")-1); + PHAR_G(has_zlib) = zend_hash_str_exists(&module_registry, "zlib", sizeof("zlib")-1); + PHAR_G(request_init) = 1; + PHAR_G(request_ends) = 0; + PHAR_G(request_done) = 0; + zend_hash_init(&(PHAR_G(phar_fname_map)), 5, zend_get_hash_value, destroy_phar_data, 0); + zend_hash_init(&(PHAR_G(phar_persist_map)), 5, zend_get_hash_value, NULL, 0); + zend_hash_init(&(PHAR_G(phar_alias_map)), 5, zend_get_hash_value, NULL, 0); + + if (PHAR_G(manifest_cached)) { + phar_archive_data *pphar; + phar_entry_fp *stuff = (phar_entry_fp *) ecalloc(zend_hash_num_elements(&cached_phars), sizeof(phar_entry_fp)); + + for (zend_hash_internal_pointer_reset(&cached_phars); + (pphar = zend_hash_get_current_data_ptr(&cached_phars)) != NULL; + zend_hash_move_forward(&cached_phars)) { + stuff[pphar->phar_pos].manifest = (phar_entry_fp_info *) ecalloc( zend_hash_num_elements(&(pphar->manifest)), sizeof(phar_entry_fp_info)); + } + + PHAR_G(cached_fp) = stuff; + } + + PHAR_G(phar_SERVER_mung_list) = 0; + PHAR_G(cwd) = NULL; + PHAR_G(cwd_len) = 0; + PHAR_G(cwd_init) = 0; + } +} +/* }}} */ + +PHP_RSHUTDOWN_FUNCTION(phar) /* {{{ */ +{ + uint32_t i; + + PHAR_G(request_ends) = 1; + + if (PHAR_G(request_init)) + { + phar_release_functions(); + zend_hash_destroy(&(PHAR_G(phar_alias_map))); + PHAR_G(phar_alias_map.u.flags) = 0; + zend_hash_destroy(&(PHAR_G(phar_fname_map))); + PHAR_G(phar_fname_map.u.flags) = 0; + zend_hash_destroy(&(PHAR_G(phar_persist_map))); + PHAR_G(phar_persist_map.u.flags) = 0; + PHAR_G(phar_SERVER_mung_list) = 0; + + if (PHAR_G(cached_fp)) { + for (i = 0; i < zend_hash_num_elements(&cached_phars); ++i) { + if (PHAR_G(cached_fp)[i].fp) { + php_stream_close(PHAR_G(cached_fp)[i].fp); + } + if (PHAR_G(cached_fp)[i].ufp) { + php_stream_close(PHAR_G(cached_fp)[i].ufp); + } + efree(PHAR_G(cached_fp)[i].manifest); + } + efree(PHAR_G(cached_fp)); + PHAR_G(cached_fp) = 0; + } + + PHAR_G(request_init) = 0; + + if (PHAR_G(cwd)) { + efree(PHAR_G(cwd)); + } + + PHAR_G(cwd) = NULL; + PHAR_G(cwd_len) = 0; + PHAR_G(cwd_init) = 0; + } + + PHAR_G(request_done) = 1; + return SUCCESS; +} +/* }}} */ + +PHP_MINFO_FUNCTION(phar) /* {{{ */ +{ + phar_request_initialize(); + php_info_print_table_start(); + php_info_print_table_header(2, "Phar: PHP Archive support", "enabled"); + php_info_print_table_row(2, "Phar EXT version", PHP_PHAR_VERSION); + php_info_print_table_row(2, "Phar API version", PHP_PHAR_API_VERSION); + php_info_print_table_row(2, "SVN revision", "$Id: 59c11f4e29768bfbbf6f41cb469abd81d8655850 $"); + php_info_print_table_row(2, "Phar-based phar archives", "enabled"); + php_info_print_table_row(2, "Tar-based phar archives", "enabled"); + php_info_print_table_row(2, "ZIP-based phar archives", "enabled"); + + if (PHAR_G(has_zlib)) { + php_info_print_table_row(2, "gzip compression", "enabled"); + } else { + php_info_print_table_row(2, "gzip compression", "disabled (install ext/zlib)"); + } + + if (PHAR_G(has_bz2)) { + php_info_print_table_row(2, "bzip2 compression", "enabled"); + } else { + php_info_print_table_row(2, "bzip2 compression", "disabled (install pecl/bz2)"); + } +#ifdef PHAR_HAVE_OPENSSL + php_info_print_table_row(2, "Native OpenSSL support", "enabled"); +#else + if (zend_hash_str_exists(&module_registry, "openssl", sizeof("openssl")-1)) { + php_info_print_table_row(2, "OpenSSL support", "enabled"); + } else { + php_info_print_table_row(2, "OpenSSL support", "disabled (install ext/openssl)"); + } +#endif + php_info_print_table_end(); + + php_info_print_box_start(0); + PUTS("Phar based on pear/PHP_Archive, original concept by Davey Shafik."); + PUTS(!sapi_module.phpinfo_as_text?"
":"\n"); + PUTS("Phar fully realized by Gregory Beaver and Marcus Boerger."); + PUTS(!sapi_module.phpinfo_as_text?"
":"\n"); + PUTS("Portions of tar implementation Copyright (c) 2003-2009 Tim Kientzle."); + php_info_print_box_end(); + + DISPLAY_INI_ENTRIES(); +} +/* }}} */ + +/* {{{ phar_module_entry + */ +static const zend_module_dep phar_deps[] = { + ZEND_MOD_OPTIONAL("apc") + ZEND_MOD_OPTIONAL("bz2") + ZEND_MOD_OPTIONAL("openssl") + ZEND_MOD_OPTIONAL("zlib") + ZEND_MOD_OPTIONAL("standard") +#if defined(HAVE_HASH) && !defined(COMPILE_DL_HASH) + ZEND_MOD_REQUIRED("hash") +#endif + ZEND_MOD_REQUIRED("spl") + ZEND_MOD_END +}; + +zend_module_entry phar_module_entry = { + STANDARD_MODULE_HEADER_EX, NULL, + phar_deps, + "Phar", + phar_functions, + PHP_MINIT(phar), + PHP_MSHUTDOWN(phar), + NULL, + PHP_RSHUTDOWN(phar), + PHP_MINFO(phar), + PHP_PHAR_VERSION, + PHP_MODULE_GLOBALS(phar), /* globals descriptor */ + PHP_GINIT(phar), /* globals ctor */ + PHP_GSHUTDOWN(phar), /* globals dtor */ + NULL, /* post deactivate */ + STANDARD_MODULE_PROPERTIES_EX +}; +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/ext/phar/phar_object.c b/ext/phar/phar_object.c index 79cc9c1e311d0..a96446f91a755 100644 --- a/ext/phar/phar_object.c +++ b/ext/phar/phar_object.c @@ -308,8 +308,7 @@ static void phar_do_403(char *entry, int entry_len) /* {{{ */ sapi_header_op(SAPI_HEADER_REPLACE, &ctr); sapi_send_headers(); PHPWRITE("\n \n Access Denied\n \n \n

403 - File ", sizeof("\n \n Access Denied\n \n \n

403 - File ") - 1); - PHPWRITE(entry, entry_len); - PHPWRITE(" Access Denied

\n \n", sizeof(" Access Denied\n \n") - 1); + PHPWRITE("Access Denied\n \n", sizeof("Access Denied\n \n") - 1); } /* }}} */ @@ -333,8 +332,7 @@ static void phar_do_404(phar_archive_data *phar, char *fname, int fname_len, cha sapi_header_op(SAPI_HEADER_REPLACE, &ctr); sapi_send_headers(); PHPWRITE("\n \n File Not Found\n \n \n

404 - File ", sizeof("\n \n File Not Found\n \n \n

404 - File ") - 1); - PHPWRITE(entry, entry_len); - PHPWRITE(" Not Found

\n \n", sizeof(" Not Found\n \n") - 1); + PHPWRITE("Not Found\n \n", sizeof("Not Found\n \n") - 1); } /* }}} */ @@ -1441,6 +1439,7 @@ static int phar_build(zend_object_iterator *iter, void *puser) /* {{{ */ char *str_key; zend_class_entry *ce = p_obj->c; phar_archive_object *phar_obj = p_obj->p; + php_stream_statbuf ssb; value = iter->funcs->get_current_data(iter); @@ -1720,6 +1719,16 @@ static int phar_build(zend_object_iterator *iter, void *puser) /* {{{ */ php_stream_copy_to_stream_ex(fp, p_obj->fp, PHP_STREAM_COPY_ALL, &contents_len); data->internal_file->uncompressed_filesize = data->internal_file->compressed_filesize = php_stream_tell(p_obj->fp) - data->internal_file->offset; + if (php_stream_stat(fp, &ssb) != -1) { + data->internal_file->flags = ssb.sb.st_mode & PHAR_ENT_PERM_MASK ; + } else { +#ifndef _WIN32 + mode_t mask; + mask = umask(0); + umask(mask); + data->internal_file->flags &= ~mask; +#endif + } } if (close_fp) { diff --git a/ext/phar/phar_object.c.orig b/ext/phar/phar_object.c.orig new file mode 100644 index 0000000000000..79cc9c1e311d0 --- /dev/null +++ b/ext/phar/phar_object.c.orig @@ -0,0 +1,5461 @@ +/* + +----------------------------------------------------------------------+ + | phar php single-file executable PHP extension | + +----------------------------------------------------------------------+ + | Copyright (c) 2005-2017 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt. | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Gregory Beaver | + | Marcus Boerger | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#include "phar_internal.h" +#include "func_interceptors.h" + +static zend_class_entry *phar_ce_archive; +static zend_class_entry *phar_ce_data; +static zend_class_entry *phar_ce_PharException; +static zend_class_entry *phar_ce_entry; + +static int phar_file_type(HashTable *mimes, char *file, char **mime_type) /* {{{ */ +{ + char *ext; + phar_mime_type *mime; + ext = strrchr(file, '.'); + if (!ext) { + *mime_type = "text/plain"; + /* no file extension = assume text/plain */ + return PHAR_MIME_OTHER; + } + ++ext; + if (NULL == (mime = zend_hash_str_find_ptr(mimes, ext, strlen(ext)))) { + *mime_type = "application/octet-stream"; + return PHAR_MIME_OTHER; + } + *mime_type = mime->mime; + return mime->type; +} +/* }}} */ + +static void phar_mung_server_vars(char *fname, char *entry, int entry_len, char *basename, int request_uri_len) /* {{{ */ +{ + HashTable *_SERVER; + zval *stuff; + char *path_info; + size_t basename_len = strlen(basename); + size_t code; + zval temp; + + /* "tweak" $_SERVER variables requested in earlier call to Phar::mungServer() */ + if (Z_TYPE(PG(http_globals)[TRACK_VARS_SERVER]) == IS_UNDEF) { + return; + } + + _SERVER = Z_ARRVAL(PG(http_globals)[TRACK_VARS_SERVER]); + + /* PATH_INFO and PATH_TRANSLATED should always be munged */ + if (NULL != (stuff = zend_hash_str_find(_SERVER, "PATH_INFO", sizeof("PATH_INFO")-1))) { + path_info = Z_STRVAL_P(stuff); + code = Z_STRLEN_P(stuff); + if (code > entry_len && !memcmp(path_info, entry, entry_len)) { + ZVAL_STR(&temp, Z_STR_P(stuff)); + ZVAL_STRINGL(stuff, path_info + entry_len, request_uri_len); + zend_hash_str_update(_SERVER, "PHAR_PATH_INFO", sizeof("PHAR_PATH_INFO")-1, &temp); + } + } + + if (NULL != (stuff = zend_hash_str_find(_SERVER, "PATH_TRANSLATED", sizeof("PATH_TRANSLATED")-1))) { + zend_string *str = strpprintf(4096, "phar://%s%s", fname, entry); + + ZVAL_STR(&temp, Z_STR_P(stuff)); + ZVAL_NEW_STR(stuff, str); + + zend_hash_str_update(_SERVER, "PHAR_PATH_TRANSLATED", sizeof("PHAR_PATH_TRANSLATED")-1, &temp); + } + + if (!PHAR_G(phar_SERVER_mung_list)) { + return; + } + + if (PHAR_G(phar_SERVER_mung_list) & PHAR_MUNG_REQUEST_URI) { + if (NULL != (stuff = zend_hash_str_find(_SERVER, "REQUEST_URI", sizeof("REQUEST_URI")-1))) { + path_info = Z_STRVAL_P(stuff); + code = Z_STRLEN_P(stuff); + if (code > basename_len && !memcmp(path_info, basename, basename_len)) { + ZVAL_STR(&temp, Z_STR_P(stuff)); + ZVAL_STRINGL(stuff, path_info + basename_len, code - basename_len); + zend_hash_str_update(_SERVER, "PHAR_REQUEST_URI", sizeof("PHAR_REQUEST_URI")-1, &temp); + } + } + } + + if (PHAR_G(phar_SERVER_mung_list) & PHAR_MUNG_PHP_SELF) { + if (NULL != (stuff = zend_hash_str_find(_SERVER, "PHP_SELF", sizeof("PHP_SELF")-1))) { + path_info = Z_STRVAL_P(stuff); + code = Z_STRLEN_P(stuff); + + if (code > basename_len && !memcmp(path_info, basename, basename_len)) { + ZVAL_STR(&temp, Z_STR_P(stuff)); + ZVAL_STRINGL(stuff, path_info + basename_len, code - basename_len); + zend_hash_str_update(_SERVER, "PHAR_PHP_SELF", sizeof("PHAR_PHP_SELF")-1, &temp); + } + } + } + + if (PHAR_G(phar_SERVER_mung_list) & PHAR_MUNG_SCRIPT_NAME) { + if (NULL != (stuff = zend_hash_str_find(_SERVER, "SCRIPT_NAME", sizeof("SCRIPT_NAME")-1))) { + ZVAL_STR(&temp, Z_STR_P(stuff)); + ZVAL_STRINGL(stuff, entry, entry_len); + zend_hash_str_update(_SERVER, "PHAR_SCRIPT_NAME", sizeof("PHAR_SCRIPT_NAME")-1, &temp); + } + } + + if (PHAR_G(phar_SERVER_mung_list) & PHAR_MUNG_SCRIPT_FILENAME) { + if (NULL != (stuff = zend_hash_str_find(_SERVER, "SCRIPT_FILENAME", sizeof("SCRIPT_FILENAME")-1))) { + zend_string *str = strpprintf(4096, "phar://%s%s", fname, entry); + + ZVAL_STR(&temp, Z_STR_P(stuff)); + ZVAL_NEW_STR(stuff, str); + + zend_hash_str_update(_SERVER, "PHAR_SCRIPT_FILENAME", sizeof("PHAR_SCRIPT_FILENAME")-1, &temp); + } + } +} +/* }}} */ + +static int phar_file_action(phar_archive_data *phar, phar_entry_info *info, char *mime_type, int code, char *entry, int entry_len, char *arch, char *basename, char *ru, int ru_len) /* {{{ */ +{ + char *name = NULL, buf[8192]; + const char *cwd; + zend_syntax_highlighter_ini syntax_highlighter_ini; + sapi_header_line ctr = {0}; + size_t got; + zval dummy; + size_t name_len; + zend_file_handle file_handle; + zend_op_array *new_op_array; + zval result; + php_stream *fp; + zend_off_t position; + + switch (code) { + case PHAR_MIME_PHPS: + efree(basename); + /* highlight source */ + if (entry[0] == '/') { + spprintf(&name, 4096, "phar://%s%s", arch, entry); + } else { + spprintf(&name, 4096, "phar://%s/%s", arch, entry); + } + php_get_highlight_struct(&syntax_highlighter_ini); + + highlight_file(name, &syntax_highlighter_ini); + + efree(name); +#ifdef PHP_WIN32 + efree(arch); +#endif + zend_bailout(); + case PHAR_MIME_OTHER: + /* send headers, output file contents */ + efree(basename); + ctr.line_len = spprintf(&(ctr.line), 0, "Content-type: %s", mime_type); + sapi_header_op(SAPI_HEADER_REPLACE, &ctr); + efree(ctr.line); + ctr.line_len = spprintf(&(ctr.line), 0, "Content-length: %u", info->uncompressed_filesize); + sapi_header_op(SAPI_HEADER_REPLACE, &ctr); + efree(ctr.line); + + if (FAILURE == sapi_send_headers()) { + zend_bailout(); + } + + /* prepare to output */ + fp = phar_get_efp(info, 1); + + if (!fp) { + char *error; + if (!phar_open_jit(phar, info, &error)) { + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } + return -1; + } + fp = phar_get_efp(info, 1); + } + position = 0; + phar_seek_efp(info, 0, SEEK_SET, 0, 1); + + do { + got = php_stream_read(fp, buf, MIN(8192, info->uncompressed_filesize - position)); + if (got > 0) { + PHPWRITE(buf, got); + position += got; + if (position == (zend_off_t) info->uncompressed_filesize) { + break; + } + } + } while (1); + + zend_bailout(); + case PHAR_MIME_PHP: + if (basename) { + phar_mung_server_vars(arch, entry, entry_len, basename, ru_len); + efree(basename); + } + + if (entry[0] == '/') { + name_len = spprintf(&name, 4096, "phar://%s%s", arch, entry); + } else { + name_len = spprintf(&name, 4096, "phar://%s/%s", arch, entry); + } + + file_handle.type = ZEND_HANDLE_FILENAME; + file_handle.handle.fd = 0; + file_handle.filename = name; + file_handle.opened_path = NULL; + file_handle.free_filename = 0; + + PHAR_G(cwd) = NULL; + PHAR_G(cwd_len) = 0; + + ZVAL_NULL(&dummy); + if (zend_hash_str_add(&EG(included_files), name, name_len, &dummy) != NULL) { + if ((cwd = zend_memrchr(entry, '/', entry_len))) { + PHAR_G(cwd_init) = 1; + if (entry == cwd) { + /* root directory */ + PHAR_G(cwd_len) = 0; + PHAR_G(cwd) = NULL; + } else if (entry[0] == '/') { + PHAR_G(cwd_len) = (int)(cwd - (entry + 1)); + PHAR_G(cwd) = estrndup(entry + 1, PHAR_G(cwd_len)); + } else { + PHAR_G(cwd_len) = (int)(cwd - entry); + PHAR_G(cwd) = estrndup(entry, PHAR_G(cwd_len)); + } + } + + new_op_array = zend_compile_file(&file_handle, ZEND_REQUIRE); + + if (!new_op_array) { + zend_hash_str_del(&EG(included_files), name, name_len); + } + + zend_destroy_file_handle(&file_handle); + + } else { + efree(name); + new_op_array = NULL; + } +#ifdef PHP_WIN32 + efree(arch); +#endif + if (new_op_array) { + ZVAL_UNDEF(&result); + + zend_try { + zend_execute(new_op_array, &result); + if (PHAR_G(cwd)) { + efree(PHAR_G(cwd)); + PHAR_G(cwd) = NULL; + PHAR_G(cwd_len) = 0; + } + + PHAR_G(cwd_init) = 0; + efree(name); + destroy_op_array(new_op_array); + efree(new_op_array); + zval_ptr_dtor(&result); + } zend_catch { + if (PHAR_G(cwd)) { + efree(PHAR_G(cwd)); + PHAR_G(cwd) = NULL; + PHAR_G(cwd_len) = 0; + } + + PHAR_G(cwd_init) = 0; + efree(name); + } zend_end_try(); + + zend_bailout(); + } + + return PHAR_MIME_PHP; + } + return -1; +} +/* }}} */ + +static void phar_do_403(char *entry, int entry_len) /* {{{ */ +{ + sapi_header_line ctr = {0}; + + ctr.response_code = 403; + ctr.line_len = sizeof("HTTP/1.0 403 Access Denied")-1; + ctr.line = "HTTP/1.0 403 Access Denied"; + sapi_header_op(SAPI_HEADER_REPLACE, &ctr); + sapi_send_headers(); + PHPWRITE("\n \n Access Denied\n \n \n

403 - File ", sizeof("\n \n Access Denied\n \n \n

403 - File ") - 1); + PHPWRITE(entry, entry_len); + PHPWRITE(" Access Denied

\n \n", sizeof(" Access Denied\n \n") - 1); +} +/* }}} */ + +static void phar_do_404(phar_archive_data *phar, char *fname, int fname_len, char *f404, int f404_len, char *entry, size_t entry_len) /* {{{ */ +{ + sapi_header_line ctr = {0}; + phar_entry_info *info; + + if (phar && f404_len) { + info = phar_get_entry_info(phar, f404, f404_len, NULL, 1); + + if (info) { + phar_file_action(phar, info, "text/html", PHAR_MIME_PHP, f404, f404_len, fname, NULL, NULL, 0); + return; + } + } + + ctr.response_code = 404; + ctr.line_len = sizeof("HTTP/1.0 404 Not Found")-1; + ctr.line = "HTTP/1.0 404 Not Found"; + sapi_header_op(SAPI_HEADER_REPLACE, &ctr); + sapi_send_headers(); + PHPWRITE("\n \n File Not Found\n \n \n

404 - File ", sizeof("\n \n File Not Found\n \n \n

404 - File ") - 1); + PHPWRITE(entry, entry_len); + PHPWRITE(" Not Found

\n \n", sizeof(" Not Found\n \n") - 1); +} +/* }}} */ + +/* post-process REQUEST_URI and retrieve the actual request URI. This is for + cases like http://localhost/blah.phar/path/to/file.php/extra/stuff + which calls "blah.phar" file "path/to/file.php" with PATH_INFO "/extra/stuff" */ +static void phar_postprocess_ru_web(char *fname, int fname_len, char **entry, int *entry_len, char **ru, int *ru_len) /* {{{ */ +{ + char *e = *entry + 1, *u = NULL, *u1 = NULL, *saveu = NULL; + int e_len = *entry_len - 1, u_len = 0; + phar_archive_data *pphar; + + /* we already know we can retrieve the phar if we reach here */ + pphar = zend_hash_str_find_ptr(&(PHAR_G(phar_fname_map)), fname, fname_len); + + if (!pphar && PHAR_G(manifest_cached)) { + pphar = zend_hash_str_find_ptr(&cached_phars, fname, fname_len); + } + + do { + if (zend_hash_str_exists(&(pphar->manifest), e, e_len)) { + if (u) { + u[0] = '/'; + *ru = estrndup(u, u_len+1); + ++u_len; + u[0] = '\0'; + } else { + *ru = NULL; + } + *ru_len = u_len; + *entry_len = e_len + 1; + return; + } + + if (u) { + u1 = strrchr(e, '/'); + u[0] = '/'; + saveu = u; + e_len += u_len + 1; + u = u1; + if (!u) { + return; + } + } else { + u = strrchr(e, '/'); + if (!u) { + if (saveu) { + saveu[0] = '/'; + } + return; + } + } + + u[0] = '\0'; + u_len = (int)strlen(u + 1); + e_len -= u_len + 1; + + if (e_len < 0) { + if (saveu) { + saveu[0] = '/'; + } + return; + } + } while (1); +} +/* }}} */ + +/* {{{ proto void Phar::running([bool retphar = true]) + * return the name of the currently running phar archive. If the optional parameter + * is set to true, return the phar:// URL to the currently running phar + */ +PHP_METHOD(Phar, running) +{ + char *fname, *arch, *entry; + int fname_len, arch_len, entry_len; + zend_bool retphar = 1; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &retphar) == FAILURE) { + return; + } + + fname = (char*)zend_get_executed_filename(); + fname_len = (int)strlen(fname); + + if (fname_len > 7 && !memcmp(fname, "phar://", 7) && SUCCESS == phar_split_fname(fname, fname_len, &arch, &arch_len, &entry, &entry_len, 2, 0)) { + efree(entry); + if (retphar) { + RETVAL_STRINGL(fname, arch_len + 7); + efree(arch); + return; + } else { + // TODO: avoid reallocation ??? + RETVAL_STRINGL(arch, arch_len); + efree(arch); + return; + } + } + + RETURN_EMPTY_STRING(); +} +/* }}} */ + +/* {{{ proto void Phar::mount(string pharpath, string externalfile) + * mount an external file or path to a location within the phar. This maps + * an external file or directory to a location within the phar archive, allowing + * reference to an external location as if it were within the phar archive. This + * is useful for writable temp files like databases + */ +PHP_METHOD(Phar, mount) +{ + char *fname, *arch = NULL, *entry = NULL, *path, *actual; + int fname_len, arch_len, entry_len; + size_t path_len, actual_len; + phar_archive_data *pphar; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "pp", &path, &path_len, &actual, &actual_len) == FAILURE) { + return; + } + + if (ZEND_SIZE_T_INT_OVFL(path_len) || ZEND_SIZE_T_INT_OVFL(actual_len)) { + RETURN_FALSE; + } + + fname = (char*)zend_get_executed_filename(); + fname_len = (int)strlen(fname); + +#ifdef PHP_WIN32 + phar_unixify_path_separators(fname, fname_len); +#endif + + if (fname_len > 7 && !memcmp(fname, "phar://", 7) && SUCCESS == phar_split_fname(fname, fname_len, &arch, &arch_len, &entry, &entry_len, 2, 0)) { + efree(entry); + entry = NULL; + + if (path_len > 7 && !memcmp(path, "phar://", 7)) { + zend_throw_exception_ex(phar_ce_PharException, 0, "Can only mount internal paths within a phar archive, use a relative path instead of \"%s\"", path); + efree(arch); + return; + } +carry_on2: + if (NULL == (pphar = zend_hash_str_find_ptr(&(PHAR_G(phar_fname_map)), arch, arch_len))) { + if (PHAR_G(manifest_cached) && NULL != (pphar = zend_hash_str_find_ptr(&cached_phars, arch, arch_len))) { + if (SUCCESS == phar_copy_on_write(&pphar)) { + goto carry_on; + } + } + + zend_throw_exception_ex(phar_ce_PharException, 0, "%s is not a phar archive, cannot mount", arch); + + if (arch) { + efree(arch); + } + return; + } +carry_on: + if (SUCCESS != phar_mount_entry(pphar, actual, (int)actual_len, path, (int)path_len)) { + zend_throw_exception_ex(phar_ce_PharException, 0, "Mounting of %s to %s within phar %s failed", path, actual, arch); + if (path && path == entry) { + efree(entry); + } + + if (arch) { + efree(arch); + } + + return; + } + + if (entry && path && path == entry) { + efree(entry); + } + + if (arch) { + efree(arch); + } + + return; + } else if (PHAR_G(phar_fname_map.u.flags) && NULL != (pphar = zend_hash_str_find_ptr(&(PHAR_G(phar_fname_map)), fname, fname_len))) { + goto carry_on; + } else if (PHAR_G(manifest_cached) && NULL != (pphar = zend_hash_str_find_ptr(&cached_phars, fname, fname_len))) { + if (SUCCESS == phar_copy_on_write(&pphar)) { + goto carry_on; + } + + goto carry_on; + } else if (SUCCESS == phar_split_fname(path, (int)path_len, &arch, &arch_len, &entry, &entry_len, 2, 0)) { + path = entry; + path_len = entry_len; + goto carry_on2; + } + + zend_throw_exception_ex(phar_ce_PharException, 0, "Mounting of %s to %s failed", path, actual); +} +/* }}} */ + +/* {{{ proto void Phar::webPhar([string alias, [string index, [string f404, [array mimetypes, [callback rewrites]]]]]) + * mapPhar for web-based phars. Reads the currently executed file (a phar) + * and registers its manifest. When executed in the CLI or CGI command-line sapi, + * this works exactly like mapPhar(). When executed by a web-based sapi, this + * reads $_SERVER['REQUEST_URI'] (the actual original value) and parses out the + * intended internal file. + */ +PHP_METHOD(Phar, webPhar) +{ + zval *mimeoverride = NULL, *rewrite = NULL; + char *alias = NULL, *error, *index_php = NULL, *f404 = NULL, *ru = NULL; + size_t alias_len = 0, f404_len = 0, free_pathinfo = 0; + int ru_len = 0; + char *fname, *path_info, *mime_type = NULL, *entry, *pt; + const char *basename; + size_t fname_len, index_php_len = 0; + int entry_len, code, not_cgi; + phar_archive_data *phar = NULL; + phar_entry_info *info = NULL; + size_t sapi_mod_name_len = strlen(sapi_module.name); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|s!s!saz", &alias, &alias_len, &index_php, &index_php_len, &f404, &f404_len, &mimeoverride, &rewrite) == FAILURE) { + return; + } + + phar_request_initialize(); + fname = (char*)zend_get_executed_filename(); + fname_len = strlen(fname); + + if (ZEND_SIZE_T_INT_OVFL(alias_len) + || ZEND_SIZE_T_INT_OVFL(f404_len) || ZEND_SIZE_T_INT_OVFL(index_php_len)) { + RETURN_FALSE; + } + + if (phar_open_executed_filename(alias, (int)alias_len, &error) != SUCCESS) { + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } + return; + } + + /* retrieve requested file within phar */ + if (!(SG(request_info).request_method + && SG(request_info).request_uri + && (!strcmp(SG(request_info).request_method, "GET") + || !strcmp(SG(request_info).request_method, "POST") + || !strcmp(SG(request_info).request_method, "DELETE") + || !strcmp(SG(request_info).request_method, "HEAD") + || !strcmp(SG(request_info).request_method, "OPTIONS") + || !strcmp(SG(request_info).request_method, "PATCH") + || !strcmp(SG(request_info).request_method, "PUT") + ) + ) + ) { + return; + } + +#ifdef PHP_WIN32 + fname = estrndup(fname, fname_len); + phar_unixify_path_separators(fname, fname_len); +#endif + basename = zend_memrchr(fname, '/', fname_len); + + if (!basename) { + basename = fname; + } else { + ++basename; + } + + if ((sapi_mod_name_len == sizeof("cgi-fcgi") - 1 && !strncmp(sapi_module.name, "cgi-fcgi", sizeof("cgi-fcgi") - 1)) + || (sapi_mod_name_len == sizeof("fpm-fcgi") - 1 && !strncmp(sapi_module.name, "fpm-fcgi", sizeof("fpm-fcgi") - 1)) + || (sapi_mod_name_len == sizeof("cgi") - 1 && !strncmp(sapi_module.name, "cgi", sizeof("cgi") - 1))) { + + if (Z_TYPE(PG(http_globals)[TRACK_VARS_SERVER]) != IS_UNDEF) { + HashTable *_server = Z_ARRVAL(PG(http_globals)[TRACK_VARS_SERVER]); + zval *z_script_name, *z_path_info; + + if (NULL == (z_script_name = zend_hash_str_find(_server, "SCRIPT_NAME", sizeof("SCRIPT_NAME")-1)) || + IS_STRING != Z_TYPE_P(z_script_name) || + !strstr(Z_STRVAL_P(z_script_name), basename)) { + return; + } + + if (NULL != (z_path_info = zend_hash_str_find(_server, "PATH_INFO", sizeof("PATH_INFO")-1)) && + IS_STRING == Z_TYPE_P(z_path_info)) { + entry_len = (int)Z_STRLEN_P(z_path_info); + entry = estrndup(Z_STRVAL_P(z_path_info), entry_len); + path_info = emalloc(Z_STRLEN_P(z_script_name) + entry_len + 1); + memcpy(path_info, Z_STRVAL_P(z_script_name), Z_STRLEN_P(z_script_name)); + memcpy(path_info + Z_STRLEN_P(z_script_name), entry, entry_len + 1); + free_pathinfo = 1; + } else { + entry_len = 0; + entry = estrndup("", 0); + path_info = Z_STRVAL_P(z_script_name); + } + + pt = estrndup(Z_STRVAL_P(z_script_name), Z_STRLEN_P(z_script_name)); + + } else { + char *testit; + + testit = sapi_getenv("SCRIPT_NAME", sizeof("SCRIPT_NAME")-1); + if (!(pt = strstr(testit, basename))) { + efree(testit); + return; + } + + path_info = sapi_getenv("PATH_INFO", sizeof("PATH_INFO")-1); + + if (path_info) { + entry = path_info; + entry_len = (int)strlen(entry); + spprintf(&path_info, 0, "%s%s", testit, path_info); + free_pathinfo = 1; + } else { + path_info = testit; + free_pathinfo = 1; + entry = estrndup("", 0); + entry_len = 0; + } + + pt = estrndup(testit, (pt - testit) + (fname_len - (basename - fname))); + } + not_cgi = 0; + } else { + path_info = SG(request_info).request_uri; + + if (!(pt = strstr(path_info, basename))) { + /* this can happen with rewrite rules - and we have no idea what to do then, so return */ + return; + } + + entry_len = (int)strlen(path_info); + entry_len -= (pt - path_info) + (fname_len - (basename - fname)); + entry = estrndup(pt + (fname_len - (basename - fname)), entry_len); + + pt = estrndup(path_info, (pt - path_info) + (fname_len - (basename - fname))); + not_cgi = 1; + } + + if (rewrite) { + zend_fcall_info fci; + zend_fcall_info_cache fcc; + zval params, retval; + + ZVAL_STRINGL(¶ms, entry, entry_len); + + if (FAILURE == zend_fcall_info_init(rewrite, 0, &fci, &fcc, NULL, NULL)) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar error: invalid rewrite callback"); + + if (free_pathinfo) { + efree(path_info); + } + efree(pt); + + return; + } + + fci.param_count = 1; + fci.params = ¶ms; + Z_ADDREF(params); + fci.retval = &retval; + + if (FAILURE == zend_call_function(&fci, &fcc)) { + if (!EG(exception)) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar error: failed to call rewrite callback"); + } + + if (free_pathinfo) { + efree(path_info); + } + efree(pt); + + return; + } + + if (Z_TYPE_P(fci.retval) == IS_UNDEF || Z_TYPE(retval) == IS_UNDEF) { + if (free_pathinfo) { + efree(path_info); + } + zend_throw_exception_ex(phar_ce_PharException, 0, "phar error: rewrite callback must return a string or false"); + efree(pt); + return; + } + + switch (Z_TYPE(retval)) { + case IS_STRING: + efree(entry); + if (ZEND_SIZE_T_INT_OVFL(Z_STRLEN_P(fci.retval))) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar error: rewrite callback returned oversized value"); + return; + } + entry = estrndup(Z_STRVAL_P(fci.retval), Z_STRLEN_P(fci.retval)); + entry_len = (int)Z_STRLEN_P(fci.retval); + break; + case IS_TRUE: + case IS_FALSE: + phar_do_403(entry, entry_len); + + if (free_pathinfo) { + efree(path_info); + } + efree(pt); + + zend_bailout(); + return; + default: + if (free_pathinfo) { + efree(path_info); + } + efree(pt); + + zend_throw_exception_ex(phar_ce_PharException, 0, "phar error: rewrite callback must return a string or false"); + return; + } + } + + if (entry_len) { + phar_postprocess_ru_web(fname, (int)fname_len, &entry, &entry_len, &ru, &ru_len); + } + + if (!entry_len || (entry_len == 1 && entry[0] == '/')) { + efree(entry); + /* direct request */ + if (index_php_len) { + entry = index_php; + entry_len = (int)index_php_len; + if (entry[0] != '/') { + spprintf(&entry, 0, "/%s", index_php); + ++entry_len; + } + } else { + /* assume "index.php" is starting point */ + entry = estrndup("/index.php", sizeof("/index.php")); + entry_len = sizeof("/index.php")-1; + } + + if (FAILURE == phar_get_archive(&phar, fname, (int)fname_len, NULL, 0, NULL) || + (info = phar_get_entry_info(phar, entry, entry_len, NULL, 0)) == NULL) { + phar_do_404(phar, fname, (int)fname_len, f404, (int)f404_len, entry, entry_len); + + if (free_pathinfo) { + efree(path_info); + } + + zend_bailout(); + } else { + char *tmp = NULL, sa = '\0'; + sapi_header_line ctr = {0}; + ctr.response_code = 301; + ctr.line_len = sizeof("HTTP/1.1 301 Moved Permanently")-1; + ctr.line = "HTTP/1.1 301 Moved Permanently"; + sapi_header_op(SAPI_HEADER_REPLACE, &ctr); + + if (not_cgi) { + tmp = strstr(path_info, basename) + fname_len; + sa = *tmp; + *tmp = '\0'; + } + + ctr.response_code = 0; + + if (path_info[strlen(path_info)-1] == '/') { + ctr.line_len = spprintf(&(ctr.line), 4096, "Location: %s%s", path_info, entry + 1); + } else { + ctr.line_len = spprintf(&(ctr.line), 4096, "Location: %s%s", path_info, entry); + } + + if (not_cgi) { + *tmp = sa; + } + + if (free_pathinfo) { + efree(path_info); + } + + sapi_header_op(SAPI_HEADER_REPLACE, &ctr); + sapi_send_headers(); + efree(ctr.line); + zend_bailout(); + } + } + + if (FAILURE == phar_get_archive(&phar, fname, (int)fname_len, NULL, 0, NULL) || + (info = phar_get_entry_info(phar, entry, entry_len, NULL, 0)) == NULL) { + phar_do_404(phar, fname, (int)fname_len, f404, (int)f404_len, entry, entry_len); +#ifdef PHP_WIN32 + efree(fname); +#endif + zend_bailout(); + } + + if (mimeoverride && zend_hash_num_elements(Z_ARRVAL_P(mimeoverride))) { + const char *ext = zend_memrchr(entry, '.', entry_len); + zval *val; + + if (ext) { + ++ext; + + if (NULL != (val = zend_hash_str_find(Z_ARRVAL_P(mimeoverride), ext, strlen(ext)))) { + switch (Z_TYPE_P(val)) { + case IS_LONG: + if (Z_LVAL_P(val) == PHAR_MIME_PHP || Z_LVAL_P(val) == PHAR_MIME_PHPS) { + mime_type = ""; + code = (int)Z_LVAL_P(val); + } else { + zend_throw_exception_ex(phar_ce_PharException, 0, "Unknown mime type specifier used, only Phar::PHP, Phar::PHPS and a mime type string are allowed"); + if (free_pathinfo) { + efree(path_info); + } + efree(pt); + efree(entry); +#ifdef PHP_WIN32 + efree(fname); +#endif + RETURN_FALSE; + } + break; + case IS_STRING: + mime_type = Z_STRVAL_P(val); + code = PHAR_MIME_OTHER; + break; + default: + zend_throw_exception_ex(phar_ce_PharException, 0, "Unknown mime type specifier used (not a string or int), only Phar::PHP, Phar::PHPS and a mime type string are allowed"); + if (free_pathinfo) { + efree(path_info); + } + efree(pt); + efree(entry); +#ifdef PHP_WIN32 + efree(fname); +#endif + RETURN_FALSE; + } + } + } + } + + if (!mime_type) { + code = phar_file_type(&PHAR_G(mime_types), entry, &mime_type); + } + phar_file_action(phar, info, mime_type, code, entry, entry_len, fname, pt, ru, ru_len); +} +/* }}} */ + +/* {{{ proto void Phar::mungServer(array munglist) + * Defines a list of up to 4 $_SERVER variables that should be modified for execution + * to mask the presence of the phar archive. This should be used in conjunction with + * Phar::webPhar(), and has no effect otherwise + * SCRIPT_NAME, PHP_SELF, REQUEST_URI and SCRIPT_FILENAME + */ +PHP_METHOD(Phar, mungServer) +{ + zval *mungvalues, *data; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "a", &mungvalues) == FAILURE) { + return; + } + + if (!zend_hash_num_elements(Z_ARRVAL_P(mungvalues))) { + zend_throw_exception_ex(phar_ce_PharException, 0, "No values passed to Phar::mungServer(), expecting an array of any of these strings: PHP_SELF, REQUEST_URI, SCRIPT_FILENAME, SCRIPT_NAME"); + return; + } + + if (zend_hash_num_elements(Z_ARRVAL_P(mungvalues)) > 4) { + zend_throw_exception_ex(phar_ce_PharException, 0, "Too many values passed to Phar::mungServer(), expecting an array of any of these strings: PHP_SELF, REQUEST_URI, SCRIPT_FILENAME, SCRIPT_NAME"); + return; + } + + phar_request_initialize(); + + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(mungvalues), data) { + + if (Z_TYPE_P(data) != IS_STRING) { + zend_throw_exception_ex(phar_ce_PharException, 0, "Non-string value passed to Phar::mungServer(), expecting an array of any of these strings: PHP_SELF, REQUEST_URI, SCRIPT_FILENAME, SCRIPT_NAME"); + return; + } + + if (Z_STRLEN_P(data) == sizeof("PHP_SELF")-1 && !strncmp(Z_STRVAL_P(data), "PHP_SELF", sizeof("PHP_SELF")-1)) { + PHAR_G(phar_SERVER_mung_list) |= PHAR_MUNG_PHP_SELF; + } + + if (Z_STRLEN_P(data) == sizeof("REQUEST_URI")-1) { + if (!strncmp(Z_STRVAL_P(data), "REQUEST_URI", sizeof("REQUEST_URI")-1)) { + PHAR_G(phar_SERVER_mung_list) |= PHAR_MUNG_REQUEST_URI; + } + if (!strncmp(Z_STRVAL_P(data), "SCRIPT_NAME", sizeof("SCRIPT_NAME")-1)) { + PHAR_G(phar_SERVER_mung_list) |= PHAR_MUNG_SCRIPT_NAME; + } + } + + if (Z_STRLEN_P(data) == sizeof("SCRIPT_FILENAME")-1 && !strncmp(Z_STRVAL_P(data), "SCRIPT_FILENAME", sizeof("SCRIPT_FILENAME")-1)) { + PHAR_G(phar_SERVER_mung_list) |= PHAR_MUNG_SCRIPT_FILENAME; + } + } ZEND_HASH_FOREACH_END(); +} +/* }}} */ + +/* {{{ proto void Phar::interceptFileFuncs() + * instructs phar to intercept fopen, file_get_contents, opendir, and all of the stat-related functions + * and return stat on files within the phar for relative paths + * + * Once called, this cannot be reversed, and continue until the end of the request. + * + * This allows legacy scripts to be pharred unmodified + */ +PHP_METHOD(Phar, interceptFileFuncs) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } + phar_intercept_functions(); +} +/* }}} */ + +/* {{{ proto array Phar::createDefaultStub([string indexfile[, string webindexfile]]) + * Return a stub that can be used to run a phar-based archive without the phar extension + * indexfile is the CLI startup filename, which defaults to "index.php", webindexfile + * is the web startup filename, and also defaults to "index.php" + */ +PHP_METHOD(Phar, createDefaultStub) +{ + char *index = NULL, *webindex = NULL, *error; + zend_string *stub; + size_t index_len = 0, webindex_len = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|pp", &index, &index_len, &webindex, &webindex_len) == FAILURE) { + return; + } + + stub = phar_create_default_stub(index, webindex, &error); + + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + return; + } + RETURN_NEW_STR(stub); +} +/* }}} */ + +/* {{{ proto mixed Phar::mapPhar([string alias, [int dataoffset]]) + * Reads the currently executed file (a phar) and registers its manifest */ +PHP_METHOD(Phar, mapPhar) +{ + char *alias = NULL, *error; + size_t alias_len = 0; + zend_long dataoffset = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|s!l", &alias, &alias_len, &dataoffset) == FAILURE) { + return; + } + + if (ZEND_SIZE_T_INT_OVFL(alias_len)) { + RETURN_FALSE; + } + phar_request_initialize(); + + RETVAL_BOOL(phar_open_executed_filename(alias, (int)alias_len, &error) == SUCCESS); + + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } +} /* }}} */ + +/* {{{ proto mixed Phar::loadPhar(string filename [, string alias]) + * Loads any phar archive with an alias */ +PHP_METHOD(Phar, loadPhar) +{ + char *fname, *alias = NULL, *error; + size_t fname_len, alias_len = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p|s!", &fname, &fname_len, &alias, &alias_len) == FAILURE) { + return; + } + + if (ZEND_SIZE_T_INT_OVFL(alias_len) || ZEND_SIZE_T_INT_OVFL(fname_len)) { + RETURN_FALSE; + } + phar_request_initialize(); + + RETVAL_BOOL(phar_open_from_filename(fname, (int)fname_len, alias, (int)alias_len, REPORT_ERRORS, NULL, &error) == SUCCESS); + + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } +} /* }}} */ + +/* {{{ proto string Phar::apiVersion() + * Returns the api version */ +PHP_METHOD(Phar, apiVersion) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } + RETURN_STRINGL(PHP_PHAR_API_VERSION, sizeof(PHP_PHAR_API_VERSION)-1); +} +/* }}}*/ + +/* {{{ proto bool Phar::canCompress([int method]) + * Returns whether phar extension supports compression using zlib/bzip2 */ +PHP_METHOD(Phar, canCompress) +{ + zend_long method = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &method) == FAILURE) { + return; + } + + phar_request_initialize(); + switch (method) { + case PHAR_ENT_COMPRESSED_GZ: + if (PHAR_G(has_zlib)) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } + case PHAR_ENT_COMPRESSED_BZ2: + if (PHAR_G(has_bz2)) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } + default: + if (PHAR_G(has_zlib) || PHAR_G(has_bz2)) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } + } +} +/* }}} */ + +/* {{{ proto bool Phar::canWrite() + * Returns whether phar extension supports writing and creating phars */ +PHP_METHOD(Phar, canWrite) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } + RETURN_BOOL(!PHAR_G(readonly)); +} +/* }}} */ + +/* {{{ proto bool Phar::isValidPharFilename(string filename[, bool executable = true]) + * Returns whether the given filename is a valid phar filename */ +PHP_METHOD(Phar, isValidPharFilename) +{ + char *fname; + const char *ext_str; + size_t fname_len; + int ext_len, is_executable; + zend_bool executable = 1; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p|b", &fname, &fname_len, &executable) == FAILURE) { + return; + } + + if (ZEND_SIZE_T_INT_OVFL(fname_len)) { + RETURN_FALSE; + } + + is_executable = executable; + RETVAL_BOOL(phar_detect_phar_fname_ext(fname, (int)fname_len, &ext_str, &ext_len, is_executable, 2, 1) == SUCCESS); +} +/* }}} */ + +/** + * from spl_directory + */ +static void phar_spl_foreign_dtor(spl_filesystem_object *object) /* {{{ */ +{ + phar_archive_data *phar = (phar_archive_data *) object->oth; + + if (!phar->is_persistent) { + phar_archive_delref(phar); + } + + object->oth = NULL; +} +/* }}} */ + +/** + * from spl_directory + */ +static void phar_spl_foreign_clone(spl_filesystem_object *src, spl_filesystem_object *dst) /* {{{ */ +{ + phar_archive_data *phar_data = (phar_archive_data *) dst->oth; + + if (!phar_data->is_persistent) { + ++(phar_data->refcount); + } +} +/* }}} */ + +static spl_other_handler phar_spl_foreign_handler = { + phar_spl_foreign_dtor, + phar_spl_foreign_clone +}; + +/* {{{ proto void Phar::__construct(string fname [, int flags [, string alias]]) + * Construct a Phar archive object + * + * proto void PharData::__construct(string fname [[, int flags [, string alias]], int file format = Phar::TAR]) + * Construct a PharData archive object + * + * This function is used as the constructor for both the Phar and PharData + * classes, hence the two prototypes above. + */ +PHP_METHOD(Phar, __construct) +{ + char *fname, *alias = NULL, *error, *arch = NULL, *entry = NULL, *save_fname; + size_t fname_len, alias_len = 0; + int arch_len, entry_len, is_data; + zend_long flags = SPL_FILE_DIR_SKIPDOTS|SPL_FILE_DIR_UNIXPATHS; + zend_long format = 0; + phar_archive_object *phar_obj; + phar_archive_data *phar_data; + zval *zobj = getThis(), arg1, arg2; + + phar_obj = (phar_archive_object*)((char*)Z_OBJ_P(zobj) - Z_OBJ_P(zobj)->handlers->offset); + + is_data = instanceof_function(Z_OBJCE_P(zobj), phar_ce_data); + + if (is_data) { + if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "p|ls!l", &fname, &fname_len, &flags, &alias, &alias_len, &format) == FAILURE) { + return; + } + } else { + if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "p|ls!", &fname, &fname_len, &flags, &alias, &alias_len) == FAILURE) { + return; + } + } + + if (ZEND_SIZE_T_INT_OVFL(alias_len) || ZEND_SIZE_T_INT_OVFL(fname_len)) { + RETURN_FALSE; + } + if (phar_obj->archive) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot call constructor twice"); + return; + } + + save_fname = fname; + if (SUCCESS == phar_split_fname(fname, (int)fname_len, &arch, &arch_len, &entry, &entry_len, !is_data, 2)) { + /* use arch (the basename for the archive) for fname instead of fname */ + /* this allows support for RecursiveDirectoryIterator of subdirectories */ +#ifdef PHP_WIN32 + phar_unixify_path_separators(arch, arch_len); +#endif + fname = arch; + fname_len = arch_len; +#ifdef PHP_WIN32 + } else { + arch = estrndup(fname, fname_len); + arch_len = fname_len; + fname = arch; + phar_unixify_path_separators(arch, arch_len); +#endif + } + + if (phar_open_or_create_filename(fname, (int)fname_len, alias, (int)alias_len, is_data, REPORT_ERRORS, &phar_data, &error) == FAILURE) { + + if (fname == arch && fname != save_fname) { + efree(arch); + fname = save_fname; + } + + if (entry) { + efree(entry); + } + + if (error) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "%s", error); + efree(error); + } else { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Phar creation or opening failed"); + } + + return; + } + + if (is_data && phar_data->is_tar && phar_data->is_brandnew && format == PHAR_FORMAT_ZIP) { + phar_data->is_zip = 1; + phar_data->is_tar = 0; + } + + if (fname == arch) { + efree(arch); + fname = save_fname; + } + + if ((is_data && !phar_data->is_data) || (!is_data && phar_data->is_data)) { + if (is_data) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "PharData class can only be used for non-executable tar and zip archives"); + } else { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Phar class can only be used for executable tar and zip archives"); + } + efree(entry); + return; + } + + is_data = phar_data->is_data; + + if (!phar_data->is_persistent) { + ++(phar_data->refcount); + } + + phar_obj->archive = phar_data; + phar_obj->spl.oth_handler = &phar_spl_foreign_handler; + + if (entry) { + fname_len = spprintf(&fname, 0, "phar://%s%s", phar_data->fname, entry); + efree(entry); + } else { + fname_len = spprintf(&fname, 0, "phar://%s", phar_data->fname); + } + + ZVAL_STRINGL(&arg1, fname, fname_len); + ZVAL_LONG(&arg2, flags); + + zend_call_method_with_2_params(zobj, Z_OBJCE_P(zobj), + &spl_ce_RecursiveDirectoryIterator->constructor, "__construct", NULL, &arg1, &arg2); + + zval_ptr_dtor(&arg1); + + if (!phar_data->is_persistent) { + phar_obj->archive->is_data = is_data; + } else if (!EG(exception)) { + /* register this guy so we can modify if necessary */ + zend_hash_str_add_ptr(&PHAR_G(phar_persist_map), (const char *) phar_obj->archive, sizeof(phar_obj->archive), phar_obj); + } + + phar_obj->spl.info_class = phar_ce_entry; + efree(fname); +} +/* }}} */ + +/* {{{ proto array Phar::getSupportedSignatures() + * Return array of supported signature types + */ +PHP_METHOD(Phar, getSupportedSignatures) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + array_init(return_value); + + add_next_index_stringl(return_value, "MD5", 3); + add_next_index_stringl(return_value, "SHA-1", 5); +#ifdef PHAR_HASH_OK + add_next_index_stringl(return_value, "SHA-256", 7); + add_next_index_stringl(return_value, "SHA-512", 7); +#endif +#if PHAR_HAVE_OPENSSL + add_next_index_stringl(return_value, "OpenSSL", 7); +#else + if (zend_hash_str_exists(&module_registry, "openssl", sizeof("openssl")-1)) { + add_next_index_stringl(return_value, "OpenSSL", 7); + } +#endif +} +/* }}} */ + +/* {{{ proto array Phar::getSupportedCompression() + * Return array of supported comparession algorithms + */ +PHP_METHOD(Phar, getSupportedCompression) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + array_init(return_value); + phar_request_initialize(); + + if (PHAR_G(has_zlib)) { + add_next_index_stringl(return_value, "GZ", 2); + } + + if (PHAR_G(has_bz2)) { + add_next_index_stringl(return_value, "BZIP2", 5); + } +} +/* }}} */ + +/* {{{ proto array Phar::unlinkArchive(string archive) + * Completely remove a phar archive from memory and disk + */ +PHP_METHOD(Phar, unlinkArchive) +{ + char *fname, *error, *zname, *arch, *entry; + size_t fname_len; + int zname_len, arch_len, entry_len; + phar_archive_data *phar; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p", &fname, &fname_len) == FAILURE) { + RETURN_FALSE; + } + + if (ZEND_SIZE_T_INT_OVFL(fname_len)) { + RETURN_FALSE; + } + if (!fname_len) { + zend_throw_exception_ex(phar_ce_PharException, 0, "Unknown phar archive \"\""); + return; + } + + if (FAILURE == phar_open_from_filename(fname, (int)fname_len, NULL, 0, REPORT_ERRORS, &phar, &error)) { + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "Unknown phar archive \"%s\": %s", fname, error); + efree(error); + } else { + zend_throw_exception_ex(phar_ce_PharException, 0, "Unknown phar archive \"%s\"", fname); + } + return; + } + + zname = (char*)zend_get_executed_filename(); + zname_len = (int)strlen(zname); + + if (zname_len > 7 && !memcmp(zname, "phar://", 7) && SUCCESS == phar_split_fname(zname, zname_len, &arch, &arch_len, &entry, &entry_len, 2, 0)) { + if ((size_t)arch_len == fname_len && !memcmp(arch, fname, arch_len)) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar archive \"%s\" cannot be unlinked from within itself", fname); + efree(arch); + efree(entry); + return; + } + efree(arch); + efree(entry); + } + + if (phar->is_persistent) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar archive \"%s\" is in phar.cache_list, cannot unlinkArchive()", fname); + return; + } + + if (phar->refcount) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar archive \"%s\" has open file handles or objects. fclose() all file handles, and unset() all objects prior to calling unlinkArchive()", fname); + return; + } + + fname = estrndup(phar->fname, phar->fname_len); + + /* invalidate phar cache */ + PHAR_G(last_phar) = NULL; + PHAR_G(last_phar_name) = PHAR_G(last_alias) = NULL; + + phar_archive_delref(phar); + unlink(fname); + efree(fname); + RETURN_TRUE; +} +/* }}} */ + +#define PHAR_ARCHIVE_OBJECT() \ + zval *zobj = getThis(); \ + phar_archive_object *phar_obj = (phar_archive_object*)((char*)Z_OBJ_P(zobj) - Z_OBJ_P(zobj)->handlers->offset); \ + if (!phar_obj->archive) { \ + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, \ + "Cannot call method on an uninitialized Phar object"); \ + return; \ + } + +/* {{{ proto void Phar::__destruct() + * if persistent, remove from the cache + */ +PHP_METHOD(Phar, __destruct) +{ + zval *zobj = getThis(); + phar_archive_object *phar_obj = (phar_archive_object*)((char*)Z_OBJ_P(zobj) - Z_OBJ_P(zobj)->handlers->offset); + + if (phar_obj->archive && phar_obj->archive->is_persistent) { + zend_hash_str_del(&PHAR_G(phar_persist_map), (const char *) phar_obj->archive, sizeof(phar_obj->archive)); + } +} +/* }}} */ + +struct _phar_t { + phar_archive_object *p; + zend_class_entry *c; + char *b; + zval *ret; + php_stream *fp; + uint l; + int count; +}; + +static int phar_build(zend_object_iterator *iter, void *puser) /* {{{ */ +{ + zval *value; + zend_bool close_fp = 1; + struct _phar_t *p_obj = (struct _phar_t*) puser; + uint base_len = p_obj->l, str_key_len; + phar_entry_data *data; + php_stream *fp; + php_stat_len fname_len; + size_t contents_len; + char *fname, *error = NULL, *base = p_obj->b, *save = NULL, *temp = NULL; + zend_string *opened; + char *str_key; + zend_class_entry *ce = p_obj->c; + phar_archive_object *phar_obj = p_obj->p; + + value = iter->funcs->get_current_data(iter); + + if (EG(exception)) { + return ZEND_HASH_APPLY_STOP; + } + + if (!value) { + /* failure in get_current_data */ + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Iterator %s returned no value", ZSTR_VAL(ce->name)); + return ZEND_HASH_APPLY_STOP; + } + + switch (Z_TYPE_P(value)) { + case IS_STRING: + break; + case IS_RESOURCE: + php_stream_from_zval_no_verify(fp, value); + + if (!fp) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Iterator %s returned an invalid stream handle", ZSTR_VAL(ce->name)); + return ZEND_HASH_APPLY_STOP; + } + + if (iter->funcs->get_current_key) { + zval key; + iter->funcs->get_current_key(iter, &key); + + if (EG(exception)) { + return ZEND_HASH_APPLY_STOP; + } + + if (Z_TYPE(key) != IS_STRING) { + zval_dtor(&key); + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Iterator %s returned an invalid key (must return a string)", ZSTR_VAL(ce->name)); + return ZEND_HASH_APPLY_STOP; + } + + if (ZEND_SIZE_T_INT_OVFL(Z_STRLEN(key))) { + zval_dtor(&key); + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Iterator %v returned an invalid key (too long)", ZSTR_VAL(ce->name)); + return ZEND_HASH_APPLY_STOP; + } + + str_key_len = (int)Z_STRLEN(key); + str_key = estrndup(Z_STRVAL(key), str_key_len); + + save = str_key; + zval_dtor(&key); + } else { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Iterator %s returned an invalid key (must return a string)", ZSTR_VAL(ce->name)); + return ZEND_HASH_APPLY_STOP; + } + + close_fp = 0; + opened = zend_string_init("[stream]", sizeof("[stream]") - 1, 0); + goto after_open_fp; + case IS_OBJECT: + if (instanceof_function(Z_OBJCE_P(value), spl_ce_SplFileInfo)) { + char *test = NULL; + zval dummy; + spl_filesystem_object *intern = (spl_filesystem_object*)((char*)Z_OBJ_P(value) - Z_OBJ_P(value)->handlers->offset); + + if (!base_len) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Iterator %s returns an SplFileInfo object, so base directory must be specified", ZSTR_VAL(ce->name)); + return ZEND_HASH_APPLY_STOP; + } + + switch (intern->type) { + case SPL_FS_DIR: + test = spl_filesystem_object_get_path(intern, NULL); + fname_len = (php_stat_len)spprintf(&fname, 0, "%s%c%s", test, DEFAULT_SLASH, intern->u.dir.entry.d_name); + php_stat(fname, fname_len, FS_IS_DIR, &dummy); + + if (Z_TYPE(dummy) == IS_TRUE) { + /* ignore directories */ + efree(fname); + return ZEND_HASH_APPLY_KEEP; + } + + test = expand_filepath(fname, NULL); + efree(fname); + + if (test) { + fname = test; + fname_len = (php_stat_len)strlen(fname); + } else { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Could not resolve file path"); + return ZEND_HASH_APPLY_STOP; + } + + save = fname; + goto phar_spl_fileinfo; + case SPL_FS_INFO: + case SPL_FS_FILE: + fname = expand_filepath(intern->file_name, NULL); + if (!fname) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Could not resolve file path"); + return ZEND_HASH_APPLY_STOP; + } + + fname_len = (php_stat_len)strlen(fname); + save = fname; + goto phar_spl_fileinfo; + } + } + /* fall-through */ + default: + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Iterator %s returned an invalid value (must return a string)", ZSTR_VAL(ce->name)); + return ZEND_HASH_APPLY_STOP; + } + + fname = Z_STRVAL_P(value); + fname_len = (php_stat_len)Z_STRLEN_P(value); + +phar_spl_fileinfo: + if (base_len) { + temp = expand_filepath(base, NULL); + if (!temp) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Could not resolve file path"); + if (save) { + efree(save); + } + return ZEND_HASH_APPLY_STOP; + } + + base = temp; + base_len = (int)strlen(base); + + if (strstr(fname, base)) { + str_key_len = fname_len - base_len; + + if (str_key_len <= 0) { + if (save) { + efree(save); + efree(temp); + } + return ZEND_HASH_APPLY_KEEP; + } + + str_key = fname + base_len; + + if (*str_key == '/' || *str_key == '\\') { + str_key++; + str_key_len--; + } + + } else { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Iterator %s returned a path \"%s\" that is not in the base directory \"%s\"", ZSTR_VAL(ce->name), fname, base); + + if (save) { + efree(save); + efree(temp); + } + + return ZEND_HASH_APPLY_STOP; + } + } else { + if (iter->funcs->get_current_key) { + zval key; + iter->funcs->get_current_key(iter, &key); + + if (EG(exception)) { + return ZEND_HASH_APPLY_STOP; + } + + if (Z_TYPE(key) != IS_STRING) { + zval_dtor(&key); + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Iterator %s returned an invalid key (must return a string)", ZSTR_VAL(ce->name)); + return ZEND_HASH_APPLY_STOP; + } + + if (ZEND_SIZE_T_INT_OVFL(Z_STRLEN(key))) { + zval_dtor(&key); + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Iterator %v returned an invalid key (too long)", ZSTR_VAL(ce->name)); + return ZEND_HASH_APPLY_STOP; + } + + str_key_len = (int)Z_STRLEN(key); + str_key = estrndup(Z_STRVAL(key), str_key_len); + + save = str_key; + zval_dtor(&key); + } else { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Iterator %s returned an invalid key (must return a string)", ZSTR_VAL(ce->name)); + return ZEND_HASH_APPLY_STOP; + } + } + + if (php_check_open_basedir(fname)) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Iterator %s returned a path \"%s\" that open_basedir prevents opening", ZSTR_VAL(ce->name), fname); + + if (save) { + efree(save); + } + + if (temp) { + efree(temp); + } + + return ZEND_HASH_APPLY_STOP; + } + + /* try to open source file, then create internal phar file and copy contents */ + fp = php_stream_open_wrapper(fname, "rb", STREAM_MUST_SEEK|0, &opened); + + if (!fp) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Iterator %s returned a file that could not be opened \"%s\"", ZSTR_VAL(ce->name), fname); + + if (save) { + efree(save); + } + + if (temp) { + efree(temp); + } + + return ZEND_HASH_APPLY_STOP; + } +after_open_fp: + if (str_key_len >= sizeof(".phar")-1 && !memcmp(str_key, ".phar", sizeof(".phar")-1)) { + /* silently skip any files that would be added to the magic .phar directory */ + if (save) { + efree(save); + } + + if (temp) { + efree(temp); + } + + if (opened) { + zend_string_release(opened); + } + + if (close_fp) { + php_stream_close(fp); + } + + return ZEND_HASH_APPLY_KEEP; + } + + if (!(data = phar_get_or_create_entry_data(phar_obj->archive->fname, phar_obj->archive->fname_len, str_key, str_key_len, "w+b", 0, &error, 1))) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Entry %s cannot be created: %s", str_key, error); + efree(error); + + if (save) { + efree(save); + } + + if (opened) { + zend_string_release(opened); + } + + if (temp) { + efree(temp); + } + + if (close_fp) { + php_stream_close(fp); + } + + return ZEND_HASH_APPLY_STOP; + + } else { + if (error) { + efree(error); + } + /* convert to PHAR_UFP */ + if (data->internal_file->fp_type == PHAR_MOD) { + php_stream_close(data->internal_file->fp); + } + + data->internal_file->fp = NULL; + data->internal_file->fp_type = PHAR_UFP; + data->internal_file->offset_abs = data->internal_file->offset = php_stream_tell(p_obj->fp); + data->fp = NULL; + php_stream_copy_to_stream_ex(fp, p_obj->fp, PHP_STREAM_COPY_ALL, &contents_len); + data->internal_file->uncompressed_filesize = data->internal_file->compressed_filesize = + php_stream_tell(p_obj->fp) - data->internal_file->offset; + } + + if (close_fp) { + php_stream_close(fp); + } + + add_assoc_str(p_obj->ret, str_key, opened); + + if (save) { + efree(save); + } + + if (temp) { + efree(temp); + } + + data->internal_file->compressed_filesize = data->internal_file->uncompressed_filesize = contents_len; + phar_entry_delref(data); + + return ZEND_HASH_APPLY_KEEP; +} +/* }}} */ + +/* {{{ proto array Phar::buildFromDirectory(string base_dir[, string regex]) + * Construct a phar archive from an existing directory, recursively. + * Optional second parameter is a regular expression for filtering directory contents. + * + * Return value is an array mapping phar index to actual files added. + */ +PHP_METHOD(Phar, buildFromDirectory) +{ + char *dir, *error, *regex = NULL; + size_t dir_len, regex_len = 0; + zend_bool apply_reg = 0; + zval arg, arg2, iter, iteriter, regexiter; + struct _phar_t pass; + + PHAR_ARCHIVE_OBJECT(); + + if (PHAR_G(readonly) && !phar_obj->archive->is_data) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot write to archive - write operations restricted by INI setting"); + return; + } + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p|s", &dir, &dir_len, ®ex, ®ex_len) == FAILURE) { + RETURN_FALSE; + } + + if (ZEND_SIZE_T_UINT_OVFL(dir_len)) { + RETURN_FALSE; + } + + if (SUCCESS != object_init_ex(&iter, spl_ce_RecursiveDirectoryIterator)) { + zval_ptr_dtor(&iter); + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Unable to instantiate directory iterator for %s", phar_obj->archive->fname); + RETURN_FALSE; + } + + ZVAL_STRINGL(&arg, dir, dir_len); + ZVAL_LONG(&arg2, SPL_FILE_DIR_SKIPDOTS|SPL_FILE_DIR_UNIXPATHS); + + zend_call_method_with_2_params(&iter, spl_ce_RecursiveDirectoryIterator, + &spl_ce_RecursiveDirectoryIterator->constructor, "__construct", NULL, &arg, &arg2); + + zval_ptr_dtor(&arg); + if (EG(exception)) { + zval_ptr_dtor(&iter); + RETURN_FALSE; + } + + if (SUCCESS != object_init_ex(&iteriter, spl_ce_RecursiveIteratorIterator)) { + zval_ptr_dtor(&iter); + zval_ptr_dtor(&iteriter); + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Unable to instantiate directory iterator for %s", phar_obj->archive->fname); + RETURN_FALSE; + } + + zend_call_method_with_1_params(&iteriter, spl_ce_RecursiveIteratorIterator, + &spl_ce_RecursiveIteratorIterator->constructor, "__construct", NULL, &iter); + + if (EG(exception)) { + zval_ptr_dtor(&iter); + zval_ptr_dtor(&iteriter); + RETURN_FALSE; + } + + zval_ptr_dtor(&iter); + + if (regex_len > 0) { + apply_reg = 1; + + if (SUCCESS != object_init_ex(®exiter, spl_ce_RegexIterator)) { + zval_ptr_dtor(&iteriter); + zval_dtor(®exiter); + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Unable to instantiate regex iterator for %s", phar_obj->archive->fname); + RETURN_FALSE; + } + + ZVAL_STRINGL(&arg2, regex, regex_len); + + zend_call_method_with_2_params(®exiter, spl_ce_RegexIterator, + &spl_ce_RegexIterator->constructor, "__construct", NULL, &iteriter, &arg2); + zval_ptr_dtor(&arg2); + } + + array_init(return_value); + + pass.c = apply_reg ? Z_OBJCE(regexiter) : Z_OBJCE(iteriter); + pass.p = phar_obj; + pass.b = dir; + pass.l = (uint)dir_len; + pass.count = 0; + pass.ret = return_value; + pass.fp = php_stream_fopen_tmpfile(); + if (pass.fp == NULL) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" unable to create temporary file", phar_obj->archive->fname); + return; + } + + if (phar_obj->archive->is_persistent && FAILURE == phar_copy_on_write(&(phar_obj->archive))) { + zval_ptr_dtor(&iteriter); + if (apply_reg) { + zval_ptr_dtor(®exiter); + } + php_stream_close(pass.fp); + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar_obj->archive->fname); + return; + } + + if (SUCCESS == spl_iterator_apply((apply_reg ? ®exiter : &iteriter), (spl_iterator_apply_func_t) phar_build, (void *) &pass)) { + zval_ptr_dtor(&iteriter); + + if (apply_reg) { + zval_ptr_dtor(®exiter); + } + + phar_obj->archive->ufp = pass.fp; + phar_flush(phar_obj->archive, 0, 0, 0, &error); + + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } + + } else { + zval_ptr_dtor(&iteriter); + if (apply_reg) { + zval_ptr_dtor(®exiter); + } + php_stream_close(pass.fp); + } +} +/* }}} */ + +/* {{{ proto array Phar::buildFromIterator(Iterator iter[, string base_directory]) + * Construct a phar archive from an iterator. The iterator must return a series of strings + * that are full paths to files that should be added to the phar. The iterator key should + * be the path that the file will have within the phar archive. + * + * If base directory is specified, then the key will be ignored, and instead the portion of + * the current value minus the base directory will be used + * + * Returned is an array mapping phar index to actual file added + */ +PHP_METHOD(Phar, buildFromIterator) +{ + zval *obj; + char *error; + size_t base_len = 0; + char *base = NULL; + struct _phar_t pass; + + PHAR_ARCHIVE_OBJECT(); + + if (PHAR_G(readonly) && !phar_obj->archive->is_data) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot write out phar archive, phar is read-only"); + return; + } + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "O|s", &obj, zend_ce_traversable, &base, &base_len) == FAILURE) { + RETURN_FALSE; + } + + if (ZEND_SIZE_T_UINT_OVFL(base_len)) { + RETURN_FALSE; + } + + if (phar_obj->archive->is_persistent && FAILURE == phar_copy_on_write(&(phar_obj->archive))) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar_obj->archive->fname); + return; + } + + array_init(return_value); + + pass.c = Z_OBJCE_P(obj); + pass.p = phar_obj; + pass.b = base; + pass.l = (uint)base_len; + pass.ret = return_value; + pass.count = 0; + pass.fp = php_stream_fopen_tmpfile(); + if (pass.fp == NULL) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\": unable to create temporary file", phar_obj->archive->fname); + return; + } + + if (SUCCESS == spl_iterator_apply(obj, (spl_iterator_apply_func_t) phar_build, (void *) &pass)) { + phar_obj->archive->ufp = pass.fp; + phar_flush(phar_obj->archive, 0, 0, 0, &error); + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } + } else { + php_stream_close(pass.fp); + } +} +/* }}} */ + +/* {{{ proto int Phar::count() + * Returns the number of entries in the Phar archive + */ +PHP_METHOD(Phar, count) +{ + /* mode can be ignored, maximum depth is 1 */ + zend_long mode; + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &mode) == FAILURE) { + RETURN_FALSE; + } + + RETURN_LONG(zend_hash_num_elements(&phar_obj->archive->manifest)); +} +/* }}} */ + +/* {{{ proto bool Phar::isFileFormat(int format) + * Returns true if the phar archive is based on the tar/zip/phar file format depending + * on whether Phar::TAR, Phar::ZIP or Phar::PHAR was passed in + */ +PHP_METHOD(Phar, isFileFormat) +{ + zend_long type; + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &type) == FAILURE) { + RETURN_FALSE; + } + + switch (type) { + case PHAR_FORMAT_TAR: + RETURN_BOOL(phar_obj->archive->is_tar); + case PHAR_FORMAT_ZIP: + RETURN_BOOL(phar_obj->archive->is_zip); + case PHAR_FORMAT_PHAR: + RETURN_BOOL(!phar_obj->archive->is_tar && !phar_obj->archive->is_zip); + default: + zend_throw_exception_ex(phar_ce_PharException, 0, "Unknown file format specified"); + } +} +/* }}} */ + +static int phar_copy_file_contents(phar_entry_info *entry, php_stream *fp) /* {{{ */ +{ + char *error; + zend_off_t offset; + phar_entry_info *link; + + if (FAILURE == phar_open_entry_fp(entry, &error, 1)) { + if (error) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot convert phar archive \"%s\", unable to open entry \"%s\" contents: %s", entry->phar->fname, entry->filename, error); + efree(error); + } else { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot convert phar archive \"%s\", unable to open entry \"%s\" contents", entry->phar->fname, entry->filename); + } + return FAILURE; + } + + /* copy old contents in entirety */ + phar_seek_efp(entry, 0, SEEK_SET, 0, 1); + offset = php_stream_tell(fp); + link = phar_get_link_source(entry); + + if (!link) { + link = entry; + } + + if (SUCCESS != php_stream_copy_to_stream_ex(phar_get_efp(link, 0), fp, link->uncompressed_filesize, NULL)) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot convert phar archive \"%s\", unable to copy entry \"%s\" contents", entry->phar->fname, entry->filename); + return FAILURE; + } + + if (entry->fp_type == PHAR_MOD) { + /* save for potential restore on error */ + entry->cfp = entry->fp; + entry->fp = NULL; + } + + /* set new location of file contents */ + entry->fp_type = PHAR_FP; + entry->offset = offset; + return SUCCESS; +} +/* }}} */ + +static zend_object *phar_rename_archive(phar_archive_data **sphar, char *ext, zend_bool compress) /* {{{ */ +{ + const char *oldname = NULL; + phar_archive_data *phar = *sphar; + char *oldpath = NULL; + char *basename = NULL, *basepath = NULL; + char *newname = NULL, *newpath = NULL; + zval ret, arg1; + zend_class_entry *ce; + char *error; + const char *pcr_error; + int ext_len = ext ? strlen(ext) : 0; + size_t new_len, oldname_len; + phar_archive_data *pphar = NULL; + php_stream_statbuf ssb; + + if (!ext) { + if (phar->is_zip) { + + if (phar->is_data) { + ext = "zip"; + } else { + ext = "phar.zip"; + } + + } else if (phar->is_tar) { + + switch (phar->flags) { + case PHAR_FILE_COMPRESSED_GZ: + if (phar->is_data) { + ext = "tar.gz"; + } else { + ext = "phar.tar.gz"; + } + break; + case PHAR_FILE_COMPRESSED_BZ2: + if (phar->is_data) { + ext = "tar.bz2"; + } else { + ext = "phar.tar.bz2"; + } + break; + default: + if (phar->is_data) { + ext = "tar"; + } else { + ext = "phar.tar"; + } + } + } else { + + switch (phar->flags) { + case PHAR_FILE_COMPRESSED_GZ: + ext = "phar.gz"; + break; + case PHAR_FILE_COMPRESSED_BZ2: + ext = "phar.bz2"; + break; + default: + ext = "phar"; + } + } + } else if (phar_path_check(&ext, &ext_len, &pcr_error) > pcr_is_ok) { + + if (phar->is_data) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "data phar converted from \"%s\" has invalid extension %s", phar->fname, ext); + } else { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "phar converted from \"%s\" has invalid extension %s", phar->fname, ext); + } + return NULL; + } + + if (ext[0] == '.') { + ++ext; + } + + oldpath = estrndup(phar->fname, phar->fname_len); + if ((oldname = zend_memrchr(phar->fname, '/', phar->fname_len))) { + ++oldname; + } else { + oldname = phar->fname; + } + oldname_len = strlen(oldname); + + basename = estrndup(oldname, oldname_len); + spprintf(&newname, 0, "%s.%s", strtok(basename, "."), ext); + efree(basename); + + basepath = estrndup(oldpath, (strlen(oldpath) - oldname_len)); + new_len = spprintf(&newpath, 0, "%s%s", basepath, newname); + if (ZEND_SIZE_T_INT_OVFL(new_len)) { + efree(oldpath); + efree(basepath); + efree(newpath); + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "New name is too long"); + return NULL; + } + phar->fname_len = (int)new_len; + phar->fname = newpath; + phar->ext = newpath + phar->fname_len - strlen(ext) - 1; + efree(basepath); + efree(newname); + + if (PHAR_G(manifest_cached) && NULL != (pphar = zend_hash_str_find_ptr(&cached_phars, newpath, phar->fname_len))) { + efree(oldpath); + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Unable to add newly converted phar \"%s\" to the list of phars, new phar name is in phar.cache_list", phar->fname); + return NULL; + } + + if (NULL != (pphar = zend_hash_str_find_ptr(&(PHAR_G(phar_fname_map)), newpath, phar->fname_len))) { + if (pphar->fname_len == phar->fname_len && !memcmp(pphar->fname, phar->fname, phar->fname_len)) { + if (!zend_hash_num_elements(&phar->manifest)) { + pphar->is_tar = phar->is_tar; + pphar->is_zip = phar->is_zip; + pphar->is_data = phar->is_data; + pphar->flags = phar->flags; + pphar->fp = phar->fp; + phar->fp = NULL; + phar_destroy_phar_data(phar); + *sphar = NULL; + phar = pphar; + phar->refcount++; + newpath = oldpath; + goto its_ok; + } + } + + efree(oldpath); + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Unable to add newly converted phar \"%s\" to the list of phars, a phar with that name already exists", phar->fname); + return NULL; + } +its_ok: + if (SUCCESS == php_stream_stat_path(newpath, &ssb)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "phar \"%s\" exists and must be unlinked prior to conversion", newpath); + efree(oldpath); + return NULL; + } + if (!phar->is_data) { + if (SUCCESS != phar_detect_phar_fname_ext(newpath, phar->fname_len, (const char **) &(phar->ext), &(phar->ext_len), 1, 1, 1)) { + efree(oldpath); + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "phar \"%s\" has invalid extension %s", phar->fname, ext); + return NULL; + } + + if (phar->alias) { + if (phar->is_temporary_alias) { + phar->alias = NULL; + phar->alias_len = 0; + } else { + phar->alias = estrndup(newpath, strlen(newpath)); + phar->alias_len = (int)strlen(newpath); + phar->is_temporary_alias = 1; + zend_hash_str_update_ptr(&(PHAR_G(phar_alias_map)), newpath, phar->fname_len, phar); + } + } + + } else { + + if (SUCCESS != phar_detect_phar_fname_ext(newpath, phar->fname_len, (const char **) &(phar->ext), &(phar->ext_len), 0, 1, 1)) { + efree(oldpath); + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "data phar \"%s\" has invalid extension %s", phar->fname, ext); + return NULL; + } + + phar->alias = NULL; + phar->alias_len = 0; + } + + if ((!pphar || phar == pphar) && NULL == zend_hash_str_update_ptr(&(PHAR_G(phar_fname_map)), newpath, phar->fname_len, phar)) { + efree(oldpath); + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Unable to add newly converted phar \"%s\" to the list of phars", phar->fname); + return NULL; + } + + phar_flush(phar, 0, 0, 1, &error); + + if (error) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "%s", error); + efree(error); + efree(oldpath); + return NULL; + } + + efree(oldpath); + + if (phar->is_data) { + ce = phar_ce_data; + } else { + ce = phar_ce_archive; + } + + ZVAL_NULL(&ret); + if (SUCCESS != object_init_ex(&ret, ce)) { + zval_dtor(&ret); + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Unable to instantiate phar object when converting archive \"%s\"", phar->fname); + return NULL; + } + + ZVAL_STRINGL(&arg1, phar->fname, phar->fname_len); + + zend_call_method_with_1_params(&ret, ce, &ce->constructor, "__construct", NULL, &arg1); + zval_ptr_dtor(&arg1); + return Z_OBJ(ret); +} +/* }}} */ + +static zend_object *phar_convert_to_other(phar_archive_data *source, int convert, char *ext, uint32_t flags) /* {{{ */ +{ + phar_archive_data *phar; + phar_entry_info *entry, newentry; + zend_object *ret; + + /* invalidate phar cache */ + PHAR_G(last_phar) = NULL; + PHAR_G(last_phar_name) = PHAR_G(last_alias) = NULL; + + phar = (phar_archive_data *) ecalloc(1, sizeof(phar_archive_data)); + /* set whole-archive compression and type from parameter */ + phar->flags = flags; + phar->is_data = source->is_data; + + switch (convert) { + case PHAR_FORMAT_TAR: + phar->is_tar = 1; + break; + case PHAR_FORMAT_ZIP: + phar->is_zip = 1; + break; + default: + phar->is_data = 0; + break; + } + + zend_hash_init(&(phar->manifest), sizeof(phar_entry_info), + zend_get_hash_value, destroy_phar_manifest_entry, 0); + zend_hash_init(&phar->mounted_dirs, sizeof(char *), + zend_get_hash_value, NULL, 0); + zend_hash_init(&phar->virtual_dirs, sizeof(char *), + zend_get_hash_value, NULL, 0); + + phar->fp = php_stream_fopen_tmpfile(); + if (phar->fp == NULL) { + zend_throw_exception_ex(phar_ce_PharException, 0, "unable to create temporary file"); + return NULL; + } + phar->fname = source->fname; + phar->fname_len = source->fname_len; + phar->is_temporary_alias = source->is_temporary_alias; + phar->alias = source->alias; + + if (Z_TYPE(source->metadata) != IS_UNDEF) { + ZVAL_DUP(&phar->metadata, &source->metadata); + phar->metadata_len = 0; + } + + /* first copy each file's uncompressed contents to a temporary file and set per-file flags */ + ZEND_HASH_FOREACH_PTR(&source->manifest, entry) { + + newentry = *entry; + + if (newentry.link) { + newentry.link = estrdup(newentry.link); + goto no_copy; + } + + if (newentry.tmp) { + newentry.tmp = estrdup(newentry.tmp); + goto no_copy; + } + + newentry.metadata_str.s = NULL; + + if (FAILURE == phar_copy_file_contents(&newentry, phar->fp)) { + zend_hash_destroy(&(phar->manifest)); + php_stream_close(phar->fp); + efree(phar); + /* exception already thrown */ + return NULL; + } +no_copy: + newentry.filename = estrndup(newentry.filename, newentry.filename_len); + + if (Z_TYPE(newentry.metadata) != IS_UNDEF) { + zval_copy_ctor(&newentry.metadata); + newentry.metadata_str.s = NULL; + } + + newentry.is_zip = phar->is_zip; + newentry.is_tar = phar->is_tar; + + if (newentry.is_tar) { + newentry.tar_type = (entry->is_dir ? TAR_DIR : TAR_FILE); + } + + newentry.is_modified = 1; + newentry.phar = phar; + newentry.old_flags = newentry.flags & ~PHAR_ENT_COMPRESSION_MASK; /* remove compression from old_flags */ + phar_set_inode(&newentry); + zend_hash_str_add_mem(&(phar->manifest), newentry.filename, newentry.filename_len, (void*)&newentry, sizeof(phar_entry_info)); + phar_add_virtual_dirs(phar, newentry.filename, newentry.filename_len); + } ZEND_HASH_FOREACH_END(); + + if ((ret = phar_rename_archive(&phar, ext, 0))) { + return ret; + } else { + if(phar != NULL) { + zend_hash_destroy(&(phar->manifest)); + zend_hash_destroy(&(phar->mounted_dirs)); + zend_hash_destroy(&(phar->virtual_dirs)); + if (phar->fp) { + php_stream_close(phar->fp); + } + efree(phar->fname); + efree(phar); + } + return NULL; + } +} +/* }}} */ + +/* {{{ proto object Phar::convertToExecutable([int format[, int compression [, string file_ext]]]) + * Convert a phar.tar or phar.zip archive to the phar file format. The + * optional parameter allows the user to determine the new + * filename extension (default is phar). + */ +PHP_METHOD(Phar, convertToExecutable) +{ + char *ext = NULL; + int is_data; + size_t ext_len = 0; + uint32_t flags; + zend_object *ret; + /* a number that is not 0, 1 or 2 (Which is also Greg's birthday, so there) */ + zend_long format = 9021976, method = 9021976; + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|lls", &format, &method, &ext, &ext_len) == FAILURE) { + return; + } + + if (PHAR_G(readonly)) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot write out executable phar archive, phar is read-only"); + return; + } + + switch (format) { + case 9021976: + case PHAR_FORMAT_SAME: /* null is converted to 0 */ + /* by default, use the existing format */ + if (phar_obj->archive->is_tar) { + format = PHAR_FORMAT_TAR; + } else if (phar_obj->archive->is_zip) { + format = PHAR_FORMAT_ZIP; + } else { + format = PHAR_FORMAT_PHAR; + } + break; + case PHAR_FORMAT_PHAR: + case PHAR_FORMAT_TAR: + case PHAR_FORMAT_ZIP: + break; + default: + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Unknown file format specified, please pass one of Phar::PHAR, Phar::TAR or Phar::ZIP"); + return; + } + + switch (method) { + case 9021976: + flags = phar_obj->archive->flags & PHAR_FILE_COMPRESSION_MASK; + break; + case 0: + flags = PHAR_FILE_COMPRESSED_NONE; + break; + case PHAR_ENT_COMPRESSED_GZ: + if (format == PHAR_FORMAT_ZIP) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress entire archive with gzip, zip archives do not support whole-archive compression"); + return; + } + + if (!PHAR_G(has_zlib)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress entire archive with gzip, enable ext/zlib in php.ini"); + return; + } + + flags = PHAR_FILE_COMPRESSED_GZ; + break; + case PHAR_ENT_COMPRESSED_BZ2: + if (format == PHAR_FORMAT_ZIP) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress entire archive with bz2, zip archives do not support whole-archive compression"); + return; + } + + if (!PHAR_G(has_bz2)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress entire archive with bz2, enable ext/bz2 in php.ini"); + return; + } + + flags = PHAR_FILE_COMPRESSED_BZ2; + break; + default: + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Unknown compression specified, please pass one of Phar::GZ or Phar::BZ2"); + return; + } + + is_data = phar_obj->archive->is_data; + phar_obj->archive->is_data = 0; + ret = phar_convert_to_other(phar_obj->archive, (int)format, ext, flags); + phar_obj->archive->is_data = is_data; + + if (ret) { + ZVAL_OBJ(return_value, ret); + } else { + RETURN_NULL(); + } +} +/* }}} */ + +/* {{{ proto object Phar::convertToData([int format[, int compression [, string file_ext]]]) + * Convert an archive to a non-executable .tar or .zip. + * The optional parameter allows the user to determine the new + * filename extension (default is .zip or .tar). + */ +PHP_METHOD(Phar, convertToData) +{ + char *ext = NULL; + int is_data; + size_t ext_len = 0; + uint32_t flags; + zend_object *ret; + /* a number that is not 0, 1 or 2 (Which is also Greg's birthday so there) */ + zend_long format = 9021976, method = 9021976; + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|lls", &format, &method, &ext, &ext_len) == FAILURE) { + return; + } + + switch (format) { + case 9021976: + case PHAR_FORMAT_SAME: /* null is converted to 0 */ + /* by default, use the existing format */ + if (phar_obj->archive->is_tar) { + format = PHAR_FORMAT_TAR; + } else if (phar_obj->archive->is_zip) { + format = PHAR_FORMAT_ZIP; + } else { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot write out data phar archive, use Phar::TAR or Phar::ZIP"); + return; + } + break; + case PHAR_FORMAT_PHAR: + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot write out data phar archive, use Phar::TAR or Phar::ZIP"); + return; + case PHAR_FORMAT_TAR: + case PHAR_FORMAT_ZIP: + break; + default: + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Unknown file format specified, please pass one of Phar::TAR or Phar::ZIP"); + return; + } + + switch (method) { + case 9021976: + flags = phar_obj->archive->flags & PHAR_FILE_COMPRESSION_MASK; + break; + case 0: + flags = PHAR_FILE_COMPRESSED_NONE; + break; + case PHAR_ENT_COMPRESSED_GZ: + if (format == PHAR_FORMAT_ZIP) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress entire archive with gzip, zip archives do not support whole-archive compression"); + return; + } + + if (!PHAR_G(has_zlib)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress entire archive with gzip, enable ext/zlib in php.ini"); + return; + } + + flags = PHAR_FILE_COMPRESSED_GZ; + break; + case PHAR_ENT_COMPRESSED_BZ2: + if (format == PHAR_FORMAT_ZIP) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress entire archive with bz2, zip archives do not support whole-archive compression"); + return; + } + + if (!PHAR_G(has_bz2)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress entire archive with bz2, enable ext/bz2 in php.ini"); + return; + } + + flags = PHAR_FILE_COMPRESSED_BZ2; + break; + default: + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Unknown compression specified, please pass one of Phar::GZ or Phar::BZ2"); + return; + } + + is_data = phar_obj->archive->is_data; + phar_obj->archive->is_data = 1; + ret = phar_convert_to_other(phar_obj->archive, (int)format, ext, flags); + phar_obj->archive->is_data = is_data; + + if (ret) { + ZVAL_OBJ(return_value, ret); + } else { + RETURN_NULL(); + } +} +/* }}} */ + +/* {{{ proto int|false Phar::isCompressed() + * Returns Phar::GZ or PHAR::BZ2 if the entire archive is compressed + * (.tar.gz/tar.bz2 and so on), or FALSE otherwise. + */ +PHP_METHOD(Phar, isCompressed) +{ + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (phar_obj->archive->flags & PHAR_FILE_COMPRESSED_GZ) { + RETURN_LONG(PHAR_ENT_COMPRESSED_GZ); + } + + if (phar_obj->archive->flags & PHAR_FILE_COMPRESSED_BZ2) { + RETURN_LONG(PHAR_ENT_COMPRESSED_BZ2); + } + + RETURN_FALSE; +} +/* }}} */ + +/* {{{ proto bool Phar::isWritable() + * Returns true if phar.readonly=0 or phar is a PharData AND the actual file is writable. + */ +PHP_METHOD(Phar, isWritable) +{ + php_stream_statbuf ssb; + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (!phar_obj->archive->is_writeable) { + RETURN_FALSE; + } + + if (SUCCESS != php_stream_stat_path(phar_obj->archive->fname, &ssb)) { + if (phar_obj->archive->is_brandnew) { + /* assume it works if the file doesn't exist yet */ + RETURN_TRUE; + } + RETURN_FALSE; + } + + RETURN_BOOL((ssb.sb.st_mode & (S_IWOTH | S_IWGRP | S_IWUSR)) != 0); +} +/* }}} */ + +/* {{{ proto bool Phar::delete(string entry) + * Deletes a named file within the archive. + */ +PHP_METHOD(Phar, delete) +{ + char *fname; + size_t fname_len; + char *error; + phar_entry_info *entry; + PHAR_ARCHIVE_OBJECT(); + + if (PHAR_G(readonly) && !phar_obj->archive->is_data) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot write out phar archive, phar is read-only"); + return; + } + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p", &fname, &fname_len) == FAILURE) { + RETURN_FALSE; + } + + if (phar_obj->archive->is_persistent && FAILURE == phar_copy_on_write(&(phar_obj->archive))) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar_obj->archive->fname); + return; + } + if (zend_hash_str_exists(&phar_obj->archive->manifest, fname, (uint) fname_len)) { + if (NULL != (entry = zend_hash_str_find_ptr(&phar_obj->archive->manifest, fname, (uint) fname_len))) { + if (entry->is_deleted) { + /* entry is deleted, but has not been flushed to disk yet */ + RETURN_TRUE; + } else { + entry->is_deleted = 1; + entry->is_modified = 1; + phar_obj->archive->is_modified = 1; + } + } + } else { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Entry %s does not exist and cannot be deleted", fname); + RETURN_FALSE; + } + + phar_flush(phar_obj->archive, NULL, 0, 0, &error); + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto int Phar::getAlias() + * Returns the alias for the Phar or NULL. + */ +PHP_METHOD(Phar, getAlias) +{ + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (phar_obj->archive->alias && phar_obj->archive->alias != phar_obj->archive->fname) { + RETURN_STRINGL(phar_obj->archive->alias, phar_obj->archive->alias_len); + } +} +/* }}} */ + +/* {{{ proto int Phar::getPath() + * Returns the real path to the phar archive on disk + */ +PHP_METHOD(Phar, getPath) +{ + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + RETURN_STRINGL(phar_obj->archive->fname, phar_obj->archive->fname_len); +} +/* }}} */ + +/* {{{ proto bool Phar::setAlias(string alias) + * Sets the alias for a Phar archive. The default value is the full path + * to the archive. + */ +PHP_METHOD(Phar, setAlias) +{ + char *alias, *error, *oldalias; + phar_archive_data *fd_ptr; + size_t alias_len, oldalias_len; + int old_temp, readd = 0; + + PHAR_ARCHIVE_OBJECT(); + + if (PHAR_G(readonly) && !phar_obj->archive->is_data) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot write out phar archive, phar is read-only"); + RETURN_FALSE; + } + + /* invalidate phar cache */ + PHAR_G(last_phar) = NULL; + PHAR_G(last_phar_name) = PHAR_G(last_alias) = NULL; + + if (phar_obj->archive->is_data) { + if (phar_obj->archive->is_tar) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "A Phar alias cannot be set in a plain tar archive"); + } else { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "A Phar alias cannot be set in a plain zip archive"); + } + RETURN_FALSE; + } + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &alias, &alias_len) == SUCCESS) { + if (ZEND_SIZE_T_INT_OVFL(alias_len)) { + RETURN_FALSE; + } + if (alias_len == (size_t)phar_obj->archive->alias_len && memcmp(phar_obj->archive->alias, alias, alias_len) == 0) { + RETURN_TRUE; + } + if (alias_len && NULL != (fd_ptr = zend_hash_str_find_ptr(&(PHAR_G(phar_alias_map)), alias, alias_len))) { + spprintf(&error, 0, "alias \"%s\" is already used for archive \"%s\" and cannot be used for other archives", alias, fd_ptr->fname); + if (SUCCESS == phar_free_alias(fd_ptr, alias, (int)alias_len)) { + efree(error); + goto valid_alias; + } + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + RETURN_FALSE; + } + if (!phar_validate_alias(alias, (int)alias_len)) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Invalid alias \"%s\" specified for phar \"%s\"", alias, phar_obj->archive->fname); + RETURN_FALSE; + } +valid_alias: + if (phar_obj->archive->is_persistent && FAILURE == phar_copy_on_write(&(phar_obj->archive))) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar_obj->archive->fname); + return; + } + if (phar_obj->archive->alias_len && NULL != (fd_ptr = zend_hash_str_find_ptr(&(PHAR_G(phar_alias_map)), phar_obj->archive->alias, phar_obj->archive->alias_len))) { + zend_hash_str_del(&(PHAR_G(phar_alias_map)), phar_obj->archive->alias, phar_obj->archive->alias_len); + readd = 1; + } + + oldalias = phar_obj->archive->alias; + oldalias_len = phar_obj->archive->alias_len; + old_temp = phar_obj->archive->is_temporary_alias; + + if (alias_len) { + phar_obj->archive->alias = estrndup(alias, alias_len); + } else { + phar_obj->archive->alias = NULL; + } + + phar_obj->archive->alias_len = (int)alias_len; + phar_obj->archive->is_temporary_alias = 0; + phar_flush(phar_obj->archive, NULL, 0, 0, &error); + + if (error) { + phar_obj->archive->alias = oldalias; + phar_obj->archive->alias_len = (int)oldalias_len; + phar_obj->archive->is_temporary_alias = old_temp; + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + if (readd) { + zend_hash_str_add_ptr(&(PHAR_G(phar_alias_map)), oldalias, oldalias_len, phar_obj->archive); + } + efree(error); + RETURN_FALSE; + } + + zend_hash_str_add_ptr(&(PHAR_G(phar_alias_map)), alias, alias_len, phar_obj->archive); + + if (oldalias) { + efree(oldalias); + } + + RETURN_TRUE; + } + + RETURN_FALSE; +} +/* }}} */ + +/* {{{ proto string Phar::getVersion() + * Return version info of Phar archive + */ +PHP_METHOD(Phar, getVersion) +{ + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + RETURN_STRING(phar_obj->archive->version); +} +/* }}} */ + +/* {{{ proto void Phar::startBuffering() + * Do not flush a writeable phar (save its contents) until explicitly requested + */ +PHP_METHOD(Phar, startBuffering) +{ + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + phar_obj->archive->donotflush = 1; +} +/* }}} */ + +/* {{{ proto bool Phar::isBuffering() + * Returns whether write operations are flushing to disk immediately. + */ +PHP_METHOD(Phar, isBuffering) +{ + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + RETURN_BOOL(phar_obj->archive->donotflush); +} +/* }}} */ + +/* {{{ proto bool Phar::stopBuffering() + * Saves the contents of a modified archive to disk. + */ +PHP_METHOD(Phar, stopBuffering) +{ + char *error; + + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (PHAR_G(readonly) && !phar_obj->archive->is_data) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot write out phar archive, phar is read-only"); + return; + } + + phar_obj->archive->donotflush = 0; + phar_flush(phar_obj->archive, 0, 0, 0, &error); + + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } +} +/* }}} */ + +/* {{{ proto bool Phar::setStub(string|stream stub [, int len]) + * Change the stub in a phar, phar.tar or phar.zip archive to something other + * than the default. The stub *must* end with a call to __HALT_COMPILER(). + */ +PHP_METHOD(Phar, setStub) +{ + zval *zstub; + char *stub, *error; + size_t stub_len; + zend_long len = -1; + php_stream *stream; + PHAR_ARCHIVE_OBJECT(); + + if (PHAR_G(readonly) && !phar_obj->archive->is_data) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot change stub, phar is read-only"); + return; + } + + if (phar_obj->archive->is_data) { + if (phar_obj->archive->is_tar) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "A Phar stub cannot be set in a plain tar archive"); + } else { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "A Phar stub cannot be set in a plain zip archive"); + } + return; + } + + if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "r|l", &zstub, &len) == SUCCESS) { + if ((php_stream_from_zval_no_verify(stream, zstub)) != NULL) { + if (len > 0) { + len = -len; + } else { + len = -1; + } + if (phar_obj->archive->is_persistent && FAILURE == phar_copy_on_write(&(phar_obj->archive))) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar_obj->archive->fname); + return; + } + phar_flush(phar_obj->archive, (char *) zstub, len, 0, &error); + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } + RETURN_TRUE; + } else { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot change stub, unable to read from input stream"); + } + } else if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &stub, &stub_len) == SUCCESS) { + if (phar_obj->archive->is_persistent && FAILURE == phar_copy_on_write(&(phar_obj->archive))) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar_obj->archive->fname); + return; + } + phar_flush(phar_obj->archive, stub, stub_len, 0, &error); + + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } + + RETURN_TRUE; + } + + RETURN_FALSE; +} +/* }}} */ + +/* {{{ proto bool Phar::setDefaultStub([string index[, string webindex]]) + * In a pure phar archive, sets a stub that can be used to run the archive + * regardless of whether the phar extension is available. The first parameter + * is the CLI startup filename, which defaults to "index.php". The second + * parameter is the web startup filename and also defaults to "index.php" + * (falling back to CLI behaviour). + * Both parameters are optional. + * In a phar.zip or phar.tar archive, the default stub is used only to + * identify the archive to the extension as a Phar object. This allows the + * extension to treat phar.zip and phar.tar types as honorary phars. Since + * files cannot be loaded via this kind of stub, no parameters are accepted + * when the Phar object is zip- or tar-based. + */ +PHP_METHOD(Phar, setDefaultStub) +{ + char *index = NULL, *webindex = NULL, *error = NULL; + zend_string *stub = NULL; + size_t index_len = 0, webindex_len = 0; + int created_stub = 0; + PHAR_ARCHIVE_OBJECT(); + + if (phar_obj->archive->is_data) { + if (phar_obj->archive->is_tar) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "A Phar stub cannot be set in a plain tar archive"); + } else { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "A Phar stub cannot be set in a plain zip archive"); + } + return; + } + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|s!s", &index, &index_len, &webindex, &webindex_len) == FAILURE) { + RETURN_FALSE; + } + + if (ZEND_NUM_ARGS() > 0 && (phar_obj->archive->is_tar || phar_obj->archive->is_zip)) { + php_error_docref(NULL, E_WARNING, "method accepts no arguments for a tar- or zip-based phar stub, %d given", ZEND_NUM_ARGS()); + RETURN_FALSE; + } + + if (PHAR_G(readonly)) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot change stub: phar.readonly=1"); + RETURN_FALSE; + } + + if (!phar_obj->archive->is_tar && !phar_obj->archive->is_zip) { + stub = phar_create_default_stub(index, webindex, &error); + + if (error) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "%s", error); + efree(error); + if (stub) { + zend_string_free(stub); + } + RETURN_FALSE; + } + + created_stub = 1; + } + + if (phar_obj->archive->is_persistent && FAILURE == phar_copy_on_write(&(phar_obj->archive))) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar_obj->archive->fname); + return; + } + phar_flush(phar_obj->archive, stub ? ZSTR_VAL(stub) : 0, stub ? ZSTR_LEN(stub) : 0, 1, &error); + + if (created_stub) { + zend_string_free(stub); + } + + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + RETURN_FALSE; + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto array Phar::setSignatureAlgorithm(int sigtype[, string privatekey]) + * Sets the signature algorithm for a phar and applies it. The signature + * algorithm must be one of Phar::MD5, Phar::SHA1, Phar::SHA256, + * Phar::SHA512, or Phar::OPENSSL. Note that zip- based phar archives + * cannot support signatures. + */ +PHP_METHOD(Phar, setSignatureAlgorithm) +{ + zend_long algo; + char *error, *key = NULL; + size_t key_len = 0; + + PHAR_ARCHIVE_OBJECT(); + + if (PHAR_G(readonly) && !phar_obj->archive->is_data) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot set signature algorithm, phar is read-only"); + return; + } + + if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "l|s", &algo, &key, &key_len) != SUCCESS) { + return; + } + if (ZEND_SIZE_T_INT_OVFL(key_len)) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot set signature algorithm, key too long"); + return; + } + + switch (algo) { + case PHAR_SIG_SHA256: + case PHAR_SIG_SHA512: +#ifndef PHAR_HASH_OK + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "SHA-256 and SHA-512 signatures are only supported if the hash extension is enabled and built non-shared"); + return; +#endif + case PHAR_SIG_MD5: + case PHAR_SIG_SHA1: + case PHAR_SIG_OPENSSL: + if (phar_obj->archive->is_persistent && FAILURE == phar_copy_on_write(&(phar_obj->archive))) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar_obj->archive->fname); + return; + } + phar_obj->archive->sig_flags = (php_uint32)algo; + phar_obj->archive->is_modified = 1; + PHAR_G(openssl_privatekey) = key; + PHAR_G(openssl_privatekey_len) = (int)key_len; + + phar_flush(phar_obj->archive, 0, 0, 0, &error); + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } + break; + default: + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Unknown signature algorithm specified"); + } +} +/* }}} */ + +/* {{{ proto array|false Phar::getSignature() + * Returns a hash signature, or FALSE if the archive is unsigned. + */ +PHP_METHOD(Phar, getSignature) +{ + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (phar_obj->archive->signature) { + zend_string *unknown; + + array_init(return_value); + add_assoc_stringl(return_value, "hash", phar_obj->archive->signature, phar_obj->archive->sig_len); + switch(phar_obj->archive->sig_flags) { + case PHAR_SIG_MD5: + add_assoc_stringl(return_value, "hash_type", "MD5", 3); + break; + case PHAR_SIG_SHA1: + add_assoc_stringl(return_value, "hash_type", "SHA-1", 5); + break; + case PHAR_SIG_SHA256: + add_assoc_stringl(return_value, "hash_type", "SHA-256", 7); + break; + case PHAR_SIG_SHA512: + add_assoc_stringl(return_value, "hash_type", "SHA-512", 7); + break; + case PHAR_SIG_OPENSSL: + add_assoc_stringl(return_value, "hash_type", "OpenSSL", 7); + break; + default: + unknown = strpprintf(0, "Unknown (%u)", phar_obj->archive->sig_flags); + add_assoc_str(return_value, "hash_type", unknown); + break; + } + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto bool Phar::getModified() + * Return whether phar was modified + */ +PHP_METHOD(Phar, getModified) +{ + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + RETURN_BOOL(phar_obj->archive->is_modified); +} +/* }}} */ + +static int phar_set_compression(zval *zv, void *argument) /* {{{ */ +{ + phar_entry_info *entry = (phar_entry_info *)Z_PTR_P(zv); + uint32_t compress = *(uint32_t *)argument; + + if (entry->is_deleted) { + return ZEND_HASH_APPLY_KEEP; + } + + entry->old_flags = entry->flags; + entry->flags &= ~PHAR_ENT_COMPRESSION_MASK; + entry->flags |= compress; + entry->is_modified = 1; + return ZEND_HASH_APPLY_KEEP; +} +/* }}} */ + +static int phar_test_compression(zval *zv, void *argument) /* {{{ */ +{ + phar_entry_info *entry = (phar_entry_info *)Z_PTR_P(zv); + + if (entry->is_deleted) { + return ZEND_HASH_APPLY_KEEP; + } + + if (!PHAR_G(has_bz2)) { + if (entry->flags & PHAR_ENT_COMPRESSED_BZ2) { + *(int *) argument = 0; + } + } + + if (!PHAR_G(has_zlib)) { + if (entry->flags & PHAR_ENT_COMPRESSED_GZ) { + *(int *) argument = 0; + } + } + + return ZEND_HASH_APPLY_KEEP; +} +/* }}} */ + +static void pharobj_set_compression(HashTable *manifest, uint32_t compress) /* {{{ */ +{ + zend_hash_apply_with_argument(manifest, phar_set_compression, &compress); +} +/* }}} */ + +static int pharobj_cancompress(HashTable *manifest) /* {{{ */ +{ + int test; + + test = 1; + zend_hash_apply_with_argument(manifest, phar_test_compression, &test); + return test; +} +/* }}} */ + +/* {{{ proto object Phar::compress(int method[, string extension]) + * Compress a .tar, or .phar.tar with whole-file compression + * The parameter can be one of Phar::GZ or Phar::BZ2 to specify + * the kind of compression desired + */ +PHP_METHOD(Phar, compress) +{ + zend_long method; + char *ext = NULL; + size_t ext_len = 0; + uint32_t flags; + zend_object *ret; + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l|s", &method, &ext, &ext_len) == FAILURE) { + return; + } + + if (PHAR_G(readonly) && !phar_obj->archive->is_data) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot compress phar archive, phar is read-only"); + return; + } + + if (phar_obj->archive->is_zip) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot compress zip-based archives with whole-archive compression"); + return; + } + + switch (method) { + case 0: + flags = PHAR_FILE_COMPRESSED_NONE; + break; + case PHAR_ENT_COMPRESSED_GZ: + if (!PHAR_G(has_zlib)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress entire archive with gzip, enable ext/zlib in php.ini"); + return; + } + flags = PHAR_FILE_COMPRESSED_GZ; + break; + + case PHAR_ENT_COMPRESSED_BZ2: + if (!PHAR_G(has_bz2)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress entire archive with bz2, enable ext/bz2 in php.ini"); + return; + } + flags = PHAR_FILE_COMPRESSED_BZ2; + break; + default: + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Unknown compression specified, please pass one of Phar::GZ or Phar::BZ2"); + return; + } + + if (phar_obj->archive->is_tar) { + ret = phar_convert_to_other(phar_obj->archive, PHAR_FORMAT_TAR, ext, flags); + } else { + ret = phar_convert_to_other(phar_obj->archive, PHAR_FORMAT_PHAR, ext, flags); + } + + if (ret) { + ZVAL_OBJ(return_value, ret); + } else { + RETURN_NULL(); + } +} +/* }}} */ + +/* {{{ proto object Phar::decompress([string extension]) + * Decompress a .tar, or .phar.tar with whole-file compression + */ +PHP_METHOD(Phar, decompress) +{ + char *ext = NULL; + size_t ext_len = 0; + zend_object *ret; + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|s", &ext, &ext_len) == FAILURE) { + return; + } + + if (PHAR_G(readonly) && !phar_obj->archive->is_data) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot decompress phar archive, phar is read-only"); + return; + } + + if (phar_obj->archive->is_zip) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot decompress zip-based archives with whole-archive compression"); + return; + } + + if (phar_obj->archive->is_tar) { + ret = phar_convert_to_other(phar_obj->archive, PHAR_FORMAT_TAR, ext, PHAR_FILE_COMPRESSED_NONE); + } else { + ret = phar_convert_to_other(phar_obj->archive, PHAR_FORMAT_PHAR, ext, PHAR_FILE_COMPRESSED_NONE); + } + + if (ret) { + ZVAL_OBJ(return_value, ret); + } else { + RETURN_NULL(); + } +} +/* }}} */ + +/* {{{ proto object Phar::compressFiles(int method) + * Compress all files within a phar or zip archive using the specified compression + * The parameter can be one of Phar::GZ or Phar::BZ2 to specify + * the kind of compression desired + */ +PHP_METHOD(Phar, compressFiles) +{ + char *error; + uint32_t flags; + zend_long method; + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &method) == FAILURE) { + return; + } + + if (PHAR_G(readonly) && !phar_obj->archive->is_data) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Phar is readonly, cannot change compression"); + return; + } + + switch (method) { + case PHAR_ENT_COMPRESSED_GZ: + if (!PHAR_G(has_zlib)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress files within archive with gzip, enable ext/zlib in php.ini"); + return; + } + flags = PHAR_ENT_COMPRESSED_GZ; + break; + + case PHAR_ENT_COMPRESSED_BZ2: + if (!PHAR_G(has_bz2)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress files within archive with bz2, enable ext/bz2 in php.ini"); + return; + } + flags = PHAR_ENT_COMPRESSED_BZ2; + break; + default: + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Unknown compression specified, please pass one of Phar::GZ or Phar::BZ2"); + return; + } + + if (phar_obj->archive->is_tar) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress with Gzip compression, tar archives cannot compress individual files, use compress() to compress the whole archive"); + return; + } + + if (!pharobj_cancompress(&phar_obj->archive->manifest)) { + if (flags == PHAR_FILE_COMPRESSED_GZ) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress all files as Gzip, some are compressed as bzip2 and cannot be decompressed"); + } else { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress all files as Bzip2, some are compressed as gzip and cannot be decompressed"); + } + return; + } + + if (phar_obj->archive->is_persistent && FAILURE == phar_copy_on_write(&(phar_obj->archive))) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar_obj->archive->fname); + return; + } + pharobj_set_compression(&phar_obj->archive->manifest, flags); + phar_obj->archive->is_modified = 1; + phar_flush(phar_obj->archive, 0, 0, 0, &error); + + if (error) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "%s", error); + efree(error); + } +} +/* }}} */ + +/* {{{ proto bool Phar::decompressFiles() + * decompress every file + */ +PHP_METHOD(Phar, decompressFiles) +{ + char *error; + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (PHAR_G(readonly) && !phar_obj->archive->is_data) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Phar is readonly, cannot change compression"); + return; + } + + if (!pharobj_cancompress(&phar_obj->archive->manifest)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot decompress all files, some are compressed as bzip2 or gzip and cannot be decompressed"); + return; + } + + if (phar_obj->archive->is_tar) { + RETURN_TRUE; + } else { + if (phar_obj->archive->is_persistent && FAILURE == phar_copy_on_write(&(phar_obj->archive))) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar_obj->archive->fname); + return; + } + pharobj_set_compression(&phar_obj->archive->manifest, PHAR_ENT_COMPRESSED_NONE); + } + + phar_obj->archive->is_modified = 1; + phar_flush(phar_obj->archive, 0, 0, 0, &error); + + if (error) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "%s", error); + efree(error); + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool Phar::copy(string oldfile, string newfile) + * copy a file internal to the phar archive to another new file within the phar + */ +PHP_METHOD(Phar, copy) +{ + char *oldfile, *newfile, *error; + const char *pcr_error; + size_t oldfile_len, newfile_len; + phar_entry_info *oldentry, newentry = {0}, *temp; + int tmp_len = 0; + + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "pp", &oldfile, &oldfile_len, &newfile, &newfile_len) == FAILURE) { + return; + } + if (ZEND_SIZE_T_INT_OVFL(newfile_len)) { + RETURN_FALSE; + } + if (PHAR_G(readonly) && !phar_obj->archive->is_data) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Cannot copy \"%s\" to \"%s\", phar is read-only", oldfile, newfile); + RETURN_FALSE; + } + + if (oldfile_len >= sizeof(".phar")-1 && !memcmp(oldfile, ".phar", sizeof(".phar")-1)) { + /* can't copy a meta file */ + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "file \"%s\" cannot be copied to file \"%s\", cannot copy Phar meta-file in %s", oldfile, newfile, phar_obj->archive->fname); + RETURN_FALSE; + } + + if (newfile_len >= sizeof(".phar")-1 && !memcmp(newfile, ".phar", sizeof(".phar")-1)) { + /* can't copy a meta file */ + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "file \"%s\" cannot be copied to file \"%s\", cannot copy to Phar meta-file in %s", oldfile, newfile, phar_obj->archive->fname); + RETURN_FALSE; + } + + if (!zend_hash_str_exists(&phar_obj->archive->manifest, oldfile, (uint) oldfile_len) || NULL == (oldentry = zend_hash_str_find_ptr(&phar_obj->archive->manifest, oldfile, (uint) oldfile_len)) || oldentry->is_deleted) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "file \"%s\" cannot be copied to file \"%s\", file does not exist in %s", oldfile, newfile, phar_obj->archive->fname); + RETURN_FALSE; + } + + if (zend_hash_str_exists(&phar_obj->archive->manifest, newfile, (uint) newfile_len)) { + if (NULL != (temp = zend_hash_str_find_ptr(&phar_obj->archive->manifest, newfile, (uint) newfile_len)) || !temp->is_deleted) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "file \"%s\" cannot be copied to file \"%s\", file must not already exist in phar %s", oldfile, newfile, phar_obj->archive->fname); + RETURN_FALSE; + } + } + + tmp_len = (int)newfile_len; + if (phar_path_check(&newfile, &tmp_len, &pcr_error) > pcr_is_ok) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "file \"%s\" contains invalid characters %s, cannot be copied from \"%s\" in phar %s", newfile, pcr_error, oldfile, phar_obj->archive->fname); + RETURN_FALSE; + } + newfile_len = tmp_len; + + if (phar_obj->archive->is_persistent) { + if (FAILURE == phar_copy_on_write(&(phar_obj->archive))) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar_obj->archive->fname); + return; + } + /* re-populate with copied-on-write entry */ + oldentry = zend_hash_str_find_ptr(&phar_obj->archive->manifest, oldfile, (uint) oldfile_len); + } + + memcpy((void *) &newentry, oldentry, sizeof(phar_entry_info)); + + if (Z_TYPE(newentry.metadata) != IS_UNDEF) { + zval_copy_ctor(&newentry.metadata); + newentry.metadata_str.s = NULL; + } + + newentry.filename = estrndup(newfile, newfile_len); + newentry.filename_len = (int)newfile_len; + newentry.fp_refcount = 0; + + if (oldentry->fp_type != PHAR_FP) { + if (FAILURE == phar_copy_entry_fp(oldentry, &newentry, &error)) { + efree(newentry.filename); + php_stream_close(newentry.fp); + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + return; + } + } + + zend_hash_str_add_mem(&oldentry->phar->manifest, newfile, newfile_len, &newentry, sizeof(phar_entry_info)); + phar_obj->archive->is_modified = 1; + phar_flush(phar_obj->archive, 0, 0, 0, &error); + + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto int Phar::offsetExists(string entry) + * determines whether a file exists in the phar + */ +PHP_METHOD(Phar, offsetExists) +{ + char *fname; + size_t fname_len; + phar_entry_info *entry; + + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p", &fname, &fname_len) == FAILURE) { + return; + } + if (ZEND_SIZE_T_INT_OVFL(fname_len)) { + RETURN_FALSE; + } + + if (zend_hash_str_exists(&phar_obj->archive->manifest, fname, (uint) fname_len)) { + if (NULL != (entry = zend_hash_str_find_ptr(&phar_obj->archive->manifest, fname, (uint) fname_len))) { + if (entry->is_deleted) { + /* entry is deleted, but has not been flushed to disk yet */ + RETURN_FALSE; + } + } + + if (fname_len >= sizeof(".phar")-1 && !memcmp(fname, ".phar", sizeof(".phar")-1)) { + /* none of these are real files, so they don't exist */ + RETURN_FALSE; + } + RETURN_TRUE; + } else { + if (zend_hash_str_exists(&phar_obj->archive->virtual_dirs, fname, (uint) fname_len)) { + RETURN_TRUE; + } + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto int Phar::offsetGet(string entry) + * get a PharFileInfo object for a specific file + */ +PHP_METHOD(Phar, offsetGet) +{ + char *fname, *error; + size_t fname_len; + zval zfname; + phar_entry_info *entry; + zend_string *sfname; + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p", &fname, &fname_len) == FAILURE) { + return; + } + + if (ZEND_SIZE_T_INT_OVFL(fname_len)) { + RETURN_FALSE; + } + + /* security is 0 here so that we can get a better error message than "entry doesn't exist" */ + if (!(entry = phar_get_entry_info_dir(phar_obj->archive, fname, (int)fname_len, 1, &error, 0))) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Entry %s does not exist%s%s", fname, error?", ":"", error?error:""); + } else { + if (fname_len == sizeof(".phar/stub.php")-1 && !memcmp(fname, ".phar/stub.php", sizeof(".phar/stub.php")-1)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot get stub \".phar/stub.php\" directly in phar \"%s\", use getStub", phar_obj->archive->fname); + return; + } + + if (fname_len == sizeof(".phar/alias.txt")-1 && !memcmp(fname, ".phar/alias.txt", sizeof(".phar/alias.txt")-1)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot get alias \".phar/alias.txt\" directly in phar \"%s\", use getAlias", phar_obj->archive->fname); + return; + } + + if (fname_len >= sizeof(".phar")-1 && !memcmp(fname, ".phar", sizeof(".phar")-1)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot directly get any files or directories in magic \".phar\" directory"); + return; + } + + if (entry->is_temp_dir) { + efree(entry->filename); + efree(entry); + } + + sfname = strpprintf(0, "phar://%s/%s", phar_obj->archive->fname, fname); + ZVAL_NEW_STR(&zfname, sfname); + spl_instantiate_arg_ex1(phar_obj->spl.info_class, return_value, &zfname); + zval_ptr_dtor(&zfname); + } +} +/* }}} */ + +/* {{{ add a file within the phar archive from a string or resource + */ +static void phar_add_file(phar_archive_data **pphar, char *filename, int filename_len, char *cont_str, size_t cont_len, zval *zresource) +{ + char *error; + size_t contents_len; + phar_entry_data *data; + php_stream *contents_file; + + if (filename_len >= (int)sizeof(".phar")-1 && !memcmp(filename, ".phar", sizeof(".phar")-1) && (filename[5] == '/' || filename[5] == '\\' || filename[5] == '\0')) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot create any files in magic \".phar\" directory"); + return; + } + + if (!(data = phar_get_or_create_entry_data((*pphar)->fname, (*pphar)->fname_len, filename, filename_len, "w+b", 0, &error, 1))) { + if (error) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Entry %s does not exist and cannot be created: %s", filename, error); + efree(error); + } else { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Entry %s does not exist and cannot be created", filename); + } + return; + } else { + if (error) { + efree(error); + } + + if (!data->internal_file->is_dir) { + if (cont_str) { + contents_len = php_stream_write(data->fp, cont_str, cont_len); + if (contents_len != cont_len) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Entry %s could not be written to", filename); + return; + } + } else { + if (!(php_stream_from_zval_no_verify(contents_file, zresource))) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Entry %s could not be written to", filename); + return; + } + php_stream_copy_to_stream_ex(contents_file, data->fp, PHP_STREAM_COPY_ALL, &contents_len); + } + + data->internal_file->compressed_filesize = data->internal_file->uncompressed_filesize = contents_len; + } + + /* check for copy-on-write */ + if (pphar[0] != data->phar) { + *pphar = data->phar; + } + phar_entry_delref(data); + phar_flush(*pphar, 0, 0, 0, &error); + + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } + } +} +/* }}} */ + +/* {{{ create a directory within the phar archive + */ +static void phar_mkdir(phar_archive_data **pphar, char *dirname, int dirname_len) +{ + char *error; + phar_entry_data *data; + + if (!(data = phar_get_or_create_entry_data((*pphar)->fname, (*pphar)->fname_len, dirname, dirname_len, "w+b", 2, &error, 1))) { + if (error) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Directory %s does not exist and cannot be created: %s", dirname, error); + efree(error); + } else { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Directory %s does not exist and cannot be created", dirname); + } + + return; + } else { + if (error) { + efree(error); + } + + /* check for copy on write */ + if (data->phar != *pphar) { + *pphar = data->phar; + } + phar_entry_delref(data); + phar_flush(*pphar, 0, 0, 0, &error); + + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } + } +} +/* }}} */ + +/* {{{ proto int Phar::offsetSet(string entry, string value) + * set the contents of an internal file to those of an external file + */ +PHP_METHOD(Phar, offsetSet) +{ + char *fname, *cont_str = NULL; + size_t fname_len, cont_len; + zval *zresource; + PHAR_ARCHIVE_OBJECT(); + + if (PHAR_G(readonly) && !phar_obj->archive->is_data) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Write operations disabled by the php.ini setting phar.readonly"); + return; + } + + if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "pr", &fname, &fname_len, &zresource) == FAILURE + && zend_parse_parameters(ZEND_NUM_ARGS(), "ps", &fname, &fname_len, &cont_str, &cont_len) == FAILURE) { + return; + } + if (ZEND_SIZE_T_INT_OVFL(fname_len)) { + RETURN_FALSE; + } + if (fname_len == sizeof(".phar/stub.php")-1 && !memcmp(fname, ".phar/stub.php", sizeof(".phar/stub.php")-1)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot set stub \".phar/stub.php\" directly in phar \"%s\", use setStub", phar_obj->archive->fname); + return; + } + + if (fname_len == sizeof(".phar/alias.txt")-1 && !memcmp(fname, ".phar/alias.txt", sizeof(".phar/alias.txt")-1)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot set alias \".phar/alias.txt\" directly in phar \"%s\", use setAlias", phar_obj->archive->fname); + return; + } + + if (fname_len >= sizeof(".phar")-1 && !memcmp(fname, ".phar", sizeof(".phar")-1)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot set any files or directories in magic \".phar\" directory"); + return; + } + + phar_add_file(&(phar_obj->archive), fname, (int)fname_len, cont_str, cont_len, zresource); +} +/* }}} */ + +/* {{{ proto int Phar::offsetUnset(string entry) + * remove a file from a phar + */ +PHP_METHOD(Phar, offsetUnset) +{ + char *fname, *error; + size_t fname_len; + phar_entry_info *entry; + PHAR_ARCHIVE_OBJECT(); + + if (PHAR_G(readonly) && !phar_obj->archive->is_data) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Write operations disabled by the php.ini setting phar.readonly"); + return; + } + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p", &fname, &fname_len) == FAILURE) { + return; + } + if (ZEND_SIZE_T_INT_OVFL(fname_len)) { + RETURN_FALSE; + } + + if (zend_hash_str_exists(&phar_obj->archive->manifest, fname, (uint) fname_len)) { + if (NULL != (entry = zend_hash_str_find_ptr(&phar_obj->archive->manifest, fname, (uint) fname_len))) { + if (entry->is_deleted) { + /* entry is deleted, but has not been flushed to disk yet */ + return; + } + + if (phar_obj->archive->is_persistent) { + if (FAILURE == phar_copy_on_write(&(phar_obj->archive))) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar_obj->archive->fname); + return; + } + /* re-populate entry after copy on write */ + entry = zend_hash_str_find_ptr(&phar_obj->archive->manifest, fname, (uint) fname_len); + } + entry->is_modified = 0; + entry->is_deleted = 1; + /* we need to "flush" the stream to save the newly deleted file on disk */ + phar_flush(phar_obj->archive, 0, 0, 0, &error); + + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } + + RETURN_TRUE; + } + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto string Phar::addEmptyDir(string dirname) + * Adds an empty directory to the phar archive + */ +PHP_METHOD(Phar, addEmptyDir) +{ + char *dirname; + size_t dirname_len; + + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p", &dirname, &dirname_len) == FAILURE) { + return; + } + if (ZEND_SIZE_T_INT_OVFL(dirname_len)) { + RETURN_FALSE; + } + + if (dirname_len >= sizeof(".phar")-1 && !memcmp(dirname, ".phar", sizeof(".phar")-1)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot create a directory in magic \".phar\" directory"); + return; + } + + phar_mkdir(&phar_obj->archive, dirname, (int)dirname_len); +} +/* }}} */ + +/* {{{ proto string Phar::addFile(string filename[, string localname]) + * Adds a file to the archive using the filename, or the second parameter as the name within the archive + */ +PHP_METHOD(Phar, addFile) +{ + char *fname, *localname = NULL; + size_t fname_len, localname_len = 0; + php_stream *resource; + zval zresource; + + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p|s", &fname, &fname_len, &localname, &localname_len) == FAILURE) { + return; + } + if (ZEND_SIZE_T_INT_OVFL(fname_len)) { + RETURN_FALSE; + } + + if (!strstr(fname, "://") && php_check_open_basedir(fname)) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "phar error: unable to open file \"%s\" to add to phar archive, open_basedir restrictions prevent this", fname); + return; + } + + if (!(resource = php_stream_open_wrapper(fname, "rb", 0, NULL))) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "phar error: unable to open file \"%s\" to add to phar archive", fname); + return; + } + + if (localname) { + fname = localname; + fname_len = localname_len; + } + + php_stream_to_zval(resource, &zresource); + phar_add_file(&(phar_obj->archive), fname, (int)fname_len, NULL, 0, &zresource); + zval_ptr_dtor(&zresource); +} +/* }}} */ + +/* {{{ proto string Phar::addFromString(string localname, string contents) + * Adds a file to the archive using its contents as a string + */ +PHP_METHOD(Phar, addFromString) +{ + char *localname, *cont_str; + size_t localname_len, cont_len; + + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ps", &localname, &localname_len, &cont_str, &cont_len) == FAILURE) { + return; + } + if (ZEND_SIZE_T_INT_OVFL(localname_len)) { + RETURN_FALSE; + } + + phar_add_file(&(phar_obj->archive), localname, (int)localname_len, cont_str, cont_len, NULL); +} +/* }}} */ + +/* {{{ proto string Phar::getStub() + * Returns the stub at the head of a phar archive as a string. + */ +PHP_METHOD(Phar, getStub) +{ + size_t len; + zend_string *buf; + php_stream *fp; + php_stream_filter *filter = NULL; + phar_entry_info *stub; + + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (phar_obj->archive->is_tar || phar_obj->archive->is_zip) { + + if (NULL != (stub = zend_hash_str_find_ptr(&(phar_obj->archive->manifest), ".phar/stub.php", sizeof(".phar/stub.php")-1))) { + if (phar_obj->archive->fp && !phar_obj->archive->is_brandnew && !(stub->flags & PHAR_ENT_COMPRESSION_MASK)) { + fp = phar_obj->archive->fp; + } else { + if (!(fp = php_stream_open_wrapper(phar_obj->archive->fname, "rb", 0, NULL))) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "phar error: unable to open phar \"%s\"", phar_obj->archive->fname); + return; + } + if (stub->flags & PHAR_ENT_COMPRESSION_MASK) { + char *filter_name; + + if ((filter_name = phar_decompress_filter(stub, 0)) != NULL) { + filter = php_stream_filter_create(filter_name, NULL, php_stream_is_persistent(fp)); + } else { + filter = NULL; + } + if (!filter) { + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "phar error: unable to read stub of phar \"%s\" (cannot create %s filter)", phar_obj->archive->fname, phar_decompress_filter(stub, 1)); + return; + } + php_stream_filter_append(&fp->readfilters, filter); + } + } + + if (!fp) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, + "Unable to read stub"); + return; + } + + php_stream_seek(fp, stub->offset_abs, SEEK_SET); + len = stub->uncompressed_filesize; + goto carry_on; + } else { + RETURN_EMPTY_STRING(); + } + } + len = phar_obj->archive->halt_offset; + + if (phar_obj->archive->fp && !phar_obj->archive->is_brandnew) { + fp = phar_obj->archive->fp; + } else { + fp = php_stream_open_wrapper(phar_obj->archive->fname, "rb", 0, NULL); + } + + if (!fp) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, + "Unable to read stub"); + return; + } + + php_stream_rewind(fp); +carry_on: + buf = zend_string_alloc(len, 0); + + if (len != php_stream_read(fp, ZSTR_VAL(buf), len)) { + if (fp != phar_obj->archive->fp) { + php_stream_close(fp); + } + zend_throw_exception_ex(spl_ce_RuntimeException, 0, + "Unable to read stub"); + zend_string_release(buf); + return; + } + + if (filter) { + php_stream_filter_flush(filter, 1); + php_stream_filter_remove(filter, 1); + } + + if (fp != phar_obj->archive->fp) { + php_stream_close(fp); + } + + ZSTR_VAL(buf)[len] = '\0'; + ZSTR_LEN(buf) = len; + RETVAL_STR(buf); +} +/* }}}*/ + +/* {{{ proto int Phar::hasMetaData() + * Returns TRUE if the phar has global metadata, FALSE otherwise. + */ +PHP_METHOD(Phar, hasMetadata) +{ + PHAR_ARCHIVE_OBJECT(); + + RETURN_BOOL(Z_TYPE(phar_obj->archive->metadata) != IS_UNDEF); +} +/* }}} */ + +/* {{{ proto int Phar::getMetaData() + * Returns the global metadata of the phar + */ +PHP_METHOD(Phar, getMetadata) +{ + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (Z_TYPE(phar_obj->archive->metadata) != IS_UNDEF) { + if (phar_obj->archive->is_persistent) { + char *buf = estrndup((char *) Z_PTR(phar_obj->archive->metadata), phar_obj->archive->metadata_len); + /* assume success, we would have failed before */ + phar_parse_metadata(&buf, return_value, phar_obj->archive->metadata_len); + efree(buf); + } else { + ZVAL_COPY(return_value, &phar_obj->archive->metadata); + } + } +} +/* }}} */ + +/* {{{ proto int Phar::setMetaData(mixed $metadata) + * Sets the global metadata of the phar + */ +PHP_METHOD(Phar, setMetadata) +{ + char *error; + zval *metadata; + + PHAR_ARCHIVE_OBJECT(); + + if (PHAR_G(readonly) && !phar_obj->archive->is_data) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Write operations disabled by the php.ini setting phar.readonly"); + return; + } + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &metadata) == FAILURE) { + return; + } + + if (phar_obj->archive->is_persistent && FAILURE == phar_copy_on_write(&(phar_obj->archive))) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar_obj->archive->fname); + return; + } + if (Z_TYPE(phar_obj->archive->metadata) != IS_UNDEF) { + zval_ptr_dtor(&phar_obj->archive->metadata); + ZVAL_UNDEF(&phar_obj->archive->metadata); + } + + ZVAL_COPY(&phar_obj->archive->metadata, metadata); + phar_obj->archive->is_modified = 1; + phar_flush(phar_obj->archive, 0, 0, 0, &error); + + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } +} +/* }}} */ + +/* {{{ proto int Phar::delMetadata() + * Deletes the global metadata of the phar + */ +PHP_METHOD(Phar, delMetadata) +{ + char *error; + + PHAR_ARCHIVE_OBJECT(); + + if (PHAR_G(readonly) && !phar_obj->archive->is_data) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Write operations disabled by the php.ini setting phar.readonly"); + return; + } + + if (Z_TYPE(phar_obj->archive->metadata) != IS_UNDEF) { + zval_ptr_dtor(&phar_obj->archive->metadata); + ZVAL_UNDEF(&phar_obj->archive->metadata); + phar_obj->archive->is_modified = 1; + phar_flush(phar_obj->archive, 0, 0, 0, &error); + + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + RETURN_FALSE; + } else { + RETURN_TRUE; + } + + } else { + RETURN_TRUE; + } +} +/* }}} */ + +static int phar_extract_file(zend_bool overwrite, phar_entry_info *entry, char *dest, int dest_len, char **error) /* {{{ */ +{ + php_stream_statbuf ssb; + size_t len; + php_stream *fp; + char *fullpath; + const char *slash; + mode_t mode; + cwd_state new_state; + char *filename; + size_t filename_len; + + if (entry->is_mounted) { + /* silently ignore mounted entries */ + return SUCCESS; + } + + if (entry->filename_len >= sizeof(".phar")-1 && !memcmp(entry->filename, ".phar", sizeof(".phar")-1)) { + return SUCCESS; + } + /* strip .. from path and restrict it to be under dest directory */ + new_state.cwd = (char*)emalloc(2); + new_state.cwd[0] = DEFAULT_SLASH; + new_state.cwd[1] = '\0'; + new_state.cwd_length = 1; + if (virtual_file_ex(&new_state, entry->filename, NULL, CWD_EXPAND) != 0 || + new_state.cwd_length <= 1) { + if (EINVAL == errno && entry->filename_len > 50) { + char *tmp = estrndup(entry->filename, 50); + spprintf(error, 4096, "Cannot extract \"%s...\" to \"%s...\", extracted filename is too long for filesystem", tmp, dest); + efree(tmp); + } else { + spprintf(error, 4096, "Cannot extract \"%s\", internal error", entry->filename); + } + efree(new_state.cwd); + return FAILURE; + } + filename = new_state.cwd + 1; + filename_len = new_state.cwd_length - 1; +#ifdef PHP_WIN32 + /* unixify the path back, otherwise non zip formats might be broken */ + { + int cnt = filename_len; + + do { + if ('\\' == filename[cnt]) { + filename[cnt] = '/'; + } + } while (cnt-- >= 0); + } +#endif + + len = spprintf(&fullpath, 0, "%s/%s", dest, filename); + + if (len >= MAXPATHLEN) { + char *tmp; + /* truncate for error message */ + fullpath[50] = '\0'; + if (entry->filename_len > 50) { + tmp = estrndup(entry->filename, 50); + spprintf(error, 4096, "Cannot extract \"%s...\" to \"%s...\", extracted filename is too long for filesystem", tmp, fullpath); + efree(tmp); + } else { + spprintf(error, 4096, "Cannot extract \"%s\" to \"%s...\", extracted filename is too long for filesystem", entry->filename, fullpath); + } + efree(fullpath); + efree(new_state.cwd); + return FAILURE; + } + + if (!len) { + spprintf(error, 4096, "Cannot extract \"%s\", internal error", entry->filename); + efree(fullpath); + efree(new_state.cwd); + return FAILURE; + } + + if (php_check_open_basedir(fullpath)) { + spprintf(error, 4096, "Cannot extract \"%s\" to \"%s\", openbasedir/safe mode restrictions in effect", entry->filename, fullpath); + efree(fullpath); + efree(new_state.cwd); + return FAILURE; + } + + /* let see if the path already exists */ + if (!overwrite && SUCCESS == php_stream_stat_path(fullpath, &ssb)) { + spprintf(error, 4096, "Cannot extract \"%s\" to \"%s\", path already exists", entry->filename, fullpath); + efree(fullpath); + efree(new_state.cwd); + return FAILURE; + } + + /* perform dirname */ + slash = zend_memrchr(filename, '/', filename_len); + + if (slash) { + fullpath[dest_len + (slash - filename) + 1] = '\0'; + } else { + fullpath[dest_len] = '\0'; + } + + if (FAILURE == php_stream_stat_path(fullpath, &ssb)) { + if (entry->is_dir) { + if (!php_stream_mkdir(fullpath, entry->flags & PHAR_ENT_PERM_MASK, PHP_STREAM_MKDIR_RECURSIVE, NULL)) { + spprintf(error, 4096, "Cannot extract \"%s\", could not create directory \"%s\"", entry->filename, fullpath); + efree(fullpath); + efree(new_state.cwd); + return FAILURE; + } + } else { + if (!php_stream_mkdir(fullpath, 0777, PHP_STREAM_MKDIR_RECURSIVE, NULL)) { + spprintf(error, 4096, "Cannot extract \"%s\", could not create directory \"%s\"", entry->filename, fullpath); + efree(fullpath); + efree(new_state.cwd); + return FAILURE; + } + } + } + + if (slash) { + fullpath[dest_len + (slash - filename) + 1] = '/'; + } else { + fullpath[dest_len] = '/'; + } + + filename = NULL; + efree(new_state.cwd); + /* it is a standalone directory, job done */ + if (entry->is_dir) { + efree(fullpath); + return SUCCESS; + } + + fp = php_stream_open_wrapper(fullpath, "w+b", REPORT_ERRORS, NULL); + + if (!fp) { + spprintf(error, 4096, "Cannot extract \"%s\", could not open for writing \"%s\"", entry->filename, fullpath); + efree(fullpath); + return FAILURE; + } + + if (!phar_get_efp(entry, 0)) { + if (FAILURE == phar_open_entry_fp(entry, error, 1)) { + if (error) { + spprintf(error, 4096, "Cannot extract \"%s\" to \"%s\", unable to open internal file pointer: %s", entry->filename, fullpath, *error); + } else { + spprintf(error, 4096, "Cannot extract \"%s\" to \"%s\", unable to open internal file pointer", entry->filename, fullpath); + } + efree(fullpath); + php_stream_close(fp); + return FAILURE; + } + } + + if (FAILURE == phar_seek_efp(entry, 0, SEEK_SET, 0, 0)) { + spprintf(error, 4096, "Cannot extract \"%s\" to \"%s\", unable to seek internal file pointer", entry->filename, fullpath); + efree(fullpath); + php_stream_close(fp); + return FAILURE; + } + + if (SUCCESS != php_stream_copy_to_stream_ex(phar_get_efp(entry, 0), fp, entry->uncompressed_filesize, NULL)) { + spprintf(error, 4096, "Cannot extract \"%s\" to \"%s\", copying contents failed", entry->filename, fullpath); + efree(fullpath); + php_stream_close(fp); + return FAILURE; + } + + php_stream_close(fp); + mode = (mode_t) entry->flags & PHAR_ENT_PERM_MASK; + + if (FAILURE == VCWD_CHMOD(fullpath, mode)) { + spprintf(error, 4096, "Cannot extract \"%s\" to \"%s\", setting file permissions failed", entry->filename, fullpath); + efree(fullpath); + return FAILURE; + } + + efree(fullpath); + return SUCCESS; +} +/* }}} */ + +/* {{{ proto bool Phar::extractTo(string pathto[[, mixed files], bool overwrite]) + * Extract one or more file from a phar archive, optionally overwriting existing files + */ +PHP_METHOD(Phar, extractTo) +{ + char *error = NULL; + php_stream *fp; + php_stream_statbuf ssb; + phar_entry_info *entry; + char *pathto, *filename; + size_t pathto_len, filename_len; + int ret, i; + int nelems; + zval *zval_files = NULL; + zend_bool overwrite = 0; + + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p|z!b", &pathto, &pathto_len, &zval_files, &overwrite) == FAILURE) { + return; + } + + fp = php_stream_open_wrapper(phar_obj->archive->fname, "rb", IGNORE_URL|STREAM_MUST_SEEK, NULL); + + if (!fp) { + zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0, + "Invalid argument, %s cannot be found", phar_obj->archive->fname); + return; + } + + php_stream_close(fp); + + if (pathto_len < 1) { + zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0, + "Invalid argument, extraction path must be non-zero length"); + return; + } + + if (pathto_len >= MAXPATHLEN) { + char *tmp = estrndup(pathto, 50); + /* truncate for error message */ + zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0, "Cannot extract to \"%s...\", destination directory is too long for filesystem", tmp); + efree(tmp); + return; + } + + if (php_stream_stat_path(pathto, &ssb) < 0) { + ret = php_stream_mkdir(pathto, 0777, PHP_STREAM_MKDIR_RECURSIVE, NULL); + if (!ret) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, + "Unable to create path \"%s\" for extraction", pathto); + return; + } + } else if (!(ssb.sb.st_mode & S_IFDIR)) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, + "Unable to use path \"%s\" for extraction, it is a file, must be a directory", pathto); + return; + } + + if (zval_files) { + switch (Z_TYPE_P(zval_files)) { + case IS_NULL: + goto all_files; + case IS_STRING: + filename = Z_STRVAL_P(zval_files); + filename_len = Z_STRLEN_P(zval_files); + break; + case IS_ARRAY: + nelems = zend_hash_num_elements(Z_ARRVAL_P(zval_files)); + if (nelems == 0 ) { + RETURN_FALSE; + } + for (i = 0; i < nelems; i++) { + zval *zval_file; + if ((zval_file = zend_hash_index_find(Z_ARRVAL_P(zval_files), i)) != NULL) { + switch (Z_TYPE_P(zval_file)) { + case IS_STRING: + break; + default: + zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0, + "Invalid argument, array of filenames to extract contains non-string value"); + return; + } + if (NULL == (entry = zend_hash_find_ptr(&phar_obj->archive->manifest, Z_STR_P(zval_file)))) { + zend_throw_exception_ex(phar_ce_PharException, 0, + "Phar Error: attempted to extract non-existent file \"%s\" from phar \"%s\"", Z_STRVAL_P(zval_file), phar_obj->archive->fname); + } + if (FAILURE == phar_extract_file(overwrite, entry, pathto, (int)pathto_len, &error)) { + zend_throw_exception_ex(phar_ce_PharException, 0, + "Extraction from phar \"%s\" failed: %s", phar_obj->archive->fname, error); + efree(error); + return; + } + } + } + RETURN_TRUE; + default: + zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0, + "Invalid argument, expected a filename (string) or array of filenames"); + return; + } + + if (NULL == (entry = zend_hash_str_find_ptr(&phar_obj->archive->manifest, filename, filename_len))) { + zend_throw_exception_ex(phar_ce_PharException, 0, + "Phar Error: attempted to extract non-existent file \"%s\" from phar \"%s\"", filename, phar_obj->archive->fname); + return; + } + + if (FAILURE == phar_extract_file(overwrite, entry, pathto, (int)pathto_len, &error)) { + zend_throw_exception_ex(phar_ce_PharException, 0, + "Extraction from phar \"%s\" failed: %s", phar_obj->archive->fname, error); + efree(error); + return; + } + } else { + phar_archive_data *phar; +all_files: + phar = phar_obj->archive; + /* Extract all files */ + if (!zend_hash_num_elements(&(phar->manifest))) { + RETURN_TRUE; + } + + ZEND_HASH_FOREACH_PTR(&phar->manifest, entry) { + if (FAILURE == phar_extract_file(overwrite, entry, pathto, (int)pathto_len, &error)) { + zend_throw_exception_ex(phar_ce_PharException, 0, + "Extraction from phar \"%s\" failed: %s", phar->fname, error); + efree(error); + return; + } + } ZEND_HASH_FOREACH_END(); + } + RETURN_TRUE; +} +/* }}} */ + + +/* {{{ proto void PharFileInfo::__construct(string entry) + * Construct a Phar entry object + */ +PHP_METHOD(PharFileInfo, __construct) +{ + char *fname, *arch, *entry, *error; + size_t fname_len; + int arch_len, entry_len; + phar_entry_object *entry_obj; + phar_entry_info *entry_info; + phar_archive_data *phar_data; + zval *zobj = getThis(), arg1; + + if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "p", &fname, &fname_len) == FAILURE) { + return; + } + + entry_obj = (phar_entry_object*)((char*)Z_OBJ_P(zobj) - Z_OBJ_P(zobj)->handlers->offset); + + if (entry_obj->entry) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot call constructor twice"); + return; + } + + if (fname_len < 7 || memcmp(fname, "phar://", 7) || phar_split_fname(fname, (int)fname_len, &arch, &arch_len, &entry, &entry_len, 2, 0) == FAILURE) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, + "'%s' is not a valid phar archive URL (must have at least phar://filename.phar)", fname); + return; + } + + if (phar_open_from_filename(arch, arch_len, NULL, 0, REPORT_ERRORS, &phar_data, &error) == FAILURE) { + efree(arch); + efree(entry); + if (error) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, + "Cannot open phar file '%s': %s", fname, error); + efree(error); + } else { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, + "Cannot open phar file '%s'", fname); + } + return; + } + + if ((entry_info = phar_get_entry_info_dir(phar_data, entry, entry_len, 1, &error, 1)) == NULL) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, + "Cannot access phar file entry '%s' in archive '%s'%s%s", entry, arch, error ? ", " : "", error ? error : ""); + efree(arch); + efree(entry); + return; + } + + efree(arch); + efree(entry); + + entry_obj->entry = entry_info; + + ZVAL_STRINGL(&arg1, fname, fname_len); + + zend_call_method_with_1_params(zobj, Z_OBJCE_P(zobj), + &spl_ce_SplFileInfo->constructor, "__construct", NULL, &arg1); + + zval_ptr_dtor(&arg1); +} +/* }}} */ + +#define PHAR_ENTRY_OBJECT() \ + zval *zobj = getThis(); \ + phar_entry_object *entry_obj = (phar_entry_object*)((char*)Z_OBJ_P(zobj) - Z_OBJ_P(zobj)->handlers->offset); \ + if (!entry_obj->entry) { \ + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, \ + "Cannot call method on an uninitialized PharFileInfo object"); \ + return; \ + } + +/* {{{ proto void PharFileInfo::__destruct() + * clean up directory-based entry objects + */ +PHP_METHOD(PharFileInfo, __destruct) +{ + zval *zobj = getThis(); + phar_entry_object *entry_obj = (phar_entry_object*)((char*)Z_OBJ_P(zobj) - Z_OBJ_P(zobj)->handlers->offset); + + if (entry_obj->entry && entry_obj->entry->is_temp_dir) { + if (entry_obj->entry->filename) { + efree(entry_obj->entry->filename); + entry_obj->entry->filename = NULL; + } + + efree(entry_obj->entry); + entry_obj->entry = NULL; + } +} +/* }}} */ + +/* {{{ proto int PharFileInfo::getCompressedSize() + * Returns the compressed size + */ +PHP_METHOD(PharFileInfo, getCompressedSize) +{ + PHAR_ENTRY_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + RETURN_LONG(entry_obj->entry->compressed_filesize); +} +/* }}} */ + +/* {{{ proto bool PharFileInfo::isCompressed([int compression_type]) + * Returns whether the entry is compressed, and whether it is compressed with Phar::GZ or Phar::BZ2 if specified + */ +PHP_METHOD(PharFileInfo, isCompressed) +{ + /* a number that is not Phar::GZ or Phar::BZ2 */ + zend_long method = 9021976; + PHAR_ENTRY_OBJECT(); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &method) == FAILURE) { + return; + } + + switch (method) { + case 9021976: + RETURN_BOOL(entry_obj->entry->flags & PHAR_ENT_COMPRESSION_MASK); + case PHAR_ENT_COMPRESSED_GZ: + RETURN_BOOL(entry_obj->entry->flags & PHAR_ENT_COMPRESSED_GZ); + case PHAR_ENT_COMPRESSED_BZ2: + RETURN_BOOL(entry_obj->entry->flags & PHAR_ENT_COMPRESSED_BZ2); + default: + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, \ + "Unknown compression type specified"); \ + } +} +/* }}} */ + +/* {{{ proto int PharFileInfo::getCRC32() + * Returns CRC32 code or throws an exception if not CRC checked + */ +PHP_METHOD(PharFileInfo, getCRC32) +{ + PHAR_ENTRY_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (entry_obj->entry->is_dir) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, \ + "Phar entry is a directory, does not have a CRC"); \ + return; + } + + if (entry_obj->entry->is_crc_checked) { + RETURN_LONG(entry_obj->entry->crc32); + } else { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, \ + "Phar entry was not CRC checked"); \ + } +} +/* }}} */ + +/* {{{ proto int PharFileInfo::isCRCChecked() + * Returns whether file entry is CRC checked + */ +PHP_METHOD(PharFileInfo, isCRCChecked) +{ + PHAR_ENTRY_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + RETURN_BOOL(entry_obj->entry->is_crc_checked); +} +/* }}} */ + +/* {{{ proto int PharFileInfo::getPharFlags() + * Returns the Phar file entry flags + */ +PHP_METHOD(PharFileInfo, getPharFlags) +{ + PHAR_ENTRY_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + RETURN_LONG(entry_obj->entry->flags & ~(PHAR_ENT_PERM_MASK|PHAR_ENT_COMPRESSION_MASK)); +} +/* }}} */ + +/* {{{ proto int PharFileInfo::chmod() + * set the file permissions for the Phar. This only allows setting execution bit, read/write + */ +PHP_METHOD(PharFileInfo, chmod) +{ + char *error; + zend_long perms; + PHAR_ENTRY_OBJECT(); + + if (entry_obj->entry->is_temp_dir) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, \ + "Phar entry \"%s\" is a temporary directory (not an actual entry in the archive), cannot chmod", entry_obj->entry->filename); \ + return; + } + + if (PHAR_G(readonly) && !entry_obj->entry->phar->is_data) { + zend_throw_exception_ex(phar_ce_PharException, 0, "Cannot modify permissions for file \"%s\" in phar \"%s\", write operations are prohibited", entry_obj->entry->filename, entry_obj->entry->phar->fname); + return; + } + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &perms) == FAILURE) { + return; + } + + if (entry_obj->entry->is_persistent) { + phar_archive_data *phar = entry_obj->entry->phar; + + if (FAILURE == phar_copy_on_write(&phar)) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar->fname); + return; + } + /* re-populate after copy-on-write */ + entry_obj->entry = zend_hash_str_find_ptr(&phar->manifest, entry_obj->entry->filename, entry_obj->entry->filename_len); + } + /* clear permissions */ + entry_obj->entry->flags &= ~PHAR_ENT_PERM_MASK; + perms &= 0777; + entry_obj->entry->flags |= perms; + entry_obj->entry->old_flags = entry_obj->entry->flags; + entry_obj->entry->phar->is_modified = 1; + entry_obj->entry->is_modified = 1; + + /* hackish cache in php_stat needs to be cleared */ + /* if this code fails to work, check main/streams/streams.c, _php_stream_stat_path */ + if (BG(CurrentLStatFile)) { + efree(BG(CurrentLStatFile)); + } + + if (BG(CurrentStatFile)) { + efree(BG(CurrentStatFile)); + } + + BG(CurrentLStatFile) = NULL; + BG(CurrentStatFile) = NULL; + phar_flush(entry_obj->entry->phar, 0, 0, 0, &error); + + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } +} +/* }}} */ + +/* {{{ proto int PharFileInfo::hasMetaData() + * Returns the metadata of the entry + */ +PHP_METHOD(PharFileInfo, hasMetadata) +{ + PHAR_ENTRY_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + RETURN_BOOL(Z_TYPE(entry_obj->entry->metadata) != IS_UNDEF); +} +/* }}} */ + +/* {{{ proto int PharFileInfo::getMetaData() + * Returns the metadata of the entry + */ +PHP_METHOD(PharFileInfo, getMetadata) +{ + PHAR_ENTRY_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (Z_TYPE(entry_obj->entry->metadata) != IS_UNDEF) { + if (entry_obj->entry->is_persistent) { + char *buf = estrndup((char *) Z_PTR(entry_obj->entry->metadata), entry_obj->entry->metadata_len); + /* assume success, we would have failed before */ + phar_parse_metadata(&buf, return_value, entry_obj->entry->metadata_len); + efree(buf); + } else { + ZVAL_COPY(return_value, &entry_obj->entry->metadata); + } + } +} +/* }}} */ + +/* {{{ proto int PharFileInfo::setMetaData(mixed $metadata) + * Sets the metadata of the entry + */ +PHP_METHOD(PharFileInfo, setMetadata) +{ + char *error; + zval *metadata; + + PHAR_ENTRY_OBJECT(); + + if (PHAR_G(readonly) && !entry_obj->entry->phar->is_data) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Write operations disabled by the php.ini setting phar.readonly"); + return; + } + + if (entry_obj->entry->is_temp_dir) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, \ + "Phar entry is a temporary directory (not an actual entry in the archive), cannot set metadata"); \ + return; + } + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &metadata) == FAILURE) { + return; + } + + if (entry_obj->entry->is_persistent) { + phar_archive_data *phar = entry_obj->entry->phar; + + if (FAILURE == phar_copy_on_write(&phar)) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar->fname); + return; + } + /* re-populate after copy-on-write */ + entry_obj->entry = zend_hash_str_find_ptr(&phar->manifest, entry_obj->entry->filename, entry_obj->entry->filename_len); + } + if (Z_TYPE(entry_obj->entry->metadata) != IS_UNDEF) { + zval_ptr_dtor(&entry_obj->entry->metadata); + ZVAL_UNDEF(&entry_obj->entry->metadata); + } + + ZVAL_COPY(&entry_obj->entry->metadata, metadata); + + entry_obj->entry->is_modified = 1; + entry_obj->entry->phar->is_modified = 1; + phar_flush(entry_obj->entry->phar, 0, 0, 0, &error); + + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } +} +/* }}} */ + +/* {{{ proto bool PharFileInfo::delMetaData() + * Deletes the metadata of the entry + */ +PHP_METHOD(PharFileInfo, delMetadata) +{ + char *error; + + PHAR_ENTRY_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (PHAR_G(readonly) && !entry_obj->entry->phar->is_data) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Write operations disabled by the php.ini setting phar.readonly"); + return; + } + + if (entry_obj->entry->is_temp_dir) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, \ + "Phar entry is a temporary directory (not an actual entry in the archive), cannot delete metadata"); \ + return; + } + + if (Z_TYPE(entry_obj->entry->metadata) != IS_UNDEF) { + if (entry_obj->entry->is_persistent) { + phar_archive_data *phar = entry_obj->entry->phar; + + if (FAILURE == phar_copy_on_write(&phar)) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar->fname); + return; + } + /* re-populate after copy-on-write */ + entry_obj->entry = zend_hash_str_find_ptr(&phar->manifest, entry_obj->entry->filename, entry_obj->entry->filename_len); + } + zval_ptr_dtor(&entry_obj->entry->metadata); + ZVAL_UNDEF(&entry_obj->entry->metadata); + entry_obj->entry->is_modified = 1; + entry_obj->entry->phar->is_modified = 1; + + phar_flush(entry_obj->entry->phar, 0, 0, 0, &error); + + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + RETURN_FALSE; + } else { + RETURN_TRUE; + } + + } else { + RETURN_TRUE; + } +} +/* }}} */ + +/* {{{ proto string PharFileInfo::getContent() + * return the complete file contents of the entry (like file_get_contents) + */ +PHP_METHOD(PharFileInfo, getContent) +{ + char *error; + php_stream *fp; + phar_entry_info *link; + zend_string *str; + + PHAR_ENTRY_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (entry_obj->entry->is_dir) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Phar error: Cannot retrieve contents, \"%s\" in phar \"%s\" is a directory", entry_obj->entry->filename, entry_obj->entry->phar->fname); + return; + } + + link = phar_get_link_source(entry_obj->entry); + + if (!link) { + link = entry_obj->entry; + } + + if (SUCCESS != phar_open_entry_fp(link, &error, 0)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Phar error: Cannot retrieve contents, \"%s\" in phar \"%s\": %s", entry_obj->entry->filename, entry_obj->entry->phar->fname, error); + efree(error); + return; + } + + if (!(fp = phar_get_efp(link, 0))) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Phar error: Cannot retrieve contents of \"%s\" in phar \"%s\"", entry_obj->entry->filename, entry_obj->entry->phar->fname); + return; + } + + phar_seek_efp(link, 0, SEEK_SET, 0, 0); + str = php_stream_copy_to_mem(fp, link->uncompressed_filesize, 0); + if (str) { + RETURN_STR(str); + } else { + RETURN_EMPTY_STRING(); + } +} +/* }}} */ + +/* {{{ proto int PharFileInfo::compress(int compression_type) + * Instructs the Phar class to compress the current file using zlib or bzip2 compression + */ +PHP_METHOD(PharFileInfo, compress) +{ + zend_long method; + char *error; + PHAR_ENTRY_OBJECT(); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &method) == FAILURE) { + return; + } + + if (entry_obj->entry->is_tar) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress with Gzip compression, not possible with tar-based phar archives"); + return; + } + + if (entry_obj->entry->is_dir) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, \ + "Phar entry is a directory, cannot set compression"); \ + return; + } + + if (PHAR_G(readonly) && !entry_obj->entry->phar->is_data) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Phar is readonly, cannot change compression"); + return; + } + + if (entry_obj->entry->is_deleted) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress deleted file"); + return; + } + + if (entry_obj->entry->is_persistent) { + phar_archive_data *phar = entry_obj->entry->phar; + + if (FAILURE == phar_copy_on_write(&phar)) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar->fname); + return; + } + /* re-populate after copy-on-write */ + entry_obj->entry = zend_hash_str_find_ptr(&phar->manifest, entry_obj->entry->filename, entry_obj->entry->filename_len); + } + switch (method) { + case PHAR_ENT_COMPRESSED_GZ: + if (entry_obj->entry->flags & PHAR_ENT_COMPRESSED_GZ) { + RETURN_TRUE; + } + + if ((entry_obj->entry->flags & PHAR_ENT_COMPRESSED_BZ2) != 0) { + if (!PHAR_G(has_bz2)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress with gzip compression, file is already compressed with bzip2 compression and bz2 extension is not enabled, cannot decompress"); + return; + } + + /* decompress this file indirectly */ + if (SUCCESS != phar_open_entry_fp(entry_obj->entry, &error, 1)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Phar error: Cannot decompress bzip2-compressed file \"%s\" in phar \"%s\" in order to compress with gzip: %s", entry_obj->entry->filename, entry_obj->entry->phar->fname, error); + efree(error); + return; + } + } + + if (!PHAR_G(has_zlib)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress with gzip compression, zlib extension is not enabled"); + return; + } + + entry_obj->entry->old_flags = entry_obj->entry->flags; + entry_obj->entry->flags &= ~PHAR_ENT_COMPRESSION_MASK; + entry_obj->entry->flags |= PHAR_ENT_COMPRESSED_GZ; + break; + case PHAR_ENT_COMPRESSED_BZ2: + if (entry_obj->entry->flags & PHAR_ENT_COMPRESSED_BZ2) { + RETURN_TRUE; + } + + if ((entry_obj->entry->flags & PHAR_ENT_COMPRESSED_GZ) != 0) { + if (!PHAR_G(has_zlib)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress with bzip2 compression, file is already compressed with gzip compression and zlib extension is not enabled, cannot decompress"); + return; + } + + /* decompress this file indirectly */ + if (SUCCESS != phar_open_entry_fp(entry_obj->entry, &error, 1)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Phar error: Cannot decompress gzip-compressed file \"%s\" in phar \"%s\" in order to compress with bzip2: %s", entry_obj->entry->filename, entry_obj->entry->phar->fname, error); + efree(error); + return; + } + } + + if (!PHAR_G(has_bz2)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress with bzip2 compression, bz2 extension is not enabled"); + return; + } + entry_obj->entry->old_flags = entry_obj->entry->flags; + entry_obj->entry->flags &= ~PHAR_ENT_COMPRESSION_MASK; + entry_obj->entry->flags |= PHAR_ENT_COMPRESSED_BZ2; + break; + default: + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, \ + "Unknown compression type specified"); \ + } + + entry_obj->entry->phar->is_modified = 1; + entry_obj->entry->is_modified = 1; + phar_flush(entry_obj->entry->phar, 0, 0, 0, &error); + + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto int PharFileInfo::decompress() + * Instructs the Phar class to decompress the current file + */ +PHP_METHOD(PharFileInfo, decompress) +{ + char *error; + PHAR_ENTRY_OBJECT(); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (entry_obj->entry->is_dir) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, \ + "Phar entry is a directory, cannot set compression"); \ + return; + } + + if ((entry_obj->entry->flags & PHAR_ENT_COMPRESSION_MASK) == 0) { + RETURN_TRUE; + } + + if (PHAR_G(readonly) && !entry_obj->entry->phar->is_data) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Phar is readonly, cannot decompress"); + return; + } + + if (entry_obj->entry->is_deleted) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot compress deleted file"); + return; + } + + if ((entry_obj->entry->flags & PHAR_ENT_COMPRESSED_GZ) != 0 && !PHAR_G(has_zlib)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot decompress Gzip-compressed file, zlib extension is not enabled"); + return; + } + + if ((entry_obj->entry->flags & PHAR_ENT_COMPRESSED_BZ2) != 0 && !PHAR_G(has_bz2)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, + "Cannot decompress Bzip2-compressed file, bz2 extension is not enabled"); + return; + } + + if (entry_obj->entry->is_persistent) { + phar_archive_data *phar = entry_obj->entry->phar; + + if (FAILURE == phar_copy_on_write(&phar)) { + zend_throw_exception_ex(phar_ce_PharException, 0, "phar \"%s\" is persistent, unable to copy on write", phar->fname); + return; + } + /* re-populate after copy-on-write */ + entry_obj->entry = zend_hash_str_find_ptr(&phar->manifest, entry_obj->entry->filename, entry_obj->entry->filename_len); + } + if (!entry_obj->entry->fp) { + if (FAILURE == phar_open_archive_fp(entry_obj->entry->phar)) { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot decompress entry \"%s\", phar error: Cannot open phar archive \"%s\" for reading", entry_obj->entry->filename, entry_obj->entry->phar->fname); + return; + } + entry_obj->entry->fp_type = PHAR_FP; + } + + entry_obj->entry->old_flags = entry_obj->entry->flags; + entry_obj->entry->flags &= ~PHAR_ENT_COMPRESSION_MASK; + entry_obj->entry->phar->is_modified = 1; + entry_obj->entry->is_modified = 1; + phar_flush(entry_obj->entry->phar, 0, 0, 0, &error); + + if (error) { + zend_throw_exception_ex(phar_ce_PharException, 0, "%s", error); + efree(error); + } + RETURN_TRUE; +} +/* }}} */ + +/* {{{ phar methods */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar___construct, 0, 0, 1) + ZEND_ARG_INFO(0, filename) + ZEND_ARG_INFO(0, flags) + ZEND_ARG_INFO(0, alias) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_createDS, 0, 0, 0) + ZEND_ARG_INFO(0, index) + ZEND_ARG_INFO(0, webindex) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_cancompress, 0, 0, 0) + ZEND_ARG_INFO(0, method) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_isvalidpharfilename, 0, 0, 1) + ZEND_ARG_INFO(0, filename) + ZEND_ARG_INFO(0, executable) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_loadPhar, 0, 0, 1) + ZEND_ARG_INFO(0, filename) + ZEND_ARG_INFO(0, alias) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_mapPhar, 0, 0, 0) + ZEND_ARG_INFO(0, alias) + ZEND_ARG_INFO(0, offset) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_mount, 0, 0, 2) + ZEND_ARG_INFO(0, inphar) + ZEND_ARG_INFO(0, externalfile) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_mungServer, 0, 0, 1) + ZEND_ARG_INFO(0, munglist) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_webPhar, 0, 0, 0) + ZEND_ARG_INFO(0, alias) + ZEND_ARG_INFO(0, index) + ZEND_ARG_INFO(0, f404) + ZEND_ARG_INFO(0, mimetypes) + ZEND_ARG_INFO(0, rewrites) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_running, 0, 0, 0) + ZEND_ARG_INFO(0, retphar) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_ua, 0, 0, 1) + ZEND_ARG_INFO(0, archive) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_build, 0, 0, 1) + ZEND_ARG_INFO(0, iterator) + ZEND_ARG_INFO(0, base_directory) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_conv, 0, 0, 0) + ZEND_ARG_INFO(0, format) + ZEND_ARG_INFO(0, compression_type) + ZEND_ARG_INFO(0, file_ext) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_comps, 0, 0, 1) + ZEND_ARG_INFO(0, compression_type) + ZEND_ARG_INFO(0, file_ext) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_decomp, 0, 0, 0) + ZEND_ARG_INFO(0, file_ext) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_comp, 0, 0, 1) + ZEND_ARG_INFO(0, compression_type) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_compo, 0, 0, 0) + ZEND_ARG_INFO(0, compression_type) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_copy, 0, 0, 2) + ZEND_ARG_INFO(0, newfile) + ZEND_ARG_INFO(0, oldfile) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_delete, 0, 0, 1) + ZEND_ARG_INFO(0, entry) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_fromdir, 0, 0, 1) + ZEND_ARG_INFO(0, base_dir) + ZEND_ARG_INFO(0, regex) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_offsetExists, 0, 0, 1) + ZEND_ARG_INFO(0, entry) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_offsetSet, 0, 0, 2) + ZEND_ARG_INFO(0, entry) + ZEND_ARG_INFO(0, value) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_setAlias, 0, 0, 1) + ZEND_ARG_INFO(0, alias) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_setMetadata, 0, 0, 1) + ZEND_ARG_INFO(0, metadata) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_setSigAlgo, 0, 0, 1) + ZEND_ARG_INFO(0, algorithm) + ZEND_ARG_INFO(0, privatekey) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_setStub, 0, 0, 1) + ZEND_ARG_INFO(0, newstub) + ZEND_ARG_INFO(0, maxlen) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_emptydir, 0, 0, 0) + ZEND_ARG_INFO(0, dirname) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_extract, 0, 0, 1) + ZEND_ARG_INFO(0, pathto) + ZEND_ARG_INFO(0, files) + ZEND_ARG_INFO(0, overwrite) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_addfile, 0, 0, 1) + ZEND_ARG_INFO(0, filename) + ZEND_ARG_INFO(0, localname) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_fromstring, 0, 0, 1) + ZEND_ARG_INFO(0, localname) + ZEND_ARG_INFO(0, contents) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_phar_isff, 0, 0, 1) + ZEND_ARG_INFO(0, fileformat) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_phar__void, 0) +ZEND_END_ARG_INFO() + + +zend_function_entry php_archive_methods[] = { + PHP_ME(Phar, __construct, arginfo_phar___construct, ZEND_ACC_PUBLIC) + PHP_ME(Phar, __destruct, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, addEmptyDir, arginfo_phar_emptydir, ZEND_ACC_PUBLIC) + PHP_ME(Phar, addFile, arginfo_phar_addfile, ZEND_ACC_PUBLIC) + PHP_ME(Phar, addFromString, arginfo_phar_fromstring, ZEND_ACC_PUBLIC) + PHP_ME(Phar, buildFromDirectory, arginfo_phar_fromdir, ZEND_ACC_PUBLIC) + PHP_ME(Phar, buildFromIterator, arginfo_phar_build, ZEND_ACC_PUBLIC) + PHP_ME(Phar, compressFiles, arginfo_phar_comp, ZEND_ACC_PUBLIC) + PHP_ME(Phar, decompressFiles, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, compress, arginfo_phar_comps, ZEND_ACC_PUBLIC) + PHP_ME(Phar, decompress, arginfo_phar_decomp, ZEND_ACC_PUBLIC) + PHP_ME(Phar, convertToExecutable, arginfo_phar_conv, ZEND_ACC_PUBLIC) + PHP_ME(Phar, convertToData, arginfo_phar_conv, ZEND_ACC_PUBLIC) + PHP_ME(Phar, copy, arginfo_phar_copy, ZEND_ACC_PUBLIC) + PHP_ME(Phar, count, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, delete, arginfo_phar_delete, ZEND_ACC_PUBLIC) + PHP_ME(Phar, delMetadata, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, extractTo, arginfo_phar_extract, ZEND_ACC_PUBLIC) + PHP_ME(Phar, getAlias, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, getPath, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, getMetadata, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, getModified, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, getSignature, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, getStub, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, getVersion, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, hasMetadata, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, isBuffering, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, isCompressed, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, isFileFormat, arginfo_phar_isff, ZEND_ACC_PUBLIC) + PHP_ME(Phar, isWritable, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, offsetExists, arginfo_phar_offsetExists, ZEND_ACC_PUBLIC) + PHP_ME(Phar, offsetGet, arginfo_phar_offsetExists, ZEND_ACC_PUBLIC) + PHP_ME(Phar, offsetSet, arginfo_phar_offsetSet, ZEND_ACC_PUBLIC) + PHP_ME(Phar, offsetUnset, arginfo_phar_offsetExists, ZEND_ACC_PUBLIC) + PHP_ME(Phar, setAlias, arginfo_phar_setAlias, ZEND_ACC_PUBLIC) + PHP_ME(Phar, setDefaultStub, arginfo_phar_createDS, ZEND_ACC_PUBLIC) + PHP_ME(Phar, setMetadata, arginfo_phar_setMetadata, ZEND_ACC_PUBLIC) + PHP_ME(Phar, setSignatureAlgorithm, arginfo_phar_setSigAlgo, ZEND_ACC_PUBLIC) + PHP_ME(Phar, setStub, arginfo_phar_setStub, ZEND_ACC_PUBLIC) + PHP_ME(Phar, startBuffering, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, stopBuffering, arginfo_phar__void, ZEND_ACC_PUBLIC) + /* static member functions */ + PHP_ME(Phar, apiVersion, arginfo_phar__void, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, canCompress, arginfo_phar_cancompress, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, canWrite, arginfo_phar__void, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, createDefaultStub, arginfo_phar_createDS, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, getSupportedCompression,arginfo_phar__void, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, getSupportedSignatures,arginfo_phar__void, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, interceptFileFuncs, arginfo_phar__void, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, isValidPharFilename, arginfo_phar_isvalidpharfilename, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, loadPhar, arginfo_phar_loadPhar, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, mapPhar, arginfo_phar_mapPhar, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, running, arginfo_phar_running, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, mount, arginfo_phar_mount, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, mungServer, arginfo_phar_mungServer, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, unlinkArchive, arginfo_phar_ua, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, webPhar, arginfo_phar_webPhar, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_FE_END +}; + + +ZEND_BEGIN_ARG_INFO_EX(arginfo_data___construct, 0, 0, 1) + ZEND_ARG_INFO(0, filename) + ZEND_ARG_INFO(0, flags) + ZEND_ARG_INFO(0, alias) + ZEND_ARG_INFO(0, fileformat) +ZEND_END_ARG_INFO() + +zend_function_entry php_data_methods[] = { + PHP_ME(Phar, __construct, arginfo_data___construct, ZEND_ACC_PUBLIC) + PHP_ME(Phar, __destruct, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, addEmptyDir, arginfo_phar_emptydir, ZEND_ACC_PUBLIC) + PHP_ME(Phar, addFile, arginfo_phar_addfile, ZEND_ACC_PUBLIC) + PHP_ME(Phar, addFromString, arginfo_phar_fromstring, ZEND_ACC_PUBLIC) + PHP_ME(Phar, buildFromDirectory, arginfo_phar_fromdir, ZEND_ACC_PUBLIC) + PHP_ME(Phar, buildFromIterator, arginfo_phar_build, ZEND_ACC_PUBLIC) + PHP_ME(Phar, compressFiles, arginfo_phar_comp, ZEND_ACC_PUBLIC) + PHP_ME(Phar, decompressFiles, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, compress, arginfo_phar_comps, ZEND_ACC_PUBLIC) + PHP_ME(Phar, decompress, arginfo_phar_decomp, ZEND_ACC_PUBLIC) + PHP_ME(Phar, convertToExecutable, arginfo_phar_conv, ZEND_ACC_PUBLIC) + PHP_ME(Phar, convertToData, arginfo_phar_conv, ZEND_ACC_PUBLIC) + PHP_ME(Phar, copy, arginfo_phar_copy, ZEND_ACC_PUBLIC) + PHP_ME(Phar, count, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, delete, arginfo_phar_delete, ZEND_ACC_PUBLIC) + PHP_ME(Phar, delMetadata, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, extractTo, arginfo_phar_extract, ZEND_ACC_PUBLIC) + PHP_ME(Phar, getAlias, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, getPath, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, getMetadata, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, getModified, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, getSignature, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, getStub, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, getVersion, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, hasMetadata, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, isBuffering, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, isCompressed, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, isFileFormat, arginfo_phar_isff, ZEND_ACC_PUBLIC) + PHP_ME(Phar, isWritable, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, offsetExists, arginfo_phar_offsetExists, ZEND_ACC_PUBLIC) + PHP_ME(Phar, offsetGet, arginfo_phar_offsetExists, ZEND_ACC_PUBLIC) + PHP_ME(Phar, offsetSet, arginfo_phar_offsetSet, ZEND_ACC_PUBLIC) + PHP_ME(Phar, offsetUnset, arginfo_phar_offsetExists, ZEND_ACC_PUBLIC) + PHP_ME(Phar, setAlias, arginfo_phar_setAlias, ZEND_ACC_PUBLIC) + PHP_ME(Phar, setDefaultStub, arginfo_phar_createDS, ZEND_ACC_PUBLIC) + PHP_ME(Phar, setMetadata, arginfo_phar_setMetadata, ZEND_ACC_PUBLIC) + PHP_ME(Phar, setSignatureAlgorithm, arginfo_phar_setSigAlgo, ZEND_ACC_PUBLIC) + PHP_ME(Phar, setStub, arginfo_phar_setStub, ZEND_ACC_PUBLIC) + PHP_ME(Phar, startBuffering, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(Phar, stopBuffering, arginfo_phar__void, ZEND_ACC_PUBLIC) + /* static member functions */ + PHP_ME(Phar, apiVersion, arginfo_phar__void, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, canCompress, arginfo_phar_cancompress, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, canWrite, arginfo_phar__void, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, createDefaultStub, arginfo_phar_createDS, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, getSupportedCompression,arginfo_phar__void, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, getSupportedSignatures,arginfo_phar__void, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, interceptFileFuncs, arginfo_phar__void, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, isValidPharFilename, arginfo_phar_isvalidpharfilename, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, loadPhar, arginfo_phar_loadPhar, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, mapPhar, arginfo_phar_mapPhar, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, running, arginfo_phar_running, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, mount, arginfo_phar_mount, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, mungServer, arginfo_phar_mungServer, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, unlinkArchive, arginfo_phar_ua, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_ME(Phar, webPhar, arginfo_phar_webPhar, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_FINAL) + PHP_FE_END +}; + +ZEND_BEGIN_ARG_INFO_EX(arginfo_entry___construct, 0, 0, 1) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_entry_chmod, 0, 0, 1) + ZEND_ARG_INFO(0, perms) +ZEND_END_ARG_INFO() + +zend_function_entry php_entry_methods[] = { + PHP_ME(PharFileInfo, __construct, arginfo_entry___construct, ZEND_ACC_PUBLIC) + PHP_ME(PharFileInfo, __destruct, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(PharFileInfo, chmod, arginfo_entry_chmod, ZEND_ACC_PUBLIC) + PHP_ME(PharFileInfo, compress, arginfo_phar_comp, ZEND_ACC_PUBLIC) + PHP_ME(PharFileInfo, decompress, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(PharFileInfo, delMetadata, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(PharFileInfo, getCompressedSize, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(PharFileInfo, getCRC32, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(PharFileInfo, getContent, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(PharFileInfo, getMetadata, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(PharFileInfo, getPharFlags, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(PharFileInfo, hasMetadata, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(PharFileInfo, isCompressed, arginfo_phar_compo, ZEND_ACC_PUBLIC) + PHP_ME(PharFileInfo, isCRCChecked, arginfo_phar__void, ZEND_ACC_PUBLIC) + PHP_ME(PharFileInfo, setMetadata, arginfo_phar_setMetadata, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + +zend_function_entry phar_exception_methods[] = { + PHP_FE_END +}; +/* }}} */ + +#define REGISTER_PHAR_CLASS_CONST_LONG(class_name, const_name, value) \ + zend_declare_class_constant_long(class_name, const_name, sizeof(const_name)-1, (zend_long)value); + +void phar_object_init(void) /* {{{ */ +{ + zend_class_entry ce; + + INIT_CLASS_ENTRY(ce, "PharException", phar_exception_methods); + phar_ce_PharException = zend_register_internal_class_ex(&ce, zend_ce_exception); + + INIT_CLASS_ENTRY(ce, "Phar", php_archive_methods); + phar_ce_archive = zend_register_internal_class_ex(&ce, spl_ce_RecursiveDirectoryIterator); + + zend_class_implements(phar_ce_archive, 2, spl_ce_Countable, zend_ce_arrayaccess); + + INIT_CLASS_ENTRY(ce, "PharData", php_data_methods); + phar_ce_data = zend_register_internal_class_ex(&ce, spl_ce_RecursiveDirectoryIterator); + + zend_class_implements(phar_ce_data, 2, spl_ce_Countable, zend_ce_arrayaccess); + + INIT_CLASS_ENTRY(ce, "PharFileInfo", php_entry_methods); + phar_ce_entry = zend_register_internal_class_ex(&ce, spl_ce_SplFileInfo); + + REGISTER_PHAR_CLASS_CONST_LONG(phar_ce_archive, "BZ2", PHAR_ENT_COMPRESSED_BZ2) + REGISTER_PHAR_CLASS_CONST_LONG(phar_ce_archive, "GZ", PHAR_ENT_COMPRESSED_GZ) + REGISTER_PHAR_CLASS_CONST_LONG(phar_ce_archive, "NONE", PHAR_ENT_COMPRESSED_NONE) + REGISTER_PHAR_CLASS_CONST_LONG(phar_ce_archive, "PHAR", PHAR_FORMAT_PHAR) + REGISTER_PHAR_CLASS_CONST_LONG(phar_ce_archive, "TAR", PHAR_FORMAT_TAR) + REGISTER_PHAR_CLASS_CONST_LONG(phar_ce_archive, "ZIP", PHAR_FORMAT_ZIP) + REGISTER_PHAR_CLASS_CONST_LONG(phar_ce_archive, "COMPRESSED", PHAR_ENT_COMPRESSION_MASK) + REGISTER_PHAR_CLASS_CONST_LONG(phar_ce_archive, "PHP", PHAR_MIME_PHP) + REGISTER_PHAR_CLASS_CONST_LONG(phar_ce_archive, "PHPS", PHAR_MIME_PHPS) + REGISTER_PHAR_CLASS_CONST_LONG(phar_ce_archive, "MD5", PHAR_SIG_MD5) + REGISTER_PHAR_CLASS_CONST_LONG(phar_ce_archive, "OPENSSL", PHAR_SIG_OPENSSL) + REGISTER_PHAR_CLASS_CONST_LONG(phar_ce_archive, "SHA1", PHAR_SIG_SHA1) + REGISTER_PHAR_CLASS_CONST_LONG(phar_ce_archive, "SHA256", PHAR_SIG_SHA256) + REGISTER_PHAR_CLASS_CONST_LONG(phar_ce_archive, "SHA512", PHAR_SIG_SHA512) +} +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/ext/phar/tests/bug77247.phpt b/ext/phar/tests/bug77247.phpt new file mode 100644 index 0000000000000..588975f9f2f88 --- /dev/null +++ b/ext/phar/tests/bug77247.phpt @@ -0,0 +1,14 @@ +--TEST-- +PHP bug #77247 (heap buffer overflow in phar_detect_phar_fname_ext) +--SKIPIF-- + +--FILE-- + +--EXPECT-- +OK \ No newline at end of file diff --git a/ext/phar/tests/bug79082.phpt b/ext/phar/tests/bug79082.phpt new file mode 100644 index 0000000000000..ca453d1b57bcf --- /dev/null +++ b/ext/phar/tests/bug79082.phpt @@ -0,0 +1,52 @@ +--TEST-- +Phar: Bug #79082: Files added to tar with Phar::buildFromIterator have all-access permissions +--SKIPIF-- + +--FILE-- + 'tar', Phar::ZIP => 'zip'] as $mode => $ext) { + clearstatcache(); + $phar = new PharData(__DIR__ . '/test79082.' . $ext, null, null, $mode); + $phar->buildFromIterator(new \RecursiveDirectoryIterator(__DIR__ . '/test79082', \FilesystemIterator::SKIP_DOTS), __DIR__ . '/test79082'); + $phar->extractTo(__DIR__); + var_dump(decoct(stat(__DIR__ . '/test79082-testfile')['mode'])); + var_dump(decoct(stat(__DIR__ . '/test79082-testfile2')['mode'])); + unlink(__DIR__ . '/test79082-testfile'); + unlink(__DIR__ . '/test79082-testfile2'); +} +foreach([Phar::TAR => 'tar', Phar::ZIP => 'zip'] as $mode => $ext) { + clearstatcache(); + $phar = new PharData(__DIR__ . '/test79082-d.' . $ext, null, null, $mode); + $phar->buildFromDirectory(__DIR__ . '/test79082'); + $phar->extractTo(__DIR__); + var_dump(decoct(stat(__DIR__ . '/test79082-testfile')['mode'])); + var_dump(decoct(stat(__DIR__ . '/test79082-testfile2')['mode'])); + unlink(__DIR__ . '/test79082-testfile'); + unlink(__DIR__ . '/test79082-testfile2'); +} +?> +--CLEAN-- + +--EXPECT-- +string(2) "22" +string(6) "100644" +string(6) "100400" +string(6) "100644" +string(6) "100400" +string(6) "100644" +string(6) "100400" +string(6) "100644" +string(6) "100400" diff --git a/ext/phar/tests/cache_list/frontcontroller10.phpt b/ext/phar/tests/cache_list/frontcontroller10.phpt index 00177d4ff5580..5fd986895d569 100644 --- a/ext/phar/tests/cache_list/frontcontroller10.phpt +++ b/ext/phar/tests/cache_list/frontcontroller10.phpt @@ -20,6 +20,6 @@ Status: 403 Access Denied Access Denied -

403 - File /hi Access Denied

+

403 - File Access Denied

diff --git a/ext/phar/tests/cache_list/frontcontroller6.phpt b/ext/phar/tests/cache_list/frontcontroller6.phpt index 2480be41293a6..a79c95851712b 100644 --- a/ext/phar/tests/cache_list/frontcontroller6.phpt +++ b/ext/phar/tests/cache_list/frontcontroller6.phpt @@ -18,6 +18,6 @@ Status: 404 Not Found File Not Found -

404 - File /notfound.php Not Found

+

404 - File Not Found

\ No newline at end of file diff --git a/ext/phar/tests/cache_list/frontcontroller8.phpt b/ext/phar/tests/cache_list/frontcontroller8.phpt index bf9b390defc43..e04f9e5a3afe4 100644 --- a/ext/phar/tests/cache_list/frontcontroller8.phpt +++ b/ext/phar/tests/cache_list/frontcontroller8.phpt @@ -18,6 +18,6 @@ Status: 404 Not Found File Not Found -

404 - File /index.php Not Found

+

404 - File Not Found

\ No newline at end of file diff --git a/ext/phar/tests/frontcontroller10.phpt b/ext/phar/tests/frontcontroller10.phpt index 667d5c243cd4e..b3f5e640dd7ba 100644 --- a/ext/phar/tests/frontcontroller10.phpt +++ b/ext/phar/tests/frontcontroller10.phpt @@ -19,6 +19,6 @@ Status: 403 Access Denied Access Denied -

403 - File /hi Access Denied

+

403 - File Access Denied

diff --git a/ext/phar/tests/frontcontroller6.phpt b/ext/phar/tests/frontcontroller6.phpt index 1a2cc2cd23d12..c5dd382b10acb 100644 --- a/ext/phar/tests/frontcontroller6.phpt +++ b/ext/phar/tests/frontcontroller6.phpt @@ -16,6 +16,6 @@ Status: 404 Not Found File Not Found -

404 - File /notfound.php Not Found

+

404 - File Not Found

\ No newline at end of file diff --git a/ext/phar/tests/frontcontroller8.phpt b/ext/phar/tests/frontcontroller8.phpt index 36e3206d66879..77d33dac38b77 100644 --- a/ext/phar/tests/frontcontroller8.phpt +++ b/ext/phar/tests/frontcontroller8.phpt @@ -16,6 +16,6 @@ Status: 404 Not Found File Not Found -

404 - File /index.php Not Found

+

404 - File Not Found

\ No newline at end of file diff --git a/ext/phar/tests/tar/frontcontroller10.phar.phpt b/ext/phar/tests/tar/frontcontroller10.phar.phpt index f1fc6e3d0fd00..23ce6f37e2554 100644 --- a/ext/phar/tests/tar/frontcontroller10.phar.phpt +++ b/ext/phar/tests/tar/frontcontroller10.phar.phpt @@ -19,6 +19,6 @@ Status: 403 Access Denied Access Denied -

403 - File /hi Access Denied

+

403 - File Access Denied

\ No newline at end of file diff --git a/ext/phar/tests/tar/frontcontroller6.phar.phpt b/ext/phar/tests/tar/frontcontroller6.phar.phpt index 5375beef8cc8b..b811f00f75d9f 100644 --- a/ext/phar/tests/tar/frontcontroller6.phar.phpt +++ b/ext/phar/tests/tar/frontcontroller6.phar.phpt @@ -16,6 +16,6 @@ Status: 404 Not Found File Not Found -

404 - File /notfound.php Not Found

+

404 - File Not Found

\ No newline at end of file diff --git a/ext/phar/tests/tar/frontcontroller8.phar.phpt b/ext/phar/tests/tar/frontcontroller8.phar.phpt index 19844cb19942d..a180e2010aec9 100644 --- a/ext/phar/tests/tar/frontcontroller8.phar.phpt +++ b/ext/phar/tests/tar/frontcontroller8.phar.phpt @@ -16,6 +16,6 @@ Status: 404 Not Found File Not Found -

404 - File /index.php Not Found

+

404 - File Not Found

\ No newline at end of file diff --git a/ext/phar/tests/test79082/test79082-testfile b/ext/phar/tests/test79082/test79082-testfile new file mode 100644 index 0000000000000..9daeafb9864cf --- /dev/null +++ b/ext/phar/tests/test79082/test79082-testfile @@ -0,0 +1 @@ +test diff --git a/ext/phar/tests/test79082/test79082-testfile2 b/ext/phar/tests/test79082/test79082-testfile2 new file mode 100644 index 0000000000000..9daeafb9864cf --- /dev/null +++ b/ext/phar/tests/test79082/test79082-testfile2 @@ -0,0 +1 @@ +test diff --git a/ext/phar/tests/zip/frontcontroller10.phar.phpt b/ext/phar/tests/zip/frontcontroller10.phar.phpt index 56d16c2064ab5..5bbe9e1affba7 100644 --- a/ext/phar/tests/zip/frontcontroller10.phar.phpt +++ b/ext/phar/tests/zip/frontcontroller10.phar.phpt @@ -19,6 +19,6 @@ Status: 403 Access Denied Access Denied -

403 - File /hi Access Denied

+

403 - File Access Denied

\ No newline at end of file diff --git a/ext/phar/tests/zip/frontcontroller6.phar.phpt b/ext/phar/tests/zip/frontcontroller6.phar.phpt index 15489f6ca7280..63f7c62e88d22 100644 --- a/ext/phar/tests/zip/frontcontroller6.phar.phpt +++ b/ext/phar/tests/zip/frontcontroller6.phar.phpt @@ -17,6 +17,6 @@ Status: 404 Not Found File Not Found -

404 - File /notfound.php Not Found

+

404 - File Not Found

\ No newline at end of file diff --git a/ext/phar/tests/zip/frontcontroller8.phar.phpt b/ext/phar/tests/zip/frontcontroller8.phar.phpt index 1b0d133bc7170..d4c3a3f9ea05c 100644 --- a/ext/phar/tests/zip/frontcontroller8.phar.phpt +++ b/ext/phar/tests/zip/frontcontroller8.phar.phpt @@ -16,6 +16,6 @@ Status: 404 Not Found File Not Found -

404 - File /index.php Not Found

+

404 - File Not Found

\ No newline at end of file diff --git a/ext/simplexml/tests/bug79971_1.phpt b/ext/simplexml/tests/bug79971_1.phpt new file mode 100644 index 0000000000000..197776d82d38d --- /dev/null +++ b/ext/simplexml/tests/bug79971_1.phpt @@ -0,0 +1,27 @@ +--TEST-- +Bug #79971 (special character is breaking the path in xml function) +--SKIPIF-- + +--FILE-- +asXML("$uri.out%00foo")); +?> +--EXPECTF-- +Warning: simplexml_load_file(): URI must not contain percent-encoded NUL bytes in %s on line %d + +Warning: simplexml_load_file(): I/O warning : failed to load external entity "%s/bug79971_1.xml%00foo" in %s on line %d +bool(false) + +Warning: SimpleXMLElement::asXML(): URI must not contain percent-encoded NUL bytes in %s on line %d +bool(false) diff --git a/ext/simplexml/tests/bug79971_1.xml b/ext/simplexml/tests/bug79971_1.xml new file mode 100644 index 0000000000000..912bb76d9d7e2 --- /dev/null +++ b/ext/simplexml/tests/bug79971_1.xml @@ -0,0 +1,2 @@ + + diff --git a/ext/spl/spl_directory.c b/ext/spl/spl_directory.c index 649161311558f..29feca529f761 100644 --- a/ext/spl/spl_directory.c +++ b/ext/spl/spl_directory.c @@ -685,10 +685,10 @@ void spl_filesystem_object_construct(INTERNAL_FUNCTION_PARAMETERS, zend_long cto if (SPL_HAS_FLAG(ctor_flags, DIT_CTOR_FLAGS)) { flags = SPL_FILE_DIR_KEY_AS_PATHNAME|SPL_FILE_DIR_CURRENT_AS_FILEINFO; - parsed = zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &path, &len, &flags); + parsed = zend_parse_parameters(ZEND_NUM_ARGS(), "p|l", &path, &len, &flags); } else { flags = SPL_FILE_DIR_KEY_AS_PATHNAME|SPL_FILE_DIR_CURRENT_AS_SELF; - parsed = zend_parse_parameters(ZEND_NUM_ARGS(), "s", &path, &len); + parsed = zend_parse_parameters(ZEND_NUM_ARGS(), "p", &path, &len); } if (SPL_HAS_FLAG(ctor_flags, SPL_FILE_DIR_SKIPDOTS)) { flags |= SPL_FILE_DIR_SKIPDOTS; diff --git a/ext/spl/spl_directory.c.orig b/ext/spl/spl_directory.c.orig new file mode 100644 index 0000000000000..649161311558f --- /dev/null +++ b/ext/spl/spl_directory.c.orig @@ -0,0 +1,3172 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2017 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Marcus Boerger | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "ext/standard/file.h" +#include "ext/standard/php_string.h" +#include "zend_compile.h" +#include "zend_exceptions.h" +#include "zend_interfaces.h" + +#include "php_spl.h" +#include "spl_functions.h" +#include "spl_engine.h" +#include "spl_iterators.h" +#include "spl_directory.h" +#include "spl_exceptions.h" + +#include "php.h" +#include "fopen_wrappers.h" + +#include "ext/standard/basic_functions.h" +#include "ext/standard/php_filestat.h" + +#define SPL_HAS_FLAG(flags, test_flag) ((flags & test_flag) ? 1 : 0) + +/* declare the class handlers */ +static zend_object_handlers spl_filesystem_object_handlers; +/* includes handler to validate object state when retrieving methods */ +static zend_object_handlers spl_filesystem_object_check_handlers; + +/* decalre the class entry */ +PHPAPI zend_class_entry *spl_ce_SplFileInfo; +PHPAPI zend_class_entry *spl_ce_DirectoryIterator; +PHPAPI zend_class_entry *spl_ce_FilesystemIterator; +PHPAPI zend_class_entry *spl_ce_RecursiveDirectoryIterator; +PHPAPI zend_class_entry *spl_ce_GlobIterator; +PHPAPI zend_class_entry *spl_ce_SplFileObject; +PHPAPI zend_class_entry *spl_ce_SplTempFileObject; + +static void spl_filesystem_file_free_line(spl_filesystem_object *intern) /* {{{ */ +{ + if (intern->u.file.current_line) { + efree(intern->u.file.current_line); + intern->u.file.current_line = NULL; + } + if (!Z_ISUNDEF(intern->u.file.current_zval)) { + zval_ptr_dtor(&intern->u.file.current_zval); + ZVAL_UNDEF(&intern->u.file.current_zval); + } +} /* }}} */ + +static void spl_filesystem_object_free_storage(zend_object *object) /* {{{ */ +{ + spl_filesystem_object *intern = spl_filesystem_from_obj(object); + + if (intern->oth_handler && intern->oth_handler->dtor) { + intern->oth_handler->dtor(intern); + } + + zend_object_std_dtor(&intern->std); + + if (intern->_path) { + efree(intern->_path); + } + if (intern->file_name) { + efree(intern->file_name); + } + switch(intern->type) { + case SPL_FS_INFO: + break; + case SPL_FS_DIR: + if (intern->u.dir.dirp) { + php_stream_close(intern->u.dir.dirp); + intern->u.dir.dirp = NULL; + } + if (intern->u.dir.sub_path) { + efree(intern->u.dir.sub_path); + } + break; + case SPL_FS_FILE: + if (intern->u.file.stream) { + /* + if (intern->u.file.zcontext) { + zend_list_delref(Z_RESVAL_P(intern->zcontext)); + } + */ + if (!intern->u.file.stream->is_persistent) { + php_stream_close(intern->u.file.stream); + } else { + php_stream_pclose(intern->u.file.stream); + } + if (intern->u.file.open_mode) { + efree(intern->u.file.open_mode); + } + if (intern->orig_path) { + efree(intern->orig_path); + } + } + spl_filesystem_file_free_line(intern); + break; + } +} /* }}} */ + +/* {{{ spl_ce_dir_object_new */ +/* creates the object by + - allocating memory + - initializing the object members + - storing the object + - setting it's handlers + + called from + - clone + - new + */ +static zend_object *spl_filesystem_object_new_ex(zend_class_entry *class_type) +{ + spl_filesystem_object *intern; + + intern = ecalloc(1, sizeof(spl_filesystem_object) + zend_object_properties_size(class_type)); + /* intern->type = SPL_FS_INFO; done by set 0 */ + intern->file_class = spl_ce_SplFileObject; + intern->info_class = spl_ce_SplFileInfo; + + zend_object_std_init(&intern->std, class_type); + object_properties_init(&intern->std, class_type); + intern->std.handlers = &spl_filesystem_object_handlers; + + return &intern->std; +} +/* }}} */ + +/* {{{ spl_filesystem_object_new */ +/* See spl_filesystem_object_new_ex */ +static zend_object *spl_filesystem_object_new(zend_class_entry *class_type) +{ + return spl_filesystem_object_new_ex(class_type); +} +/* }}} */ + +/* {{{ spl_filesystem_object_new_check */ +static zend_object *spl_filesystem_object_new_check(zend_class_entry *class_type) +{ + spl_filesystem_object *ret = spl_filesystem_from_obj(spl_filesystem_object_new_ex(class_type)); + ret->std.handlers = &spl_filesystem_object_check_handlers; + return &ret->std; +} +/* }}} */ + +PHPAPI char* spl_filesystem_object_get_path(spl_filesystem_object *intern, size_t *len) /* {{{ */ +{ +#ifdef HAVE_GLOB + if (intern->type == SPL_FS_DIR) { + if (php_stream_is(intern->u.dir.dirp ,&php_glob_stream_ops)) { + return php_glob_stream_get_path(intern->u.dir.dirp, 0, len); + } + } +#endif + if (len) { + *len = intern->_path_len; + } + return intern->_path; +} /* }}} */ + +static inline void spl_filesystem_object_get_file_name(spl_filesystem_object *intern) /* {{{ */ +{ + char slash = SPL_HAS_FLAG(intern->flags, SPL_FILE_DIR_UNIXPATHS) ? '/' : DEFAULT_SLASH; + + switch (intern->type) { + case SPL_FS_INFO: + case SPL_FS_FILE: + if (!intern->file_name) { + php_error_docref(NULL, E_ERROR, "Object not initialized"); + } + break; + case SPL_FS_DIR: + if (intern->file_name) { + efree(intern->file_name); + } + intern->file_name_len = spprintf(&intern->file_name, 0, "%s%c%s", + spl_filesystem_object_get_path(intern, NULL), + slash, intern->u.dir.entry.d_name); + break; + } +} /* }}} */ + +static int spl_filesystem_dir_read(spl_filesystem_object *intern) /* {{{ */ +{ + if (!intern->u.dir.dirp || !php_stream_readdir(intern->u.dir.dirp, &intern->u.dir.entry)) { + intern->u.dir.entry.d_name[0] = '\0'; + return 0; + } else { + return 1; + } +} +/* }}} */ + +#define IS_SLASH_AT(zs, pos) (IS_SLASH(zs[pos])) + +static inline int spl_filesystem_is_dot(const char * d_name) /* {{{ */ +{ + return !strcmp(d_name, ".") || !strcmp(d_name, ".."); +} +/* }}} */ + +/* {{{ spl_filesystem_dir_open */ +/* open a directory resource */ +static void spl_filesystem_dir_open(spl_filesystem_object* intern, char *path) +{ + int skip_dots = SPL_HAS_FLAG(intern->flags, SPL_FILE_DIR_SKIPDOTS); + + intern->type = SPL_FS_DIR; + intern->_path_len = strlen(path); + intern->u.dir.dirp = php_stream_opendir(path, REPORT_ERRORS, FG(default_context)); + + if (intern->_path_len > 1 && IS_SLASH_AT(path, intern->_path_len-1)) { + intern->_path = estrndup(path, --intern->_path_len); + } else { + intern->_path = estrndup(path, intern->_path_len); + } + intern->u.dir.index = 0; + + if (EG(exception) || intern->u.dir.dirp == NULL) { + intern->u.dir.entry.d_name[0] = '\0'; + if (!EG(exception)) { + /* open failed w/out notice (turned to exception due to EH_THROW) */ + zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, + "Failed to open directory \"%s\"", path); + } + } else { + do { + spl_filesystem_dir_read(intern); + } while (skip_dots && spl_filesystem_is_dot(intern->u.dir.entry.d_name)); + } +} +/* }}} */ + +static int spl_filesystem_file_open(spl_filesystem_object *intern, int use_include_path, int silent) /* {{{ */ +{ + zval tmp; + + intern->type = SPL_FS_FILE; + + php_stat(intern->file_name, intern->file_name_len, FS_IS_DIR, &tmp); + if (Z_TYPE(tmp) == IS_TRUE) { + intern->u.file.open_mode = NULL; + intern->file_name = NULL; + zend_throw_exception_ex(spl_ce_LogicException, 0, "Cannot use SplFileObject with directories"); + return FAILURE; + } + + intern->u.file.context = php_stream_context_from_zval(intern->u.file.zcontext, 0); + intern->u.file.stream = php_stream_open_wrapper_ex(intern->file_name, intern->u.file.open_mode, (use_include_path ? USE_PATH : 0) | REPORT_ERRORS, NULL, intern->u.file.context); + + if (!intern->file_name_len || !intern->u.file.stream) { + if (!EG(exception)) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Cannot open file '%s'", intern->file_name_len ? intern->file_name : ""); + } + intern->file_name = NULL; /* until here it is not a copy */ + intern->u.file.open_mode = NULL; + return FAILURE; + } + + /* + if (intern->u.file.zcontext) { + //zend_list_addref(Z_RES_VAL(intern->u.file.zcontext)); + Z_ADDREF_P(intern->u.file.zcontext); + } + */ + + if (intern->file_name_len > 1 && IS_SLASH_AT(intern->file_name, intern->file_name_len-1)) { + intern->file_name_len--; + } + + intern->orig_path = estrndup(intern->u.file.stream->orig_path, strlen(intern->u.file.stream->orig_path)); + + intern->file_name = estrndup(intern->file_name, intern->file_name_len); + intern->u.file.open_mode = estrndup(intern->u.file.open_mode, intern->u.file.open_mode_len); + + /* avoid reference counting in debug mode, thus do it manually */ + ZVAL_RES(&intern->u.file.zresource, intern->u.file.stream->res); + /*!!! TODO: maybe bug? + Z_SET_REFCOUNT(intern->u.file.zresource, 1); + */ + + intern->u.file.delimiter = ','; + intern->u.file.enclosure = '"'; + intern->u.file.escape = '\\'; + + intern->u.file.func_getCurr = zend_hash_str_find_ptr(&intern->std.ce->function_table, "getcurrentline", sizeof("getcurrentline") - 1); + + return SUCCESS; +} /* }}} */ + +/* {{{ spl_filesystem_object_clone */ +/* Local zend_object creation (on stack) + Load the 'other' object + Create a new empty object (See spl_filesystem_object_new_ex) + Open the directory + Clone other members (properties) + */ +static zend_object *spl_filesystem_object_clone(zval *zobject) +{ + zend_object *old_object; + zend_object *new_object; + spl_filesystem_object *intern; + spl_filesystem_object *source; + int index, skip_dots; + + old_object = Z_OBJ_P(zobject); + source = spl_filesystem_from_obj(old_object); + new_object = spl_filesystem_object_new_ex(old_object->ce); + intern = spl_filesystem_from_obj(new_object); + + intern->flags = source->flags; + + switch (source->type) { + case SPL_FS_INFO: + intern->_path_len = source->_path_len; + intern->_path = estrndup(source->_path, source->_path_len); + intern->file_name_len = source->file_name_len; + intern->file_name = estrndup(source->file_name, intern->file_name_len); + break; + case SPL_FS_DIR: + spl_filesystem_dir_open(intern, source->_path); + /* read until we hit the position in which we were before */ + skip_dots = SPL_HAS_FLAG(source->flags, SPL_FILE_DIR_SKIPDOTS); + for(index = 0; index < source->u.dir.index; ++index) { + do { + spl_filesystem_dir_read(intern); + } while (skip_dots && spl_filesystem_is_dot(intern->u.dir.entry.d_name)); + } + intern->u.dir.index = index; + break; + case SPL_FS_FILE: + zend_throw_error(NULL, "An object of class %s cannot be cloned", ZSTR_VAL(old_object->ce->name)); + return new_object; + } + + intern->file_class = source->file_class; + intern->info_class = source->info_class; + intern->oth = source->oth; + intern->oth_handler = source->oth_handler; + + zend_objects_clone_members(new_object, old_object); + + if (intern->oth_handler && intern->oth_handler->clone) { + intern->oth_handler->clone(source, intern); + } + + return new_object; +} +/* }}} */ + +void spl_filesystem_info_set_filename(spl_filesystem_object *intern, char *path, size_t len, size_t use_copy) /* {{{ */ +{ + char *p1, *p2; + + if (intern->file_name) { + efree(intern->file_name); + } + + intern->file_name = use_copy ? estrndup(path, len) : path; + intern->file_name_len = len; + + while (intern->file_name_len > 1 && IS_SLASH_AT(intern->file_name, intern->file_name_len-1)) { + intern->file_name[intern->file_name_len-1] = 0; + intern->file_name_len--; + } + + p1 = strrchr(intern->file_name, '/'); +#if defined(PHP_WIN32) || defined(NETWARE) + p2 = strrchr(intern->file_name, '\\'); +#else + p2 = 0; +#endif + if (p1 || p2) { + intern->_path_len = ((p1 > p2 ? p1 : p2) - intern->file_name); + } else { + intern->_path_len = 0; + } + + if (intern->_path) { + efree(intern->_path); + } + intern->_path = estrndup(path, intern->_path_len); +} /* }}} */ + +static spl_filesystem_object *spl_filesystem_object_create_info(spl_filesystem_object *source, char *file_path, size_t file_path_len, int use_copy, zend_class_entry *ce, zval *return_value) /* {{{ */ +{ + spl_filesystem_object *intern; + zval arg1; + zend_error_handling error_handling; + + if (!file_path || !file_path_len) { +#if defined(PHP_WIN32) + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Cannot create SplFileInfo for empty path"); + if (file_path && !use_copy) { + efree(file_path); + } +#else + if (file_path && !use_copy) { + efree(file_path); + } + file_path_len = 1; + file_path = "/"; +#endif + return NULL; + } + + zend_replace_error_handling(EH_THROW, spl_ce_RuntimeException, &error_handling); + + ce = ce ? ce : source->info_class; + + zend_update_class_constants(ce); + + intern = spl_filesystem_from_obj(spl_filesystem_object_new_ex(ce)); + ZVAL_OBJ(return_value, &intern->std); + + if (ce->constructor->common.scope != spl_ce_SplFileInfo) { + ZVAL_STRINGL(&arg1, file_path, file_path_len); + zend_call_method_with_1_params(return_value, ce, &ce->constructor, "__construct", NULL, &arg1); + zval_ptr_dtor(&arg1); + } else { + spl_filesystem_info_set_filename(intern, file_path, file_path_len, use_copy); + } + + zend_restore_error_handling(&error_handling); + return intern; +} /* }}} */ + +static spl_filesystem_object *spl_filesystem_object_create_type(int ht, spl_filesystem_object *source, int type, zend_class_entry *ce, zval *return_value) /* {{{ */ +{ + spl_filesystem_object *intern; + zend_bool use_include_path = 0; + zval arg1, arg2; + zend_error_handling error_handling; + + zend_replace_error_handling(EH_THROW, spl_ce_RuntimeException, &error_handling); + + switch (source->type) { + case SPL_FS_INFO: + case SPL_FS_FILE: + break; + case SPL_FS_DIR: + if (!source->u.dir.entry.d_name[0]) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Could not open file"); + zend_restore_error_handling(&error_handling); + return NULL; + } + } + + switch (type) { + case SPL_FS_INFO: + ce = ce ? ce : source->info_class; + + if (UNEXPECTED(zend_update_class_constants(ce) != SUCCESS)) { + break; + } + + intern = spl_filesystem_from_obj(spl_filesystem_object_new_ex(ce)); + ZVAL_OBJ(return_value, &intern->std); + + spl_filesystem_object_get_file_name(source); + if (ce->constructor->common.scope != spl_ce_SplFileInfo) { + ZVAL_STRINGL(&arg1, source->file_name, source->file_name_len); + zend_call_method_with_1_params(return_value, ce, &ce->constructor, "__construct", NULL, &arg1); + zval_ptr_dtor(&arg1); + } else { + intern->file_name = estrndup(source->file_name, source->file_name_len); + intern->file_name_len = source->file_name_len; + intern->_path = spl_filesystem_object_get_path(source, &intern->_path_len); + intern->_path = estrndup(intern->_path, intern->_path_len); + } + break; + case SPL_FS_FILE: + ce = ce ? ce : source->file_class; + + if (UNEXPECTED(zend_update_class_constants(ce) != SUCCESS)) { + break; + } + + intern = spl_filesystem_from_obj(spl_filesystem_object_new_ex(ce)); + + ZVAL_OBJ(return_value, &intern->std); + + spl_filesystem_object_get_file_name(source); + + if (ce->constructor->common.scope != spl_ce_SplFileObject) { + ZVAL_STRINGL(&arg1, source->file_name, source->file_name_len); + ZVAL_STRINGL(&arg2, "r", 1); + zend_call_method_with_2_params(return_value, ce, &ce->constructor, "__construct", NULL, &arg1, &arg2); + zval_ptr_dtor(&arg1); + zval_ptr_dtor(&arg2); + } else { + intern->file_name = source->file_name; + intern->file_name_len = source->file_name_len; + intern->_path = spl_filesystem_object_get_path(source, &intern->_path_len); + intern->_path = estrndup(intern->_path, intern->_path_len); + + intern->u.file.open_mode = "r"; + intern->u.file.open_mode_len = 1; + + if (ht && zend_parse_parameters(ht, "|sbr", + &intern->u.file.open_mode, &intern->u.file.open_mode_len, + &use_include_path, &intern->u.file.zcontext) == FAILURE) { + zend_restore_error_handling(&error_handling); + intern->u.file.open_mode = NULL; + intern->file_name = NULL; + zval_ptr_dtor(return_value); + ZVAL_NULL(return_value); + return NULL; + } + + if (spl_filesystem_file_open(intern, use_include_path, 0) == FAILURE) { + zend_restore_error_handling(&error_handling); + zval_ptr_dtor(return_value); + ZVAL_NULL(return_value); + return NULL; + } + } + break; + case SPL_FS_DIR: + zend_restore_error_handling(&error_handling); + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Operation not supported"); + return NULL; + } + zend_restore_error_handling(&error_handling); + return NULL; +} /* }}} */ + +static int spl_filesystem_is_invalid_or_dot(const char * d_name) /* {{{ */ +{ + return d_name[0] == '\0' || spl_filesystem_is_dot(d_name); +} +/* }}} */ + +static char *spl_filesystem_object_get_pathname(spl_filesystem_object *intern, size_t *len) { /* {{{ */ + switch (intern->type) { + case SPL_FS_INFO: + case SPL_FS_FILE: + *len = intern->file_name_len; + return intern->file_name; + case SPL_FS_DIR: + if (intern->u.dir.entry.d_name[0]) { + spl_filesystem_object_get_file_name(intern); + *len = intern->file_name_len; + return intern->file_name; + } + } + *len = 0; + return NULL; +} +/* }}} */ + +static HashTable *spl_filesystem_object_get_debug_info(zval *object, int *is_temp) /* {{{ */ +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(object); + zval tmp; + HashTable *rv; + zend_string *pnstr; + char *path; + size_t path_len; + char stmp[2]; + + *is_temp = 1; + + if (!intern->std.properties) { + rebuild_object_properties(&intern->std); + } + + rv = zend_array_dup(intern->std.properties); + + pnstr = spl_gen_private_prop_name(spl_ce_SplFileInfo, "pathName", sizeof("pathName")-1); + path = spl_filesystem_object_get_pathname(intern, &path_len); + ZVAL_STRINGL(&tmp, path, path_len); + zend_symtable_update(rv, pnstr, &tmp); + zend_string_release(pnstr); + + if (intern->file_name) { + pnstr = spl_gen_private_prop_name(spl_ce_SplFileInfo, "fileName", sizeof("fileName")-1); + spl_filesystem_object_get_path(intern, &path_len); + + if (path_len && path_len < intern->file_name_len) { + ZVAL_STRINGL(&tmp, intern->file_name + path_len + 1, intern->file_name_len - (path_len + 1)); + } else { + ZVAL_STRINGL(&tmp, intern->file_name, intern->file_name_len); + } + zend_symtable_update(rv, pnstr, &tmp); + zend_string_release(pnstr); + } + if (intern->type == SPL_FS_DIR) { +#ifdef HAVE_GLOB + pnstr = spl_gen_private_prop_name(spl_ce_DirectoryIterator, "glob", sizeof("glob")-1); + if (php_stream_is(intern->u.dir.dirp ,&php_glob_stream_ops)) { + ZVAL_STRINGL(&tmp, intern->_path, intern->_path_len); + } else { + ZVAL_FALSE(&tmp); + } + zend_symtable_update(rv, pnstr, &tmp); + zend_string_release(pnstr); +#endif + pnstr = spl_gen_private_prop_name(spl_ce_RecursiveDirectoryIterator, "subPathName", sizeof("subPathName")-1); + if (intern->u.dir.sub_path) { + ZVAL_STRINGL(&tmp, intern->u.dir.sub_path, intern->u.dir.sub_path_len); + } else { + ZVAL_EMPTY_STRING(&tmp); + } + zend_symtable_update(rv, pnstr, &tmp); + zend_string_release(pnstr); + } + if (intern->type == SPL_FS_FILE) { + pnstr = spl_gen_private_prop_name(spl_ce_SplFileObject, "openMode", sizeof("openMode")-1); + ZVAL_STRINGL(&tmp, intern->u.file.open_mode, intern->u.file.open_mode_len); + zend_symtable_update(rv, pnstr, &tmp); + zend_string_release(pnstr); + stmp[1] = '\0'; + stmp[0] = intern->u.file.delimiter; + pnstr = spl_gen_private_prop_name(spl_ce_SplFileObject, "delimiter", sizeof("delimiter")-1); + ZVAL_STRINGL(&tmp, stmp, 1); + zend_symtable_update(rv, pnstr, &tmp); + zend_string_release(pnstr); + stmp[0] = intern->u.file.enclosure; + pnstr = spl_gen_private_prop_name(spl_ce_SplFileObject, "enclosure", sizeof("enclosure")-1); + ZVAL_STRINGL(&tmp, stmp, 1); + zend_symtable_update(rv, pnstr, &tmp); + zend_string_release(pnstr); + } + + return rv; +} +/* }}} */ + +zend_function *spl_filesystem_object_get_method_check(zend_object **object, zend_string *method, const zval *key) /* {{{ */ +{ + spl_filesystem_object *fsobj = spl_filesystem_from_obj(*object); + + if (fsobj->u.dir.dirp == NULL && fsobj->orig_path == NULL) { + zend_function *func; + zend_string *tmp = zend_string_init("_bad_state_ex", sizeof("_bad_state_ex") - 1, 0); + func = zend_get_std_object_handlers()->get_method(object, tmp, NULL); + zend_string_release(tmp); + return func; + } + + return zend_get_std_object_handlers()->get_method(object, method, key); +} +/* }}} */ + +#define DIT_CTOR_FLAGS 0x00000001 +#define DIT_CTOR_GLOB 0x00000002 + +void spl_filesystem_object_construct(INTERNAL_FUNCTION_PARAMETERS, zend_long ctor_flags) /* {{{ */ +{ + spl_filesystem_object *intern; + char *path; + int parsed; + size_t len; + zend_long flags; + zend_error_handling error_handling; + + zend_replace_error_handling(EH_THROW, spl_ce_UnexpectedValueException, &error_handling); + + if (SPL_HAS_FLAG(ctor_flags, DIT_CTOR_FLAGS)) { + flags = SPL_FILE_DIR_KEY_AS_PATHNAME|SPL_FILE_DIR_CURRENT_AS_FILEINFO; + parsed = zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &path, &len, &flags); + } else { + flags = SPL_FILE_DIR_KEY_AS_PATHNAME|SPL_FILE_DIR_CURRENT_AS_SELF; + parsed = zend_parse_parameters(ZEND_NUM_ARGS(), "s", &path, &len); + } + if (SPL_HAS_FLAG(ctor_flags, SPL_FILE_DIR_SKIPDOTS)) { + flags |= SPL_FILE_DIR_SKIPDOTS; + } + if (SPL_HAS_FLAG(ctor_flags, SPL_FILE_DIR_UNIXPATHS)) { + flags |= SPL_FILE_DIR_UNIXPATHS; + } + if (parsed == FAILURE) { + zend_restore_error_handling(&error_handling); + return; + } + if (!len) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Directory name must not be empty."); + zend_restore_error_handling(&error_handling); + return; + } + + intern = Z_SPLFILESYSTEM_P(getThis()); + if (intern->_path) { + /* object is alreay initialized */ + zend_restore_error_handling(&error_handling); + php_error_docref(NULL, E_WARNING, "Directory object is already initialized"); + return; + } + intern->flags = flags; +#ifdef HAVE_GLOB + if (SPL_HAS_FLAG(ctor_flags, DIT_CTOR_GLOB) && strstr(path, "glob://") != path) { + spprintf(&path, 0, "glob://%s", path); + spl_filesystem_dir_open(intern, path); + efree(path); + } else +#endif + { + spl_filesystem_dir_open(intern, path); + + } + + intern->u.dir.is_recursive = instanceof_function(intern->std.ce, spl_ce_RecursiveDirectoryIterator) ? 1 : 0; + + zend_restore_error_handling(&error_handling); +} +/* }}} */ + +/* {{{ proto void DirectoryIterator::__construct(string path) + Cronstructs a new dir iterator from a path. */ +SPL_METHOD(DirectoryIterator, __construct) +{ + spl_filesystem_object_construct(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); +} +/* }}} */ + +/* {{{ proto void DirectoryIterator::rewind() + Rewind dir back to the start */ +SPL_METHOD(DirectoryIterator, rewind) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + intern->u.dir.index = 0; + if (intern->u.dir.dirp) { + php_stream_rewinddir(intern->u.dir.dirp); + } + spl_filesystem_dir_read(intern); +} +/* }}} */ + +/* {{{ proto string DirectoryIterator::key() + Return current dir entry */ +SPL_METHOD(DirectoryIterator, key) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (intern->u.dir.dirp) { + RETURN_LONG(intern->u.dir.index); + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto DirectoryIterator DirectoryIterator::current() + Return this (needed for Iterator interface) */ +SPL_METHOD(DirectoryIterator, current) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } + ZVAL_OBJ(return_value, Z_OBJ_P(getThis())); + Z_ADDREF_P(return_value); +} +/* }}} */ + +/* {{{ proto void DirectoryIterator::next() + Move to next entry */ +SPL_METHOD(DirectoryIterator, next) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + int skip_dots = SPL_HAS_FLAG(intern->flags, SPL_FILE_DIR_SKIPDOTS); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + intern->u.dir.index++; + do { + spl_filesystem_dir_read(intern); + } while (skip_dots && spl_filesystem_is_dot(intern->u.dir.entry.d_name)); + if (intern->file_name) { + efree(intern->file_name); + intern->file_name = NULL; + } +} +/* }}} */ + +/* {{{ proto void DirectoryIterator::seek(int position) + Seek to the given position */ +SPL_METHOD(DirectoryIterator, seek) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + zval retval; + zend_long pos; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &pos) == FAILURE) { + return; + } + + if (intern->u.dir.index > pos) { + /* we first rewind */ + zend_call_method_with_0_params(&EX(This), Z_OBJCE(EX(This)), &intern->u.dir.func_rewind, "rewind", NULL); + } + + while (intern->u.dir.index < pos) { + int valid = 0; + zend_call_method_with_0_params(&EX(This), Z_OBJCE(EX(This)), &intern->u.dir.func_valid, "valid", &retval); + if (!Z_ISUNDEF(retval)) { + valid = zend_is_true(&retval); + zval_ptr_dtor(&retval); + } + if (!valid) { + zend_throw_exception_ex(spl_ce_OutOfBoundsException, 0, "Seek position " ZEND_LONG_FMT " is out of range", pos); + return; + } + zend_call_method_with_0_params(&EX(This), Z_OBJCE(EX(This)), &intern->u.dir.func_next, "next", NULL); + } +} /* }}} */ + +/* {{{ proto string DirectoryIterator::valid() + Check whether dir contains more entries */ +SPL_METHOD(DirectoryIterator, valid) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + RETURN_BOOL(intern->u.dir.entry.d_name[0] != '\0'); +} +/* }}} */ + +/* {{{ proto string SplFileInfo::getPath() + Return the path */ +SPL_METHOD(SplFileInfo, getPath) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + char *path; + size_t path_len; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + path = spl_filesystem_object_get_path(intern, &path_len); + RETURN_STRINGL(path, path_len); +} +/* }}} */ + +/* {{{ proto string SplFileInfo::getFilename() + Return filename only */ +SPL_METHOD(SplFileInfo, getFilename) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + size_t path_len; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + spl_filesystem_object_get_path(intern, &path_len); + + if (path_len && path_len < intern->file_name_len) { + RETURN_STRINGL(intern->file_name + path_len + 1, intern->file_name_len - (path_len + 1)); + } else { + RETURN_STRINGL(intern->file_name, intern->file_name_len); + } +} +/* }}} */ + +/* {{{ proto string DirectoryIterator::getFilename() + Return filename of current dir entry */ +SPL_METHOD(DirectoryIterator, getFilename) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + RETURN_STRING(intern->u.dir.entry.d_name); +} +/* }}} */ + +/* {{{ proto string SplFileInfo::getExtension() + Returns file extension component of path */ +SPL_METHOD(SplFileInfo, getExtension) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + char *fname = NULL; + const char *p; + size_t flen; + size_t path_len; + size_t idx; + zend_string *ret; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + spl_filesystem_object_get_path(intern, &path_len); + + if (path_len && path_len < intern->file_name_len) { + fname = intern->file_name + path_len + 1; + flen = intern->file_name_len - (path_len + 1); + } else { + fname = intern->file_name; + flen = intern->file_name_len; + } + + ret = php_basename(fname, flen, NULL, 0); + + p = zend_memrchr(ZSTR_VAL(ret), '.', ZSTR_LEN(ret)); + if (p) { + idx = p - ZSTR_VAL(ret); + RETVAL_STRINGL(ZSTR_VAL(ret) + idx + 1, ZSTR_LEN(ret) - idx - 1); + zend_string_release(ret); + return; + } else { + zend_string_release(ret); + RETURN_EMPTY_STRING(); + } +} +/* }}}*/ + +/* {{{ proto string DirectoryIterator::getExtension() + Returns the file extension component of path */ +SPL_METHOD(DirectoryIterator, getExtension) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + const char *p; + size_t idx; + zend_string *fname; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + fname = php_basename(intern->u.dir.entry.d_name, strlen(intern->u.dir.entry.d_name), NULL, 0); + + p = zend_memrchr(ZSTR_VAL(fname), '.', ZSTR_LEN(fname)); + if (p) { + idx = p - ZSTR_VAL(fname); + RETVAL_STRINGL(ZSTR_VAL(fname) + idx + 1, ZSTR_LEN(fname) - idx - 1); + zend_string_release(fname); + } else { + zend_string_release(fname); + RETURN_EMPTY_STRING(); + } +} +/* }}} */ + +/* {{{ proto string SplFileInfo::getBasename([string $suffix]) + Returns filename component of path */ +SPL_METHOD(SplFileInfo, getBasename) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + char *fname, *suffix = 0; + size_t flen; + size_t slen = 0, path_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|s", &suffix, &slen) == FAILURE) { + return; + } + + spl_filesystem_object_get_path(intern, &path_len); + + if (path_len && path_len < intern->file_name_len) { + fname = intern->file_name + path_len + 1; + flen = intern->file_name_len - (path_len + 1); + } else { + fname = intern->file_name; + flen = intern->file_name_len; + } + + RETURN_STR(php_basename(fname, flen, suffix, slen)); +} +/* }}}*/ + +/* {{{ proto string DirectoryIterator::getBasename([string $suffix]) + Returns filename component of current dir entry */ +SPL_METHOD(DirectoryIterator, getBasename) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + char *suffix = 0; + size_t slen = 0; + zend_string *fname; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|s", &suffix, &slen) == FAILURE) { + return; + } + + fname = php_basename(intern->u.dir.entry.d_name, strlen(intern->u.dir.entry.d_name), suffix, slen); + + RETVAL_STR(fname); +} +/* }}} */ + +/* {{{ proto string SplFileInfo::getPathname() + Return path and filename */ +SPL_METHOD(SplFileInfo, getPathname) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + char *path; + size_t path_len; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + path = spl_filesystem_object_get_pathname(intern, &path_len); + if (path != NULL) { + RETURN_STRINGL(path, path_len); + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto string FilesystemIterator::key() + Return getPathname() or getFilename() depending on flags */ +SPL_METHOD(FilesystemIterator, key) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (SPL_FILE_DIR_KEY(intern, SPL_FILE_DIR_KEY_AS_FILENAME)) { + RETURN_STRING(intern->u.dir.entry.d_name); + } else { + spl_filesystem_object_get_file_name(intern); + RETURN_STRINGL(intern->file_name, intern->file_name_len); + } +} +/* }}} */ + +/* {{{ proto string FilesystemIterator::current() + Return getFilename(), getFileInfo() or $this depending on flags */ +SPL_METHOD(FilesystemIterator, current) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (SPL_FILE_DIR_CURRENT(intern, SPL_FILE_DIR_CURRENT_AS_PATHNAME)) { + spl_filesystem_object_get_file_name(intern); + RETURN_STRINGL(intern->file_name, intern->file_name_len); + } else if (SPL_FILE_DIR_CURRENT(intern, SPL_FILE_DIR_CURRENT_AS_FILEINFO)) { + spl_filesystem_object_get_file_name(intern); + spl_filesystem_object_create_type(0, intern, SPL_FS_INFO, NULL, return_value); + } else { + ZVAL_OBJ(return_value, Z_OBJ_P(getThis())); + Z_ADDREF_P(return_value); + /*RETURN_STRING(intern->u.dir.entry.d_name, 1);*/ + } +} +/* }}} */ + +/* {{{ proto bool DirectoryIterator::isDot() + Returns true if current entry is '.' or '..' */ +SPL_METHOD(DirectoryIterator, isDot) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + RETURN_BOOL(spl_filesystem_is_dot(intern->u.dir.entry.d_name)); +} +/* }}} */ + +/* {{{ proto void SplFileInfo::__construct(string file_name) + Cronstructs a new SplFileInfo from a path. */ +/* When the constructor gets called the object is already created + by the engine, so we must only call 'additional' initializations. + */ +SPL_METHOD(SplFileInfo, __construct) +{ + spl_filesystem_object *intern; + char *path; + size_t len; + + if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "s", &path, &len) == FAILURE) { + return; + } + + intern = Z_SPLFILESYSTEM_P(getThis()); + + spl_filesystem_info_set_filename(intern, path, len, 1); + + /* intern->type = SPL_FS_INFO; already set */ +} +/* }}} */ + +/* {{{ FileInfoFunction */ +#define FileInfoFunction(func_name, func_num) \ +SPL_METHOD(SplFileInfo, func_name) \ +{ \ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); \ + zend_error_handling error_handling; \ + if (zend_parse_parameters_none() == FAILURE) { \ + return; \ + } \ + \ + zend_replace_error_handling(EH_THROW, spl_ce_RuntimeException, &error_handling);\ + spl_filesystem_object_get_file_name(intern); \ + php_stat(intern->file_name, intern->file_name_len, func_num, return_value); \ + zend_restore_error_handling(&error_handling); \ +} +/* }}} */ + +/* {{{ proto int SplFileInfo::getPerms() + Get file permissions */ +FileInfoFunction(getPerms, FS_PERMS) +/* }}} */ + +/* {{{ proto int SplFileInfo::getInode() + Get file inode */ +FileInfoFunction(getInode, FS_INODE) +/* }}} */ + +/* {{{ proto int SplFileInfo::getSize() + Get file size */ +FileInfoFunction(getSize, FS_SIZE) +/* }}} */ + +/* {{{ proto int SplFileInfo::getOwner() + Get file owner */ +FileInfoFunction(getOwner, FS_OWNER) +/* }}} */ + +/* {{{ proto int SplFileInfo::getGroup() + Get file group */ +FileInfoFunction(getGroup, FS_GROUP) +/* }}} */ + +/* {{{ proto int SplFileInfo::getATime() + Get last access time of file */ +FileInfoFunction(getATime, FS_ATIME) +/* }}} */ + +/* {{{ proto int SplFileInfo::getMTime() + Get last modification time of file */ +FileInfoFunction(getMTime, FS_MTIME) +/* }}} */ + +/* {{{ proto int SplFileInfo::getCTime() + Get inode modification time of file */ +FileInfoFunction(getCTime, FS_CTIME) +/* }}} */ + +/* {{{ proto string SplFileInfo::getType() + Get file type */ +FileInfoFunction(getType, FS_TYPE) +/* }}} */ + +/* {{{ proto bool SplFileInfo::isWritable() + Returns true if file can be written */ +FileInfoFunction(isWritable, FS_IS_W) +/* }}} */ + +/* {{{ proto bool SplFileInfo::isReadable() + Returns true if file can be read */ +FileInfoFunction(isReadable, FS_IS_R) +/* }}} */ + +/* {{{ proto bool SplFileInfo::isExecutable() + Returns true if file is executable */ +FileInfoFunction(isExecutable, FS_IS_X) +/* }}} */ + +/* {{{ proto bool SplFileInfo::isFile() + Returns true if file is a regular file */ +FileInfoFunction(isFile, FS_IS_FILE) +/* }}} */ + +/* {{{ proto bool SplFileInfo::isDir() + Returns true if file is directory */ +FileInfoFunction(isDir, FS_IS_DIR) +/* }}} */ + +/* {{{ proto bool SplFileInfo::isLink() + Returns true if file is symbolic link */ +FileInfoFunction(isLink, FS_IS_LINK) +/* }}} */ + +/* {{{ proto string SplFileInfo::getLinkTarget() + Return the target of a symbolic link */ +SPL_METHOD(SplFileInfo, getLinkTarget) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + int ret; + char buff[MAXPATHLEN]; + zend_error_handling error_handling; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + zend_replace_error_handling(EH_THROW, spl_ce_RuntimeException, &error_handling); + +#if defined(PHP_WIN32) || HAVE_SYMLINK + if (intern->file_name == NULL) { + php_error_docref(NULL, E_WARNING, "Empty filename"); + RETURN_FALSE; + } else if (!IS_ABSOLUTE_PATH(intern->file_name, intern->file_name_len)) { + char expanded_path[MAXPATHLEN]; + if (!expand_filepath_with_mode(intern->file_name, expanded_path, NULL, 0, CWD_EXPAND )) { + php_error_docref(NULL, E_WARNING, "No such file or directory"); + RETURN_FALSE; + } + ret = php_sys_readlink(expanded_path, buff, MAXPATHLEN - 1); + } else { + ret = php_sys_readlink(intern->file_name, buff, MAXPATHLEN-1); + } +#else + ret = -1; /* always fail if not implemented */ +#endif + + if (ret == -1) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Unable to read link %s, error: %s", intern->file_name, strerror(errno)); + RETVAL_FALSE; + } else { + /* Append NULL to the end of the string */ + buff[ret] = '\0'; + + RETVAL_STRINGL(buff, ret); + } + + zend_restore_error_handling(&error_handling); +} +/* }}} */ + +#if (!defined(__BEOS__) && !defined(NETWARE) && HAVE_REALPATH) || defined(ZTS) +/* {{{ proto string SplFileInfo::getRealPath() + Return the resolved path */ +SPL_METHOD(SplFileInfo, getRealPath) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + char buff[MAXPATHLEN]; + char *filename; + zend_error_handling error_handling; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + zend_replace_error_handling(EH_THROW, spl_ce_RuntimeException, &error_handling); + + if (intern->type == SPL_FS_DIR && !intern->file_name && intern->u.dir.entry.d_name[0]) { + spl_filesystem_object_get_file_name(intern); + } + + if (intern->orig_path) { + filename = intern->orig_path; + } else { + filename = intern->file_name; + } + + + if (filename && VCWD_REALPATH(filename, buff)) { +#ifdef ZTS + if (VCWD_ACCESS(buff, F_OK)) { + RETVAL_FALSE; + } else +#endif + RETVAL_STRING(buff); + } else { + RETVAL_FALSE; + } + + zend_restore_error_handling(&error_handling); +} +/* }}} */ +#endif + +/* {{{ proto SplFileObject SplFileInfo::openFile([string mode = 'r' [, bool use_include_path [, resource context]]]) + Open the current file */ +SPL_METHOD(SplFileInfo, openFile) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + spl_filesystem_object_create_type(ZEND_NUM_ARGS(), intern, SPL_FS_FILE, NULL, return_value); +} +/* }}} */ + +/* {{{ proto void SplFileInfo::setFileClass([string class_name]) + Class to use in openFile() */ +SPL_METHOD(SplFileInfo, setFileClass) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + zend_class_entry *ce = spl_ce_SplFileObject; + zend_error_handling error_handling; + + zend_replace_error_handling(EH_THROW, spl_ce_UnexpectedValueException, &error_handling); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|C", &ce) == SUCCESS) { + intern->file_class = ce; + } + + zend_restore_error_handling(&error_handling); +} +/* }}} */ + +/* {{{ proto void SplFileInfo::setInfoClass([string class_name]) + Class to use in getFileInfo(), getPathInfo() */ +SPL_METHOD(SplFileInfo, setInfoClass) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + zend_class_entry *ce = spl_ce_SplFileInfo; + zend_error_handling error_handling; + + zend_replace_error_handling(EH_THROW, spl_ce_UnexpectedValueException, &error_handling ); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|C", &ce) == SUCCESS) { + intern->info_class = ce; + } + + zend_restore_error_handling(&error_handling); +} +/* }}} */ + +/* {{{ proto SplFileInfo SplFileInfo::getFileInfo([string $class_name]) + Get/copy file info */ +SPL_METHOD(SplFileInfo, getFileInfo) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + zend_class_entry *ce = intern->info_class; + zend_error_handling error_handling; + + zend_replace_error_handling(EH_THROW, spl_ce_UnexpectedValueException, &error_handling); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|C", &ce) == SUCCESS) { + spl_filesystem_object_create_type(ZEND_NUM_ARGS(), intern, SPL_FS_INFO, ce, return_value); + } + + zend_restore_error_handling(&error_handling); +} +/* }}} */ + +/* {{{ proto SplFileInfo SplFileInfo::getPathInfo([string $class_name]) + Get/copy file info */ +SPL_METHOD(SplFileInfo, getPathInfo) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + zend_class_entry *ce = intern->info_class; + zend_error_handling error_handling; + + zend_replace_error_handling(EH_THROW, spl_ce_UnexpectedValueException, &error_handling); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|C", &ce) == SUCCESS) { + size_t path_len; + char *path = spl_filesystem_object_get_pathname(intern, &path_len); + if (path) { + char *dpath = estrndup(path, path_len); + path_len = php_dirname(dpath, path_len); + spl_filesystem_object_create_info(intern, dpath, path_len, 1, ce, return_value); + efree(dpath); + } + } + + zend_restore_error_handling(&error_handling); +} +/* }}} */ + +/* {{{ proto SplFileInfo::_bad_state_ex(void) */ +SPL_METHOD(SplFileInfo, _bad_state_ex) +{ + zend_throw_exception_ex(spl_ce_LogicException, 0, + "The parent constructor was not called: the object is in an " + "invalid state "); +} +/* }}} */ + +/* {{{ proto void FilesystemIterator::__construct(string path [, int flags]) + Cronstructs a new dir iterator from a path. */ +SPL_METHOD(FilesystemIterator, __construct) +{ + spl_filesystem_object_construct(INTERNAL_FUNCTION_PARAM_PASSTHRU, DIT_CTOR_FLAGS | SPL_FILE_DIR_SKIPDOTS); +} +/* }}} */ + +/* {{{ proto void FilesystemIterator::rewind() + Rewind dir back to the start */ +SPL_METHOD(FilesystemIterator, rewind) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + int skip_dots = SPL_HAS_FLAG(intern->flags, SPL_FILE_DIR_SKIPDOTS); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + intern->u.dir.index = 0; + if (intern->u.dir.dirp) { + php_stream_rewinddir(intern->u.dir.dirp); + } + do { + spl_filesystem_dir_read(intern); + } while (skip_dots && spl_filesystem_is_dot(intern->u.dir.entry.d_name)); +} +/* }}} */ + +/* {{{ proto int FilesystemIterator::getFlags() + Get handling flags */ +SPL_METHOD(FilesystemIterator, getFlags) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + RETURN_LONG(intern->flags & (SPL_FILE_DIR_KEY_MODE_MASK | SPL_FILE_DIR_CURRENT_MODE_MASK | SPL_FILE_DIR_OTHERS_MASK)); +} /* }}} */ + +/* {{{ proto void FilesystemIterator::setFlags(long $flags) + Set handling flags */ +SPL_METHOD(FilesystemIterator, setFlags) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + zend_long flags; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &flags) == FAILURE) { + return; + } + + intern->flags &= ~(SPL_FILE_DIR_KEY_MODE_MASK|SPL_FILE_DIR_CURRENT_MODE_MASK|SPL_FILE_DIR_OTHERS_MASK); + intern->flags |= ((SPL_FILE_DIR_KEY_MODE_MASK|SPL_FILE_DIR_CURRENT_MODE_MASK|SPL_FILE_DIR_OTHERS_MASK) & flags); +} /* }}} */ + +/* {{{ proto bool RecursiveDirectoryIterator::hasChildren([bool $allow_links = false]) + Returns whether current entry is a directory and not '.' or '..' */ +SPL_METHOD(RecursiveDirectoryIterator, hasChildren) +{ + zend_bool allow_links = 0; + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &allow_links) == FAILURE) { + return; + } + if (spl_filesystem_is_invalid_or_dot(intern->u.dir.entry.d_name)) { + RETURN_FALSE; + } else { + spl_filesystem_object_get_file_name(intern); + if (!allow_links && !(intern->flags & SPL_FILE_DIR_FOLLOW_SYMLINKS)) { + php_stat(intern->file_name, intern->file_name_len, FS_IS_LINK, return_value); + if (zend_is_true(return_value)) { + RETURN_FALSE; + } + } + php_stat(intern->file_name, intern->file_name_len, FS_IS_DIR, return_value); + } +} +/* }}} */ + +/* {{{ proto RecursiveDirectoryIterator DirectoryIterator::getChildren() + Returns an iterator for the current entry if it is a directory */ +SPL_METHOD(RecursiveDirectoryIterator, getChildren) +{ + zval zpath, zflags; + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + spl_filesystem_object *subdir; + char slash = SPL_HAS_FLAG(intern->flags, SPL_FILE_DIR_UNIXPATHS) ? '/' : DEFAULT_SLASH; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + spl_filesystem_object_get_file_name(intern); + + ZVAL_LONG(&zflags, intern->flags); + ZVAL_STRINGL(&zpath, intern->file_name, intern->file_name_len); + spl_instantiate_arg_ex2(Z_OBJCE_P(getThis()), return_value, &zpath, &zflags); + zval_ptr_dtor(&zpath); + zval_ptr_dtor(&zflags); + + subdir = Z_SPLFILESYSTEM_P(return_value); + if (subdir) { + if (intern->u.dir.sub_path && intern->u.dir.sub_path[0]) { + subdir->u.dir.sub_path_len = spprintf(&subdir->u.dir.sub_path, 0, "%s%c%s", intern->u.dir.sub_path, slash, intern->u.dir.entry.d_name); + } else { + subdir->u.dir.sub_path_len = strlen(intern->u.dir.entry.d_name); + subdir->u.dir.sub_path = estrndup(intern->u.dir.entry.d_name, subdir->u.dir.sub_path_len); + } + subdir->info_class = intern->info_class; + subdir->file_class = intern->file_class; + subdir->oth = intern->oth; + } +} +/* }}} */ + +/* {{{ proto void RecursiveDirectoryIterator::getSubPath() + Get sub path */ +SPL_METHOD(RecursiveDirectoryIterator, getSubPath) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (intern->u.dir.sub_path) { + RETURN_STRINGL(intern->u.dir.sub_path, intern->u.dir.sub_path_len); + } else { + RETURN_EMPTY_STRING(); + } +} +/* }}} */ + +/* {{{ proto void RecursiveDirectoryIterator::getSubPathname() + Get sub path and file name */ +SPL_METHOD(RecursiveDirectoryIterator, getSubPathname) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + char slash = SPL_HAS_FLAG(intern->flags, SPL_FILE_DIR_UNIXPATHS) ? '/' : DEFAULT_SLASH; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (intern->u.dir.sub_path) { + RETURN_NEW_STR(strpprintf(0, "%s%c%s", intern->u.dir.sub_path, slash, intern->u.dir.entry.d_name)); + } else { + RETURN_STRING(intern->u.dir.entry.d_name); + } +} +/* }}} */ + +/* {{{ proto int RecursiveDirectoryIterator::__construct(string path [, int flags]) + Cronstructs a new dir iterator from a path. */ +SPL_METHOD(RecursiveDirectoryIterator, __construct) +{ + spl_filesystem_object_construct(INTERNAL_FUNCTION_PARAM_PASSTHRU, DIT_CTOR_FLAGS); +} +/* }}} */ + +#ifdef HAVE_GLOB +/* {{{ proto int GlobIterator::__construct(string path [, int flags]) + Cronstructs a new dir iterator from a glob expression (no glob:// needed). */ +SPL_METHOD(GlobIterator, __construct) +{ + spl_filesystem_object_construct(INTERNAL_FUNCTION_PARAM_PASSTHRU, DIT_CTOR_FLAGS|DIT_CTOR_GLOB); +} +/* }}} */ + +/* {{{ proto int GlobIterator::cont() + Return the number of directories and files found by globbing */ +SPL_METHOD(GlobIterator, count) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (intern->u.dir.dirp && php_stream_is(intern->u.dir.dirp ,&php_glob_stream_ops)) { + RETURN_LONG(php_glob_stream_get_count(intern->u.dir.dirp, NULL)); + } else { + /* should not happen */ + php_error_docref(NULL, E_ERROR, "GlobIterator lost glob state"); + } +} +/* }}} */ +#endif /* HAVE_GLOB */ + +/* {{{ forward declarations to the iterator handlers */ +static void spl_filesystem_dir_it_dtor(zend_object_iterator *iter); +static int spl_filesystem_dir_it_valid(zend_object_iterator *iter); +static zval *spl_filesystem_dir_it_current_data(zend_object_iterator *iter); +static void spl_filesystem_dir_it_current_key(zend_object_iterator *iter, zval *key); +static void spl_filesystem_dir_it_move_forward(zend_object_iterator *iter); +static void spl_filesystem_dir_it_rewind(zend_object_iterator *iter); + +/* iterator handler table */ +zend_object_iterator_funcs spl_filesystem_dir_it_funcs = { + spl_filesystem_dir_it_dtor, + spl_filesystem_dir_it_valid, + spl_filesystem_dir_it_current_data, + spl_filesystem_dir_it_current_key, + spl_filesystem_dir_it_move_forward, + spl_filesystem_dir_it_rewind, + NULL +}; +/* }}} */ + +/* {{{ spl_ce_dir_get_iterator */ +zend_object_iterator *spl_filesystem_dir_get_iterator(zend_class_entry *ce, zval *object, int by_ref) +{ + spl_filesystem_iterator *iterator; + spl_filesystem_object *dir_object; + + if (by_ref) { + zend_error(E_ERROR, "An iterator cannot be used with foreach by reference"); + } + dir_object = Z_SPLFILESYSTEM_P(object); + iterator = spl_filesystem_object_to_iterator(dir_object); + ZVAL_COPY(&iterator->intern.data, object); + iterator->intern.funcs = &spl_filesystem_dir_it_funcs; + /* ->current must be initialized; rewind doesn't set it and valid + * doesn't check whether it's set */ + iterator->current = *object; + + return &iterator->intern; +} +/* }}} */ + +/* {{{ spl_filesystem_dir_it_dtor */ +static void spl_filesystem_dir_it_dtor(zend_object_iterator *iter) +{ + spl_filesystem_iterator *iterator = (spl_filesystem_iterator *)iter; + + if (!Z_ISUNDEF(iterator->intern.data)) { + zval *object = &iterator->intern.data; + zval_ptr_dtor(object); + } + /* Otherwise we were called from the owning object free storage handler as + * it sets iterator->intern.data to IS_UNDEF. + * We don't even need to destroy iterator->current as we didn't add a + * reference to it in move_forward or get_iterator */ +} +/* }}} */ + +/* {{{ spl_filesystem_dir_it_valid */ +static int spl_filesystem_dir_it_valid(zend_object_iterator *iter) +{ + spl_filesystem_object *object = spl_filesystem_iterator_to_object((spl_filesystem_iterator *)iter); + + return object->u.dir.entry.d_name[0] != '\0' ? SUCCESS : FAILURE; +} +/* }}} */ + +/* {{{ spl_filesystem_dir_it_current_data */ +static zval *spl_filesystem_dir_it_current_data(zend_object_iterator *iter) +{ + spl_filesystem_iterator *iterator = (spl_filesystem_iterator *)iter; + + return &iterator->current; +} +/* }}} */ + +/* {{{ spl_filesystem_dir_it_current_key */ +static void spl_filesystem_dir_it_current_key(zend_object_iterator *iter, zval *key) +{ + spl_filesystem_object *object = spl_filesystem_iterator_to_object((spl_filesystem_iterator *)iter); + + ZVAL_LONG(key, object->u.dir.index); +} +/* }}} */ + +/* {{{ spl_filesystem_dir_it_move_forward */ +static void spl_filesystem_dir_it_move_forward(zend_object_iterator *iter) +{ + spl_filesystem_object *object = spl_filesystem_iterator_to_object((spl_filesystem_iterator *)iter); + + object->u.dir.index++; + spl_filesystem_dir_read(object); + if (object->file_name) { + efree(object->file_name); + object->file_name = NULL; + } +} +/* }}} */ + +/* {{{ spl_filesystem_dir_it_rewind */ +static void spl_filesystem_dir_it_rewind(zend_object_iterator *iter) +{ + spl_filesystem_object *object = spl_filesystem_iterator_to_object((spl_filesystem_iterator *)iter); + + object->u.dir.index = 0; + if (object->u.dir.dirp) { + php_stream_rewinddir(object->u.dir.dirp); + } + spl_filesystem_dir_read(object); +} +/* }}} */ + +/* {{{ spl_filesystem_tree_it_dtor */ +static void spl_filesystem_tree_it_dtor(zend_object_iterator *iter) +{ + spl_filesystem_iterator *iterator = (spl_filesystem_iterator *)iter; + + if (!Z_ISUNDEF(iterator->intern.data)) { + zval *object = &iterator->intern.data; + zval_ptr_dtor(object); + } else { + if (!Z_ISUNDEF(iterator->current)) { + zval_ptr_dtor(&iterator->current); + ZVAL_UNDEF(&iterator->current); + } + } +} +/* }}} */ + +/* {{{ spl_filesystem_tree_it_current_data */ +static zval *spl_filesystem_tree_it_current_data(zend_object_iterator *iter) +{ + spl_filesystem_iterator *iterator = (spl_filesystem_iterator *)iter; + spl_filesystem_object *object = spl_filesystem_iterator_to_object(iterator); + + if (SPL_FILE_DIR_CURRENT(object, SPL_FILE_DIR_CURRENT_AS_PATHNAME)) { + if (Z_ISUNDEF(iterator->current)) { + spl_filesystem_object_get_file_name(object); + ZVAL_STRINGL(&iterator->current, object->file_name, object->file_name_len); + } + return &iterator->current; + } else if (SPL_FILE_DIR_CURRENT(object, SPL_FILE_DIR_CURRENT_AS_FILEINFO)) { + if (Z_ISUNDEF(iterator->current)) { + spl_filesystem_object_get_file_name(object); + spl_filesystem_object_create_type(0, object, SPL_FS_INFO, NULL, &iterator->current); + } + return &iterator->current; + } else { + return &iterator->intern.data; + } +} +/* }}} */ + +/* {{{ spl_filesystem_tree_it_current_key */ +static void spl_filesystem_tree_it_current_key(zend_object_iterator *iter, zval *key) +{ + spl_filesystem_object *object = spl_filesystem_iterator_to_object((spl_filesystem_iterator *)iter); + + if (SPL_FILE_DIR_KEY(object, SPL_FILE_DIR_KEY_AS_FILENAME)) { + ZVAL_STRING(key, object->u.dir.entry.d_name); + } else { + spl_filesystem_object_get_file_name(object); + ZVAL_STRINGL(key, object->file_name, object->file_name_len); + } +} +/* }}} */ + +/* {{{ spl_filesystem_tree_it_move_forward */ +static void spl_filesystem_tree_it_move_forward(zend_object_iterator *iter) +{ + spl_filesystem_iterator *iterator = (spl_filesystem_iterator *)iter; + spl_filesystem_object *object = spl_filesystem_iterator_to_object(iterator); + + object->u.dir.index++; + do { + spl_filesystem_dir_read(object); + } while (spl_filesystem_is_dot(object->u.dir.entry.d_name)); + if (object->file_name) { + efree(object->file_name); + object->file_name = NULL; + } + if (!Z_ISUNDEF(iterator->current)) { + zval_ptr_dtor(&iterator->current); + ZVAL_UNDEF(&iterator->current); + } +} +/* }}} */ + +/* {{{ spl_filesystem_tree_it_rewind */ +static void spl_filesystem_tree_it_rewind(zend_object_iterator *iter) +{ + spl_filesystem_iterator *iterator = (spl_filesystem_iterator *)iter; + spl_filesystem_object *object = spl_filesystem_iterator_to_object(iterator); + + object->u.dir.index = 0; + if (object->u.dir.dirp) { + php_stream_rewinddir(object->u.dir.dirp); + } + do { + spl_filesystem_dir_read(object); + } while (spl_filesystem_is_dot(object->u.dir.entry.d_name)); + if (!Z_ISUNDEF(iterator->current)) { + zval_ptr_dtor(&iterator->current); + ZVAL_UNDEF(&iterator->current); + } +} +/* }}} */ + +/* {{{ iterator handler table */ +zend_object_iterator_funcs spl_filesystem_tree_it_funcs = { + spl_filesystem_tree_it_dtor, + spl_filesystem_dir_it_valid, + spl_filesystem_tree_it_current_data, + spl_filesystem_tree_it_current_key, + spl_filesystem_tree_it_move_forward, + spl_filesystem_tree_it_rewind, + NULL +}; +/* }}} */ + +/* {{{ spl_ce_dir_get_iterator */ +zend_object_iterator *spl_filesystem_tree_get_iterator(zend_class_entry *ce, zval *object, int by_ref) +{ + spl_filesystem_iterator *iterator; + spl_filesystem_object *dir_object; + + if (by_ref) { + zend_error(E_ERROR, "An iterator cannot be used with foreach by reference"); + } + dir_object = Z_SPLFILESYSTEM_P(object); + iterator = spl_filesystem_object_to_iterator(dir_object); + + ZVAL_COPY(&iterator->intern.data, object); + iterator->intern.funcs = &spl_filesystem_tree_it_funcs; + + return &iterator->intern; +} +/* }}} */ + +/* {{{ spl_filesystem_object_cast */ +static int spl_filesystem_object_cast(zval *readobj, zval *writeobj, int type) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(readobj); + + if (type == IS_STRING) { + if (Z_OBJCE_P(readobj)->__tostring) { + return std_object_handlers.cast_object(readobj, writeobj, type); + } + + switch (intern->type) { + case SPL_FS_INFO: + case SPL_FS_FILE: + if (readobj == writeobj) { + zval retval; + zval *retval_ptr = &retval; + + ZVAL_STRINGL(retval_ptr, intern->file_name, intern->file_name_len); + zval_ptr_dtor(readobj); + ZVAL_NEW_STR(writeobj, Z_STR_P(retval_ptr)); + } else { + ZVAL_STRINGL(writeobj, intern->file_name, intern->file_name_len); + } + return SUCCESS; + case SPL_FS_DIR: + if (readobj == writeobj) { + zval retval; + zval *retval_ptr = &retval; + + ZVAL_STRING(retval_ptr, intern->u.dir.entry.d_name); + zval_ptr_dtor(readobj); + ZVAL_NEW_STR(writeobj, Z_STR_P(retval_ptr)); + } else { + ZVAL_STRING(writeobj, intern->u.dir.entry.d_name); + } + return SUCCESS; + } + } else if (type == _IS_BOOL) { + ZVAL_TRUE(writeobj); + return SUCCESS; + } + if (readobj == writeobj) { + zval_ptr_dtor(readobj); + } + ZVAL_NULL(writeobj); + return FAILURE; +} +/* }}} */ + +/* {{{ declare method parameters */ +/* supply a name and default to call by parameter */ +ZEND_BEGIN_ARG_INFO(arginfo_info___construct, 0) + ZEND_ARG_INFO(0, file_name) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_info_openFile, 0, 0, 0) + ZEND_ARG_INFO(0, open_mode) + ZEND_ARG_INFO(0, use_include_path) + ZEND_ARG_INFO(0, context) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_info_optinalFileClass, 0, 0, 0) + ZEND_ARG_INFO(0, class_name) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_optinalSuffix, 0, 0, 0) + ZEND_ARG_INFO(0, suffix) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_splfileinfo_void, 0) +ZEND_END_ARG_INFO() + +/* the method table */ +/* each method can have its own parameters and visibility */ +static const zend_function_entry spl_SplFileInfo_functions[] = { + SPL_ME(SplFileInfo, __construct, arginfo_info___construct, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, getPath, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, getFilename, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, getExtension, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, getBasename, arginfo_optinalSuffix, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, getPathname, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, getPerms, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, getInode, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, getSize, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, getOwner, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, getGroup, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, getATime, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, getMTime, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, getCTime, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, getType, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, isWritable, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, isReadable, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, isExecutable, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, isFile, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, isDir, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, isLink, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, getLinkTarget, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) +#if (!defined(__BEOS__) && !defined(NETWARE) && HAVE_REALPATH) || defined(ZTS) + SPL_ME(SplFileInfo, getRealPath, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) +#endif + SPL_ME(SplFileInfo, getFileInfo, arginfo_info_optinalFileClass, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, getPathInfo, arginfo_info_optinalFileClass, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, openFile, arginfo_info_openFile, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, setFileClass, arginfo_info_optinalFileClass, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, setInfoClass, arginfo_info_optinalFileClass, ZEND_ACC_PUBLIC) + SPL_ME(SplFileInfo, _bad_state_ex, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) + SPL_MA(SplFileInfo, __toString, SplFileInfo, getPathname, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + +ZEND_BEGIN_ARG_INFO(arginfo_dir___construct, 0) + ZEND_ARG_INFO(0, path) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_dir_it_seek, 0) + ZEND_ARG_INFO(0, position) +ZEND_END_ARG_INFO(); + +/* the method table */ +/* each method can have its own parameters and visibility */ +static const zend_function_entry spl_DirectoryIterator_functions[] = { + SPL_ME(DirectoryIterator, __construct, arginfo_dir___construct, ZEND_ACC_PUBLIC) + SPL_ME(DirectoryIterator, getFilename, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(DirectoryIterator, getExtension, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(DirectoryIterator, getBasename, arginfo_optinalSuffix, ZEND_ACC_PUBLIC) + SPL_ME(DirectoryIterator, isDot, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(DirectoryIterator, rewind, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(DirectoryIterator, valid, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(DirectoryIterator, key, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(DirectoryIterator, current, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(DirectoryIterator, next, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(DirectoryIterator, seek, arginfo_dir_it_seek, ZEND_ACC_PUBLIC) + SPL_MA(DirectoryIterator, __toString, DirectoryIterator, getFilename, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + +ZEND_BEGIN_ARG_INFO_EX(arginfo_r_dir___construct, 0, 0, 1) + ZEND_ARG_INFO(0, path) + ZEND_ARG_INFO(0, flags) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_r_dir_hasChildren, 0, 0, 0) + ZEND_ARG_INFO(0, allow_links) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_r_dir_setFlags, 0, 0, 0) + ZEND_ARG_INFO(0, flags) +ZEND_END_ARG_INFO() + +static const zend_function_entry spl_FilesystemIterator_functions[] = { + SPL_ME(FilesystemIterator, __construct, arginfo_r_dir___construct, ZEND_ACC_PUBLIC) + SPL_ME(FilesystemIterator, rewind, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(DirectoryIterator, next, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(FilesystemIterator, key, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(FilesystemIterator, current, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(FilesystemIterator, getFlags, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(FilesystemIterator, setFlags, arginfo_r_dir_setFlags, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + +static const zend_function_entry spl_RecursiveDirectoryIterator_functions[] = { + SPL_ME(RecursiveDirectoryIterator, __construct, arginfo_r_dir___construct, ZEND_ACC_PUBLIC) + SPL_ME(RecursiveDirectoryIterator, hasChildren, arginfo_r_dir_hasChildren, ZEND_ACC_PUBLIC) + SPL_ME(RecursiveDirectoryIterator, getChildren, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(RecursiveDirectoryIterator, getSubPath, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(RecursiveDirectoryIterator, getSubPathname,arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + +#ifdef HAVE_GLOB +static const zend_function_entry spl_GlobIterator_functions[] = { + SPL_ME(GlobIterator, __construct, arginfo_r_dir___construct, ZEND_ACC_PUBLIC) + SPL_ME(GlobIterator, count, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + PHP_FE_END +}; +#endif +/* }}} */ + +static int spl_filesystem_file_read(spl_filesystem_object *intern, int silent) /* {{{ */ +{ + char *buf; + size_t line_len = 0; + zend_long line_add = (intern->u.file.current_line || !Z_ISUNDEF(intern->u.file.current_zval)) ? 1 : 0; + + spl_filesystem_file_free_line(intern); + + if (php_stream_eof(intern->u.file.stream)) { + if (!silent) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Cannot read from file %s", intern->file_name); + } + return FAILURE; + } + + if (intern->u.file.max_line_len > 0) { + buf = safe_emalloc((intern->u.file.max_line_len + 1), sizeof(char), 0); + if (php_stream_get_line(intern->u.file.stream, buf, intern->u.file.max_line_len + 1, &line_len) == NULL) { + efree(buf); + buf = NULL; + } else { + buf[line_len] = '\0'; + } + } else { + buf = php_stream_get_line(intern->u.file.stream, NULL, 0, &line_len); + } + + if (!buf) { + intern->u.file.current_line = estrdup(""); + intern->u.file.current_line_len = 0; + } else { + if (SPL_HAS_FLAG(intern->flags, SPL_FILE_OBJECT_DROP_NEW_LINE)) { + line_len = strcspn(buf, "\r\n"); + buf[line_len] = '\0'; + } + + intern->u.file.current_line = buf; + intern->u.file.current_line_len = line_len; + } + intern->u.file.current_line_num += line_add; + + return SUCCESS; +} /* }}} */ + +static int spl_filesystem_file_call(spl_filesystem_object *intern, zend_function *func_ptr, int pass_num_args, zval *return_value, zval *arg2) /* {{{ */ +{ + zend_fcall_info fci; + zend_fcall_info_cache fcic; + zval *zresource_ptr = &intern->u.file.zresource, retval; + int result; + int num_args = pass_num_args + (arg2 ? 2 : 1); + + zval *params = (zval*)safe_emalloc(num_args, sizeof(zval), 0); + + params[0] = *zresource_ptr; + + if (arg2) { + params[1] = *arg2; + } + + if (zend_get_parameters_array_ex(pass_num_args, params + (arg2 ? 2 : 1)) != SUCCESS) { + efree(params); + WRONG_PARAM_COUNT_WITH_RETVAL(FAILURE); + } + + ZVAL_UNDEF(&retval); + + fci.size = sizeof(fci); + fci.object = NULL; + fci.retval = &retval; + fci.param_count = num_args; + fci.params = params; + fci.no_separation = 1; + ZVAL_STR(&fci.function_name, func_ptr->common.function_name); + + fcic.initialized = 1; + fcic.function_handler = func_ptr; + fcic.calling_scope = NULL; + fcic.called_scope = NULL; + fcic.object = NULL; + + result = zend_call_function(&fci, &fcic); + + if (result == FAILURE || Z_ISUNDEF(retval)) { + RETVAL_FALSE; + } else { + ZVAL_ZVAL(return_value, &retval, 0, 0); + } + + efree(params); + return result; +} /* }}} */ + +#define FileFunctionCall(func_name, pass_num_args, arg2) /* {{{ */ \ +{ \ + zend_function *func_ptr; \ + func_ptr = (zend_function *)zend_hash_str_find_ptr(EG(function_table), #func_name, sizeof(#func_name) - 1); \ + if (func_ptr == NULL) { \ + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Internal error, function '%s' not found. Please report", #func_name); \ + return; \ + } \ + spl_filesystem_file_call(intern, func_ptr, pass_num_args, return_value, arg2); \ +} /* }}} */ + +static int spl_filesystem_file_read_csv(spl_filesystem_object *intern, char delimiter, char enclosure, char escape, zval *return_value) /* {{{ */ +{ + int ret = SUCCESS; + zval *value; + + do { + ret = spl_filesystem_file_read(intern, 1); + } while (ret == SUCCESS && !intern->u.file.current_line_len && SPL_HAS_FLAG(intern->flags, SPL_FILE_OBJECT_SKIP_EMPTY)); + + if (ret == SUCCESS) { + size_t buf_len = intern->u.file.current_line_len; + char *buf = estrndup(intern->u.file.current_line, buf_len); + + if (!Z_ISUNDEF(intern->u.file.current_zval)) { + zval_ptr_dtor(&intern->u.file.current_zval); + ZVAL_UNDEF(&intern->u.file.current_zval); + } + + php_fgetcsv(intern->u.file.stream, delimiter, enclosure, escape, buf_len, buf, &intern->u.file.current_zval); + if (return_value) { + zval_ptr_dtor(return_value); + value = &intern->u.file.current_zval; + ZVAL_DEREF(value); + ZVAL_COPY(return_value, value); + } + } + return ret; +} +/* }}} */ + +static int spl_filesystem_file_read_line_ex(zval * this_ptr, spl_filesystem_object *intern, int silent) /* {{{ */ +{ + zval retval; + + /* 1) use fgetcsv? 2) overloaded call the function, 3) do it directly */ + if (SPL_HAS_FLAG(intern->flags, SPL_FILE_OBJECT_READ_CSV) || intern->u.file.func_getCurr->common.scope != spl_ce_SplFileObject) { + if (php_stream_eof(intern->u.file.stream)) { + if (!silent) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Cannot read from file %s", intern->file_name); + } + return FAILURE; + } + if (SPL_HAS_FLAG(intern->flags, SPL_FILE_OBJECT_READ_CSV)) { + return spl_filesystem_file_read_csv(intern, intern->u.file.delimiter, intern->u.file.enclosure, intern->u.file.escape, NULL); + } else { + zend_execute_data *execute_data = EG(current_execute_data); + zend_call_method_with_0_params(this_ptr, Z_OBJCE(EX(This)), &intern->u.file.func_getCurr, "getCurrentLine", &retval); + } + if (!Z_ISUNDEF(retval)) { + if (intern->u.file.current_line || !Z_ISUNDEF(intern->u.file.current_zval)) { + intern->u.file.current_line_num++; + } + spl_filesystem_file_free_line(intern); + if (Z_TYPE(retval) == IS_STRING) { + intern->u.file.current_line = estrndup(Z_STRVAL(retval), Z_STRLEN(retval)); + intern->u.file.current_line_len = Z_STRLEN(retval); + } else { + zval *value = &retval; + + ZVAL_DEREF(value); + ZVAL_COPY(&intern->u.file.current_zval, value); + } + zval_ptr_dtor(&retval); + return SUCCESS; + } else { + return FAILURE; + } + } else { + return spl_filesystem_file_read(intern, silent); + } +} /* }}} */ + +static int spl_filesystem_file_is_empty_line(spl_filesystem_object *intern) /* {{{ */ +{ + if (intern->u.file.current_line) { + return intern->u.file.current_line_len == 0; + } else if (!Z_ISUNDEF(intern->u.file.current_zval)) { + switch(Z_TYPE(intern->u.file.current_zval)) { + case IS_STRING: + return Z_STRLEN(intern->u.file.current_zval) == 0; + case IS_ARRAY: + if (SPL_HAS_FLAG(intern->flags, SPL_FILE_OBJECT_READ_CSV) + && zend_hash_num_elements(Z_ARRVAL(intern->u.file.current_zval)) == 1) { + uint idx = 0; + zval *first; + + while (Z_ISUNDEF(Z_ARRVAL(intern->u.file.current_zval)->arData[idx].val)) { + idx++; + } + first = &Z_ARRVAL(intern->u.file.current_zval)->arData[idx].val; + return Z_TYPE_P(first) == IS_STRING && Z_STRLEN_P(first) == 0; + } + return zend_hash_num_elements(Z_ARRVAL(intern->u.file.current_zval)) == 0; + case IS_NULL: + return 1; + default: + return 0; + } + } else { + return 1; + } +} +/* }}} */ + +static int spl_filesystem_file_read_line(zval * this_ptr, spl_filesystem_object *intern, int silent) /* {{{ */ +{ + int ret = spl_filesystem_file_read_line_ex(this_ptr, intern, silent); + + while (SPL_HAS_FLAG(intern->flags, SPL_FILE_OBJECT_SKIP_EMPTY) && ret == SUCCESS && spl_filesystem_file_is_empty_line(intern)) { + spl_filesystem_file_free_line(intern); + ret = spl_filesystem_file_read_line_ex(this_ptr, intern, silent); + } + + return ret; +} +/* }}} */ + +static void spl_filesystem_file_rewind(zval * this_ptr, spl_filesystem_object *intern) /* {{{ */ +{ + if(!intern->u.file.stream) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Object not initialized"); + return; + } + if (-1 == php_stream_rewind(intern->u.file.stream)) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Cannot rewind file %s", intern->file_name); + } else { + spl_filesystem_file_free_line(intern); + intern->u.file.current_line_num = 0; + } + if (SPL_HAS_FLAG(intern->flags, SPL_FILE_OBJECT_READ_AHEAD)) { + spl_filesystem_file_read_line(this_ptr, intern, 1); + } +} /* }}} */ + +/* {{{ proto void SplFileObject::__construct(string filename [, string mode = 'r' [, bool use_include_path [, resource context]]]]) + Construct a new file object */ +SPL_METHOD(SplFileObject, __construct) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + zend_bool use_include_path = 0; + char *p1, *p2; + char *tmp_path; + size_t tmp_path_len; + zend_error_handling error_handling; + + intern->u.file.open_mode = NULL; + intern->u.file.open_mode_len = 0; + + if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "p|sbr!", + &intern->file_name, &intern->file_name_len, + &intern->u.file.open_mode, &intern->u.file.open_mode_len, + &use_include_path, &intern->u.file.zcontext) == FAILURE) { + intern->u.file.open_mode = NULL; + intern->file_name = NULL; + return; + } + + if (intern->u.file.open_mode == NULL) { + intern->u.file.open_mode = "r"; + intern->u.file.open_mode_len = 1; + } + + zend_replace_error_handling(EH_THROW, spl_ce_RuntimeException, &error_handling); + + if (spl_filesystem_file_open(intern, use_include_path, 0) == SUCCESS) { + tmp_path_len = strlen(intern->u.file.stream->orig_path); + + if (tmp_path_len > 1 && IS_SLASH_AT(intern->u.file.stream->orig_path, tmp_path_len-1)) { + tmp_path_len--; + } + + tmp_path = estrndup(intern->u.file.stream->orig_path, tmp_path_len); + + p1 = strrchr(tmp_path, '/'); +#if defined(PHP_WIN32) || defined(NETWARE) + p2 = strrchr(tmp_path, '\\'); +#else + p2 = 0; +#endif + if (p1 || p2) { + intern->_path_len = ((p1 > p2 ? p1 : p2) - tmp_path); + } else { + intern->_path_len = 0; + } + + efree(tmp_path); + + intern->_path = estrndup(intern->u.file.stream->orig_path, intern->_path_len); + } + + zend_restore_error_handling(&error_handling); + +} /* }}} */ + +/* {{{ proto void SplTempFileObject::__construct([int max_memory]) + Construct a new temp file object */ +SPL_METHOD(SplTempFileObject, __construct) +{ + zend_long max_memory = PHP_STREAM_MAX_MEM; + char tmp_fname[48]; + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + zend_error_handling error_handling; + + if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "|l", &max_memory) == FAILURE) { + return; + } + + if (max_memory < 0) { + intern->file_name = "php://memory"; + intern->file_name_len = 12; + } else if (ZEND_NUM_ARGS()) { + intern->file_name_len = slprintf(tmp_fname, sizeof(tmp_fname), "php://temp/maxmemory:" ZEND_LONG_FMT, max_memory); + intern->file_name = tmp_fname; + } else { + intern->file_name = "php://temp"; + intern->file_name_len = 10; + } + intern->u.file.open_mode = "wb"; + intern->u.file.open_mode_len = 1; + + zend_replace_error_handling(EH_THROW, spl_ce_RuntimeException, &error_handling); + if (spl_filesystem_file_open(intern, 0, 0) == SUCCESS) { + intern->_path_len = 0; + intern->_path = estrndup("", 0); + } + zend_restore_error_handling(&error_handling); +} /* }}} */ + +/* {{{ proto void SplFileObject::rewind() + Rewind the file and read the first line */ +SPL_METHOD(SplFileObject, rewind) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + spl_filesystem_file_rewind(getThis(), intern); +} /* }}} */ + +/* {{{ proto void SplFileObject::eof() + Return whether end of file is reached */ +SPL_METHOD(SplFileObject, eof) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if(!intern->u.file.stream) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Object not initialized"); + return; + } + + RETURN_BOOL(php_stream_eof(intern->u.file.stream)); +} /* }}} */ + +/* {{{ proto void SplFileObject::valid() + Return !eof() */ +SPL_METHOD(SplFileObject, valid) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (SPL_HAS_FLAG(intern->flags, SPL_FILE_OBJECT_READ_AHEAD)) { + RETURN_BOOL(intern->u.file.current_line || !Z_ISUNDEF(intern->u.file.current_zval)); + } else { + if(!intern->u.file.stream) { + RETURN_FALSE; + } + RETVAL_BOOL(!php_stream_eof(intern->u.file.stream)); + } +} /* }}} */ + +/* {{{ proto string SplFileObject::fgets() + Rturn next line from file */ +SPL_METHOD(SplFileObject, fgets) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if(!intern->u.file.stream) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Object not initialized"); + return; + } + + if (spl_filesystem_file_read(intern, 0) == FAILURE) { + RETURN_FALSE; + } + RETURN_STRINGL(intern->u.file.current_line, intern->u.file.current_line_len); +} /* }}} */ + +/* {{{ proto string SplFileObject::current() + Return current line from file */ +SPL_METHOD(SplFileObject, current) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if(!intern->u.file.stream) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Object not initialized"); + return; + } + + if (!intern->u.file.current_line && Z_ISUNDEF(intern->u.file.current_zval)) { + spl_filesystem_file_read_line(getThis(), intern, 1); + } + if (intern->u.file.current_line && (!SPL_HAS_FLAG(intern->flags, SPL_FILE_OBJECT_READ_CSV) || Z_ISUNDEF(intern->u.file.current_zval))) { + RETURN_STRINGL(intern->u.file.current_line, intern->u.file.current_line_len); + } else if (!Z_ISUNDEF(intern->u.file.current_zval)) { + zval *value = &intern->u.file.current_zval; + + ZVAL_DEREF(value); + ZVAL_COPY(return_value, value); + return; + } + RETURN_FALSE; +} /* }}} */ + +/* {{{ proto int SplFileObject::key() + Return line number */ +SPL_METHOD(SplFileObject, key) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + +/* Do not read the next line to support correct counting with fgetc() + if (!intern->current_line) { + spl_filesystem_file_read_line(getThis(), intern, 1); + } */ + RETURN_LONG(intern->u.file.current_line_num); +} /* }}} */ + +/* {{{ proto void SplFileObject::next() + Read next line */ +SPL_METHOD(SplFileObject, next) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + spl_filesystem_file_free_line(intern); + if (SPL_HAS_FLAG(intern->flags, SPL_FILE_OBJECT_READ_AHEAD)) { + spl_filesystem_file_read_line(getThis(), intern, 1); + } + intern->u.file.current_line_num++; +} /* }}} */ + +/* {{{ proto void SplFileObject::setFlags(int flags) + Set file handling flags */ +SPL_METHOD(SplFileObject, setFlags) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &intern->flags) == FAILURE) { + return; + } +} /* }}} */ + +/* {{{ proto int SplFileObject::getFlags() + Get file handling flags */ +SPL_METHOD(SplFileObject, getFlags) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + RETURN_LONG(intern->flags & SPL_FILE_OBJECT_MASK); +} /* }}} */ + +/* {{{ proto void SplFileObject::setMaxLineLen(int max_len) + Set maximum line length */ +SPL_METHOD(SplFileObject, setMaxLineLen) +{ + zend_long max_len; + + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &max_len) == FAILURE) { + return; + } + + if (max_len < 0) { + zend_throw_exception_ex(spl_ce_DomainException, 0, "Maximum line length must be greater than or equal zero"); + return; + } + + intern->u.file.max_line_len = max_len; +} /* }}} */ + +/* {{{ proto int SplFileObject::getMaxLineLen() + Get maximum line length */ +SPL_METHOD(SplFileObject, getMaxLineLen) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + RETURN_LONG((zend_long)intern->u.file.max_line_len); +} /* }}} */ + +/* {{{ proto bool SplFileObject::hasChildren() + Return false */ +SPL_METHOD(SplFileObject, hasChildren) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + RETURN_FALSE; +} /* }}} */ + +/* {{{ proto bool SplFileObject::getChildren() + Read NULL */ +SPL_METHOD(SplFileObject, getChildren) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } + /* return NULL */ +} /* }}} */ + +/* {{{ FileFunction */ +#define FileFunction(func_name) \ +SPL_METHOD(SplFileObject, func_name) \ +{ \ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); \ + FileFunctionCall(func_name, ZEND_NUM_ARGS(), NULL); \ +} +/* }}} */ + +/* {{{ proto array SplFileObject::fgetcsv([string delimiter [, string enclosure [, escape = '\\']]]) + Return current line as csv */ +SPL_METHOD(SplFileObject, fgetcsv) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + char delimiter = intern->u.file.delimiter, enclosure = intern->u.file.enclosure, escape = intern->u.file.escape; + char *delim = NULL, *enclo = NULL, *esc = NULL; + size_t d_len = 0, e_len = 0, esc_len = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|sss", &delim, &d_len, &enclo, &e_len, &esc, &esc_len) == SUCCESS) { + + if(!intern->u.file.stream) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Object not initialized"); + return; + } + + switch(ZEND_NUM_ARGS()) + { + case 3: + if (esc_len != 1) { + php_error_docref(NULL, E_WARNING, "escape must be a character"); + RETURN_FALSE; + } + escape = esc[0]; + /* no break */ + case 2: + if (e_len != 1) { + php_error_docref(NULL, E_WARNING, "enclosure must be a character"); + RETURN_FALSE; + } + enclosure = enclo[0]; + /* no break */ + case 1: + if (d_len != 1) { + php_error_docref(NULL, E_WARNING, "delimiter must be a character"); + RETURN_FALSE; + } + delimiter = delim[0]; + /* no break */ + case 0: + break; + } + spl_filesystem_file_read_csv(intern, delimiter, enclosure, escape, return_value); + } +} +/* }}} */ + +/* {{{ proto int SplFileObject::fputcsv(array fields, [string delimiter [, string enclosure [, string escape]]]) + Output a field array as a CSV line */ +SPL_METHOD(SplFileObject, fputcsv) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + char delimiter = intern->u.file.delimiter, enclosure = intern->u.file.enclosure, escape = intern->u.file.escape; + char *delim = NULL, *enclo = NULL, *esc = NULL; + size_t d_len = 0, e_len = 0, esc_len = 0; + zend_long ret; + zval *fields = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "a|sss", &fields, &delim, &d_len, &enclo, &e_len, &esc, &esc_len) == SUCCESS) { + switch(ZEND_NUM_ARGS()) + { + case 4: + if (esc_len != 1) { + php_error_docref(NULL, E_WARNING, "escape must be a character"); + RETURN_FALSE; + } + escape = esc[0]; + /* no break */ + case 3: + if (e_len != 1) { + php_error_docref(NULL, E_WARNING, "enclosure must be a character"); + RETURN_FALSE; + } + enclosure = enclo[0]; + /* no break */ + case 2: + if (d_len != 1) { + php_error_docref(NULL, E_WARNING, "delimiter must be a character"); + RETURN_FALSE; + } + delimiter = delim[0]; + /* no break */ + case 1: + case 0: + break; + } + ret = php_fputcsv(intern->u.file.stream, fields, delimiter, enclosure, escape); + RETURN_LONG(ret); + } +} +/* }}} */ + +/* {{{ proto void SplFileObject::setCsvControl([string delimiter [, string enclosure [, string escape ]]]) + Set the delimiter, enclosure and escape character used in fgetcsv */ +SPL_METHOD(SplFileObject, setCsvControl) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + char delimiter = ',', enclosure = '"', escape='\\'; + char *delim = NULL, *enclo = NULL, *esc = NULL; + size_t d_len = 0, e_len = 0, esc_len = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|sss", &delim, &d_len, &enclo, &e_len, &esc, &esc_len) == SUCCESS) { + switch(ZEND_NUM_ARGS()) + { + case 3: + if (esc_len != 1) { + php_error_docref(NULL, E_WARNING, "escape must be a character"); + RETURN_FALSE; + } + escape = esc[0]; + /* no break */ + case 2: + if (e_len != 1) { + php_error_docref(NULL, E_WARNING, "enclosure must be a character"); + RETURN_FALSE; + } + enclosure = enclo[0]; + /* no break */ + case 1: + if (d_len != 1) { + php_error_docref(NULL, E_WARNING, "delimiter must be a character"); + RETURN_FALSE; + } + delimiter = delim[0]; + /* no break */ + case 0: + break; + } + intern->u.file.delimiter = delimiter; + intern->u.file.enclosure = enclosure; + intern->u.file.escape = escape; + } +} +/* }}} */ + +/* {{{ proto array SplFileObject::getCsvControl() + Get the delimiter, enclosure and escape character used in fgetcsv */ +SPL_METHOD(SplFileObject, getCsvControl) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + char delimiter[2], enclosure[2], escape[2]; + + array_init(return_value); + + delimiter[0] = intern->u.file.delimiter; + delimiter[1] = '\0'; + enclosure[0] = intern->u.file.enclosure; + enclosure[1] = '\0'; + escape[0] = intern->u.file.escape; + escape[1] = '\0'; + + add_next_index_string(return_value, delimiter); + add_next_index_string(return_value, enclosure); + add_next_index_string(return_value, escape); +} +/* }}} */ + +/* {{{ proto bool SplFileObject::flock(int operation [, int &wouldblock]) + Portable file locking */ +FileFunction(flock) +/* }}} */ + +/* {{{ proto bool SplFileObject::fflush() + Flush the file */ +SPL_METHOD(SplFileObject, fflush) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if(!intern->u.file.stream) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Object not initialized"); + return; + } + + RETURN_BOOL(!php_stream_flush(intern->u.file.stream)); +} /* }}} */ + +/* {{{ proto int SplFileObject::ftell() + Return current file position */ +SPL_METHOD(SplFileObject, ftell) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + zend_long ret; + + if(!intern->u.file.stream) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Object not initialized"); + return; + } + + ret = php_stream_tell(intern->u.file.stream); + + if (ret == -1) { + RETURN_FALSE; + } else { + RETURN_LONG(ret); + } +} /* }}} */ + +/* {{{ proto int SplFileObject::fseek(int pos [, int whence = SEEK_SET]) + Return current file position */ +SPL_METHOD(SplFileObject, fseek) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + zend_long pos, whence = SEEK_SET; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l|l", &pos, &whence) == FAILURE) { + return; + } + + if(!intern->u.file.stream) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Object not initialized"); + return; + } + + spl_filesystem_file_free_line(intern); + RETURN_LONG(php_stream_seek(intern->u.file.stream, pos, (int)whence)); +} /* }}} */ + +/* {{{ proto int SplFileObject::fgetc() + Get a character form the file */ +SPL_METHOD(SplFileObject, fgetc) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + char buf[2]; + int result; + + if(!intern->u.file.stream) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Object not initialized"); + return; + } + + spl_filesystem_file_free_line(intern); + + result = php_stream_getc(intern->u.file.stream); + + if (result == EOF) { + RETVAL_FALSE; + } else { + if (result == '\n') { + intern->u.file.current_line_num++; + } + buf[0] = result; + buf[1] = '\0'; + + RETURN_STRINGL(buf, 1); + } +} /* }}} */ + +/* {{{ proto string SplFileObject::fgetss([string allowable_tags]) + Get a line from file pointer and strip HTML tags */ +SPL_METHOD(SplFileObject, fgetss) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + zval arg2; + + if(!intern->u.file.stream) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Object not initialized"); + return; + } + + if (intern->u.file.max_line_len > 0) { + ZVAL_LONG(&arg2, intern->u.file.max_line_len); + } else { + ZVAL_LONG(&arg2, 1024); + } + + spl_filesystem_file_free_line(intern); + intern->u.file.current_line_num++; + + FileFunctionCall(fgetss, ZEND_NUM_ARGS(), &arg2); +} /* }}} */ + +/* {{{ proto int SplFileObject::fpassthru() + Output all remaining data from a file pointer */ +SPL_METHOD(SplFileObject, fpassthru) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if(!intern->u.file.stream) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Object not initialized"); + return; + } + + RETURN_LONG(php_stream_passthru(intern->u.file.stream)); +} /* }}} */ + +/* {{{ proto bool SplFileObject::fscanf(string format [, string ...]) + Implements a mostly ANSI compatible fscanf() */ +SPL_METHOD(SplFileObject, fscanf) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + + if(!intern->u.file.stream) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Object not initialized"); + return; + } + + spl_filesystem_file_free_line(intern); + intern->u.file.current_line_num++; + + FileFunctionCall(fscanf, ZEND_NUM_ARGS(), NULL); +} +/* }}} */ + +/* {{{ proto mixed SplFileObject::fwrite(string str [, int length]) + Binary-safe file write */ +SPL_METHOD(SplFileObject, fwrite) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + char *str; + size_t str_len; + zend_long length = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &str, &str_len, &length) == FAILURE) { + return; + } + + if(!intern->u.file.stream) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Object not initialized"); + return; + } + + if (ZEND_NUM_ARGS() > 1) { + if (length >= 0) { + str_len = MIN((size_t)length, str_len); + } else { + /* Negative length given, nothing to write */ + str_len = 0; + } + } + if (!str_len) { + RETURN_LONG(0); + } + + RETURN_LONG(php_stream_write(intern->u.file.stream, str, str_len)); +} /* }}} */ + +SPL_METHOD(SplFileObject, fread) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + zend_long length = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &length) == FAILURE) { + return; + } + + if(!intern->u.file.stream) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Object not initialized"); + return; + } + + if (length <= 0) { + php_error_docref(NULL, E_WARNING, "Length parameter must be greater than 0"); + RETURN_FALSE; + } + + ZVAL_NEW_STR(return_value, zend_string_alloc(length, 0)); + Z_STRLEN_P(return_value) = php_stream_read(intern->u.file.stream, Z_STRVAL_P(return_value), length); + + /* needed because recv/read/gzread doesnt put a null at the end*/ + Z_STRVAL_P(return_value)[Z_STRLEN_P(return_value)] = 0; +} + +/* {{{ proto bool SplFileObject::fstat() + Stat() on a filehandle */ +FileFunction(fstat) +/* }}} */ + +/* {{{ proto bool SplFileObject::ftruncate(int size) + Truncate file to 'size' length */ +SPL_METHOD(SplFileObject, ftruncate) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + zend_long size; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &size) == FAILURE) { + return; + } + + if(!intern->u.file.stream) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Object not initialized"); + return; + } + + if (!php_stream_truncate_supported(intern->u.file.stream)) { + zend_throw_exception_ex(spl_ce_LogicException, 0, "Can't truncate file %s", intern->file_name); + RETURN_FALSE; + } + + RETURN_BOOL(0 == php_stream_truncate_set_size(intern->u.file.stream, size)); +} /* }}} */ + +/* {{{ proto void SplFileObject::seek(int line_pos) + Seek to specified line */ +SPL_METHOD(SplFileObject, seek) +{ + spl_filesystem_object *intern = Z_SPLFILESYSTEM_P(getThis()); + zend_long line_pos; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &line_pos) == FAILURE) { + return; + } + if(!intern->u.file.stream) { + zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Object not initialized"); + return; + } + + if (line_pos < 0) { + zend_throw_exception_ex(spl_ce_LogicException, 0, "Can't seek file %s to negative line " ZEND_LONG_FMT, intern->file_name, line_pos); + RETURN_FALSE; + } + + spl_filesystem_file_rewind(getThis(), intern); + + while(intern->u.file.current_line_num < line_pos) { + if (spl_filesystem_file_read_line(getThis(), intern, 1) == FAILURE) { + break; + } + } +} /* }}} */ + +/* {{{ Function/Class/Method definitions */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_file_object___construct, 0, 0, 1) + ZEND_ARG_INFO(0, file_name) + ZEND_ARG_INFO(0, open_mode) + ZEND_ARG_INFO(0, use_include_path) + ZEND_ARG_INFO(0, context) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_file_object_setFlags, 0) + ZEND_ARG_INFO(0, flags) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_file_object_setMaxLineLen, 0) + ZEND_ARG_INFO(0, max_len) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_file_object_fgetcsv, 0, 0, 0) + ZEND_ARG_INFO(0, delimiter) + ZEND_ARG_INFO(0, enclosure) + ZEND_ARG_INFO(0, escape) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_file_object_fputcsv, 0, 0, 1) + ZEND_ARG_INFO(0, fields) + ZEND_ARG_INFO(0, delimiter) + ZEND_ARG_INFO(0, enclosure) + ZEND_ARG_INFO(0, escape) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_file_object_flock, 0, 0, 1) + ZEND_ARG_INFO(0, operation) + ZEND_ARG_INFO(1, wouldblock) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_file_object_fseek, 0, 0, 1) + ZEND_ARG_INFO(0, pos) + ZEND_ARG_INFO(0, whence) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_file_object_fgetss, 0, 0, 0) + ZEND_ARG_INFO(0, allowable_tags) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_file_object_fscanf, 0, 0, 1) + ZEND_ARG_INFO(0, format) + ZEND_ARG_VARIADIC_INFO(1, vars) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_file_object_fwrite, 0, 0, 1) + ZEND_ARG_INFO(0, str) + ZEND_ARG_INFO(0, length) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_file_object_fread, 0, 0, 1) + ZEND_ARG_INFO(0, length) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_file_object_ftruncate, 0, 0, 1) + ZEND_ARG_INFO(0, size) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_file_object_seek, 0, 0, 1) + ZEND_ARG_INFO(0, line_pos) +ZEND_END_ARG_INFO() + +static const zend_function_entry spl_SplFileObject_functions[] = { + SPL_ME(SplFileObject, __construct, arginfo_file_object___construct, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, rewind, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, eof, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, valid, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, fgets, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, fgetcsv, arginfo_file_object_fgetcsv, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, fputcsv, arginfo_file_object_fputcsv, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, setCsvControl, arginfo_file_object_fgetcsv, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, getCsvControl, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, flock, arginfo_file_object_flock, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, fflush, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, ftell, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, fseek, arginfo_file_object_fseek, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, fgetc, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, fpassthru, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, fgetss, arginfo_file_object_fgetss, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, fscanf, arginfo_file_object_fscanf, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, fwrite, arginfo_file_object_fwrite, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, fread, arginfo_file_object_fread, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, fstat, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, ftruncate, arginfo_file_object_ftruncate, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, current, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, key, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, next, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, setFlags, arginfo_file_object_setFlags, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, getFlags, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, setMaxLineLen, arginfo_file_object_setMaxLineLen, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, getMaxLineLen, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, hasChildren, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, getChildren, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_ME(SplFileObject, seek, arginfo_file_object_seek, ZEND_ACC_PUBLIC) + /* mappings */ + SPL_MA(SplFileObject, getCurrentLine, SplFileObject, fgets, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + SPL_MA(SplFileObject, __toString, SplFileObject, current, arginfo_splfileinfo_void, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + +ZEND_BEGIN_ARG_INFO_EX(arginfo_temp_file_object___construct, 0, 0, 0) + ZEND_ARG_INFO(0, max_memory) +ZEND_END_ARG_INFO() + +static const zend_function_entry spl_SplTempFileObject_functions[] = { + SPL_ME(SplTempFileObject, __construct, arginfo_temp_file_object___construct, ZEND_ACC_PUBLIC) + PHP_FE_END +}; +/* }}} */ + +/* {{{ PHP_MINIT_FUNCTION(spl_directory) + */ +PHP_MINIT_FUNCTION(spl_directory) +{ + REGISTER_SPL_STD_CLASS_EX(SplFileInfo, spl_filesystem_object_new, spl_SplFileInfo_functions); + memcpy(&spl_filesystem_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); + spl_filesystem_object_handlers.offset = XtOffsetOf(spl_filesystem_object, std); + spl_filesystem_object_handlers.clone_obj = spl_filesystem_object_clone; + spl_filesystem_object_handlers.cast_object = spl_filesystem_object_cast; + spl_filesystem_object_handlers.get_debug_info = spl_filesystem_object_get_debug_info; + spl_filesystem_object_handlers.dtor_obj = zend_objects_destroy_object; + spl_filesystem_object_handlers.free_obj = spl_filesystem_object_free_storage; + spl_ce_SplFileInfo->serialize = zend_class_serialize_deny; + spl_ce_SplFileInfo->unserialize = zend_class_unserialize_deny; + + + REGISTER_SPL_SUB_CLASS_EX(DirectoryIterator, SplFileInfo, spl_filesystem_object_new, spl_DirectoryIterator_functions); + zend_class_implements(spl_ce_DirectoryIterator, 1, zend_ce_iterator); + REGISTER_SPL_IMPLEMENTS(DirectoryIterator, SeekableIterator); + + spl_ce_DirectoryIterator->get_iterator = spl_filesystem_dir_get_iterator; + + REGISTER_SPL_SUB_CLASS_EX(FilesystemIterator, DirectoryIterator, spl_filesystem_object_new, spl_FilesystemIterator_functions); + + REGISTER_SPL_CLASS_CONST_LONG(FilesystemIterator, "CURRENT_MODE_MASK", SPL_FILE_DIR_CURRENT_MODE_MASK); + REGISTER_SPL_CLASS_CONST_LONG(FilesystemIterator, "CURRENT_AS_PATHNAME", SPL_FILE_DIR_CURRENT_AS_PATHNAME); + REGISTER_SPL_CLASS_CONST_LONG(FilesystemIterator, "CURRENT_AS_FILEINFO", SPL_FILE_DIR_CURRENT_AS_FILEINFO); + REGISTER_SPL_CLASS_CONST_LONG(FilesystemIterator, "CURRENT_AS_SELF", SPL_FILE_DIR_CURRENT_AS_SELF); + REGISTER_SPL_CLASS_CONST_LONG(FilesystemIterator, "KEY_MODE_MASK", SPL_FILE_DIR_KEY_MODE_MASK); + REGISTER_SPL_CLASS_CONST_LONG(FilesystemIterator, "KEY_AS_PATHNAME", SPL_FILE_DIR_KEY_AS_PATHNAME); + REGISTER_SPL_CLASS_CONST_LONG(FilesystemIterator, "FOLLOW_SYMLINKS", SPL_FILE_DIR_FOLLOW_SYMLINKS); + REGISTER_SPL_CLASS_CONST_LONG(FilesystemIterator, "KEY_AS_FILENAME", SPL_FILE_DIR_KEY_AS_FILENAME); + REGISTER_SPL_CLASS_CONST_LONG(FilesystemIterator, "NEW_CURRENT_AND_KEY", SPL_FILE_DIR_KEY_AS_FILENAME|SPL_FILE_DIR_CURRENT_AS_FILEINFO); + REGISTER_SPL_CLASS_CONST_LONG(FilesystemIterator, "OTHER_MODE_MASK", SPL_FILE_DIR_OTHERS_MASK); + REGISTER_SPL_CLASS_CONST_LONG(FilesystemIterator, "SKIP_DOTS", SPL_FILE_DIR_SKIPDOTS); + REGISTER_SPL_CLASS_CONST_LONG(FilesystemIterator, "UNIX_PATHS", SPL_FILE_DIR_UNIXPATHS); + + spl_ce_FilesystemIterator->get_iterator = spl_filesystem_tree_get_iterator; + + REGISTER_SPL_SUB_CLASS_EX(RecursiveDirectoryIterator, FilesystemIterator, spl_filesystem_object_new, spl_RecursiveDirectoryIterator_functions); + REGISTER_SPL_IMPLEMENTS(RecursiveDirectoryIterator, RecursiveIterator); + + memcpy(&spl_filesystem_object_check_handlers, &spl_filesystem_object_handlers, sizeof(zend_object_handlers)); + spl_filesystem_object_check_handlers.get_method = spl_filesystem_object_get_method_check; + +#ifdef HAVE_GLOB + REGISTER_SPL_SUB_CLASS_EX(GlobIterator, FilesystemIterator, spl_filesystem_object_new_check, spl_GlobIterator_functions); + REGISTER_SPL_IMPLEMENTS(GlobIterator, Countable); +#endif + + REGISTER_SPL_SUB_CLASS_EX(SplFileObject, SplFileInfo, spl_filesystem_object_new_check, spl_SplFileObject_functions); + REGISTER_SPL_IMPLEMENTS(SplFileObject, RecursiveIterator); + REGISTER_SPL_IMPLEMENTS(SplFileObject, SeekableIterator); + + REGISTER_SPL_CLASS_CONST_LONG(SplFileObject, "DROP_NEW_LINE", SPL_FILE_OBJECT_DROP_NEW_LINE); + REGISTER_SPL_CLASS_CONST_LONG(SplFileObject, "READ_AHEAD", SPL_FILE_OBJECT_READ_AHEAD); + REGISTER_SPL_CLASS_CONST_LONG(SplFileObject, "SKIP_EMPTY", SPL_FILE_OBJECT_SKIP_EMPTY); + REGISTER_SPL_CLASS_CONST_LONG(SplFileObject, "READ_CSV", SPL_FILE_OBJECT_READ_CSV); + + REGISTER_SPL_SUB_CLASS_EX(SplTempFileObject, SplFileObject, spl_filesystem_object_new_check, spl_SplTempFileObject_functions); + return SUCCESS; +} +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/ext/spl/tests/bug78863.phpt b/ext/spl/tests/bug78863.phpt new file mode 100644 index 0000000000000..dc88d98deee8c --- /dev/null +++ b/ext/spl/tests/bug78863.phpt @@ -0,0 +1,31 @@ +--TEST-- +Bug #78863 (DirectoryIterator class silently truncates after a null byte) +--FILE-- +isDot()) { + var_dump($fileinfo->getFilename()); + } +} +?> +--EXPECTF-- +Fatal error: Uncaught UnexpectedValueException: DirectoryIterator::__construct() expects parameter 1 to be a valid path, string given in %s:%d +Stack trace: +#0 %s(%d): DirectoryIterator->__construct('%s') +#1 {main} + thrown in %s on line %d +--CLEAN-- + diff --git a/ext/standard/crypt.c b/ext/standard/crypt.c index 3a6826e9fd8da..5122bca5c137c 100644 --- a/ext/standard/crypt.c +++ b/ext/standard/crypt.c @@ -156,6 +156,7 @@ PHPAPI zend_string *php_crypt(const char *password, const int pass_len, const ch } else if ( salt[0] == '$' && salt[1] == '2' && + salt[2] != 0 && salt[3] == '$') { char output[PHP_MAX_SALT_LEN + 1]; diff --git a/ext/standard/crypt.c.orig b/ext/standard/crypt.c.orig new file mode 100644 index 0000000000000..3a6826e9fd8da --- /dev/null +++ b/ext/standard/crypt.c.orig @@ -0,0 +1,294 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2017 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Stig Bakken | + | Zeev Suraski | + | Rasmus Lerdorf | + | Pierre Joye | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#include + +#include "php.h" + +#if HAVE_UNISTD_H +#include +#endif +#if PHP_USE_PHP_CRYPT_R +# include "php_crypt_r.h" +# include "crypt_freesec.h" +#else +# if HAVE_CRYPT_H +# if defined(CRYPT_R_GNU_SOURCE) && !defined(_GNU_SOURCE) +# define _GNU_SOURCE +# endif +# include +# endif +#endif +#if TM_IN_SYS_TIME +#include +#else +#include +#endif +#if HAVE_STRING_H +#include +#else +#include +#endif + +#ifdef PHP_WIN32 +#include +#endif + +#include "php_crypt.h" +#include "php_random.h" + +/* sha512 crypt has the maximal salt length of 123 characters */ +#define PHP_MAX_SALT_LEN 123 + +/* Used to check DES salts to ensure that they contain only valid characters */ +#define IS_VALID_SALT_CHARACTER(c) (((c) >= '.' && (c) <= '9') || ((c) >= 'A' && (c) <= 'Z') || ((c) >= 'a' && (c) <= 'z')) + +#define DES_INVALID_SALT_ERROR "Supplied salt is not valid for DES. Possible bug in provided salt format." + + +PHP_MINIT_FUNCTION(crypt) /* {{{ */ +{ + REGISTER_LONG_CONSTANT("CRYPT_SALT_LENGTH", PHP_MAX_SALT_LEN, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("CRYPT_STD_DES", 1, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("CRYPT_EXT_DES", 1, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("CRYPT_MD5", 1, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("CRYPT_BLOWFISH", 1, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("CRYPT_SHA256", 1, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("CRYPT_SHA512", 1, CONST_CS | CONST_PERSISTENT); + +#if PHP_USE_PHP_CRYPT_R + php_init_crypt_r(); +#endif + + return SUCCESS; +} +/* }}} */ + +PHP_MSHUTDOWN_FUNCTION(crypt) /* {{{ */ +{ +#if PHP_USE_PHP_CRYPT_R + php_shutdown_crypt_r(); +#endif + + return SUCCESS; +} +/* }}} */ + +static unsigned char itoa64[] = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + +static void php_to64(char *s, int n) /* {{{ */ +{ + while (--n >= 0) { + *s = itoa64[*s & 0x3f]; + s++; + } +} +/* }}} */ + +PHPAPI zend_string *php_crypt(const char *password, const int pass_len, const char *salt, int salt_len, zend_bool quiet) +{ + char *crypt_res; + zend_string *result; +/* Windows (win32/crypt) has a stripped down version of libxcrypt and + a CryptoApi md5_crypt implementation */ +#if PHP_USE_PHP_CRYPT_R + { + struct php_crypt_extended_data buffer; + + if (salt[0]=='$' && salt[1]=='1' && salt[2]=='$') { + char output[MD5_HASH_MAX_LEN], *out; + + out = php_md5_crypt_r(password, salt, output); + if (out) { + return zend_string_init(out, strlen(out), 0); + } + return NULL; + } else if (salt[0]=='$' && salt[1]=='6' && salt[2]=='$') { + char *output; + output = emalloc(PHP_MAX_SALT_LEN); + + crypt_res = php_sha512_crypt_r(password, salt, output, PHP_MAX_SALT_LEN); + if (!crypt_res) { + memset(output, 0, PHP_MAX_SALT_LEN); + efree(output); + return NULL; + } else { + result = zend_string_init(output, strlen(output), 0); + memset(output, 0, PHP_MAX_SALT_LEN); + efree(output); + return result; + } + } else if (salt[0]=='$' && salt[1]=='5' && salt[2]=='$') { + char *output; + output = emalloc(PHP_MAX_SALT_LEN); + + crypt_res = php_sha256_crypt_r(password, salt, output, PHP_MAX_SALT_LEN); + if (!crypt_res) { + memset(output, 0, PHP_MAX_SALT_LEN); + efree(output); + return NULL; + } else { + result = zend_string_init(output, strlen(output), 0); + memset(output, 0, PHP_MAX_SALT_LEN); + efree(output); + return result; + } + } else if ( + salt[0] == '$' && + salt[1] == '2' && + salt[3] == '$') { + char output[PHP_MAX_SALT_LEN + 1]; + + memset(output, 0, PHP_MAX_SALT_LEN + 1); + + crypt_res = php_crypt_blowfish_rn(password, salt, output, sizeof(output)); + if (!crypt_res) { + ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN + 1); + return NULL; + } else { + result = zend_string_init(output, strlen(output), 0); + ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN + 1); + return result; + } + } else if (salt[0] == '*' && (salt[1] == '0' || salt[1] == '1')) { + return NULL; + } else { + /* DES Fallback */ + + /* Only check the salt if it's not EXT_DES */ + if (salt[0] != '_') { + /* DES style hashes */ + if (!IS_VALID_SALT_CHARACTER(salt[0]) || !IS_VALID_SALT_CHARACTER(salt[1])) { + if (!quiet) { + /* error consistently about invalid DES fallbacks */ + php_error_docref(NULL, E_DEPRECATED, DES_INVALID_SALT_ERROR); + } + } + } + + memset(&buffer, 0, sizeof(buffer)); + _crypt_extended_init_r(); + + crypt_res = _crypt_extended_r(password, salt, &buffer); + if (!crypt_res || (salt[0] == '*' && salt[1] == '0')) { + return NULL; + } else { + result = zend_string_init(crypt_res, strlen(crypt_res), 0); + return result; + } + } + } +#else + + if (salt[0] != '$' && salt[0] != '_' && (!IS_VALID_SALT_CHARACTER(salt[0]) || !IS_VALID_SALT_CHARACTER(salt[1]))) { + if (!quiet) { + /* error consistently about invalid DES fallbacks */ + php_error_docref(NULL, E_DEPRECATED, DES_INVALID_SALT_ERROR); + } + } + +# if defined(HAVE_CRYPT_R) && (defined(_REENTRANT) || defined(_THREAD_SAFE)) + { +# if defined(CRYPT_R_STRUCT_CRYPT_DATA) + struct crypt_data buffer; + memset(&buffer, 0, sizeof(buffer)); +# elif defined(CRYPT_R_CRYPTD) + CRYPTD buffer; +# else +# error Data struct used by crypt_r() is unknown. Please report. +# endif + crypt_res = crypt_r(password, salt, &buffer); + } +# elif defined(HAVE_CRYPT) + crypt_res = crypt(password, salt); +# else +# error No crypt() implementation +# endif +#endif + + if (!crypt_res || (salt[0] == '*' && salt[1] == '0')) { + return NULL; + } else { + result = zend_string_init(crypt_res, strlen(crypt_res), 0); + return result; + } +} +/* }}} */ + + +/* {{{ proto string crypt(string str [, string salt]) + Hash a string */ +PHP_FUNCTION(crypt) +{ + char salt[PHP_MAX_SALT_LEN + 1]; + char *str, *salt_in = NULL; + size_t str_len, salt_in_len = 0; + zend_string *result; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|s", &str, &str_len, &salt_in, &salt_in_len) == FAILURE) { + return; + } + + salt[0] = salt[PHP_MAX_SALT_LEN] = '\0'; + + /* This will produce suitable results if people depend on DES-encryption + * available (passing always 2-character salt). At least for glibc6.1 */ + memset(&salt[1], '$', PHP_MAX_SALT_LEN - 1); + + if (salt_in) { + memcpy(salt, salt_in, MIN(PHP_MAX_SALT_LEN, salt_in_len)); + } else { + php_error_docref(NULL, E_NOTICE, "No salt parameter was specified. You must use a randomly generated salt and a strong hash function to produce a secure hash."); + } + + /* The automatic salt generation covers standard DES, md5-crypt and Blowfish (simple) */ + if (!*salt) { + strncpy(salt, "$1$", 3); + php_random_bytes_throw(&salt[3], 8); + php_to64(&salt[3], 8); + strncpy(&salt[11], "$", PHP_MAX_SALT_LEN - 11); + salt_in_len = strlen(salt); + } else { + salt_in_len = MIN(PHP_MAX_SALT_LEN, salt_in_len); + } + salt[salt_in_len] = '\0'; + + if ((result = php_crypt(str, (int)str_len, salt, (int)salt_in_len, 0)) == NULL) { + if (salt[0] == '*' && salt[1] == '0') { + RETURN_STRING("*1"); + } else { + RETURN_STRING("*0"); + } + } + RETURN_STR(result); +} +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/ext/standard/crypt_blowfish.c b/ext/standard/crypt_blowfish.c index 5cf306715f139..e923b55ed008b 100644 --- a/ext/standard/crypt_blowfish.c +++ b/ext/standard/crypt_blowfish.c @@ -377,7 +377,6 @@ static unsigned char BF_atoi64[0x60] = { #define BF_safe_atoi64(dst, src) \ { \ tmp = (unsigned char)(src); \ - if (tmp == '$') break; /* PHP hack */ \ if ((unsigned int)(tmp -= 0x20) >= 0x60) return -1; \ tmp = BF_atoi64[tmp]; \ if (tmp > 63) return -1; \ @@ -405,13 +404,6 @@ static int BF_decode(BF_word *dst, const char *src, int size) *dptr++ = ((c3 & 0x03) << 6) | c4; } while (dptr < end); - if (end - dptr == size) { - return -1; - } - - while (dptr < end) /* PHP hack */ - *dptr++ = 0; - return 0; } diff --git a/ext/standard/crypt_blowfish.c.orig b/ext/standard/crypt_blowfish.c.orig new file mode 100644 index 0000000000000..5cf306715f139 --- /dev/null +++ b/ext/standard/crypt_blowfish.c.orig @@ -0,0 +1,918 @@ +/* $Id$ */ +/* + * The crypt_blowfish homepage is: + * + * http://www.openwall.com/crypt/ + * + * This code comes from John the Ripper password cracker, with reentrant + * and crypt(3) interfaces added, but optimizations specific to password + * cracking removed. + * + * Written by Solar Designer in 1998-2015. + * No copyright is claimed, and the software is hereby placed in the public + * domain. In case this attempt to disclaim copyright and place the software + * in the public domain is deemed null and void, then the software is + * Copyright (c) 1998-2015 Solar Designer and it is hereby released to the + * general public under the following terms: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted. + * + * There's ABSOLUTELY NO WARRANTY, express or implied. + * + * It is my intent that you should be able to use this on your system, + * as part of a software package, or anywhere else to improve security, + * ensure compatibility, or for any other purpose. I would appreciate + * it if you give credit where it is due and keep your modifications in + * the public domain as well, but I don't require that in order to let + * you place this code and any modifications you make under a license + * of your choice. + * + * This implementation is fully compatible with OpenBSD's bcrypt.c for prefix + * "$2b$", originally by Niels Provos , and it uses + * some of his ideas. The password hashing algorithm was designed by David + * Mazieres . For information on the level of + * compatibility for bcrypt hash prefixes other than "$2b$", please refer to + * the comments in BF_set_key() below and to the included crypt(3) man page. + * + * There's a paper on the algorithm that explains its design decisions: + * + * http://www.usenix.org/events/usenix99/provos.html + * + * Some of the tricks in BF_ROUND might be inspired by Eric Young's + * Blowfish library (I can't be sure if I would think of something if I + * hadn't seen his code). + */ + +#include + +#include +#ifndef __set_errno +#define __set_errno(val) errno = (val) +#endif + +/* Just to make sure the prototypes match the actual definitions */ +#include "crypt_blowfish.h" + +#ifdef __i386__ +#define BF_ASM 0 +#define BF_SCALE 1 +#elif defined(__x86_64__) || defined(__alpha__) || defined(__hppa__) +#define BF_ASM 0 +#define BF_SCALE 1 +#else +#define BF_ASM 0 +#define BF_SCALE 0 +#endif + +typedef unsigned int BF_word; +typedef signed int BF_word_signed; + +/* Number of Blowfish rounds, this is also hardcoded into a few places */ +#define BF_N 16 + +typedef BF_word BF_key[BF_N + 2]; + +typedef struct { + BF_word S[4][0x100]; + BF_key P; +} BF_ctx; + +/* + * Magic IV for 64 Blowfish encryptions that we do at the end. + * The string is "OrpheanBeholderScryDoubt" on big-endian. + */ +static BF_word BF_magic_w[6] = { + 0x4F727068, 0x65616E42, 0x65686F6C, + 0x64657253, 0x63727944, 0x6F756274 +}; + +/* + * P-box and S-box tables initialized with digits of Pi. + */ +static BF_ctx BF_init_state = { + { + { + 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, + 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, + 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, + 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, + 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, + 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, + 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, + 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, + 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, + 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, + 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, + 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, + 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, + 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, + 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, + 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, + 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, + 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, + 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, + 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, + 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, + 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, + 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, + 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, + 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, + 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, + 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, + 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, + 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, + 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, + 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, + 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09, + 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, + 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, + 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, + 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, + 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, + 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, + 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, + 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, + 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, + 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, + 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, + 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, + 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, + 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, + 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, + 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, + 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, + 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, + 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, + 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, + 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, + 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, + 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, + 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af, + 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, + 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, + 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, + 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, + 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, + 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, + 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, + 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a + }, { + 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, + 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, + 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, + 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, + 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, + 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, + 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, + 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1, + 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, + 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, + 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, + 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, + 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, + 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, + 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, + 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, + 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, + 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, + 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, + 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, + 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, + 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, + 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, + 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, + 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, + 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509, + 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, + 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, + 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, + 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, + 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, + 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, + 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, + 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, + 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, + 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, + 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, + 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, + 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, + 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, + 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, + 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, + 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, + 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281, + 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, + 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, + 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, + 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, + 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, + 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, + 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, + 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, + 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, + 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, + 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, + 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061, + 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, + 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, + 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, + 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, + 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, + 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, + 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, + 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7 + }, { + 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, + 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, + 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, + 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, + 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, + 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, + 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, + 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, + 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, + 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, + 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, + 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, + 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, + 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, + 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, + 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, + 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, + 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, + 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, + 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, + 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, + 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, + 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, + 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, + 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, + 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, + 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, + 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, + 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, + 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, + 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, + 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, + 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, + 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, + 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, + 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, + 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, + 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, + 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, + 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, + 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, + 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, + 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, + 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, + 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, + 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, + 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, + 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, + 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, + 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, + 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, + 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, + 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, + 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, + 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, + 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62, + 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, + 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, + 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, + 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, + 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, + 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, + 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, + 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0 + }, { + 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, + 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, + 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, + 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, + 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, + 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, + 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, + 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22, + 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, + 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, + 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, + 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, + 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, + 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, + 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, + 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, + 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, + 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, + 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, + 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd, + 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, + 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, + 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, + 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, + 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, + 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, + 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, + 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, + 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, + 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, + 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, + 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, + 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, + 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, + 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, + 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, + 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, + 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd, + 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, + 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, + 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, + 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, + 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, + 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525, + 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, + 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, + 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, + 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, + 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, + 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, + 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, + 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, + 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, + 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, + 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, + 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a, + 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, + 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, + 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, + 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, + 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, + 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, + 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, + 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6 + } + }, { + 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, + 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, + 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, + 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, + 0x9216d5d9, 0x8979fb1b + } +}; + +static unsigned char BF_itoa64[64 + 1] = + "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + +static unsigned char BF_atoi64[0x60] = { + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 0, 1, + 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 64, 64, 64, 64, 64, + 64, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 64, 64, 64, 64, 64, + 64, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, + 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 64, 64, 64, 64, 64 +}; + +#define BF_safe_atoi64(dst, src) \ +{ \ + tmp = (unsigned char)(src); \ + if (tmp == '$') break; /* PHP hack */ \ + if ((unsigned int)(tmp -= 0x20) >= 0x60) return -1; \ + tmp = BF_atoi64[tmp]; \ + if (tmp > 63) return -1; \ + (dst) = tmp; \ +} + +static int BF_decode(BF_word *dst, const char *src, int size) +{ + unsigned char *dptr = (unsigned char *)dst; + unsigned char *end = dptr + size; + const unsigned char *sptr = (const unsigned char *)src; + unsigned int tmp, c1, c2, c3, c4; + + do { + BF_safe_atoi64(c1, *sptr++); + BF_safe_atoi64(c2, *sptr++); + *dptr++ = (c1 << 2) | ((c2 & 0x30) >> 4); + if (dptr >= end) break; + + BF_safe_atoi64(c3, *sptr++); + *dptr++ = ((c2 & 0x0F) << 4) | ((c3 & 0x3C) >> 2); + if (dptr >= end) break; + + BF_safe_atoi64(c4, *sptr++); + *dptr++ = ((c3 & 0x03) << 6) | c4; + } while (dptr < end); + + if (end - dptr == size) { + return -1; + } + + while (dptr < end) /* PHP hack */ + *dptr++ = 0; + + return 0; +} + +static void BF_encode(char *dst, const BF_word *src, int size) +{ + const unsigned char *sptr = (const unsigned char *)src; + const unsigned char *end = sptr + size; + unsigned char *dptr = (unsigned char *)dst; + unsigned int c1, c2; + + do { + c1 = *sptr++; + *dptr++ = BF_itoa64[c1 >> 2]; + c1 = (c1 & 0x03) << 4; + if (sptr >= end) { + *dptr++ = BF_itoa64[c1]; + break; + } + + c2 = *sptr++; + c1 |= c2 >> 4; + *dptr++ = BF_itoa64[c1]; + c1 = (c2 & 0x0f) << 2; + if (sptr >= end) { + *dptr++ = BF_itoa64[c1]; + break; + } + + c2 = *sptr++; + c1 |= c2 >> 6; + *dptr++ = BF_itoa64[c1]; + *dptr++ = BF_itoa64[c2 & 0x3f]; + } while (sptr < end); +} + +static void BF_swap(BF_word *x, int count) +{ + static int endianness_check = 1; + char *is_little_endian = (char *)&endianness_check; + BF_word tmp; + + if (*is_little_endian) + do { + tmp = *x; + tmp = (tmp << 16) | (tmp >> 16); + *x++ = ((tmp & 0x00FF00FF) << 8) | ((tmp >> 8) & 0x00FF00FF); + } while (--count); +} + +#if BF_SCALE +/* Architectures which can shift addresses left by 2 bits with no extra cost */ +#define BF_ROUND(L, R, N) \ + tmp1 = L & 0xFF; \ + tmp2 = L >> 8; \ + tmp2 &= 0xFF; \ + tmp3 = L >> 16; \ + tmp3 &= 0xFF; \ + tmp4 = L >> 24; \ + tmp1 = data.ctx.S[3][tmp1]; \ + tmp2 = data.ctx.S[2][tmp2]; \ + tmp3 = data.ctx.S[1][tmp3]; \ + tmp3 += data.ctx.S[0][tmp4]; \ + tmp3 ^= tmp2; \ + R ^= data.ctx.P[N + 1]; \ + tmp3 += tmp1; \ + R ^= tmp3; +#else +/* Architectures with no complicated addressing modes supported */ +#define BF_INDEX(S, i) \ + (*((BF_word *)(((unsigned char *)S) + (i)))) +#define BF_ROUND(L, R, N) \ + tmp1 = L & 0xFF; \ + tmp1 <<= 2; \ + tmp2 = L >> 6; \ + tmp2 &= 0x3FC; \ + tmp3 = L >> 14; \ + tmp3 &= 0x3FC; \ + tmp4 = L >> 22; \ + tmp4 &= 0x3FC; \ + tmp1 = BF_INDEX(data.ctx.S[3], tmp1); \ + tmp2 = BF_INDEX(data.ctx.S[2], tmp2); \ + tmp3 = BF_INDEX(data.ctx.S[1], tmp3); \ + tmp3 += BF_INDEX(data.ctx.S[0], tmp4); \ + tmp3 ^= tmp2; \ + R ^= data.ctx.P[N + 1]; \ + tmp3 += tmp1; \ + R ^= tmp3; +#endif + +/* + * Encrypt one block, BF_N is hardcoded here. + */ +#define BF_ENCRYPT \ + L ^= data.ctx.P[0]; \ + BF_ROUND(L, R, 0); \ + BF_ROUND(R, L, 1); \ + BF_ROUND(L, R, 2); \ + BF_ROUND(R, L, 3); \ + BF_ROUND(L, R, 4); \ + BF_ROUND(R, L, 5); \ + BF_ROUND(L, R, 6); \ + BF_ROUND(R, L, 7); \ + BF_ROUND(L, R, 8); \ + BF_ROUND(R, L, 9); \ + BF_ROUND(L, R, 10); \ + BF_ROUND(R, L, 11); \ + BF_ROUND(L, R, 12); \ + BF_ROUND(R, L, 13); \ + BF_ROUND(L, R, 14); \ + BF_ROUND(R, L, 15); \ + tmp4 = R; \ + R = L; \ + L = tmp4 ^ data.ctx.P[BF_N + 1]; + +#if BF_ASM +#define BF_body() \ + _BF_body_r(&data.ctx); +#else +#define BF_body() \ + L = R = 0; \ + ptr = data.ctx.P; \ + do { \ + ptr += 2; \ + BF_ENCRYPT; \ + *(ptr - 2) = L; \ + *(ptr - 1) = R; \ + } while (ptr < &data.ctx.P[BF_N + 2]); \ +\ + ptr = data.ctx.S[0]; \ + do { \ + ptr += 2; \ + BF_ENCRYPT; \ + *(ptr - 2) = L; \ + *(ptr - 1) = R; \ + } while (ptr < &data.ctx.S[3][0xFF]); +#endif + +static void BF_set_key(const char *key, BF_key expanded, BF_key initial, + unsigned char flags) +{ + const char *ptr = key; + unsigned int bug, i, j; + BF_word safety, sign, diff, tmp[2]; + +/* + * There was a sign extension bug in older revisions of this function. While + * we would have liked to simply fix the bug and move on, we have to provide + * a backwards compatibility feature (essentially the bug) for some systems and + * a safety measure for some others. The latter is needed because for certain + * multiple inputs to the buggy algorithm there exist easily found inputs to + * the correct algorithm that produce the same hash. Thus, we optionally + * deviate from the correct algorithm just enough to avoid such collisions. + * While the bug itself affected the majority of passwords containing + * characters with the 8th bit set (although only a percentage of those in a + * collision-producing way), the anti-collision safety measure affects + * only a subset of passwords containing the '\xff' character (not even all of + * those passwords, just some of them). This character is not found in valid + * UTF-8 sequences and is rarely used in popular 8-bit character encodings. + * Thus, the safety measure is unlikely to cause much annoyance, and is a + * reasonable tradeoff to use when authenticating against existing hashes that + * are not reliably known to have been computed with the correct algorithm. + * + * We use an approach that tries to minimize side-channel leaks of password + * information - that is, we mostly use fixed-cost bitwise operations instead + * of branches or table lookups. (One conditional branch based on password + * length remains. It is not part of the bug aftermath, though, and is + * difficult and possibly unreasonable to avoid given the use of C strings by + * the caller, which results in similar timing leaks anyway.) + * + * For actual implementation, we set an array index in the variable "bug" + * (0 means no bug, 1 means sign extension bug emulation) and a flag in the + * variable "safety" (bit 16 is set when the safety measure is requested). + * Valid combinations of settings are: + * + * Prefix "$2a$": bug = 0, safety = 0x10000 + * Prefix "$2b$": bug = 0, safety = 0 + * Prefix "$2x$": bug = 1, safety = 0 + * Prefix "$2y$": bug = 0, safety = 0 + */ + bug = (unsigned int)flags & 1; + safety = ((BF_word)flags & 2) << 15; + + sign = diff = 0; + + for (i = 0; i < BF_N + 2; i++) { + tmp[0] = tmp[1] = 0; + for (j = 0; j < 4; j++) { + tmp[0] <<= 8; + tmp[0] |= (unsigned char)*ptr; /* correct */ + tmp[1] <<= 8; + tmp[1] |= (BF_word_signed)(signed char)*ptr; /* bug */ +/* + * Sign extension in the first char has no effect - nothing to overwrite yet, + * and those extra 24 bits will be fully shifted out of the 32-bit word. For + * chars 2, 3, 4 in each four-char block, we set bit 7 of "sign" if sign + * extension in tmp[1] occurs. Once this flag is set, it remains set. + */ + if (j) + sign |= tmp[1] & 0x80; + if (!*ptr) + ptr = key; + else + ptr++; + } + diff |= tmp[0] ^ tmp[1]; /* Non-zero on any differences */ + + expanded[i] = tmp[bug]; + initial[i] = BF_init_state.P[i] ^ tmp[bug]; + } + +/* + * At this point, "diff" is zero iff the correct and buggy algorithms produced + * exactly the same result. If so and if "sign" is non-zero, which indicates + * that there was a non-benign sign extension, this means that we have a + * collision between the correctly computed hash for this password and a set of + * passwords that could be supplied to the buggy algorithm. Our safety measure + * is meant to protect from such many-buggy to one-correct collisions, by + * deviating from the correct algorithm in such cases. Let's check for this. + */ + diff |= diff >> 16; /* still zero iff exact match */ + diff &= 0xffff; /* ditto */ + diff += 0xffff; /* bit 16 set iff "diff" was non-zero (on non-match) */ + sign <<= 9; /* move the non-benign sign extension flag to bit 16 */ + sign &= ~diff & safety; /* action needed? */ + +/* + * If we have determined that we need to deviate from the correct algorithm, + * flip bit 16 in initial expanded key. (The choice of 16 is arbitrary, but + * let's stick to it now. It came out of the approach we used above, and it's + * not any worse than any other choice we could make.) + * + * It is crucial that we don't do the same to the expanded key used in the main + * Eksblowfish loop. By doing it to only one of these two, we deviate from a + * state that could be directly specified by a password to the buggy algorithm + * (and to the fully correct one as well, but that's a side-effect). + */ + initial[0] ^= sign; +} + +static const unsigned char flags_by_subtype[26] = + {2, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 0}; + +static char *BF_crypt(const char *key, const char *setting, + char *output, int size, + BF_word min) +{ +#if BF_ASM + extern void _BF_body_r(BF_ctx *ctx); +#endif + struct { + BF_ctx ctx; + BF_key expanded_key; + union { + BF_word salt[4]; + BF_word output[6]; + } binary; + } data; + BF_word L, R; + BF_word tmp1, tmp2, tmp3, tmp4; + BF_word *ptr; + BF_word count; + int i; + + if (size < 7 + 22 + 31 + 1) { + __set_errno(ERANGE); + return NULL; + } + + if (setting[0] != '$' || + setting[1] != '2' || + setting[2] < 'a' || setting[2] > 'z' || + !flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a'] || + setting[3] != '$' || + setting[4] < '0' || setting[4] > '3' || + setting[5] < '0' || setting[5] > '9' || + (setting[4] == '3' && setting[5] > '1') || + setting[6] != '$') { + __set_errno(EINVAL); + return NULL; + } + + count = (BF_word)1 << ((setting[4] - '0') * 10 + (setting[5] - '0')); + if (count < min || BF_decode(data.binary.salt, &setting[7], 16)) { + __set_errno(EINVAL); + return NULL; + } + BF_swap(data.binary.salt, 4); + + BF_set_key(key, data.expanded_key, data.ctx.P, + flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a']); + + memcpy(data.ctx.S, BF_init_state.S, sizeof(data.ctx.S)); + + L = R = 0; + for (i = 0; i < BF_N + 2; i += 2) { + L ^= data.binary.salt[i & 2]; + R ^= data.binary.salt[(i & 2) + 1]; + BF_ENCRYPT; + data.ctx.P[i] = L; + data.ctx.P[i + 1] = R; + } + + ptr = data.ctx.S[0]; + do { + ptr += 4; + L ^= data.binary.salt[(BF_N + 2) & 3]; + R ^= data.binary.salt[(BF_N + 3) & 3]; + BF_ENCRYPT; + *(ptr - 4) = L; + *(ptr - 3) = R; + + L ^= data.binary.salt[(BF_N + 4) & 3]; + R ^= data.binary.salt[(BF_N + 5) & 3]; + BF_ENCRYPT; + *(ptr - 2) = L; + *(ptr - 1) = R; + } while (ptr < &data.ctx.S[3][0xFF]); + + do { + int done; + + for (i = 0; i < BF_N + 2; i += 2) { + data.ctx.P[i] ^= data.expanded_key[i]; + data.ctx.P[i + 1] ^= data.expanded_key[i + 1]; + } + + done = 0; + do { + BF_body(); + if (done) + break; + done = 1; + + tmp1 = data.binary.salt[0]; + tmp2 = data.binary.salt[1]; + tmp3 = data.binary.salt[2]; + tmp4 = data.binary.salt[3]; + for (i = 0; i < BF_N; i += 4) { + data.ctx.P[i] ^= tmp1; + data.ctx.P[i + 1] ^= tmp2; + data.ctx.P[i + 2] ^= tmp3; + data.ctx.P[i + 3] ^= tmp4; + } + data.ctx.P[16] ^= tmp1; + data.ctx.P[17] ^= tmp2; + } while (1); + } while (--count); + + for (i = 0; i < 6; i += 2) { + L = BF_magic_w[i]; + R = BF_magic_w[i + 1]; + + count = 64; + do { + BF_ENCRYPT; + } while (--count); + + data.binary.output[i] = L; + data.binary.output[i + 1] = R; + } + + memcpy(output, setting, 7 + 22 - 1); + output[7 + 22 - 1] = BF_itoa64[(int) + BF_atoi64[(int)setting[7 + 22 - 1] - 0x20] & 0x30]; + +/* This has to be bug-compatible with the original implementation, so + * only encode 23 of the 24 bytes. :-) */ + BF_swap(data.binary.output, 6); + BF_encode(&output[7 + 22], data.binary.output, 23); + output[7 + 22 + 31] = '\0'; + + return output; +} + +static int _crypt_output_magic(const char *setting, char *output, int size) +{ + if (size < 3) + return -1; + + output[0] = '*'; + output[1] = '0'; + output[2] = '\0'; + + if (setting[0] == '*' && setting[1] == '0') + output[1] = '1'; + + return 0; +} + +/* + * Please preserve the runtime self-test. It serves two purposes at once: + * + * 1. We really can't afford the risk of producing incompatible hashes e.g. + * when there's something like gcc bug 26587 again, whereas an application or + * library integrating this code might not also integrate our external tests or + * it might not run them after every build. Even if it does, the miscompile + * might only occur on the production build, but not on a testing build (such + * as because of different optimization settings). It is painful to recover + * from incorrectly-computed hashes - merely fixing whatever broke is not + * enough. Thus, a proactive measure like this self-test is needed. + * + * 2. We don't want to leave sensitive data from our actual password hash + * computation on the stack or in registers. Previous revisions of the code + * would do explicit cleanups, but simply running the self-test after hash + * computation is more reliable. + * + * The performance cost of this quick self-test is around 0.6% at the "$2a$08" + * setting. + */ +char *php_crypt_blowfish_rn(const char *key, const char *setting, + char *output, int size) +{ + const char *test_key = "8b \xd0\xc1\xd2\xcf\xcc\xd8"; + const char *test_setting = "$2a$00$abcdefghijklmnopqrstuu"; + static const char * const test_hashes[2] = + {"i1D709vfamulimlGcq0qq3UvuUasvEa\0\x55", /* 'a', 'b', 'y' */ + "VUrPmXD6q/nVSSp7pNDhCR9071IfIRe\0\x55"}; /* 'x' */ + const char *test_hash = test_hashes[0]; + char *retval; + const char *p; + int save_errno, ok; + struct { + char s[7 + 22 + 1]; + char o[7 + 22 + 31 + 1 + 1 + 1]; + } buf; + +/* Hash the supplied password */ + _crypt_output_magic(setting, output, size); + retval = BF_crypt(key, setting, output, size, 16); + save_errno = errno; + +/* + * Do a quick self-test. It is important that we make both calls to BF_crypt() + * from the same scope such that they likely use the same stack locations, + * which makes the second call overwrite the first call's sensitive data on the + * stack and makes it more likely that any alignment related issues would be + * detected by the self-test. + */ + memcpy(buf.s, test_setting, sizeof(buf.s)); + if (retval) { + unsigned int flags = flags_by_subtype[ + (unsigned int)(unsigned char)setting[2] - 'a']; + test_hash = test_hashes[flags & 1]; + buf.s[2] = setting[2]; + } + memset(buf.o, 0x55, sizeof(buf.o)); + buf.o[sizeof(buf.o) - 1] = 0; + p = BF_crypt(test_key, buf.s, buf.o, sizeof(buf.o) - (1 + 1), 1); + + ok = (p == buf.o && + !memcmp(p, buf.s, 7 + 22) && + !memcmp(p + (7 + 22), test_hash, 31 + 1 + 1 + 1)); + + { + const char *k = "\xff\xa3" "34" "\xff\xff\xff\xa3" "345"; + BF_key ae, ai, ye, yi; + BF_set_key(k, ae, ai, 2); /* $2a$ */ + BF_set_key(k, ye, yi, 4); /* $2y$ */ + ai[0] ^= 0x10000; /* undo the safety (for comparison) */ + ok = ok && ai[0] == 0xdb9c59bc && ye[17] == 0x33343500 && + !memcmp(ae, ye, sizeof(ae)) && + !memcmp(ai, yi, sizeof(ai)); + } + + __set_errno(save_errno); + if (ok) + return retval; + +/* Should not happen */ + _crypt_output_magic(setting, output, size); + __set_errno(EINVAL); /* pretend we don't support this hash type */ + return NULL; +} + +#if 0 +char *_crypt_gensalt_blowfish_rn(const char *prefix, unsigned long count, + const char *input, int size, char *output, int output_size) +{ + if (size < 16 || output_size < 7 + 22 + 1 || + (count && (count < 4 || count > 31)) || + prefix[0] != '$' || prefix[1] != '2' || + (prefix[2] != 'a' && prefix[2] != 'b' && prefix[2] != 'y')) { + if (output_size > 0) output[0] = '\0'; + __set_errno((output_size < 7 + 22 + 1) ? ERANGE : EINVAL); + return NULL; + } + + if (!count) count = 5; + + output[0] = '$'; + output[1] = '2'; + output[2] = prefix[2]; + output[3] = '$'; + output[4] = '0' + count / 10; + output[5] = '0' + count % 10; + output[6] = '$'; + + BF_encode(&output[7], (const BF_word *)input, 16); + output[7 + 22] = '\0'; + + return output; +} +#endif diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c index 32f06c0750f61..b26132a085a87 100644 --- a/ext/standard/http_fopen_wrapper.c +++ b/ext/standard/http_fopen_wrapper.c @@ -736,9 +736,9 @@ php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, tmp_line, response_code); } } - if (tmp_line[tmp_line_len - 1] == '\n') { + if (tmp_line_len >= 1 && tmp_line[tmp_line_len - 1] == '\n') { --tmp_line_len; - if (tmp_line[tmp_line_len - 1] == '\r') { + if (tmp_line_len >= 1 &&tmp_line[tmp_line_len - 1] == '\r') { --tmp_line_len; } } diff --git a/ext/standard/http_fopen_wrapper.c.orig b/ext/standard/http_fopen_wrapper.c.orig new file mode 100644 index 0000000000000..32f06c0750f61 --- /dev/null +++ b/ext/standard/http_fopen_wrapper.c.orig @@ -0,0 +1,1029 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2017 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Rasmus Lerdorf | + | Jim Winstead | + | Hartmut Holzgraefe | + | Wez Furlong | + | Sara Golemon | + +----------------------------------------------------------------------+ + */ +/* $Id$ */ + +#include "php.h" +#include "php_globals.h" +#include "php_streams.h" +#include "php_network.h" +#include "php_ini.h" +#include "ext/standard/basic_functions.h" +#include "zend_smart_str.h" + +#include +#include +#include +#include +#include +#include + +#ifdef PHP_WIN32 +#define O_RDONLY _O_RDONLY +#include "win32/param.h" +#else +#include +#endif + +#include "php_standard.h" + +#include +#if HAVE_SYS_SOCKET_H +#include +#endif + +#ifdef PHP_WIN32 +#include +#elif defined(NETWARE) && defined(USE_WINSOCK) +#include +#else +#include +#include +#if HAVE_ARPA_INET_H +#include +#endif +#endif + +#if defined(PHP_WIN32) || defined(__riscos__) || defined(NETWARE) +#undef AF_UNIX +#endif + +#if defined(AF_UNIX) +#include +#endif + +#include "php_fopen_wrappers.h" + +#define HTTP_HEADER_BLOCK_SIZE 1024 +#define PHP_URL_REDIRECT_MAX 20 +#define HTTP_HEADER_USER_AGENT 1 +#define HTTP_HEADER_HOST 2 +#define HTTP_HEADER_AUTH 4 +#define HTTP_HEADER_FROM 8 +#define HTTP_HEADER_CONTENT_LENGTH 16 +#define HTTP_HEADER_TYPE 32 +#define HTTP_HEADER_CONNECTION 64 + +#define HTTP_WRAPPER_HEADER_INIT 1 +#define HTTP_WRAPPER_REDIRECTED 2 + +static inline void strip_header(char *header_bag, char *lc_header_bag, + const char *lc_header_name) +{ + char *lc_header_start = strstr(lc_header_bag, lc_header_name); + char *header_start = header_bag + (lc_header_start - lc_header_bag); + + if (lc_header_start + && (lc_header_start == lc_header_bag || *(lc_header_start-1) == '\n') + ) { + char *lc_eol = strchr(lc_header_start, '\n'); + char *eol = header_start + (lc_eol - lc_header_start); + + if (lc_eol) { + size_t eollen = strlen(lc_eol); + + memmove(lc_header_start, lc_eol+1, eollen); + memmove(header_start, eol+1, eollen); + } else { + *lc_header_start = '\0'; + *header_start = '\0'; + } + } +} + +php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, + const char *path, const char *mode, int options, zend_string **opened_path, + php_stream_context *context, int redirect_max, int flags STREAMS_DC) /* {{{ */ +{ + php_stream *stream = NULL; + php_url *resource = NULL; + int use_ssl; + int use_proxy = 0; + zend_string *tmp = NULL; + char *ua_str = NULL; + zval *ua_zval = NULL, *tmpzval = NULL, ssl_proxy_peer_name; + char location[HTTP_HEADER_BLOCK_SIZE]; + zval response_header; + int reqok = 0; + char *http_header_line = NULL; + char tmp_line[128]; + size_t chunk_size = 0, file_size = 0; + int eol_detect = 0; + char *transport_string; + zend_string *errstr = NULL; + size_t transport_len; + int have_header = 0; + zend_bool request_fulluri = 0, ignore_errors = 0; + struct timeval timeout; + char *user_headers = NULL; + int header_init = ((flags & HTTP_WRAPPER_HEADER_INIT) != 0); + int redirected = ((flags & HTTP_WRAPPER_REDIRECTED) != 0); + zend_bool follow_location = 1; + php_stream_filter *transfer_encoding = NULL; + int response_code; + zend_array *symbol_table; + smart_str req_buf = {0}; + zend_bool custom_request_method; + + ZVAL_UNDEF(&response_header); + tmp_line[0] = '\0'; + + if (redirect_max < 1) { + php_stream_wrapper_log_error(wrapper, options, "Redirection limit reached, aborting"); + return NULL; + } + + resource = php_url_parse(path); + if (resource == NULL) { + return NULL; + } + + if (strncasecmp(resource->scheme, "http", sizeof("http")) && strncasecmp(resource->scheme, "https", sizeof("https"))) { + if (!context || + (tmpzval = php_stream_context_get_option(context, wrapper->wops->label, "proxy")) == NULL || + Z_TYPE_P(tmpzval) != IS_STRING || + Z_STRLEN_P(tmpzval) <= 0) { + php_url_free(resource); + return php_stream_open_wrapper_ex(path, mode, REPORT_ERRORS, NULL, context); + } + /* Called from a non-http wrapper with http proxying requested (i.e. ftp) */ + request_fulluri = 1; + use_ssl = 0; + use_proxy = 1; + + transport_len = Z_STRLEN_P(tmpzval); + transport_string = estrndup(Z_STRVAL_P(tmpzval), Z_STRLEN_P(tmpzval)); + } else { + /* Normal http request (possibly with proxy) */ + + if (strpbrk(mode, "awx+")) { + php_stream_wrapper_log_error(wrapper, options, "HTTP wrapper does not support writeable connections"); + php_url_free(resource); + return NULL; + } + + use_ssl = resource->scheme && (strlen(resource->scheme) > 4) && resource->scheme[4] == 's'; + /* choose default ports */ + if (use_ssl && resource->port == 0) + resource->port = 443; + else if (resource->port == 0) + resource->port = 80; + + if (context && + (tmpzval = php_stream_context_get_option(context, wrapper->wops->label, "proxy")) != NULL && + Z_TYPE_P(tmpzval) == IS_STRING && + Z_STRLEN_P(tmpzval) > 0) { + use_proxy = 1; + transport_len = Z_STRLEN_P(tmpzval); + transport_string = estrndup(Z_STRVAL_P(tmpzval), Z_STRLEN_P(tmpzval)); + } else { + transport_len = spprintf(&transport_string, 0, "%s://%s:%d", use_ssl ? "ssl" : "tcp", resource->host, resource->port); + } + } + + if (context && (tmpzval = php_stream_context_get_option(context, wrapper->wops->label, "timeout")) != NULL) { + double d = zval_get_double(tmpzval); +#ifndef PHP_WIN32 + timeout.tv_sec = (time_t) d; + timeout.tv_usec = (size_t) ((d - timeout.tv_sec) * 1000000); +#else + timeout.tv_sec = (long) d; + timeout.tv_usec = (long) ((d - timeout.tv_sec) * 1000000); +#endif + } else { +#ifndef PHP_WIN32 + timeout.tv_sec = FG(default_socket_timeout); +#else + timeout.tv_sec = (long)FG(default_socket_timeout); +#endif + timeout.tv_usec = 0; + } + + stream = php_stream_xport_create(transport_string, transport_len, options, + STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT, + NULL, &timeout, context, &errstr, NULL); + + if (stream) { + php_stream_set_option(stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &timeout); + } + + if (errstr) { + php_stream_wrapper_log_error(wrapper, options, "%s", ZSTR_VAL(errstr)); + zend_string_release(errstr); + errstr = NULL; + } + + efree(transport_string); + + if (stream && use_proxy && use_ssl) { + smart_str header = {0}; + + /* Set peer_name or name verification will try to use the proxy server name */ + if (!context || (tmpzval = php_stream_context_get_option(context, "ssl", "peer_name")) == NULL) { + ZVAL_STRING(&ssl_proxy_peer_name, resource->host); + php_stream_context_set_option(PHP_STREAM_CONTEXT(stream), "ssl", "peer_name", &ssl_proxy_peer_name); + zval_ptr_dtor(&ssl_proxy_peer_name); + } + + smart_str_appendl(&header, "CONNECT ", sizeof("CONNECT ")-1); + smart_str_appends(&header, resource->host); + smart_str_appendc(&header, ':'); + smart_str_append_unsigned(&header, resource->port); + smart_str_appendl(&header, " HTTP/1.0\r\n", sizeof(" HTTP/1.0\r\n")-1); + + /* check if we have Proxy-Authorization header */ + if (context && (tmpzval = php_stream_context_get_option(context, "http", "header")) != NULL) { + char *s, *p; + + if (Z_TYPE_P(tmpzval) == IS_ARRAY) { + zval *tmpheader = NULL; + + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(tmpzval), tmpheader) { + if (Z_TYPE_P(tmpheader) == IS_STRING) { + s = Z_STRVAL_P(tmpheader); + do { + while (*s == ' ' || *s == '\t') s++; + p = s; + while (*p != 0 && *p != ':' && *p != '\r' && *p !='\n') p++; + if (*p == ':') { + p++; + if (p - s == sizeof("Proxy-Authorization:") - 1 && + zend_binary_strcasecmp(s, sizeof("Proxy-Authorization:") - 1, + "Proxy-Authorization:", sizeof("Proxy-Authorization:") - 1) == 0) { + while (*p != 0 && *p != '\r' && *p !='\n') p++; + smart_str_appendl(&header, s, p - s); + smart_str_appendl(&header, "\r\n", sizeof("\r\n")-1); + goto finish; + } else { + while (*p != 0 && *p != '\r' && *p !='\n') p++; + } + } + s = p; + while (*s == '\r' || *s == '\n') s++; + } while (*s != 0); + } + } ZEND_HASH_FOREACH_END(); + } else if (Z_TYPE_P(tmpzval) == IS_STRING && Z_STRLEN_P(tmpzval)) { + s = Z_STRVAL_P(tmpzval); + do { + while (*s == ' ' || *s == '\t') s++; + p = s; + while (*p != 0 && *p != ':' && *p != '\r' && *p !='\n') p++; + if (*p == ':') { + p++; + if (p - s == sizeof("Proxy-Authorization:") - 1 && + zend_binary_strcasecmp(s, sizeof("Proxy-Authorization:") - 1, + "Proxy-Authorization:", sizeof("Proxy-Authorization:") - 1) == 0) { + while (*p != 0 && *p != '\r' && *p !='\n') p++; + smart_str_appendl(&header, s, p - s); + smart_str_appendl(&header, "\r\n", sizeof("\r\n")-1); + goto finish; + } else { + while (*p != 0 && *p != '\r' && *p !='\n') p++; + } + } + s = p; + while (*s == '\r' || *s == '\n') s++; + } while (*s != 0); + } + } +finish: + smart_str_appendl(&header, "\r\n", sizeof("\r\n")-1); + + if (php_stream_write(stream, ZSTR_VAL(header.s), ZSTR_LEN(header.s)) != ZSTR_LEN(header.s)) { + php_stream_wrapper_log_error(wrapper, options, "Cannot connect to HTTPS server through proxy"); + php_stream_close(stream); + stream = NULL; + } + smart_str_free(&header); + + if (stream) { + char header_line[HTTP_HEADER_BLOCK_SIZE]; + + /* get response header */ + while (php_stream_gets(stream, header_line, HTTP_HEADER_BLOCK_SIZE-1) != NULL) { + if (header_line[0] == '\n' || + header_line[0] == '\r' || + header_line[0] == '\0') { + break; + } + } + } + + /* enable SSL transport layer */ + if (stream) { + if (php_stream_xport_crypto_setup(stream, STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL) < 0 || + php_stream_xport_crypto_enable(stream, 1) < 0) { + php_stream_wrapper_log_error(wrapper, options, "Cannot connect to HTTPS server through proxy"); + php_stream_close(stream); + stream = NULL; + } + } + } + + if (stream == NULL) + goto out; + + /* avoid buffering issues while reading header */ + if (options & STREAM_WILL_CAST) + chunk_size = php_stream_set_chunk_size(stream, 1); + + /* avoid problems with auto-detecting when reading the headers -> the headers + * are always in canonical \r\n format */ + eol_detect = stream->flags & (PHP_STREAM_FLAG_DETECT_EOL | PHP_STREAM_FLAG_EOL_MAC); + stream->flags &= ~(PHP_STREAM_FLAG_DETECT_EOL | PHP_STREAM_FLAG_EOL_MAC); + + php_stream_context_set(stream, context); + + php_stream_notify_info(context, PHP_STREAM_NOTIFY_CONNECT, NULL, 0); + + if (header_init && context && (tmpzval = php_stream_context_get_option(context, "http", "max_redirects")) != NULL) { + redirect_max = (int)zval_get_long(tmpzval); + } + + custom_request_method = 0; + if (context && (tmpzval = php_stream_context_get_option(context, "http", "method")) != NULL) { + if (Z_TYPE_P(tmpzval) == IS_STRING && Z_STRLEN_P(tmpzval) > 0) { + /* As per the RFC, automatically redirected requests MUST NOT use other methods than + * GET and HEAD unless it can be confirmed by the user */ + if (!redirected + || (Z_STRLEN_P(tmpzval) == 3 && memcmp("GET", Z_STRVAL_P(tmpzval), 3) == 0) + || (Z_STRLEN_P(tmpzval) == 4 && memcmp("HEAD",Z_STRVAL_P(tmpzval), 4) == 0) + ) { + custom_request_method = 1; + smart_str_append(&req_buf, Z_STR_P(tmpzval)); + smart_str_appendc(&req_buf, ' '); + } + } + } + + if (!custom_request_method) { + smart_str_appends(&req_buf, "GET "); + } + + /* Should we send the entire path in the request line, default to no. */ + if (!request_fulluri && context && + (tmpzval = php_stream_context_get_option(context, "http", "request_fulluri")) != NULL) { + request_fulluri = zend_is_true(tmpzval); + } + + if (request_fulluri) { + /* Ask for everything */ + smart_str_appends(&req_buf, path); + } else { + /* Send the traditional /path/to/file?query_string */ + + /* file */ + if (resource->path && *resource->path) { + smart_str_appends(&req_buf, resource->path); + } else { + smart_str_appendc(&req_buf, '/'); + } + + /* query string */ + if (resource->query) { + smart_str_appendc(&req_buf, '?'); + smart_str_appends(&req_buf, resource->query); + } + } + + /* protocol version we are speaking */ + if (context && (tmpzval = php_stream_context_get_option(context, "http", "protocol_version")) != NULL) { + char *protocol_version; + spprintf(&protocol_version, 0, "%.1F", zval_get_double(tmpzval)); + + smart_str_appends(&req_buf, " HTTP/"); + smart_str_appends(&req_buf, protocol_version); + smart_str_appends(&req_buf, "\r\n"); + efree(protocol_version); + } else { + smart_str_appends(&req_buf, " HTTP/1.0\r\n"); + } + + if (context && (tmpzval = php_stream_context_get_option(context, "http", "header")) != NULL) { + tmp = NULL; + + if (Z_TYPE_P(tmpzval) == IS_ARRAY) { + zval *tmpheader = NULL; + smart_str tmpstr = {0}; + + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(tmpzval), tmpheader) { + if (Z_TYPE_P(tmpheader) == IS_STRING) { + smart_str_append(&tmpstr, Z_STR_P(tmpheader)); + smart_str_appendl(&tmpstr, "\r\n", sizeof("\r\n") - 1); + } + } ZEND_HASH_FOREACH_END(); + smart_str_0(&tmpstr); + /* Remove newlines and spaces from start and end. there's at least one extra \r\n at the end that needs to go. */ + if (tmpstr.s) { + tmp = php_trim(tmpstr.s, NULL, 0, 3); + smart_str_free(&tmpstr); + } + } else if (Z_TYPE_P(tmpzval) == IS_STRING && Z_STRLEN_P(tmpzval)) { + /* Remove newlines and spaces from start and end php_trim will estrndup() */ + tmp = php_trim(Z_STR_P(tmpzval), NULL, 0, 3); + } + if (tmp && ZSTR_LEN(tmp)) { + char *s; + char *t; + + user_headers = estrndup(ZSTR_VAL(tmp), ZSTR_LEN(tmp)); + + if (ZSTR_IS_INTERNED(tmp)) { + tmp = zend_string_init(ZSTR_VAL(tmp), ZSTR_LEN(tmp), 0); + } else if (GC_REFCOUNT(tmp) > 1) { + GC_REFCOUNT(tmp)--; + tmp = zend_string_init(ZSTR_VAL(tmp), ZSTR_LEN(tmp), 0); + } + + /* Make lowercase for easy comparison against 'standard' headers */ + php_strtolower(ZSTR_VAL(tmp), ZSTR_LEN(tmp)); + t = ZSTR_VAL(tmp); + + if (!header_init) { + /* strip POST headers on redirect */ + strip_header(user_headers, t, "content-length:"); + strip_header(user_headers, t, "content-type:"); + } + + if ((s = strstr(t, "user-agent:")) && + (s == t || *(s-1) == '\r' || *(s-1) == '\n' || + *(s-1) == '\t' || *(s-1) == ' ')) { + have_header |= HTTP_HEADER_USER_AGENT; + } + if ((s = strstr(t, "host:")) && + (s == t || *(s-1) == '\r' || *(s-1) == '\n' || + *(s-1) == '\t' || *(s-1) == ' ')) { + have_header |= HTTP_HEADER_HOST; + } + if ((s = strstr(t, "from:")) && + (s == t || *(s-1) == '\r' || *(s-1) == '\n' || + *(s-1) == '\t' || *(s-1) == ' ')) { + have_header |= HTTP_HEADER_FROM; + } + if ((s = strstr(t, "authorization:")) && + (s == t || *(s-1) == '\r' || *(s-1) == '\n' || + *(s-1) == '\t' || *(s-1) == ' ')) { + have_header |= HTTP_HEADER_AUTH; + } + if ((s = strstr(t, "content-length:")) && + (s == t || *(s-1) == '\r' || *(s-1) == '\n' || + *(s-1) == '\t' || *(s-1) == ' ')) { + have_header |= HTTP_HEADER_CONTENT_LENGTH; + } + if ((s = strstr(t, "content-type:")) && + (s == t || *(s-1) == '\r' || *(s-1) == '\n' || + *(s-1) == '\t' || *(s-1) == ' ')) { + have_header |= HTTP_HEADER_TYPE; + } + if ((s = strstr(t, "connection:")) && + (s == t || *(s-1) == '\r' || *(s-1) == '\n' || + *(s-1) == '\t' || *(s-1) == ' ')) { + have_header |= HTTP_HEADER_CONNECTION; + } + /* remove Proxy-Authorization header */ + if (use_proxy && use_ssl && (s = strstr(t, "proxy-authorization:")) && + (s == t || *(s-1) == '\r' || *(s-1) == '\n' || + *(s-1) == '\t' || *(s-1) == ' ')) { + char *p = s + sizeof("proxy-authorization:") - 1; + + while (s > t && (*(s-1) == ' ' || *(s-1) == '\t')) s--; + while (*p != 0 && *p != '\r' && *p != '\n') p++; + while (*p == '\r' || *p == '\n') p++; + if (*p == 0) { + if (s == t) { + efree(user_headers); + user_headers = NULL; + } else { + while (s > t && (*(s-1) == '\r' || *(s-1) == '\n')) s--; + user_headers[s - t] = 0; + } + } else { + memmove(user_headers + (s - t), user_headers + (p - t), strlen(p) + 1); + } + } + + } + if (tmp) { + zend_string_release(tmp); + } + } + + /* auth header if it was specified */ + if (((have_header & HTTP_HEADER_AUTH) == 0) && resource->user) { + /* make scratch large enough to hold the whole URL (over-estimate) */ + size_t scratch_len = strlen(path) + 1; + char *scratch = emalloc(scratch_len); + zend_string *stmp; + + /* decode the strings first */ + php_url_decode(resource->user, strlen(resource->user)); + + strcpy(scratch, resource->user); + strcat(scratch, ":"); + + /* Note: password is optional! */ + if (resource->pass) { + php_url_decode(resource->pass, strlen(resource->pass)); + strcat(scratch, resource->pass); + } + + stmp = php_base64_encode((unsigned char*)scratch, strlen(scratch)); + + smart_str_appends(&req_buf, "Authorization: Basic "); + smart_str_appends(&req_buf, ZSTR_VAL(stmp)); + smart_str_appends(&req_buf, "\r\n"); + + php_stream_notify_info(context, PHP_STREAM_NOTIFY_AUTH_REQUIRED, NULL, 0); + + zend_string_free(stmp); + efree(scratch); + } + + /* if the user has configured who they are, send a From: line */ + if (!(have_header & HTTP_HEADER_FROM) && FG(from_address)) { + smart_str_appends(&req_buf, "From: "); + smart_str_appends(&req_buf, FG(from_address)); + smart_str_appends(&req_buf, "\r\n"); + } + + /* Send Host: header so name-based virtual hosts work */ + if ((have_header & HTTP_HEADER_HOST) == 0) { + smart_str_appends(&req_buf, "Host: "); + smart_str_appends(&req_buf, resource->host); + if ((use_ssl && resource->port != 443 && resource->port != 0) || + (!use_ssl && resource->port != 80 && resource->port != 0)) { + smart_str_appendc(&req_buf, ':'); + smart_str_append_unsigned(&req_buf, resource->port); + } + smart_str_appends(&req_buf, "\r\n"); + } + + /* Send a Connection: close header to avoid hanging when the server + * interprets the RFC literally and establishes a keep-alive connection, + * unless the user specifically requests something else by specifying a + * Connection header in the context options. Send that header even for + * HTTP/1.0 to avoid issues when the server respond with a HTTP/1.1 + * keep-alive response, which is the preferred response type. */ + if ((have_header & HTTP_HEADER_CONNECTION) == 0) { + smart_str_appends(&req_buf, "Connection: close\r\n"); + } + + if (context && + (ua_zval = php_stream_context_get_option(context, "http", "user_agent")) != NULL && + Z_TYPE_P(ua_zval) == IS_STRING) { + ua_str = Z_STRVAL_P(ua_zval); + } else if (FG(user_agent)) { + ua_str = FG(user_agent); + } + + if (((have_header & HTTP_HEADER_USER_AGENT) == 0) && ua_str) { +#define _UA_HEADER "User-Agent: %s\r\n" + char *ua; + size_t ua_len; + + ua_len = sizeof(_UA_HEADER) + strlen(ua_str); + + /* ensure the header is only sent if user_agent is not blank */ + if (ua_len > sizeof(_UA_HEADER)) { + ua = emalloc(ua_len + 1); + if ((ua_len = slprintf(ua, ua_len, _UA_HEADER, ua_str)) > 0) { + ua[ua_len] = 0; + smart_str_appendl(&req_buf, ua, ua_len); + } else { + php_error_docref(NULL, E_WARNING, "Cannot construct User-agent header"); + } + efree(ua); + } + } + + if (user_headers) { + /* A bit weird, but some servers require that Content-Length be sent prior to Content-Type for POST + * see bug #44603 for details. Since Content-Type maybe part of user's headers we need to do this check first. + */ + if ( + header_init && + context && + !(have_header & HTTP_HEADER_CONTENT_LENGTH) && + (tmpzval = php_stream_context_get_option(context, "http", "content")) != NULL && + Z_TYPE_P(tmpzval) == IS_STRING && Z_STRLEN_P(tmpzval) > 0 + ) { + smart_str_appends(&req_buf, "Content-Length: "); + smart_str_append_unsigned(&req_buf, Z_STRLEN_P(tmpzval)); + smart_str_appends(&req_buf, "\r\n"); + have_header |= HTTP_HEADER_CONTENT_LENGTH; + } + + smart_str_appends(&req_buf, user_headers); + smart_str_appends(&req_buf, "\r\n"); + efree(user_headers); + } + + /* Request content, such as for POST requests */ + if (header_init && context && + (tmpzval = php_stream_context_get_option(context, "http", "content")) != NULL && + Z_TYPE_P(tmpzval) == IS_STRING && Z_STRLEN_P(tmpzval) > 0) { + if (!(have_header & HTTP_HEADER_CONTENT_LENGTH)) { + smart_str_appends(&req_buf, "Content-Length: "); + smart_str_append_unsigned(&req_buf, Z_STRLEN_P(tmpzval)); + smart_str_appends(&req_buf, "\r\n"); + } + if (!(have_header & HTTP_HEADER_TYPE)) { + smart_str_appends(&req_buf, "Content-Type: application/x-www-form-urlencoded\r\n"); + php_error_docref(NULL, E_NOTICE, "Content-type not specified assuming application/x-www-form-urlencoded"); + } + smart_str_appends(&req_buf, "\r\n"); + smart_str_appendl(&req_buf, Z_STRVAL_P(tmpzval), Z_STRLEN_P(tmpzval)); + } else { + smart_str_appends(&req_buf, "\r\n"); + } + + /* send it */ + php_stream_write(stream, ZSTR_VAL(req_buf.s), ZSTR_LEN(req_buf.s)); + + location[0] = '\0'; + + symbol_table = zend_rebuild_symbol_table(); + + if (header_init) { + zval ztmp; + array_init(&ztmp); + zend_set_local_var_str("http_response_header", sizeof("http_response_header")-1, &ztmp, 0); + } + + { + zval *response_header_ptr = zend_hash_str_find_ind(symbol_table, "http_response_header", sizeof("http_response_header")-1); + if (!response_header_ptr || Z_TYPE_P(response_header_ptr) != IS_ARRAY) { + ZVAL_UNDEF(&response_header); + goto out; + } else { + ZVAL_COPY(&response_header, response_header_ptr); + } + } + + if (!php_stream_eof(stream)) { + size_t tmp_line_len; + /* get response header */ + + if (php_stream_get_line(stream, tmp_line, sizeof(tmp_line) - 1, &tmp_line_len) != NULL) { + zval http_response; + + if (tmp_line_len > 9) { + response_code = atoi(tmp_line + 9); + } else { + response_code = 0; + } + if (context && NULL != (tmpzval = php_stream_context_get_option(context, "http", "ignore_errors"))) { + ignore_errors = zend_is_true(tmpzval); + } + /* when we request only the header, don't fail even on error codes */ + if ((options & STREAM_ONLY_GET_HEADERS) || ignore_errors) { + reqok = 1; + } + + /* status codes of 1xx are "informational", and will be followed by a real response + * e.g "100 Continue". RFC 7231 states that unexpected 1xx status MUST be parsed, + * and MAY be ignored. As such, we need to skip ahead to the "real" status*/ + if (response_code >= 100 && response_code < 200) { + /* consume lines until we find a line starting 'HTTP/1' */ + while ( + !php_stream_eof(stream) + && php_stream_get_line(stream, tmp_line, sizeof(tmp_line) - 1, &tmp_line_len) != NULL + && ( tmp_line_len < sizeof("HTTP/1") - 1 || strncasecmp(tmp_line, "HTTP/1", sizeof("HTTP/1") - 1) ) + ); + + if (tmp_line_len > 9) { + response_code = atoi(tmp_line + 9); + } else { + response_code = 0; + } + } + /* all status codes in the 2xx range are defined by the specification as successful; + * all status codes in the 3xx range are for redirection, and so also should never + * fail */ + if (response_code >= 200 && response_code < 400) { + reqok = 1; + } else { + switch(response_code) { + case 403: + php_stream_notify_error(context, PHP_STREAM_NOTIFY_AUTH_RESULT, + tmp_line, response_code); + break; + default: + /* safety net in the event tmp_line == NULL */ + if (!tmp_line_len) { + tmp_line[0] = '\0'; + } + php_stream_notify_error(context, PHP_STREAM_NOTIFY_FAILURE, + tmp_line, response_code); + } + } + if (tmp_line[tmp_line_len - 1] == '\n') { + --tmp_line_len; + if (tmp_line[tmp_line_len - 1] == '\r') { + --tmp_line_len; + } + } + ZVAL_STRINGL(&http_response, tmp_line, tmp_line_len); + zend_hash_next_index_insert(Z_ARRVAL(response_header), &http_response); + } + } else { + php_stream_wrapper_log_error(wrapper, options, "HTTP request failed, unexpected end of socket!"); + goto out; + } + + /* read past HTTP headers */ + + http_header_line = emalloc(HTTP_HEADER_BLOCK_SIZE); + + while (!php_stream_eof(stream)) { + size_t http_header_line_length; + + if (php_stream_get_line(stream, http_header_line, HTTP_HEADER_BLOCK_SIZE, &http_header_line_length) && *http_header_line != '\n' && *http_header_line != '\r') { + char *e = http_header_line + http_header_line_length - 1; + char *http_header_value; + if (*e != '\n') { + do { /* partial header */ + if (php_stream_get_line(stream, http_header_line, HTTP_HEADER_BLOCK_SIZE, &http_header_line_length) == NULL) { + php_stream_wrapper_log_error(wrapper, options, "Failed to read HTTP headers"); + goto out; + } + e = http_header_line + http_header_line_length - 1; + } while (*e != '\n'); + continue; + } + while (e >= http_header_line && (*e == '\n' || *e == '\r')) { + e--; + } + + /* The primary definition of an HTTP header in RFC 7230 states: + * > Each header field consists of a case-insensitive field name followed + * > by a colon (":"), optional leading whitespace, the field value, and + * > optional trailing whitespace. */ + + /* Strip trailing whitespace */ + while (e >= http_header_line && (*e == ' ' || *e == '\t')) { + e--; + } + + /* Terminate header line */ + e++; + *e = '\0'; + http_header_line_length = e - http_header_line; + + http_header_value = memchr(http_header_line, ':', http_header_line_length); + if (http_header_value) { + http_header_value++; /* Skip ':' */ + + /* Strip leading whitespace */ + while (http_header_value < e + && (*http_header_value == ' ' || *http_header_value == '\t')) { + http_header_value++; + } + } + + if (!strncasecmp(http_header_line, "Location:", sizeof("Location:")-1)) { + if (context && (tmpzval = php_stream_context_get_option(context, "http", "follow_location")) != NULL) { + follow_location = zval_is_true(tmpzval); + } else if (!((response_code >= 300 && response_code < 304) + || 307 == response_code || 308 == response_code)) { + /* we shouldn't redirect automatically + if follow_location isn't set and response_code not in (300, 301, 302, 303 and 307) + see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.1 + RFC 7238 defines 308: http://tools.ietf.org/html/rfc7238 */ + follow_location = 0; + } + strlcpy(location, http_header_value, sizeof(location)); + } else if (!strncasecmp(http_header_line, "Content-Type:", sizeof("Content-Type:")-1)) { + php_stream_notify_info(context, PHP_STREAM_NOTIFY_MIME_TYPE_IS, http_header_value, 0); + } else if (!strncasecmp(http_header_line, "Content-Length:", sizeof("Content-Length")-1)) { + file_size = atoi(http_header_value); + php_stream_notify_file_size(context, file_size, http_header_line, 0); + } else if ( + !strncasecmp(http_header_line, "Transfer-Encoding:", sizeof("Transfer-Encoding")-1) + && !strncasecmp(http_header_value, "Chunked", sizeof("Chunked")-1) + ) { + + /* create filter to decode response body */ + if (!(options & STREAM_ONLY_GET_HEADERS)) { + zend_long decode = 1; + + if (context && (tmpzval = php_stream_context_get_option(context, "http", "auto_decode")) != NULL) { + decode = zend_is_true(tmpzval); + } + if (decode) { + transfer_encoding = php_stream_filter_create("dechunk", NULL, php_stream_is_persistent(stream)); + if (transfer_encoding) { + /* don't store transfer-encodeing header */ + continue; + } + } + } + } + + { + zval http_header; + ZVAL_STRINGL(&http_header, http_header_line, http_header_line_length); + zend_hash_next_index_insert(Z_ARRVAL(response_header), &http_header); + } + } else { + break; + } + } + + if (!reqok || (location[0] != '\0' && follow_location)) { + if (!follow_location || (((options & STREAM_ONLY_GET_HEADERS) || ignore_errors) && redirect_max <= 1)) { + goto out; + } + + if (location[0] != '\0') + php_stream_notify_info(context, PHP_STREAM_NOTIFY_REDIRECTED, location, 0); + + php_stream_close(stream); + stream = NULL; + + if (location[0] != '\0') { + + char new_path[HTTP_HEADER_BLOCK_SIZE]; + char loc_path[HTTP_HEADER_BLOCK_SIZE]; + + *new_path='\0'; + if (strlen(location)<8 || (strncasecmp(location, "http://", sizeof("http://")-1) && + strncasecmp(location, "https://", sizeof("https://")-1) && + strncasecmp(location, "ftp://", sizeof("ftp://")-1) && + strncasecmp(location, "ftps://", sizeof("ftps://")-1))) + { + if (*location != '/') { + if (*(location+1) != '\0' && resource->path) { + char *s = strrchr(resource->path, '/'); + if (!s) { + s = resource->path; + if (!s[0]) { + efree(s); + s = resource->path = estrdup("/"); + } else { + *s = '/'; + } + } + s[1] = '\0'; + if (resource->path && *(resource->path) == '/' && *(resource->path + 1) == '\0') { + snprintf(loc_path, sizeof(loc_path) - 1, "%s%s", resource->path, location); + } else { + snprintf(loc_path, sizeof(loc_path) - 1, "%s/%s", resource->path, location); + } + } else { + snprintf(loc_path, sizeof(loc_path) - 1, "/%s", location); + } + } else { + strlcpy(loc_path, location, sizeof(loc_path)); + } + if ((use_ssl && resource->port != 443) || (!use_ssl && resource->port != 80)) { + snprintf(new_path, sizeof(new_path) - 1, "%s://%s:%d%s", resource->scheme, resource->host, resource->port, loc_path); + } else { + snprintf(new_path, sizeof(new_path) - 1, "%s://%s%s", resource->scheme, resource->host, loc_path); + } + } else { + strlcpy(new_path, location, sizeof(new_path)); + } + + php_url_free(resource); + /* check for invalid redirection URLs */ + if ((resource = php_url_parse(new_path)) == NULL) { + php_stream_wrapper_log_error(wrapper, options, "Invalid redirect URL! %s", new_path); + goto out; + } + +#define CHECK_FOR_CNTRL_CHARS(val) { \ + if (val) { \ + unsigned char *s, *e; \ + size_t l; \ + l = php_url_decode(val, strlen(val)); \ + s = (unsigned char*)val; e = s + l; \ + while (s < e) { \ + if (iscntrl(*s)) { \ + php_stream_wrapper_log_error(wrapper, options, "Invalid redirect URL! %s", new_path); \ + goto out; \ + } \ + s++; \ + } \ + } \ +} + /* check for control characters in login, password & path */ + if (strncasecmp(new_path, "http://", sizeof("http://") - 1) || strncasecmp(new_path, "https://", sizeof("https://") - 1)) { + CHECK_FOR_CNTRL_CHARS(resource->user) + CHECK_FOR_CNTRL_CHARS(resource->pass) + CHECK_FOR_CNTRL_CHARS(resource->path) + } + stream = php_stream_url_wrap_http_ex(wrapper, new_path, mode, options, opened_path, context, --redirect_max, HTTP_WRAPPER_REDIRECTED STREAMS_CC); + } else { + php_stream_wrapper_log_error(wrapper, options, "HTTP request failed! %s", tmp_line); + } + } +out: + + smart_str_free(&req_buf); + + if (http_header_line) { + efree(http_header_line); + } + + if (resource) { + php_url_free(resource); + } + + if (stream) { + if (header_init) { + ZVAL_COPY(&stream->wrapperdata, &response_header); + } + php_stream_notify_progress_init(context, 0, file_size); + + /* Restore original chunk size now that we're done with headers */ + if (options & STREAM_WILL_CAST) + php_stream_set_chunk_size(stream, (int)chunk_size); + + /* restore the users auto-detect-line-endings setting */ + stream->flags |= eol_detect; + + /* as far as streams are concerned, we are now at the start of + * the stream */ + stream->position = 0; + + /* restore mode */ + strlcpy(stream->mode, mode, sizeof(stream->mode)); + + if (transfer_encoding) { + php_stream_filter_append(&stream->readfilters, transfer_encoding); + } + } else { + if (transfer_encoding) { + php_stream_filter_free(transfer_encoding); + } + } + + zval_ptr_dtor(&response_header); + + return stream; +} +/* }}} */ + +php_stream *php_stream_url_wrap_http(php_stream_wrapper *wrapper, const char *path, const char *mode, int options, zend_string **opened_path, php_stream_context *context STREAMS_DC) /* {{{ */ +{ + return php_stream_url_wrap_http_ex(wrapper, path, mode, options, opened_path, context, PHP_URL_REDIRECT_MAX, HTTP_WRAPPER_HEADER_INIT STREAMS_CC); +} +/* }}} */ + +static int php_stream_http_stream_stat(php_stream_wrapper *wrapper, php_stream *stream, php_stream_statbuf *ssb) /* {{{ */ +{ + /* one day, we could fill in the details based on Date: and Content-Length: + * headers. For now, we return with a failure code to prevent the underlying + * file's details from being used instead. */ + return -1; +} +/* }}} */ + +static php_stream_wrapper_ops http_stream_wops = { + php_stream_url_wrap_http, + NULL, /* stream_close */ + php_stream_http_stream_stat, + NULL, /* stat_url */ + NULL, /* opendir */ + "http", + NULL, /* unlink */ + NULL, /* rename */ + NULL, /* mkdir */ + NULL, /* rmdir */ + NULL +}; + +PHPAPI php_stream_wrapper php_stream_http_wrapper = { + &http_stream_wops, + NULL, + 1 /* is_url */ +}; + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/ext/standard/link_win32.c b/ext/standard/link_win32.c index 53ce7fbb4d8cd..f7832e41ce626 100644 --- a/ext/standard/link_win32.c +++ b/ext/standard/link_win32.c @@ -87,6 +87,7 @@ PHP_FUNCTION(readlink) PHP_FUNCTION(linkinfo) { char *link; + char *dirname; size_t link_len; zend_stat_t sb; int ret; @@ -95,12 +96,22 @@ PHP_FUNCTION(linkinfo) return; } + dirname = estrndup(link, link_len); + php_dirname(dirname, link_len); + + if (php_check_open_basedir(dirname)) { + efree(dirname); + RETURN_FALSE; + } + ret = VCWD_STAT(link, &sb); if (ret == -1) { php_error_docref(NULL, E_WARNING, "%s", strerror(errno)); + efree(dirname); RETURN_LONG(Z_L(-1)); } + efree(dirname); RETURN_LONG((zend_long) sb.st_dev); } /* }}} */ diff --git a/ext/standard/string.c b/ext/standard/string.c index 565caa3bfb4dd..ce1c75ecc7413 100644 --- a/ext/standard/string.c +++ b/ext/standard/string.c @@ -4781,7 +4781,7 @@ PHPAPI size_t php_strip_tags_ex(char *rbuf, size_t len, int *stateptr, const cha if (state == 4) { /* Inside */ break; - } else if (state == 2 && *(p-1) != '\\') { + } else if (state == 2 && p >= buf + 1 && *(p-1) != '\\') { if (lc == c) { lc = '\0'; } else if (lc != '\\') { @@ -4808,7 +4808,7 @@ PHPAPI size_t php_strip_tags_ex(char *rbuf, size_t len, int *stateptr, const cha case '!': /* JavaScript & Other HTML scripting languages */ - if (state == 1 && *(p-1) == '<') { + if (state == 1 && p >= buf + 1 && *(p-1) == '<') { state = 3; lc = c; } else { @@ -4835,7 +4835,7 @@ PHPAPI size_t php_strip_tags_ex(char *rbuf, size_t len, int *stateptr, const cha case '?': - if (state == 1 && *(p-1) == '<') { + if (state == 1 && p >= buf + 1 && *(p-1) == '<') { br=0; state=2; break; diff --git a/ext/standard/string.c.orig b/ext/standard/string.c.orig new file mode 100644 index 0000000000000..565caa3bfb4dd --- /dev/null +++ b/ext/standard/string.c.orig @@ -0,0 +1,5673 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2017 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Rasmus Lerdorf | + | Stig S�ther Bakken | + | Zeev Suraski | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#include +#include "php.h" +#include "php_rand.h" +#include "php_string.h" +#include "php_variables.h" +#ifdef HAVE_LOCALE_H +# include +#endif +#ifdef HAVE_LANGINFO_H +# include +#endif +#ifdef HAVE_MONETARY_H +# include +#endif +/* + * This define is here because some versions of libintl redefine setlocale + * to point to libintl_setlocale. That's a ridiculous thing to do as far + * as I am concerned, but with this define and the subsequent undef we + * limit the damage to just the actual setlocale() call in this file + * without turning zif_setlocale into zif_libintl_setlocale. -Rasmus + */ +#define php_my_setlocale setlocale +#ifdef HAVE_LIBINTL +# include /* For LC_MESSAGES */ + #ifdef setlocale + # undef setlocale + #endif +#endif + +#include "scanf.h" +#include "zend_API.h" +#include "zend_execute.h" +#include "php_globals.h" +#include "basic_functions.h" +#include "zend_smart_str.h" +#include +#ifdef ZTS +#include "TSRM.h" +#endif + +/* For str_getcsv() support */ +#include "ext/standard/file.h" + +#define STR_PAD_LEFT 0 +#define STR_PAD_RIGHT 1 +#define STR_PAD_BOTH 2 +#define PHP_PATHINFO_DIRNAME 1 +#define PHP_PATHINFO_BASENAME 2 +#define PHP_PATHINFO_EXTENSION 4 +#define PHP_PATHINFO_FILENAME 8 +#define PHP_PATHINFO_ALL (PHP_PATHINFO_DIRNAME | PHP_PATHINFO_BASENAME | PHP_PATHINFO_EXTENSION | PHP_PATHINFO_FILENAME) + +#define STR_STRSPN 0 +#define STR_STRCSPN 1 + +/* {{{ register_string_constants + */ +void register_string_constants(INIT_FUNC_ARGS) +{ + REGISTER_LONG_CONSTANT("STR_PAD_LEFT", STR_PAD_LEFT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("STR_PAD_RIGHT", STR_PAD_RIGHT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("STR_PAD_BOTH", STR_PAD_BOTH, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PATHINFO_DIRNAME", PHP_PATHINFO_DIRNAME, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PATHINFO_BASENAME", PHP_PATHINFO_BASENAME, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PATHINFO_EXTENSION", PHP_PATHINFO_EXTENSION, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PATHINFO_FILENAME", PHP_PATHINFO_FILENAME, CONST_CS | CONST_PERSISTENT); + +#ifdef HAVE_LOCALECONV + /* If last members of struct lconv equal CHAR_MAX, no grouping is done */ + +/* This is bad, but since we are going to be hardcoding in the POSIX stuff anyway... */ +# ifndef HAVE_LIMITS_H +# define CHAR_MAX 127 +# endif + + REGISTER_LONG_CONSTANT("CHAR_MAX", CHAR_MAX, CONST_CS | CONST_PERSISTENT); +#endif + +#ifdef HAVE_LOCALE_H + REGISTER_LONG_CONSTANT("LC_CTYPE", LC_CTYPE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LC_NUMERIC", LC_NUMERIC, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LC_TIME", LC_TIME, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LC_COLLATE", LC_COLLATE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LC_MONETARY", LC_MONETARY, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("LC_ALL", LC_ALL, CONST_CS | CONST_PERSISTENT); +# ifdef LC_MESSAGES + REGISTER_LONG_CONSTANT("LC_MESSAGES", LC_MESSAGES, CONST_CS | CONST_PERSISTENT); +# endif +#endif + +} +/* }}} */ + +int php_tag_find(char *tag, size_t len, const char *set); + +#ifdef PHP_WIN32 +# define SET_ALIGNED(alignment, decl) __declspec(align(alignment)) decl +#elif HAVE_ATTRIBUTE_ALIGNED +# define SET_ALIGNED(alignment, decl) decl __attribute__ ((__aligned__ (alignment))) +#else +# define SET_ALIGNED(alignment, decl) decl +#endif + +/* this is read-only, so it's ok */ +SET_ALIGNED(16, static char hexconvtab[]) = "0123456789abcdef"; + +/* localeconv mutex */ +#ifdef ZTS +static MUTEX_T locale_mutex = NULL; +#endif + +/* {{{ php_bin2hex + */ +static zend_string *php_bin2hex(const unsigned char *old, const size_t oldlen) +{ + zend_string *result; + size_t i, j; + + result = zend_string_safe_alloc(oldlen, 2 * sizeof(char), 0, 0); + + for (i = j = 0; i < oldlen; i++) { + ZSTR_VAL(result)[j++] = hexconvtab[old[i] >> 4]; + ZSTR_VAL(result)[j++] = hexconvtab[old[i] & 15]; + } + ZSTR_VAL(result)[j] = '\0'; + + return result; +} +/* }}} */ + +/* {{{ php_hex2bin + */ +static zend_string *php_hex2bin(const unsigned char *old, const size_t oldlen) +{ + size_t target_length = oldlen >> 1; + zend_string *str = zend_string_alloc(target_length, 0); + unsigned char *ret = (unsigned char *)ZSTR_VAL(str); + size_t i, j; + + for (i = j = 0; i < target_length; i++) { + unsigned char c = old[j++]; + unsigned char l = c & ~0x20; + int is_letter = ((unsigned int) ((l - 'A') ^ (l - 'F' - 1))) >> (8 * sizeof(unsigned int) - 1); + unsigned char d; + + /* basically (c >= '0' && c <= '9') || (l >= 'A' && l <= 'F') */ + if (EXPECTED((((c ^ '0') - 10) >> (8 * sizeof(unsigned int) - 1)) | is_letter)) { + d = (l - 0x10 - 0x27 * is_letter) << 4; + } else { + zend_string_free(str); + return NULL; + } + c = old[j++]; + l = c & ~0x20; + is_letter = ((unsigned int) ((l - 'A') ^ (l - 'F' - 1))) >> (8 * sizeof(unsigned int) - 1); + if (EXPECTED((((c ^ '0') - 10) >> (8 * sizeof(unsigned int) - 1)) | is_letter)) { + d |= l - 0x10 - 0x27 * is_letter; + } else { + zend_string_free(str); + return NULL; + } + ret[i] = d; + } + ret[i] = '\0'; + + return str; +} +/* }}} */ + +#ifdef HAVE_LOCALECONV +/* {{{ localeconv_r + * glibc's localeconv is not reentrant, so lets make it so ... sorta */ +PHPAPI struct lconv *localeconv_r(struct lconv *out) +{ + +# ifdef ZTS + tsrm_mutex_lock( locale_mutex ); +# endif + +/* cur->locinfo is struct __crt_locale_info which implementation is + hidden in vc14. TODO revisit this and check if a workaround available + and needed. */ +#if defined(PHP_WIN32) && _MSC_VER < 1900 && defined(ZTS) + { + /* Even with the enabled per thread locale, localeconv + won't check any locale change in the master thread. */ + _locale_t cur = _get_current_locale(); + *out = *cur->locinfo->lconv; + _free_locale(cur); + } +#else + /* localeconv doesn't return an error condition */ + *out = *localeconv(); +#endif + +# ifdef ZTS + tsrm_mutex_unlock( locale_mutex ); +# endif + + return out; +} +/* }}} */ + +# ifdef ZTS +/* {{{ PHP_MINIT_FUNCTION + */ +PHP_MINIT_FUNCTION(localeconv) +{ + locale_mutex = tsrm_mutex_alloc(); + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_MSHUTDOWN_FUNCTION + */ +PHP_MSHUTDOWN_FUNCTION(localeconv) +{ + tsrm_mutex_free( locale_mutex ); + locale_mutex = NULL; + return SUCCESS; +} +/* }}} */ +# endif +#endif + +/* {{{ proto string bin2hex(string data) + Converts the binary representation of data to hex */ +PHP_FUNCTION(bin2hex) +{ + zend_string *result; + zend_string *data; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &data) == FAILURE) { + return; + } + + result = php_bin2hex((unsigned char *)ZSTR_VAL(data), ZSTR_LEN(data)); + + if (!result) { + RETURN_FALSE; + } + + RETURN_STR(result); +} +/* }}} */ + +/* {{{ proto string hex2bin(string data) + Converts the hex representation of data to binary */ +PHP_FUNCTION(hex2bin) +{ + zend_string *result, *data; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &data) == FAILURE) { + return; + } + + if (ZSTR_LEN(data) % 2 != 0) { + php_error_docref(NULL, E_WARNING, "Hexadecimal input string must have an even length"); + RETURN_FALSE; + } + + result = php_hex2bin((unsigned char *)ZSTR_VAL(data), ZSTR_LEN(data)); + + if (!result) { + php_error_docref(NULL, E_WARNING, "Input string must be hexadecimal string"); + RETURN_FALSE; + } + + RETVAL_STR(result); +} +/* }}} */ + +static void php_spn_common_handler(INTERNAL_FUNCTION_PARAMETERS, int behavior) /* {{{ */ +{ + zend_string *s11, *s22; + zend_long start = 0, len = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS|ll", &s11, + &s22, &start, &len) == FAILURE) { + return; + } + + if (ZEND_NUM_ARGS() < 4) { + len = ZSTR_LEN(s11); + } + + /* look at substr() function for more information */ + + if (start < 0) { + start += (zend_long)ZSTR_LEN(s11); + if (start < 0) { + start = 0; + } + } else if ((size_t)start > ZSTR_LEN(s11)) { + RETURN_FALSE; + } + + if (len < 0) { + len += (ZSTR_LEN(s11) - start); + if (len < 0) { + len = 0; + } + } + + if (len > (zend_long)ZSTR_LEN(s11) - start) { + len = ZSTR_LEN(s11) - start; + } + + if(len == 0) { + RETURN_LONG(0); + } + + if (behavior == STR_STRSPN) { + RETURN_LONG(php_strspn(ZSTR_VAL(s11) + start /*str1_start*/, + ZSTR_VAL(s22) /*str2_start*/, + ZSTR_VAL(s11) + start + len /*str1_end*/, + ZSTR_VAL(s22) + ZSTR_LEN(s22) /*str2_end*/)); + } else if (behavior == STR_STRCSPN) { + RETURN_LONG(php_strcspn(ZSTR_VAL(s11) + start /*str1_start*/, + ZSTR_VAL(s22) /*str2_start*/, + ZSTR_VAL(s11) + start + len /*str1_end*/, + ZSTR_VAL(s22) + ZSTR_LEN(s22) /*str2_end*/)); + } + +} +/* }}} */ + +/* {{{ proto int strspn(string str, string mask [, int start [, int len]]) + Finds length of initial segment consisting entirely of characters found in mask. If start or/and length is provided works like strspn(substr($s,$start,$len),$good_chars) */ +PHP_FUNCTION(strspn) +{ + php_spn_common_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, STR_STRSPN); +} +/* }}} */ + +/* {{{ proto int strcspn(string str, string mask [, int start [, int len]]) + Finds length of initial segment consisting entirely of characters not found in mask. If start or/and length is provide works like strcspn(substr($s,$start,$len),$bad_chars) */ +PHP_FUNCTION(strcspn) +{ + php_spn_common_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, STR_STRCSPN); +} +/* }}} */ + +/* {{{ PHP_MINIT_FUNCTION(nl_langinfo) */ +#if HAVE_NL_LANGINFO +PHP_MINIT_FUNCTION(nl_langinfo) +{ +#define REGISTER_NL_LANGINFO_CONSTANT(x) REGISTER_LONG_CONSTANT(#x, x, CONST_CS | CONST_PERSISTENT) +#ifdef ABDAY_1 + REGISTER_NL_LANGINFO_CONSTANT(ABDAY_1); + REGISTER_NL_LANGINFO_CONSTANT(ABDAY_2); + REGISTER_NL_LANGINFO_CONSTANT(ABDAY_3); + REGISTER_NL_LANGINFO_CONSTANT(ABDAY_4); + REGISTER_NL_LANGINFO_CONSTANT(ABDAY_5); + REGISTER_NL_LANGINFO_CONSTANT(ABDAY_6); + REGISTER_NL_LANGINFO_CONSTANT(ABDAY_7); +#endif +#ifdef DAY_1 + REGISTER_NL_LANGINFO_CONSTANT(DAY_1); + REGISTER_NL_LANGINFO_CONSTANT(DAY_2); + REGISTER_NL_LANGINFO_CONSTANT(DAY_3); + REGISTER_NL_LANGINFO_CONSTANT(DAY_4); + REGISTER_NL_LANGINFO_CONSTANT(DAY_5); + REGISTER_NL_LANGINFO_CONSTANT(DAY_6); + REGISTER_NL_LANGINFO_CONSTANT(DAY_7); +#endif +#ifdef ABMON_1 + REGISTER_NL_LANGINFO_CONSTANT(ABMON_1); + REGISTER_NL_LANGINFO_CONSTANT(ABMON_2); + REGISTER_NL_LANGINFO_CONSTANT(ABMON_3); + REGISTER_NL_LANGINFO_CONSTANT(ABMON_4); + REGISTER_NL_LANGINFO_CONSTANT(ABMON_5); + REGISTER_NL_LANGINFO_CONSTANT(ABMON_6); + REGISTER_NL_LANGINFO_CONSTANT(ABMON_7); + REGISTER_NL_LANGINFO_CONSTANT(ABMON_8); + REGISTER_NL_LANGINFO_CONSTANT(ABMON_9); + REGISTER_NL_LANGINFO_CONSTANT(ABMON_10); + REGISTER_NL_LANGINFO_CONSTANT(ABMON_11); + REGISTER_NL_LANGINFO_CONSTANT(ABMON_12); +#endif +#ifdef MON_1 + REGISTER_NL_LANGINFO_CONSTANT(MON_1); + REGISTER_NL_LANGINFO_CONSTANT(MON_2); + REGISTER_NL_LANGINFO_CONSTANT(MON_3); + REGISTER_NL_LANGINFO_CONSTANT(MON_4); + REGISTER_NL_LANGINFO_CONSTANT(MON_5); + REGISTER_NL_LANGINFO_CONSTANT(MON_6); + REGISTER_NL_LANGINFO_CONSTANT(MON_7); + REGISTER_NL_LANGINFO_CONSTANT(MON_8); + REGISTER_NL_LANGINFO_CONSTANT(MON_9); + REGISTER_NL_LANGINFO_CONSTANT(MON_10); + REGISTER_NL_LANGINFO_CONSTANT(MON_11); + REGISTER_NL_LANGINFO_CONSTANT(MON_12); +#endif +#ifdef AM_STR + REGISTER_NL_LANGINFO_CONSTANT(AM_STR); +#endif +#ifdef PM_STR + REGISTER_NL_LANGINFO_CONSTANT(PM_STR); +#endif +#ifdef D_T_FMT + REGISTER_NL_LANGINFO_CONSTANT(D_T_FMT); +#endif +#ifdef D_FMT + REGISTER_NL_LANGINFO_CONSTANT(D_FMT); +#endif +#ifdef T_FMT + REGISTER_NL_LANGINFO_CONSTANT(T_FMT); +#endif +#ifdef T_FMT_AMPM + REGISTER_NL_LANGINFO_CONSTANT(T_FMT_AMPM); +#endif +#ifdef ERA + REGISTER_NL_LANGINFO_CONSTANT(ERA); +#endif +#ifdef ERA_YEAR + REGISTER_NL_LANGINFO_CONSTANT(ERA_YEAR); +#endif +#ifdef ERA_D_T_FMT + REGISTER_NL_LANGINFO_CONSTANT(ERA_D_T_FMT); +#endif +#ifdef ERA_D_FMT + REGISTER_NL_LANGINFO_CONSTANT(ERA_D_FMT); +#endif +#ifdef ERA_T_FMT + REGISTER_NL_LANGINFO_CONSTANT(ERA_T_FMT); +#endif +#ifdef ALT_DIGITS + REGISTER_NL_LANGINFO_CONSTANT(ALT_DIGITS); +#endif +#ifdef INT_CURR_SYMBOL + REGISTER_NL_LANGINFO_CONSTANT(INT_CURR_SYMBOL); +#endif +#ifdef CURRENCY_SYMBOL + REGISTER_NL_LANGINFO_CONSTANT(CURRENCY_SYMBOL); +#endif +#ifdef CRNCYSTR + REGISTER_NL_LANGINFO_CONSTANT(CRNCYSTR); +#endif +#ifdef MON_DECIMAL_POINT + REGISTER_NL_LANGINFO_CONSTANT(MON_DECIMAL_POINT); +#endif +#ifdef MON_THOUSANDS_SEP + REGISTER_NL_LANGINFO_CONSTANT(MON_THOUSANDS_SEP); +#endif +#ifdef MON_GROUPING + REGISTER_NL_LANGINFO_CONSTANT(MON_GROUPING); +#endif +#ifdef POSITIVE_SIGN + REGISTER_NL_LANGINFO_CONSTANT(POSITIVE_SIGN); +#endif +#ifdef NEGATIVE_SIGN + REGISTER_NL_LANGINFO_CONSTANT(NEGATIVE_SIGN); +#endif +#ifdef INT_FRAC_DIGITS + REGISTER_NL_LANGINFO_CONSTANT(INT_FRAC_DIGITS); +#endif +#ifdef FRAC_DIGITS + REGISTER_NL_LANGINFO_CONSTANT(FRAC_DIGITS); +#endif +#ifdef P_CS_PRECEDES + REGISTER_NL_LANGINFO_CONSTANT(P_CS_PRECEDES); +#endif +#ifdef P_SEP_BY_SPACE + REGISTER_NL_LANGINFO_CONSTANT(P_SEP_BY_SPACE); +#endif +#ifdef N_CS_PRECEDES + REGISTER_NL_LANGINFO_CONSTANT(N_CS_PRECEDES); +#endif +#ifdef N_SEP_BY_SPACE + REGISTER_NL_LANGINFO_CONSTANT(N_SEP_BY_SPACE); +#endif +#ifdef P_SIGN_POSN + REGISTER_NL_LANGINFO_CONSTANT(P_SIGN_POSN); +#endif +#ifdef N_SIGN_POSN + REGISTER_NL_LANGINFO_CONSTANT(N_SIGN_POSN); +#endif +#ifdef DECIMAL_POINT + REGISTER_NL_LANGINFO_CONSTANT(DECIMAL_POINT); +#endif +#ifdef RADIXCHAR + REGISTER_NL_LANGINFO_CONSTANT(RADIXCHAR); +#endif +#ifdef THOUSANDS_SEP + REGISTER_NL_LANGINFO_CONSTANT(THOUSANDS_SEP); +#endif +#ifdef THOUSEP + REGISTER_NL_LANGINFO_CONSTANT(THOUSEP); +#endif +#ifdef GROUPING + REGISTER_NL_LANGINFO_CONSTANT(GROUPING); +#endif +#ifdef YESEXPR + REGISTER_NL_LANGINFO_CONSTANT(YESEXPR); +#endif +#ifdef NOEXPR + REGISTER_NL_LANGINFO_CONSTANT(NOEXPR); +#endif +#ifdef YESSTR + REGISTER_NL_LANGINFO_CONSTANT(YESSTR); +#endif +#ifdef NOSTR + REGISTER_NL_LANGINFO_CONSTANT(NOSTR); +#endif +#ifdef CODESET + REGISTER_NL_LANGINFO_CONSTANT(CODESET); +#endif +#undef REGISTER_NL_LANGINFO_CONSTANT + return SUCCESS; +} +/* }}} */ + +/* {{{ proto string nl_langinfo(int item) + Query language and locale information */ +PHP_FUNCTION(nl_langinfo) +{ + zend_long item; + char *value; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &item) == FAILURE) { + return; + } + + switch(item) { /* {{{ */ +#ifdef ABDAY_1 + case ABDAY_1: + case ABDAY_2: + case ABDAY_3: + case ABDAY_4: + case ABDAY_5: + case ABDAY_6: + case ABDAY_7: +#endif +#ifdef DAY_1 + case DAY_1: + case DAY_2: + case DAY_3: + case DAY_4: + case DAY_5: + case DAY_6: + case DAY_7: +#endif +#ifdef ABMON_1 + case ABMON_1: + case ABMON_2: + case ABMON_3: + case ABMON_4: + case ABMON_5: + case ABMON_6: + case ABMON_7: + case ABMON_8: + case ABMON_9: + case ABMON_10: + case ABMON_11: + case ABMON_12: +#endif +#ifdef MON_1 + case MON_1: + case MON_2: + case MON_3: + case MON_4: + case MON_5: + case MON_6: + case MON_7: + case MON_8: + case MON_9: + case MON_10: + case MON_11: + case MON_12: +#endif +#ifdef AM_STR + case AM_STR: +#endif +#ifdef PM_STR + case PM_STR: +#endif +#ifdef D_T_FMT + case D_T_FMT: +#endif +#ifdef D_FMT + case D_FMT: +#endif +#ifdef T_FMT + case T_FMT: +#endif +#ifdef T_FMT_AMPM + case T_FMT_AMPM: +#endif +#ifdef ERA + case ERA: +#endif +#ifdef ERA_YEAR + case ERA_YEAR: +#endif +#ifdef ERA_D_T_FMT + case ERA_D_T_FMT: +#endif +#ifdef ERA_D_FMT + case ERA_D_FMT: +#endif +#ifdef ERA_T_FMT + case ERA_T_FMT: +#endif +#ifdef ALT_DIGITS + case ALT_DIGITS: +#endif +#ifdef INT_CURR_SYMBOL + case INT_CURR_SYMBOL: +#endif +#ifdef CURRENCY_SYMBOL + case CURRENCY_SYMBOL: +#endif +#ifdef CRNCYSTR + case CRNCYSTR: +#endif +#ifdef MON_DECIMAL_POINT + case MON_DECIMAL_POINT: +#endif +#ifdef MON_THOUSANDS_SEP + case MON_THOUSANDS_SEP: +#endif +#ifdef MON_GROUPING + case MON_GROUPING: +#endif +#ifdef POSITIVE_SIGN + case POSITIVE_SIGN: +#endif +#ifdef NEGATIVE_SIGN + case NEGATIVE_SIGN: +#endif +#ifdef INT_FRAC_DIGITS + case INT_FRAC_DIGITS: +#endif +#ifdef FRAC_DIGITS + case FRAC_DIGITS: +#endif +#ifdef P_CS_PRECEDES + case P_CS_PRECEDES: +#endif +#ifdef P_SEP_BY_SPACE + case P_SEP_BY_SPACE: +#endif +#ifdef N_CS_PRECEDES + case N_CS_PRECEDES: +#endif +#ifdef N_SEP_BY_SPACE + case N_SEP_BY_SPACE: +#endif +#ifdef P_SIGN_POSN + case P_SIGN_POSN: +#endif +#ifdef N_SIGN_POSN + case N_SIGN_POSN: +#endif +#ifdef DECIMAL_POINT + case DECIMAL_POINT: +#elif defined(RADIXCHAR) + case RADIXCHAR: +#endif +#ifdef THOUSANDS_SEP + case THOUSANDS_SEP: +#elif defined(THOUSEP) + case THOUSEP: +#endif +#ifdef GROUPING + case GROUPING: +#endif +#ifdef YESEXPR + case YESEXPR: +#endif +#ifdef NOEXPR + case NOEXPR: +#endif +#ifdef YESSTR + case YESSTR: +#endif +#ifdef NOSTR + case NOSTR: +#endif +#ifdef CODESET + case CODESET: +#endif + break; + default: + php_error_docref(NULL, E_WARNING, "Item '" ZEND_LONG_FMT "' is not valid", item); + RETURN_FALSE; + } + /* }}} */ + + value = nl_langinfo(item); + if (value == NULL) { + RETURN_FALSE; + } else { + RETURN_STRING(value); + } +} +#endif +/* }}} */ + +#ifdef HAVE_STRCOLL +/* {{{ proto int strcoll(string str1, string str2) + Compares two strings using the current locale */ +PHP_FUNCTION(strcoll) +{ + zend_string *s1, *s2; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS", &s1, &s2) == FAILURE) { + return; + } + + RETURN_LONG(strcoll((const char *) ZSTR_VAL(s1), + (const char *) ZSTR_VAL(s2))); +} +/* }}} */ +#endif + +/* {{{ php_charmask + * Fills a 256-byte bytemask with input. You can specify a range like 'a..z', + * it needs to be incrementing. + * Returns: FAILURE/SUCCESS whether the input was correct (i.e. no range errors) + */ +static inline int php_charmask(unsigned char *input, size_t len, char *mask) +{ + unsigned char *end; + unsigned char c; + int result = SUCCESS; + + memset(mask, 0, 256); + for (end = input+len; input < end; input++) { + c=*input; + if ((input+3 < end) && input[1] == '.' && input[2] == '.' + && input[3] >= c) { + memset(mask+c, 1, input[3] - c + 1); + input+=3; + } else if ((input+1 < end) && input[0] == '.' && input[1] == '.') { + /* Error, try to be as helpful as possible: + (a range ending/starting with '.' won't be captured here) */ + if (end-len >= input) { /* there was no 'left' char */ + php_error_docref(NULL, E_WARNING, "Invalid '..'-range, no character to the left of '..'"); + result = FAILURE; + continue; + } + if (input+2 >= end) { /* there is no 'right' char */ + php_error_docref(NULL, E_WARNING, "Invalid '..'-range, no character to the right of '..'"); + result = FAILURE; + continue; + } + if (input[-1] > input[2]) { /* wrong order */ + php_error_docref(NULL, E_WARNING, "Invalid '..'-range, '..'-range needs to be incrementing"); + result = FAILURE; + continue; + } + /* FIXME: better error (a..b..c is the only left possibility?) */ + php_error_docref(NULL, E_WARNING, "Invalid '..'-range"); + result = FAILURE; + continue; + } else { + mask[c]=1; + } + } + return result; +} +/* }}} */ + +/* {{{ php_trim() + * mode 1 : trim left + * mode 2 : trim right + * mode 3 : trim left and right + * what indicates which chars are to be trimmed. NULL->default (' \t\n\r\v\0') + */ +PHPAPI zend_string *php_trim(zend_string *str, char *what, size_t what_len, int mode) +{ + const char *c = ZSTR_VAL(str); + size_t len = ZSTR_LEN(str); + register size_t i; + size_t trimmed = 0; + char mask[256]; + + if (what) { + if (what_len == 1) { + char p = *what; + if (mode & 1) { + for (i = 0; i < len; i++) { + if (c[i] == p) { + trimmed++; + } else { + break; + } + } + len -= trimmed; + c += trimmed; + } + if (mode & 2) { + if (len > 0) { + i = len - 1; + do { + if (c[i] == p) { + len--; + } else { + break; + } + } while (i-- != 0); + } + } + } else { + php_charmask((unsigned char*)what, what_len, mask); + + if (mode & 1) { + for (i = 0; i < len; i++) { + if (mask[(unsigned char)c[i]]) { + trimmed++; + } else { + break; + } + } + len -= trimmed; + c += trimmed; + } + if (mode & 2) { + if (len > 0) { + i = len - 1; + do { + if (mask[(unsigned char)c[i]]) { + len--; + } else { + break; + } + } while (i-- != 0); + } + } + } + } else { + if (mode & 1) { + for (i = 0; i < len; i++) { + if ((unsigned char)c[i] <= ' ' && + (c[i] == ' ' || c[i] == '\n' || c[i] == '\r' || c[i] == '\t' || c[i] == '\v' || c[i] == '\0')) { + trimmed++; + } else { + break; + } + } + len -= trimmed; + c += trimmed; + } + if (mode & 2) { + if (len > 0) { + i = len - 1; + do { + if ((unsigned char)c[i] <= ' ' && + (c[i] == ' ' || c[i] == '\n' || c[i] == '\r' || c[i] == '\t' || c[i] == '\v' || c[i] == '\0')) { + len--; + } else { + break; + } + } while (i-- != 0); + } + } + } + + if (ZSTR_LEN(str) == len) { + return zend_string_copy(str); + } else { + return zend_string_init(c, len, 0); + } +} +/* }}} */ + +/* {{{ php_do_trim + * Base for trim(), rtrim() and ltrim() functions. + */ +static void php_do_trim(INTERNAL_FUNCTION_PARAMETERS, int mode) +{ + zend_string *str; + zend_string *what = NULL; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STR(str) + Z_PARAM_OPTIONAL + Z_PARAM_STR(what) + ZEND_PARSE_PARAMETERS_END(); + + ZVAL_STR(return_value, php_trim(str, (what ? ZSTR_VAL(what) : NULL), (what ? ZSTR_LEN(what) : 0), mode)); +} +/* }}} */ + +/* {{{ proto string trim(string str [, string character_mask]) + Strips whitespace from the beginning and end of a string */ +PHP_FUNCTION(trim) +{ + php_do_trim(INTERNAL_FUNCTION_PARAM_PASSTHRU, 3); +} +/* }}} */ + +/* {{{ proto string rtrim(string str [, string character_mask]) + Removes trailing whitespace */ +PHP_FUNCTION(rtrim) +{ + php_do_trim(INTERNAL_FUNCTION_PARAM_PASSTHRU, 2); +} +/* }}} */ + +/* {{{ proto string ltrim(string str [, string character_mask]) + Strips whitespace from the beginning of a string */ +PHP_FUNCTION(ltrim) +{ + php_do_trim(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); +} +/* }}} */ + +/* {{{ proto string wordwrap(string str [, int width [, string break [, boolean cut]]]) + Wraps buffer to selected number of characters using string break char */ +PHP_FUNCTION(wordwrap) +{ + zend_string *text; + char *breakchar = "\n"; + size_t newtextlen, chk, breakchar_len = 1; + size_t alloced; + zend_long current = 0, laststart = 0, lastspace = 0; + zend_long linelength = 75; + zend_bool docut = 0; + zend_string *newtext; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|lsb", &text, &linelength, &breakchar, &breakchar_len, &docut) == FAILURE) { + return; + } + + if (ZSTR_LEN(text) == 0) { + RETURN_EMPTY_STRING(); + } + + if (breakchar_len == 0) { + php_error_docref(NULL, E_WARNING, "Break string cannot be empty"); + RETURN_FALSE; + } + + if (linelength == 0 && docut) { + php_error_docref(NULL, E_WARNING, "Can't force cut when width is zero"); + RETURN_FALSE; + } + + /* Special case for a single-character break as it needs no + additional storage space */ + if (breakchar_len == 1 && !docut) { + newtext = zend_string_init(ZSTR_VAL(text), ZSTR_LEN(text), 0); + + laststart = lastspace = 0; + for (current = 0; current < (zend_long)ZSTR_LEN(text); current++) { + if (ZSTR_VAL(text)[current] == breakchar[0]) { + laststart = lastspace = current + 1; + } else if (ZSTR_VAL(text)[current] == ' ') { + if (current - laststart >= linelength) { + ZSTR_VAL(newtext)[current] = breakchar[0]; + laststart = current + 1; + } + lastspace = current; + } else if (current - laststart >= linelength && laststart != lastspace) { + ZSTR_VAL(newtext)[lastspace] = breakchar[0]; + laststart = lastspace + 1; + } + } + + RETURN_NEW_STR(newtext); + } else { + /* Multiple character line break or forced cut */ + if (linelength > 0) { + chk = (size_t)(ZSTR_LEN(text)/linelength + 1); + newtext = zend_string_safe_alloc(chk, breakchar_len, ZSTR_LEN(text), 0); + alloced = ZSTR_LEN(text) + chk * breakchar_len + 1; + } else { + chk = ZSTR_LEN(text); + alloced = ZSTR_LEN(text) * (breakchar_len + 1) + 1; + newtext = zend_string_safe_alloc(ZSTR_LEN(text), breakchar_len + 1, 0, 0); + } + + /* now keep track of the actual new text length */ + newtextlen = 0; + + laststart = lastspace = 0; + for (current = 0; current < (zend_long)ZSTR_LEN(text); current++) { + if (chk <= 0) { + alloced += (size_t) (((ZSTR_LEN(text) - current + 1)/linelength + 1) * breakchar_len) + 1; + newtext = zend_string_extend(newtext, alloced, 0); + chk = (size_t) ((ZSTR_LEN(text) - current)/linelength) + 1; + } + /* when we hit an existing break, copy to new buffer, and + * fix up laststart and lastspace */ + if (ZSTR_VAL(text)[current] == breakchar[0] + && current + breakchar_len < ZSTR_LEN(text) + && !strncmp(ZSTR_VAL(text) + current, breakchar, breakchar_len)) { + memcpy(ZSTR_VAL(newtext) + newtextlen, ZSTR_VAL(text) + laststart, current - laststart + breakchar_len); + newtextlen += current - laststart + breakchar_len; + current += breakchar_len - 1; + laststart = lastspace = current + 1; + chk--; + } + /* if it is a space, check if it is at the line boundary, + * copy and insert a break, or just keep track of it */ + else if (ZSTR_VAL(text)[current] == ' ') { + if (current - laststart >= linelength) { + memcpy(ZSTR_VAL(newtext) + newtextlen, ZSTR_VAL(text) + laststart, current - laststart); + newtextlen += current - laststart; + memcpy(ZSTR_VAL(newtext) + newtextlen, breakchar, breakchar_len); + newtextlen += breakchar_len; + laststart = current + 1; + chk--; + } + lastspace = current; + } + /* if we are cutting, and we've accumulated enough + * characters, and we haven't see a space for this line, + * copy and insert a break. */ + else if (current - laststart >= linelength + && docut && laststart >= lastspace) { + memcpy(ZSTR_VAL(newtext) + newtextlen, ZSTR_VAL(text) + laststart, current - laststart); + newtextlen += current - laststart; + memcpy(ZSTR_VAL(newtext) + newtextlen, breakchar, breakchar_len); + newtextlen += breakchar_len; + laststart = lastspace = current; + chk--; + } + /* if the current word puts us over the linelength, copy + * back up until the last space, insert a break, and move + * up the laststart */ + else if (current - laststart >= linelength + && laststart < lastspace) { + memcpy(ZSTR_VAL(newtext) + newtextlen, ZSTR_VAL(text) + laststart, lastspace - laststart); + newtextlen += lastspace - laststart; + memcpy(ZSTR_VAL(newtext) + newtextlen, breakchar, breakchar_len); + newtextlen += breakchar_len; + laststart = lastspace = lastspace + 1; + chk--; + } + } + + /* copy over any stragglers */ + if (laststart != current) { + memcpy(ZSTR_VAL(newtext) + newtextlen, ZSTR_VAL(text) + laststart, current - laststart); + newtextlen += current - laststart; + } + + ZSTR_VAL(newtext)[newtextlen] = '\0'; + /* free unused memory */ + newtext = zend_string_truncate(newtext, newtextlen, 0); + + RETURN_NEW_STR(newtext); + } +} +/* }}} */ + +/* {{{ php_explode + */ +PHPAPI void php_explode(const zend_string *delim, zend_string *str, zval *return_value, zend_long limit) +{ + char *p1 = ZSTR_VAL(str); + char *endp = ZSTR_VAL(str) + ZSTR_LEN(str); + char *p2 = (char *) php_memnstr(ZSTR_VAL(str), ZSTR_VAL(delim), ZSTR_LEN(delim), endp); + zval tmp; + + if (p2 == NULL) { + ZVAL_STR_COPY(&tmp, str); + zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &tmp); + } else { + do { + ZVAL_STRINGL(&tmp, p1, p2 - p1); + zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &tmp); + p1 = p2 + ZSTR_LEN(delim); + p2 = (char *) php_memnstr(p1, ZSTR_VAL(delim), ZSTR_LEN(delim), endp); + } while (p2 != NULL && --limit > 1); + + if (p1 <= endp) { + ZVAL_STRINGL(&tmp, p1, endp - p1); + zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &tmp); + } + } +} +/* }}} */ + +/* {{{ php_explode_negative_limit + */ +PHPAPI void php_explode_negative_limit(const zend_string *delim, zend_string *str, zval *return_value, zend_long limit) +{ +#define EXPLODE_ALLOC_STEP 64 + char *p1 = ZSTR_VAL(str); + char *endp = ZSTR_VAL(str) + ZSTR_LEN(str); + char *p2 = (char *) php_memnstr(ZSTR_VAL(str), ZSTR_VAL(delim), ZSTR_LEN(delim), endp); + zval tmp; + + if (p2 == NULL) { + /* + do nothing since limit <= -1, thus if only one chunk - 1 + (limit) <= 0 + by doing nothing we return empty array + */ + } else { + size_t allocated = EXPLODE_ALLOC_STEP, found = 0; + zend_long i, to_return; + char **positions = emalloc(allocated * sizeof(char *)); + + positions[found++] = p1; + do { + if (found >= allocated) { + allocated = found + EXPLODE_ALLOC_STEP;/* make sure we have enough memory */ + positions = erealloc(positions, allocated*sizeof(char *)); + } + positions[found++] = p1 = p2 + ZSTR_LEN(delim); + p2 = (char *) php_memnstr(p1, ZSTR_VAL(delim), ZSTR_LEN(delim), endp); + } while (p2 != NULL); + + to_return = limit + found; + /* limit is at least -1 therefore no need of bounds checking : i will be always less than found */ + for (i = 0; i < to_return; i++) { /* this checks also for to_return > 0 */ + ZVAL_STRINGL(&tmp, positions[i], (positions[i+1] - ZSTR_LEN(delim)) - positions[i]); + zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &tmp); + } + efree(positions); + } +#undef EXPLODE_ALLOC_STEP +} +/* }}} */ + +/* {{{ proto array explode(string separator, string str [, int limit]) + Splits a string on string separator and return array of components. If limit is positive only limit number of components is returned. If limit is negative all components except the last abs(limit) are returned. */ +PHP_FUNCTION(explode) +{ + zend_string *str, *delim; + zend_long limit = ZEND_LONG_MAX; /* No limit */ + zval tmp; + + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_STR(delim) + Z_PARAM_STR(str) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(limit) + ZEND_PARSE_PARAMETERS_END(); + + if (ZSTR_LEN(delim) == 0) { + php_error_docref(NULL, E_WARNING, "Empty delimiter"); + RETURN_FALSE; + } + + array_init(return_value); + + if (ZSTR_LEN(str) == 0) { + if (limit >= 0) { + ZVAL_EMPTY_STRING(&tmp); + zend_hash_index_add_new(Z_ARRVAL_P(return_value), 0, &tmp); + } + return; + } + + if (limit > 1) { + php_explode(delim, str, return_value, limit); + } else if (limit < 0) { + php_explode_negative_limit(delim, str, return_value, limit); + } else { + ZVAL_STR_COPY(&tmp, str); + zend_hash_index_add_new(Z_ARRVAL_P(return_value), 0, &tmp); + } +} +/* }}} */ + +/* {{{ proto string join(array src, string glue) + An alias for implode */ +/* }}} */ + +/* {{{ php_implode + */ +PHPAPI void php_implode(const zend_string *delim, zval *arr, zval *return_value) +{ + zval *tmp; + int numelems; + zend_string *str; + char *cptr; + size_t len = 0; + zend_string **strings, **strptr; + + numelems = zend_hash_num_elements(Z_ARRVAL_P(arr)); + + if (numelems == 0) { + RETURN_EMPTY_STRING(); + } else if (numelems == 1) { + /* loop to search the first not undefined element... */ + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(arr), tmp) { + RETURN_STR(zval_get_string(tmp)); + } ZEND_HASH_FOREACH_END(); + } + + strings = emalloc((sizeof(zend_long) + sizeof(zend_string *)) * numelems); + strptr = strings - 1; + + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(arr), tmp) { + if (Z_TYPE_P(tmp) == IS_LONG) { + zend_long val = Z_LVAL_P(tmp); + + *++strptr = NULL; + ((zend_long *) (strings + numelems))[strptr - strings] = Z_LVAL_P(tmp); + if (val <= 0) { + len++; + } + while (val) { + val /= 10; + len++; + } + } else { + *++strptr = zval_get_string(tmp); + len += ZSTR_LEN(*strptr); + } + } ZEND_HASH_FOREACH_END(); + /* numelems can not be 0, we checked above */ + str = zend_string_safe_alloc(numelems - 1, ZSTR_LEN(delim), len, 0); + cptr = ZSTR_VAL(str) + ZSTR_LEN(str); + *cptr = 0; + + do { + if (*strptr) { + cptr -= ZSTR_LEN(*strptr); + memcpy(cptr, ZSTR_VAL(*strptr), ZSTR_LEN(*strptr)); + zend_string_release(*strptr); + } else { + char *oldPtr = cptr; + char oldVal = *cptr; + zend_long val = ((zend_long *) (strings + numelems))[strptr - strings]; + cptr = zend_print_long_to_buf(cptr, val); + *oldPtr = oldVal; + } + + cptr -= ZSTR_LEN(delim); + memcpy(cptr, ZSTR_VAL(delim), ZSTR_LEN(delim)); + } while (--strptr > strings); + + if (*strptr) { + memcpy(ZSTR_VAL(str), ZSTR_VAL(*strptr), ZSTR_LEN(*strptr)); + zend_string_release(*strptr); + } else { + char *oldPtr = cptr; + char oldVal = *cptr; + zend_print_long_to_buf(cptr, ((zend_long *) (strings + numelems))[strptr - strings]); + *oldPtr = oldVal; + } + + efree(strings); + RETURN_NEW_STR(str); +} +/* }}} */ + +/* {{{ proto string implode([string glue,] array pieces) + Joins array elements placing glue string between items and return one string */ +PHP_FUNCTION(implode) +{ + zval *arg1, *arg2 = NULL, *arr; + zend_string *delim; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_ZVAL(arg1) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL(arg2) + ZEND_PARSE_PARAMETERS_END(); + + if (arg2 == NULL) { + if (Z_TYPE_P(arg1) != IS_ARRAY) { + php_error_docref(NULL, E_WARNING, "Argument must be an array"); + return; + } + + delim = ZSTR_EMPTY_ALLOC(); + arr = arg1; + } else { + if (Z_TYPE_P(arg1) == IS_ARRAY) { + delim = zval_get_string(arg2); + arr = arg1; + } else if (Z_TYPE_P(arg2) == IS_ARRAY) { + delim = zval_get_string(arg1); + arr = arg2; + } else { + php_error_docref(NULL, E_WARNING, "Invalid arguments passed"); + return; + } + } + + php_implode(delim, arr, return_value); + zend_string_release(delim); +} +/* }}} */ + +#define STRTOK_TABLE(p) BG(strtok_table)[(unsigned char) *p] + +/* {{{ proto string strtok([string str,] string token) + Tokenize a string */ +PHP_FUNCTION(strtok) +{ + zend_string *str, *tok = NULL; + char *token; + char *token_end; + char *p; + char *pe; + size_t skipped = 0; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STR(str) + Z_PARAM_OPTIONAL + Z_PARAM_STR(tok) + ZEND_PARSE_PARAMETERS_END(); + + if (ZEND_NUM_ARGS() == 1) { + tok = str; + } else { + zval_ptr_dtor(&BG(strtok_zval)); + ZVAL_STRINGL(&BG(strtok_zval), ZSTR_VAL(str), ZSTR_LEN(str)); + BG(strtok_last) = BG(strtok_string) = Z_STRVAL(BG(strtok_zval)); + BG(strtok_len) = ZSTR_LEN(str); + } + + p = BG(strtok_last); /* Where we start to search */ + pe = BG(strtok_string) + BG(strtok_len); + + if (!p || p >= pe) { + RETURN_FALSE; + } + + token = ZSTR_VAL(tok); + token_end = token + ZSTR_LEN(tok); + + while (token < token_end) { + STRTOK_TABLE(token++) = 1; + } + + /* Skip leading delimiters */ + while (STRTOK_TABLE(p)) { + if (++p >= pe) { + /* no other chars left */ + BG(strtok_last) = NULL; + RETVAL_FALSE; + goto restore; + } + skipped++; + } + + /* We know at this place that *p is no delimiter, so skip it */ + while (++p < pe) { + if (STRTOK_TABLE(p)) { + goto return_token; + } + } + + if (p - BG(strtok_last)) { +return_token: + RETVAL_STRINGL(BG(strtok_last) + skipped, (p - BG(strtok_last)) - skipped); + BG(strtok_last) = p + 1; + } else { + RETVAL_FALSE; + BG(strtok_last) = NULL; + } + + /* Restore table -- usually faster then memset'ing the table on every invocation */ +restore: + token = ZSTR_VAL(tok); + + while (token < token_end) { + STRTOK_TABLE(token++) = 0; + } +} +/* }}} */ + +/* {{{ php_strtoupper + */ +PHPAPI char *php_strtoupper(char *s, size_t len) +{ + unsigned char *c, *e; + + c = (unsigned char *)s; + e = (unsigned char *)c+len; + + while (c < e) { + *c = toupper(*c); + c++; + } + return s; +} +/* }}} */ + +/* {{{ php_string_toupper + */ +PHPAPI zend_string *php_string_toupper(zend_string *s) +{ + unsigned char *c, *e; + + c = (unsigned char *)ZSTR_VAL(s); + e = c + ZSTR_LEN(s); + + while (c < e) { + if (islower(*c)) { + register unsigned char *r; + zend_string *res = zend_string_alloc(ZSTR_LEN(s), 0); + + if (c != (unsigned char*)ZSTR_VAL(s)) { + memcpy(ZSTR_VAL(res), ZSTR_VAL(s), c - (unsigned char*)ZSTR_VAL(s)); + } + r = c + (ZSTR_VAL(res) - ZSTR_VAL(s)); + while (c < e) { + *r = toupper(*c); + r++; + c++; + } + *r = '\0'; + return res; + } + c++; + } + return zend_string_copy(s); +} +/* }}} */ + +/* {{{ proto string strtoupper(string str) + Makes a string uppercase */ +PHP_FUNCTION(strtoupper) +{ + zend_string *arg; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(arg) + ZEND_PARSE_PARAMETERS_END(); + + RETURN_STR(php_string_toupper(arg)); +} +/* }}} */ + +/* {{{ php_strtolower + */ +PHPAPI char *php_strtolower(char *s, size_t len) +{ + unsigned char *c, *e; + + c = (unsigned char *)s; + e = c+len; + + while (c < e) { + *c = tolower(*c); + c++; + } + return s; +} +/* }}} */ + +/* {{{ php_string_tolower + */ +PHPAPI zend_string *php_string_tolower(zend_string *s) +{ + unsigned char *c, *e; + + c = (unsigned char *)ZSTR_VAL(s); + e = c + ZSTR_LEN(s); + + while (c < e) { + if (isupper(*c)) { + register unsigned char *r; + zend_string *res = zend_string_alloc(ZSTR_LEN(s), 0); + + if (c != (unsigned char*)ZSTR_VAL(s)) { + memcpy(ZSTR_VAL(res), ZSTR_VAL(s), c - (unsigned char*)ZSTR_VAL(s)); + } + r = c + (ZSTR_VAL(res) - ZSTR_VAL(s)); + while (c < e) { + *r = tolower(*c); + r++; + c++; + } + *r = '\0'; + return res; + } + c++; + } + return zend_string_copy(s); +} +/* }}} */ + +/* {{{ proto string strtolower(string str) + Makes a string lowercase */ +PHP_FUNCTION(strtolower) +{ + zend_string *str; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(str) + ZEND_PARSE_PARAMETERS_END(); + + RETURN_STR(php_string_tolower(str)); +} +/* }}} */ + +/* {{{ php_basename + */ +PHPAPI zend_string *php_basename(const char *s, size_t len, char *suffix, size_t sufflen) +{ + char *c, *comp, *cend; + size_t inc_len, cnt; + int state; + zend_string *ret; + + c = comp = cend = (char*)s; + cnt = len; + state = 0; + while (cnt > 0) { + inc_len = (*c == '\0' ? 1 : php_mblen(c, cnt)); + + switch (inc_len) { + case -2: + case -1: + inc_len = 1; + php_mb_reset(); + break; + case 0: + goto quit_loop; + case 1: +#if defined(PHP_WIN32) || defined(NETWARE) + if (*c == '/' || *c == '\\') { +#else + if (*c == '/') { +#endif + if (state == 1) { + state = 0; + cend = c; + } +#if defined(PHP_WIN32) || defined(NETWARE) + /* Catch relative paths in c:file.txt style. They're not to confuse + with the NTFS streams. This part ensures also, that no drive + letter traversing happens. */ + } else if ((*c == ':' && (c - comp == 1))) { + if (state == 0) { + comp = c; + state = 1; + } else { + cend = c; + state = 0; + } +#endif + } else { + if (state == 0) { + comp = c; + state = 1; + } + } + break; + default: + if (state == 0) { + comp = c; + state = 1; + } + break; + } + c += inc_len; + cnt -= inc_len; + } + +quit_loop: + if (state == 1) { + cend = c; + } + if (suffix != NULL && sufflen < (size_t)(cend - comp) && + memcmp(cend - sufflen, suffix, sufflen) == 0) { + cend -= sufflen; + } + + len = cend - comp; + + ret = zend_string_init(comp, len, 0); + return ret; +} +/* }}} */ + +/* {{{ proto string basename(string path [, string suffix]) + Returns the filename component of the path */ +PHP_FUNCTION(basename) +{ + char *string, *suffix = NULL; + size_t string_len, suffix_len = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|s", &string, &string_len, &suffix, &suffix_len) == FAILURE) { + return; + } + + RETURN_STR(php_basename(string, string_len, suffix, suffix_len)); +} +/* }}} */ + +/* {{{ php_dirname + Returns directory name component of path */ +PHPAPI size_t php_dirname(char *path, size_t len) +{ + return zend_dirname(path, len); +} +/* }}} */ + +/* {{{ proto string dirname(string path[, int levels]) + Returns the directory name component of the path */ +PHP_FUNCTION(dirname) +{ + char *str; + size_t str_len; + zend_string *ret; + zend_long levels = 1; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &str, &str_len, &levels) == FAILURE) { + return; + } + + ret = zend_string_init(str, str_len, 0); + + if (levels == 1) { + /* Defaut case */ +#ifdef PHP_WIN32 + ZSTR_LEN(ret) = php_win32_ioutil_dirname(ZSTR_VAL(ret), str_len); +#else + ZSTR_LEN(ret) = zend_dirname(ZSTR_VAL(ret), str_len); +#endif + } else if (levels < 1) { + php_error_docref(NULL, E_WARNING, "Invalid argument, levels must be >= 1"); + zend_string_free(ret); + return; + } else { + /* Some levels up */ + do { +#ifdef PHP_WIN32 + ZSTR_LEN(ret) = php_win32_ioutil_dirname(ZSTR_VAL(ret), str_len = ZSTR_LEN(ret)); +#else + ZSTR_LEN(ret) = zend_dirname(ZSTR_VAL(ret), str_len = ZSTR_LEN(ret)); +#endif + } while (ZSTR_LEN(ret) < str_len && --levels); + } + + RETURN_NEW_STR(ret); +} +/* }}} */ + +/* {{{ proto array pathinfo(string path[, int options]) + Returns information about a certain string */ +PHP_FUNCTION(pathinfo) +{ + zval tmp; + char *path, *dirname; + size_t path_len; + int have_basename; + zend_long opt = PHP_PATHINFO_ALL; + zend_string *ret = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &path, &path_len, &opt) == FAILURE) { + return; + } + + have_basename = ((opt & PHP_PATHINFO_BASENAME) == PHP_PATHINFO_BASENAME); + + array_init(&tmp); + + if ((opt & PHP_PATHINFO_DIRNAME) == PHP_PATHINFO_DIRNAME) { + dirname = estrndup(path, path_len); + php_dirname(dirname, path_len); + if (*dirname) { + add_assoc_string(&tmp, "dirname", dirname); + } + efree(dirname); + } + + if (have_basename) { + ret = php_basename(path, path_len, NULL, 0); + add_assoc_str(&tmp, "basename", zend_string_copy(ret)); + } + + if ((opt & PHP_PATHINFO_EXTENSION) == PHP_PATHINFO_EXTENSION) { + const char *p; + ptrdiff_t idx; + + if (!have_basename) { + ret = php_basename(path, path_len, NULL, 0); + } + + p = zend_memrchr(ZSTR_VAL(ret), '.', ZSTR_LEN(ret)); + + if (p) { + idx = p - ZSTR_VAL(ret); + add_assoc_stringl(&tmp, "extension", ZSTR_VAL(ret) + idx + 1, ZSTR_LEN(ret) - idx - 1); + } + } + + if ((opt & PHP_PATHINFO_FILENAME) == PHP_PATHINFO_FILENAME) { + const char *p; + ptrdiff_t idx; + + /* Have we already looked up the basename? */ + if (!have_basename && !ret) { + ret = php_basename(path, path_len, NULL, 0); + } + + p = zend_memrchr(ZSTR_VAL(ret), '.', ZSTR_LEN(ret)); + + idx = p ? (p - ZSTR_VAL(ret)) : (ptrdiff_t)ZSTR_LEN(ret); + add_assoc_stringl(&tmp, "filename", ZSTR_VAL(ret), idx); + } + + if (ret) { + zend_string_release(ret); + } + + if (opt == PHP_PATHINFO_ALL) { + ZVAL_COPY_VALUE(return_value, &tmp); + } else { + zval *element; + if ((element = zend_hash_get_current_data(Z_ARRVAL(tmp))) != NULL) { + ZVAL_DEREF(element); + ZVAL_COPY(return_value, element); + } else { + ZVAL_EMPTY_STRING(return_value); + } + zval_ptr_dtor(&tmp); + } +} +/* }}} */ + +/* {{{ php_stristr + case insensitve strstr */ +PHPAPI char *php_stristr(char *s, char *t, size_t s_len, size_t t_len) +{ + php_strtolower(s, s_len); + php_strtolower(t, t_len); + return (char*)php_memnstr(s, t, t_len, s + s_len); +} +/* }}} */ + +/* {{{ php_strspn + */ +PHPAPI size_t php_strspn(char *s1, char *s2, char *s1_end, char *s2_end) +{ + register const char *p = s1, *spanp; + register char c = *p; + +cont: + for (spanp = s2; p != s1_end && spanp != s2_end;) { + if (*spanp++ == c) { + c = *(++p); + goto cont; + } + } + return (p - s1); +} +/* }}} */ + +/* {{{ php_strcspn + */ +PHPAPI size_t php_strcspn(char *s1, char *s2, char *s1_end, char *s2_end) +{ + register const char *p, *spanp; + register char c = *s1; + + for (p = s1;;) { + spanp = s2; + do { + if (*spanp == c || p == s1_end) { + return p - s1; + } + } while (spanp++ < (s2_end - 1)); + c = *++p; + } + /* NOTREACHED */ +} +/* }}} */ + +/* {{{ php_needle_char + */ +static int php_needle_char(zval *needle, char *target) +{ + switch (Z_TYPE_P(needle)) { + case IS_LONG: + *target = (char)Z_LVAL_P(needle); + return SUCCESS; + case IS_NULL: + case IS_FALSE: + *target = '\0'; + return SUCCESS; + case IS_TRUE: + *target = '\1'; + return SUCCESS; + case IS_DOUBLE: + *target = (char)(int)Z_DVAL_P(needle); + return SUCCESS; + case IS_OBJECT: + *target = (char) zval_get_long(needle); + return SUCCESS; + default: + php_error_docref(NULL, E_WARNING, "needle is not a string or an integer"); + return FAILURE; + } +} +/* }}} */ + +/* {{{ proto string stristr(string haystack, string needle[, bool part]) + Finds first occurrence of a string within another, case insensitive */ +PHP_FUNCTION(stristr) +{ + zval *needle; + zend_string *haystack; + char *found = NULL; + size_t found_offset; + char *haystack_dup; + char needle_char[2]; + zend_bool part = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sz|b", &haystack, &needle, &part) == FAILURE) { + return; + } + + haystack_dup = estrndup(ZSTR_VAL(haystack), ZSTR_LEN(haystack)); + + if (Z_TYPE_P(needle) == IS_STRING) { + char *orig_needle; + if (!Z_STRLEN_P(needle)) { + php_error_docref(NULL, E_WARNING, "Empty needle"); + efree(haystack_dup); + RETURN_FALSE; + } + orig_needle = estrndup(Z_STRVAL_P(needle), Z_STRLEN_P(needle)); + found = php_stristr(haystack_dup, orig_needle, ZSTR_LEN(haystack), Z_STRLEN_P(needle)); + efree(orig_needle); + } else { + if (php_needle_char(needle, needle_char) != SUCCESS) { + efree(haystack_dup); + RETURN_FALSE; + } + needle_char[1] = 0; + + found = php_stristr(haystack_dup, needle_char, ZSTR_LEN(haystack), 1); + } + + if (found) { + found_offset = found - haystack_dup; + if (part) { + RETVAL_STRINGL(ZSTR_VAL(haystack), found_offset); + } else { + RETVAL_STRINGL(ZSTR_VAL(haystack) + found_offset, ZSTR_LEN(haystack) - found_offset); + } + } else { + RETVAL_FALSE; + } + + efree(haystack_dup); +} +/* }}} */ + +/* {{{ proto string strstr(string haystack, string needle[, bool part]) + Finds first occurrence of a string within another */ +PHP_FUNCTION(strstr) +{ + zval *needle; + zend_string *haystack; + char *found = NULL; + char needle_char[2]; + zend_long found_offset; + zend_bool part = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sz|b", &haystack, &needle, &part) == FAILURE) { + return; + } + + if (Z_TYPE_P(needle) == IS_STRING) { + if (!Z_STRLEN_P(needle)) { + php_error_docref(NULL, E_WARNING, "Empty needle"); + RETURN_FALSE; + } + + found = (char*)php_memnstr(ZSTR_VAL(haystack), Z_STRVAL_P(needle), Z_STRLEN_P(needle), ZSTR_VAL(haystack) + ZSTR_LEN(haystack)); + } else { + if (php_needle_char(needle, needle_char) != SUCCESS) { + RETURN_FALSE; + } + needle_char[1] = 0; + + found = (char*)php_memnstr(ZSTR_VAL(haystack), needle_char, 1, ZSTR_VAL(haystack) + ZSTR_LEN(haystack)); + } + + if (found) { + found_offset = found - ZSTR_VAL(haystack); + if (part) { + RETURN_STRINGL(ZSTR_VAL(haystack), found_offset); + } else { + RETURN_STRINGL(found, ZSTR_LEN(haystack) - found_offset); + } + } + RETURN_FALSE; +} +/* }}} */ + +/* {{{ proto string strchr(string haystack, string needle) + An alias for strstr */ +/* }}} */ + +/* {{{ proto int strpos(string haystack, string needle [, int offset]) + Finds position of first occurrence of a string within another */ +PHP_FUNCTION(strpos) +{ + zval *needle; + zend_string *haystack; + char *found = NULL; + char needle_char[2]; + zend_long offset = 0; + + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_STR(haystack) + Z_PARAM_ZVAL(needle) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(offset) + ZEND_PARSE_PARAMETERS_END(); + + if (offset < 0) { + offset += (zend_long)ZSTR_LEN(haystack); + } + if (offset < 0 || (size_t)offset > ZSTR_LEN(haystack)) { + php_error_docref(NULL, E_WARNING, "Offset not contained in string"); + RETURN_FALSE; + } + + if (Z_TYPE_P(needle) == IS_STRING) { + if (!Z_STRLEN_P(needle)) { + php_error_docref(NULL, E_WARNING, "Empty needle"); + RETURN_FALSE; + } + + found = (char*)php_memnstr(ZSTR_VAL(haystack) + offset, + Z_STRVAL_P(needle), + Z_STRLEN_P(needle), + ZSTR_VAL(haystack) + ZSTR_LEN(haystack)); + } else { + if (php_needle_char(needle, needle_char) != SUCCESS) { + RETURN_FALSE; + } + needle_char[1] = 0; + + found = (char*)php_memnstr(ZSTR_VAL(haystack) + offset, + needle_char, + 1, + ZSTR_VAL(haystack) + ZSTR_LEN(haystack)); + } + + if (found) { + RETURN_LONG(found - ZSTR_VAL(haystack)); + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto int stripos(string haystack, string needle [, int offset]) + Finds position of first occurrence of a string within another, case insensitive */ +PHP_FUNCTION(stripos) +{ + char *found = NULL; + zend_string *haystack; + zend_long offset = 0; + char needle_char[2]; + zval *needle; + zend_string *needle_dup = NULL, *haystack_dup; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sz|l", &haystack, &needle, &offset) == FAILURE) { + return; + } + + if (offset < 0) { + offset += (zend_long)ZSTR_LEN(haystack); + } + if (offset < 0 || (size_t)offset > ZSTR_LEN(haystack)) { + php_error_docref(NULL, E_WARNING, "Offset not contained in string"); + RETURN_FALSE; + } + + if (ZSTR_LEN(haystack) == 0) { + RETURN_FALSE; + } + + if (Z_TYPE_P(needle) == IS_STRING) { + if (Z_STRLEN_P(needle) == 0 || Z_STRLEN_P(needle) > ZSTR_LEN(haystack)) { + RETURN_FALSE; + } + + haystack_dup = php_string_tolower(haystack); + needle_dup = php_string_tolower(Z_STR_P(needle)); + found = (char*)php_memnstr(ZSTR_VAL(haystack_dup) + offset, + ZSTR_VAL(needle_dup), ZSTR_LEN(needle_dup), ZSTR_VAL(haystack_dup) + ZSTR_LEN(haystack)); + } else { + if (php_needle_char(needle, needle_char) != SUCCESS) { + RETURN_FALSE; + } + haystack_dup = php_string_tolower(haystack); + needle_char[0] = tolower(needle_char[0]); + needle_char[1] = '\0'; + found = (char*)php_memnstr(ZSTR_VAL(haystack_dup) + offset, + needle_char, + sizeof(needle_char) - 1, + ZSTR_VAL(haystack_dup) + ZSTR_LEN(haystack)); + } + + + if (found) { + RETVAL_LONG(found - ZSTR_VAL(haystack_dup)); + } else { + RETVAL_FALSE; + } + + zend_string_release(haystack_dup); + if (needle_dup) { + zend_string_release(needle_dup); + } +} +/* }}} */ + +/* {{{ proto int strrpos(string haystack, string needle [, int offset]) + Finds position of last occurrence of a string within another string */ +PHP_FUNCTION(strrpos) +{ + zval *zneedle; + char *needle; + zend_string *haystack; + size_t needle_len; + zend_long offset = 0; + char *p, *e, ord_needle[2]; + char *found; + + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_STR(haystack) + Z_PARAM_ZVAL(zneedle) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(offset) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + if (Z_TYPE_P(zneedle) == IS_STRING) { + needle = Z_STRVAL_P(zneedle); + needle_len = Z_STRLEN_P(zneedle); + } else { + if (php_needle_char(zneedle, ord_needle) != SUCCESS) { + RETURN_FALSE; + } + ord_needle[1] = '\0'; + needle = ord_needle; + needle_len = 1; + } + + if ((ZSTR_LEN(haystack) == 0) || (needle_len == 0)) { + RETURN_FALSE; + } + + if (offset >= 0) { + if ((size_t)offset > ZSTR_LEN(haystack)) { + php_error_docref(NULL, E_WARNING, "Offset is greater than the length of haystack string"); + RETURN_FALSE; + } + p = ZSTR_VAL(haystack) + (size_t)offset; + e = ZSTR_VAL(haystack) + ZSTR_LEN(haystack); + } else { + if (offset < -INT_MAX || (size_t)(-offset) > ZSTR_LEN(haystack)) { + php_error_docref(NULL, E_WARNING, "Offset is greater than the length of haystack string"); + RETURN_FALSE; + } + p = ZSTR_VAL(haystack); + if ((size_t)-offset < needle_len) { + e = ZSTR_VAL(haystack) + ZSTR_LEN(haystack); + } else { + e = ZSTR_VAL(haystack) + ZSTR_LEN(haystack) + offset + needle_len; + } + } + + if ((found = (char *)zend_memnrstr(p, needle, needle_len, e))) { + RETURN_LONG(found - ZSTR_VAL(haystack)); + } + + RETURN_FALSE; +} +/* }}} */ + +/* {{{ proto int strripos(string haystack, string needle [, int offset]) + Finds position of last occurrence of a string within another string */ +PHP_FUNCTION(strripos) +{ + zval *zneedle; + zend_string *needle; + zend_string *haystack; + zend_long offset = 0; + char *p, *e; + char *found; + zend_string *needle_dup, *haystack_dup, *ord_needle = NULL; + ALLOCA_FLAG(use_heap); + + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sz|l", &haystack, &zneedle, &offset) == FAILURE) { + RETURN_FALSE; + } + + ZSTR_ALLOCA_ALLOC(ord_needle, 1, use_heap); + if (Z_TYPE_P(zneedle) == IS_STRING) { + needle = Z_STR_P(zneedle); + } else { + if (php_needle_char(zneedle, ZSTR_VAL(ord_needle)) != SUCCESS) { + ZSTR_ALLOCA_FREE(ord_needle, use_heap); + RETURN_FALSE; + } + ZSTR_VAL(ord_needle)[1] = '\0'; + needle = ord_needle; + } + + if ((ZSTR_LEN(haystack) == 0) || (ZSTR_LEN(needle) == 0)) { + ZSTR_ALLOCA_FREE(ord_needle, use_heap); + RETURN_FALSE; + } + + if (ZSTR_LEN(needle) == 1) { + /* Single character search can shortcut memcmps + Can also avoid tolower emallocs */ + if (offset >= 0) { + if ((size_t)offset > ZSTR_LEN(haystack)) { + ZSTR_ALLOCA_FREE(ord_needle, use_heap); + php_error_docref(NULL, E_WARNING, "Offset is greater than the length of haystack string"); + RETURN_FALSE; + } + p = ZSTR_VAL(haystack) + (size_t)offset; + e = ZSTR_VAL(haystack) + ZSTR_LEN(haystack) - 1; + } else { + p = ZSTR_VAL(haystack); + if (offset < -INT_MAX || (size_t)(-offset) > ZSTR_LEN(haystack)) { + ZSTR_ALLOCA_FREE(ord_needle, use_heap); + php_error_docref(NULL, E_WARNING, "Offset is greater than the length of haystack string"); + RETURN_FALSE; + } + e = ZSTR_VAL(haystack) + ZSTR_LEN(haystack) + (size_t)offset; + } + /* Borrow that ord_needle buffer to avoid repeatedly tolower()ing needle */ + *ZSTR_VAL(ord_needle) = tolower(*ZSTR_VAL(needle)); + while (e >= p) { + if (tolower(*e) == *ZSTR_VAL(ord_needle)) { + ZSTR_ALLOCA_FREE(ord_needle, use_heap); + RETURN_LONG(e - p + (offset > 0 ? offset : 0)); + } + e--; + } + ZSTR_ALLOCA_FREE(ord_needle, use_heap); + RETURN_FALSE; + } + + haystack_dup = php_string_tolower(haystack); + if (offset >= 0) { + if ((size_t)offset > ZSTR_LEN(haystack)) { + zend_string_release(haystack_dup); + ZSTR_ALLOCA_FREE(ord_needle, use_heap); + php_error_docref(NULL, E_WARNING, "Offset is greater than the length of haystack string"); + RETURN_FALSE; + } + p = ZSTR_VAL(haystack_dup) + offset; + e = ZSTR_VAL(haystack_dup) + ZSTR_LEN(haystack); + } else { + if (offset < -INT_MAX || (size_t)(-offset) > ZSTR_LEN(haystack)) { + zend_string_release(haystack_dup); + ZSTR_ALLOCA_FREE(ord_needle, use_heap); + php_error_docref(NULL, E_WARNING, "Offset is greater than the length of haystack string"); + RETURN_FALSE; + } + p = ZSTR_VAL(haystack_dup); + if ((size_t)-offset < ZSTR_LEN(needle)) { + e = ZSTR_VAL(haystack_dup) + ZSTR_LEN(haystack); + } else { + e = ZSTR_VAL(haystack_dup) + ZSTR_LEN(haystack) + offset + ZSTR_LEN(needle); + } + } + + needle_dup = php_string_tolower(needle); + if ((found = (char *)zend_memnrstr(p, ZSTR_VAL(needle_dup), ZSTR_LEN(needle_dup), e))) { + RETVAL_LONG(found - ZSTR_VAL(haystack_dup)); + zend_string_release(needle_dup); + zend_string_release(haystack_dup); + ZSTR_ALLOCA_FREE(ord_needle, use_heap); + } else { + zend_string_release(needle_dup); + zend_string_release(haystack_dup); + ZSTR_ALLOCA_FREE(ord_needle, use_heap); + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto string strrchr(string haystack, string needle) + Finds the last occurrence of a character in a string within another */ +PHP_FUNCTION(strrchr) +{ + zval *needle; + zend_string *haystack; + const char *found = NULL; + zend_long found_offset; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sz", &haystack, &needle) == FAILURE) { + return; + } + + if (Z_TYPE_P(needle) == IS_STRING) { + found = zend_memrchr(ZSTR_VAL(haystack), *Z_STRVAL_P(needle), ZSTR_LEN(haystack)); + } else { + char needle_chr; + if (php_needle_char(needle, &needle_chr) != SUCCESS) { + RETURN_FALSE; + } + + found = zend_memrchr(ZSTR_VAL(haystack), needle_chr, ZSTR_LEN(haystack)); + } + + if (found) { + found_offset = found - ZSTR_VAL(haystack); + RETURN_STRINGL(found, ZSTR_LEN(haystack) - found_offset); + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ php_chunk_split + */ +static zend_string *php_chunk_split(char *src, size_t srclen, char *end, size_t endlen, size_t chunklen) +{ + char *p, *q; + size_t chunks; /* complete chunks! */ + size_t restlen; + size_t out_len; + zend_string *dest; + + chunks = srclen / chunklen; + restlen = srclen - chunks * chunklen; /* srclen % chunklen */ + + if (chunks > INT_MAX - 1) { + return NULL; + } + out_len = chunks + 1; + if (endlen !=0 && out_len > INT_MAX/endlen) { + return NULL; + } + out_len *= endlen; + if (out_len > INT_MAX - srclen - 1) { + return NULL; + } + out_len += srclen + 1; + + dest = zend_string_alloc(out_len * sizeof(char), 0); + + for (p = src, q = ZSTR_VAL(dest); p < (src + srclen - chunklen + 1); ) { + memcpy(q, p, chunklen); + q += chunklen; + memcpy(q, end, endlen); + q += endlen; + p += chunklen; + } + + if (restlen) { + memcpy(q, p, restlen); + q += restlen; + memcpy(q, end, endlen); + q += endlen; + } + + *q = '\0'; + ZSTR_LEN(dest) = q - ZSTR_VAL(dest); + + return dest; +} +/* }}} */ + +/* {{{ proto string chunk_split(string str [, int chunklen [, string ending]]) + Returns split line */ +PHP_FUNCTION(chunk_split) +{ + zend_string *str; + char *end = "\r\n"; + size_t endlen = 2; + zend_long chunklen = 76; + zend_string *result; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|ls", &str, &chunklen, &end, &endlen) == FAILURE) { + return; + } + + if (chunklen <= 0) { + php_error_docref(NULL, E_WARNING, "Chunk length should be greater than zero"); + RETURN_FALSE; + } + + if ((size_t)chunklen > ZSTR_LEN(str)) { + /* to maintain BC, we must return original string + ending */ + result = zend_string_safe_alloc(ZSTR_LEN(str), 1, endlen, 0); + memcpy(ZSTR_VAL(result), ZSTR_VAL(str), ZSTR_LEN(str)); + memcpy(ZSTR_VAL(result) + ZSTR_LEN(str), end, endlen); + ZSTR_VAL(result)[ZSTR_LEN(result)] = '\0'; + RETURN_NEW_STR(result); + } + + if (!ZSTR_LEN(str)) { + RETURN_EMPTY_STRING(); + } + + result = php_chunk_split(ZSTR_VAL(str), ZSTR_LEN(str), end, endlen, (size_t)chunklen); + + if (result) { + RETURN_STR(result); + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto string substr(string str, int start [, int length]) + Returns part of a string */ +PHP_FUNCTION(substr) +{ + zend_string *str; + zend_long l = 0, f; + int argc = ZEND_NUM_ARGS(); + + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_STR(str) + Z_PARAM_LONG(f) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(l) + ZEND_PARSE_PARAMETERS_END(); + + if (argc > 2) { + if ((l < 0 && (size_t)(-l) > ZSTR_LEN(str))) { + RETURN_FALSE; + } else if (l > (zend_long)ZSTR_LEN(str)) { + l = ZSTR_LEN(str); + } + } else { + l = ZSTR_LEN(str); + } + + if (f > (zend_long)ZSTR_LEN(str)) { + RETURN_FALSE; + } else if (f < 0 && (size_t)-f > ZSTR_LEN(str)) { + f = 0; + } + + if (l < 0 && (l + (zend_long)ZSTR_LEN(str) - f) < 0) { + RETURN_FALSE; + } + + /* if "from" position is negative, count start position from the end + * of the string + */ + if (f < 0) { + f = (zend_long)ZSTR_LEN(str) + f; + if (f < 0) { + f = 0; + } + } + + /* if "length" position is negative, set it to the length + * needed to stop that many chars from the end of the string + */ + if (l < 0) { + l = ((zend_long)ZSTR_LEN(str) - f) + l; + if (l < 0) { + l = 0; + } + } + + if (f > (zend_long)ZSTR_LEN(str)) { + RETURN_FALSE; + } + + if ((size_t)l > ZSTR_LEN(str) - (size_t)f) { + l = ZSTR_LEN(str) - f; + } + + RETURN_STRINGL(ZSTR_VAL(str) + f, l); +} +/* }}} */ + +/* {{{ proto mixed substr_replace(mixed str, mixed repl, mixed start [, mixed length]) + Replaces part of a string with another string */ +PHP_FUNCTION(substr_replace) +{ + zval *str; + zval *from; + zval *len = NULL; + zval *repl; + zend_long l = 0; + zend_long f; + int argc = ZEND_NUM_ARGS(); + zend_string *result; + HashPosition from_idx, repl_idx, len_idx; + zval *tmp_str = NULL, *tmp_from = NULL, *tmp_repl = NULL, *tmp_len= NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zzz|z/", &str, &repl, &from, &len) == FAILURE) { + return; + } + + if (Z_TYPE_P(str) != IS_ARRAY) { + convert_to_string_ex(str); + } + if (Z_TYPE_P(repl) != IS_ARRAY) { + convert_to_string_ex(repl); + } + if (Z_TYPE_P(from) != IS_ARRAY) { + convert_to_long_ex(from); + } + + if (argc > 3) { + if (Z_TYPE_P(len) != IS_ARRAY) { + convert_to_long_ex(len); + l = zval_get_long(len); + } + } else { + if (Z_TYPE_P(str) != IS_ARRAY) { + l = Z_STRLEN_P(str); + } + } + + if (Z_TYPE_P(str) == IS_STRING) { + if ( + (argc == 3 && Z_TYPE_P(from) == IS_ARRAY) || + (argc == 4 && Z_TYPE_P(from) != Z_TYPE_P(len)) + ) { + php_error_docref(NULL, E_WARNING, "'start' and 'length' should be of same type - numerical or array "); + RETURN_STR_COPY(Z_STR_P(str)); + } + if (argc == 4 && Z_TYPE_P(from) == IS_ARRAY) { + if (zend_hash_num_elements(Z_ARRVAL_P(from)) != zend_hash_num_elements(Z_ARRVAL_P(len))) { + php_error_docref(NULL, E_WARNING, "'start' and 'length' should have the same number of elements"); + RETURN_STR_COPY(Z_STR_P(str)); + } + } + } + + if (Z_TYPE_P(str) != IS_ARRAY) { + if (Z_TYPE_P(from) != IS_ARRAY) { + zend_string *repl_str; + zend_bool repl_release = 0; + f = Z_LVAL_P(from); + + /* if "from" position is negative, count start position from the end + * of the string + */ + if (f < 0) { + f = (zend_long)Z_STRLEN_P(str) + f; + if (f < 0) { + f = 0; + } + } else if ((size_t)f > Z_STRLEN_P(str)) { + f = Z_STRLEN_P(str); + } + /* if "length" position is negative, set it to the length + * needed to stop that many chars from the end of the string + */ + if (l < 0) { + l = ((zend_long)Z_STRLEN_P(str) - f) + l; + if (l < 0) { + l = 0; + } + } + + if ((size_t)l > Z_STRLEN_P(str) || (l < 0 && (size_t)(-l) > Z_STRLEN_P(str))) { + l = Z_STRLEN_P(str); + } + + if ((f + l) > (zend_long)Z_STRLEN_P(str)) { + l = Z_STRLEN_P(str) - f; + } + if (Z_TYPE_P(repl) == IS_ARRAY) { + repl_idx = 0; + while (repl_idx < Z_ARRVAL_P(repl)->nNumUsed) { + tmp_repl = &Z_ARRVAL_P(repl)->arData[repl_idx].val; + if (Z_TYPE_P(tmp_repl) != IS_UNDEF) { + break; + } + repl_idx++; + } + if (repl_idx < Z_ARRVAL_P(repl)->nNumUsed) { + repl_str = zval_get_string(tmp_repl); + repl_release = 1; + } else { + repl_str = STR_EMPTY_ALLOC(); + } + } else { + repl_str = Z_STR_P(repl); + } + + result = zend_string_safe_alloc(1, Z_STRLEN_P(str) - l + ZSTR_LEN(repl_str), 0, 0); + + memcpy(ZSTR_VAL(result), Z_STRVAL_P(str), f); + if (ZSTR_LEN(repl_str)) { + memcpy((ZSTR_VAL(result) + f), ZSTR_VAL(repl_str), ZSTR_LEN(repl_str)); + } + memcpy((ZSTR_VAL(result) + f + ZSTR_LEN(repl_str)), Z_STRVAL_P(str) + f + l, Z_STRLEN_P(str) - f - l); + ZSTR_VAL(result)[ZSTR_LEN(result)] = '\0'; + if (repl_release) { + zend_string_release(repl_str); + } + RETURN_NEW_STR(result); + } else { + php_error_docref(NULL, E_WARNING, "Functionality of 'start' and 'length' as arrays is not implemented"); + RETURN_STR_COPY(Z_STR_P(str)); + } + } else { /* str is array of strings */ + zend_string *str_index = NULL; + size_t result_len; + zend_ulong num_index; + + array_init(return_value); + + from_idx = len_idx = repl_idx = 0; + + ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(str), num_index, str_index, tmp_str) { + zend_string *orig_str = zval_get_string(tmp_str); + + if (Z_TYPE_P(from) == IS_ARRAY) { + while (from_idx < Z_ARRVAL_P(from)->nNumUsed) { + tmp_from = &Z_ARRVAL_P(from)->arData[from_idx].val; + if (Z_TYPE_P(tmp_from) != IS_UNDEF) { + break; + } + from_idx++; + } + if (from_idx < Z_ARRVAL_P(from)->nNumUsed) { + f = zval_get_long(tmp_from); + + if (f < 0) { + f = (zend_long)ZSTR_LEN(orig_str) + f; + if (f < 0) { + f = 0; + } + } else if (f > (zend_long)ZSTR_LEN(orig_str)) { + f = ZSTR_LEN(orig_str); + } + from_idx++; + } else { + f = 0; + } + } else { + f = Z_LVAL_P(from); + if (f < 0) { + f = (zend_long)ZSTR_LEN(orig_str) + f; + if (f < 0) { + f = 0; + } + } else if (f > (zend_long)ZSTR_LEN(orig_str)) { + f = ZSTR_LEN(orig_str); + } + } + + if (argc > 3 && Z_TYPE_P(len) == IS_ARRAY) { + while (len_idx < Z_ARRVAL_P(len)->nNumUsed) { + tmp_len = &Z_ARRVAL_P(len)->arData[len_idx].val; + if (Z_TYPE_P(tmp_len) != IS_UNDEF) { + break; + } + len_idx++; + } + if (len_idx < Z_ARRVAL_P(len)->nNumUsed) { + l = zval_get_long(tmp_len); + len_idx++; + } else { + l = ZSTR_LEN(orig_str); + } + } else if (argc > 3) { + l = Z_LVAL_P(len); + } else { + l = ZSTR_LEN(orig_str); + } + + if (l < 0) { + l = (ZSTR_LEN(orig_str) - f) + l; + if (l < 0) { + l = 0; + } + } + + if ((f + l) > (zend_long)ZSTR_LEN(orig_str)) { + l = ZSTR_LEN(orig_str) - f; + } + + result_len = ZSTR_LEN(orig_str) - l; + + if (Z_TYPE_P(repl) == IS_ARRAY) { + while (repl_idx < Z_ARRVAL_P(repl)->nNumUsed) { + tmp_repl = &Z_ARRVAL_P(repl)->arData[repl_idx].val; + if (Z_TYPE_P(tmp_repl) != IS_UNDEF) { + break; + } + repl_idx++; + } + if (repl_idx < Z_ARRVAL_P(repl)->nNumUsed) { + zend_string *repl_str = zval_get_string(tmp_repl); + + result_len += ZSTR_LEN(repl_str); + repl_idx++; + result = zend_string_safe_alloc(1, result_len, 0, 0); + + memcpy(ZSTR_VAL(result), ZSTR_VAL(orig_str), f); + memcpy((ZSTR_VAL(result) + f), ZSTR_VAL(repl_str), ZSTR_LEN(repl_str)); + memcpy((ZSTR_VAL(result) + f + ZSTR_LEN(repl_str)), ZSTR_VAL(orig_str) + f + l, ZSTR_LEN(orig_str) - f - l); + zend_string_release(repl_str); + } else { + result = zend_string_safe_alloc(1, result_len, 0, 0); + + memcpy(ZSTR_VAL(result), ZSTR_VAL(orig_str), f); + memcpy((ZSTR_VAL(result) + f), ZSTR_VAL(orig_str) + f + l, ZSTR_LEN(orig_str) - f - l); + } + } else { + result_len += Z_STRLEN_P(repl); + + result = zend_string_safe_alloc(1, result_len, 0, 0); + + memcpy(ZSTR_VAL(result), ZSTR_VAL(orig_str), f); + memcpy((ZSTR_VAL(result) + f), Z_STRVAL_P(repl), Z_STRLEN_P(repl)); + memcpy((ZSTR_VAL(result) + f + Z_STRLEN_P(repl)), ZSTR_VAL(orig_str) + f + l, ZSTR_LEN(orig_str) - f - l); + } + + ZSTR_VAL(result)[ZSTR_LEN(result)] = '\0'; + + if (str_index) { + zval tmp; + + ZVAL_NEW_STR(&tmp, result); + zend_symtable_update(Z_ARRVAL_P(return_value), str_index, &tmp); + } else { + add_index_str(return_value, num_index, result); + } + + zend_string_release(orig_str); + } ZEND_HASH_FOREACH_END(); + } /* if */ +} +/* }}} */ + +/* {{{ proto string quotemeta(string str) + Quotes meta characters */ +PHP_FUNCTION(quotemeta) +{ + zend_string *old; + char *old_end; + char *p, *q; + char c; + zend_string *str; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &old) == FAILURE) { + return; + } + + old_end = ZSTR_VAL(old) + ZSTR_LEN(old); + + if (ZSTR_VAL(old) == old_end) { + RETURN_FALSE; + } + + str = zend_string_safe_alloc(2, ZSTR_LEN(old), 0, 0); + + for (p = ZSTR_VAL(old), q = ZSTR_VAL(str); p != old_end; p++) { + c = *p; + switch (c) { + case '.': + case '\\': + case '+': + case '*': + case '?': + case '[': + case '^': + case ']': + case '$': + case '(': + case ')': + *q++ = '\\'; + /* break is missing _intentionally_ */ + default: + *q++ = c; + } + } + + *q = '\0'; + + RETURN_NEW_STR(zend_string_truncate(str, q - ZSTR_VAL(str), 0)); +} +/* }}} */ + +/* {{{ proto int ord(string character) + Returns ASCII value of character + Warning: This function is special-cased by zend_compile.c and so is bypassed for constant string argument */ +PHP_FUNCTION(ord) +{ + char *str; + size_t str_len; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STRING(str, str_len) + ZEND_PARSE_PARAMETERS_END(); + + RETURN_LONG((unsigned char) str[0]); +} +/* }}} */ + +/* {{{ proto string chr(int ascii) + Converts ASCII code to a character + Warning: This function is special-cased by zend_compile.c and so is bypassed for constant integer argument */ +PHP_FUNCTION(chr) +{ + zend_long c; + + if (ZEND_NUM_ARGS() != 1) { + WRONG_PARAM_COUNT; + } + + ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_QUIET, 1, 1) + Z_PARAM_LONG(c) + ZEND_PARSE_PARAMETERS_END_EX(c = 0); + + c &= 0xff; + if (CG(one_char_string)[c]) { + ZVAL_INTERNED_STR(return_value, CG(one_char_string)[c]); + } else { + ZVAL_NEW_STR(return_value, zend_string_alloc(1, 0)); + Z_STRVAL_P(return_value)[0] = (char)c; + Z_STRVAL_P(return_value)[1] = '\0'; + } +} +/* }}} */ + +/* {{{ php_ucfirst + Uppercase the first character of the word in a native string */ +static void php_ucfirst(char *str) +{ + register char *r; + r = str; + *r = toupper((unsigned char) *r); +} +/* }}} */ + +/* {{{ proto string ucfirst(string str) + Makes a string's first character uppercase */ +PHP_FUNCTION(ucfirst) +{ + zend_string *str; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(str) + ZEND_PARSE_PARAMETERS_END(); + + if (!ZSTR_LEN(str)) { + RETURN_EMPTY_STRING(); + } + + ZVAL_STRINGL(return_value, ZSTR_VAL(str), ZSTR_LEN(str)); + php_ucfirst(Z_STRVAL_P(return_value)); +} +/* }}} */ + +/* {{{ + Lowercase the first character of the word in a native string */ +static void php_lcfirst(char *str) +{ + register char *r; + r = str; + *r = tolower((unsigned char) *r); +} +/* }}} */ + +/* {{{ proto string lcfirst(string str) + Make a string's first character lowercase */ +PHP_FUNCTION(lcfirst) +{ + zend_string *str; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &str) == FAILURE) { + return; + } + + if (!ZSTR_LEN(str)) { + RETURN_EMPTY_STRING(); + } + + ZVAL_STRINGL(return_value, ZSTR_VAL(str), ZSTR_LEN(str)); + php_lcfirst(Z_STRVAL_P(return_value)); +} +/* }}} */ + +/* {{{ proto string ucwords(string str [, string delims]) + Uppercase the first character of every word in a string */ +PHP_FUNCTION(ucwords) +{ + zend_string *str; + char *delims = " \t\r\n\f\v"; + register char *r, *r_end; + size_t delims_len = 6; + char mask[256]; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STR(str) + Z_PARAM_OPTIONAL + Z_PARAM_STRING(delims, delims_len) + ZEND_PARSE_PARAMETERS_END(); + + if (!ZSTR_LEN(str)) { + RETURN_EMPTY_STRING(); + } + + php_charmask((unsigned char *)delims, delims_len, mask); + + ZVAL_STRINGL(return_value, ZSTR_VAL(str), ZSTR_LEN(str)); + r = Z_STRVAL_P(return_value); + + *r = toupper((unsigned char) *r); + for (r_end = r + Z_STRLEN_P(return_value) - 1; r < r_end; ) { + if (mask[(unsigned char)*r++]) { + *r = toupper((unsigned char) *r); + } + } +} +/* }}} */ + +/* {{{ php_strtr + */ +PHPAPI char *php_strtr(char *str, size_t len, char *str_from, char *str_to, size_t trlen) +{ + size_t i; + + if (UNEXPECTED(trlen < 1)) { + return str; + } else if (trlen == 1) { + char ch_from = *str_from; + char ch_to = *str_to; + + for (i = 0; i < len; i++) { + if (str[i] == ch_from) { + str[i] = ch_to; + } + } + } else { + unsigned char xlat[256], j = 0; + + do { xlat[j] = j; } while (++j != 0); + + for (i = 0; i < trlen; i++) { + xlat[(size_t)(unsigned char) str_from[i]] = str_to[i]; + } + + for (i = 0; i < len; i++) { + str[i] = xlat[(size_t)(unsigned char) str[i]]; + } + } + + return str; +} +/* }}} */ + +/* {{{ php_strtr_ex + */ +static zend_string *php_strtr_ex(zend_string *str, char *str_from, char *str_to, size_t trlen) +{ + zend_string *new_str = NULL; + size_t i; + + if (UNEXPECTED(trlen < 1)) { + return zend_string_copy(str); + } else if (trlen == 1) { + char ch_from = *str_from; + char ch_to = *str_to; + + for (i = 0; i < ZSTR_LEN(str); i++) { + if (ZSTR_VAL(str)[i] == ch_from) { + new_str = zend_string_alloc(ZSTR_LEN(str), 0); + memcpy(ZSTR_VAL(new_str), ZSTR_VAL(str), i); + ZSTR_VAL(new_str)[i] = ch_to; + break; + } + } + for (; i < ZSTR_LEN(str); i++) { + ZSTR_VAL(new_str)[i] = (ZSTR_VAL(str)[i] != ch_from) ? ZSTR_VAL(str)[i] : ch_to; + } + } else { + unsigned char xlat[256], j = 0; + + do { xlat[j] = j; } while (++j != 0); + + for (i = 0; i < trlen; i++) { + xlat[(size_t)(unsigned char) str_from[i]] = str_to[i]; + } + + for (i = 0; i < ZSTR_LEN(str); i++) { + if (ZSTR_VAL(str)[i] != xlat[(size_t)(unsigned char) ZSTR_VAL(str)[i]]) { + new_str = zend_string_alloc(ZSTR_LEN(str), 0); + memcpy(ZSTR_VAL(new_str), ZSTR_VAL(str), i); + ZSTR_VAL(new_str)[i] = xlat[(size_t)(unsigned char) ZSTR_VAL(str)[i]]; + break; + } + } + + for (;i < ZSTR_LEN(str); i++) { + ZSTR_VAL(new_str)[i] = xlat[(size_t)(unsigned char) ZSTR_VAL(str)[i]]; + } + } + + if (!new_str) { + return zend_string_copy(str); + } + + ZSTR_VAL(new_str)[ZSTR_LEN(new_str)] = 0; + return new_str; +} +/* }}} */ + +/* {{{ php_strtr_array */ +static void php_strtr_array(zval *return_value, zend_string *input, HashTable *pats) +{ + char *str = ZSTR_VAL(input); + size_t slen = ZSTR_LEN(input); + zend_ulong num_key; + zend_string *str_key; + size_t len, pos, old_pos; + int num_keys = 0; + size_t minlen = 128*1024; + size_t maxlen = 0; + HashTable str_hash; + zval *entry; + char *key; + smart_str result = {0}; + zend_ulong bitset[256/sizeof(zend_ulong)]; + zend_ulong *num_bitset; + + /* we will collect all possible key lengths */ + num_bitset = ecalloc((slen + sizeof(zend_ulong)) / sizeof(zend_ulong), sizeof(zend_ulong)); + memset(bitset, 0, sizeof(bitset)); + + /* check if original array has numeric keys */ + ZEND_HASH_FOREACH_STR_KEY(pats, str_key) { + if (UNEXPECTED(!str_key)) { + num_keys = 1; + } else { + len = ZSTR_LEN(str_key); + if (UNEXPECTED(len < 1)) { + efree(num_bitset); + RETURN_FALSE; + } else if (UNEXPECTED(len > slen)) { + /* skip long patterns */ + continue; + } + if (len > maxlen) { + maxlen = len; + } + if (len < minlen) { + minlen = len; + } + /* remember possible key length */ + num_bitset[len / sizeof(zend_ulong)] |= Z_UL(1) << (len % sizeof(zend_ulong)); + bitset[((unsigned char)ZSTR_VAL(str_key)[0]) / sizeof(zend_ulong)] |= Z_UL(1) << (((unsigned char)ZSTR_VAL(str_key)[0]) % sizeof(zend_ulong)); + } + } ZEND_HASH_FOREACH_END(); + + if (UNEXPECTED(num_keys)) { + zend_string *key_used; + /* we have to rebuild HashTable with numeric keys */ + zend_hash_init(&str_hash, zend_hash_num_elements(pats), NULL, NULL, 0); + ZEND_HASH_FOREACH_KEY_VAL(pats, num_key, str_key, entry) { + if (UNEXPECTED(!str_key)) { + key_used = zend_long_to_str(num_key); + len = ZSTR_LEN(key_used); + if (UNEXPECTED(len > slen)) { + /* skip long patterns */ + continue; + } + if (len > maxlen) { + maxlen = len; + } + if (len < minlen) { + minlen = len; + } + /* remember possible key length */ + num_bitset[len / sizeof(zend_ulong)] |= Z_UL(1) << (len % sizeof(zend_ulong)); + bitset[((unsigned char)ZSTR_VAL(key_used)[0]) / sizeof(zend_ulong)] |= Z_UL(1) << (((unsigned char)ZSTR_VAL(key_used)[0]) % sizeof(zend_ulong)); + } else { + key_used = str_key; + len = ZSTR_LEN(key_used); + if (UNEXPECTED(len > slen)) { + /* skip long patterns */ + continue; + } + } + zend_hash_add(&str_hash, key_used, entry); + if (UNEXPECTED(!str_key)) { + zend_string_release(key_used); + } + } ZEND_HASH_FOREACH_END(); + pats = &str_hash; + } + + if (UNEXPECTED(minlen > maxlen)) { + /* return the original string */ + if (pats == &str_hash) { + zend_hash_destroy(&str_hash); + } + efree(num_bitset); + RETURN_STR_COPY(input); + } + + old_pos = pos = 0; + while (pos <= slen - minlen) { + key = str + pos; + if (bitset[((unsigned char)key[0]) / sizeof(zend_ulong)] & (Z_UL(1) << (((unsigned char)key[0]) % sizeof(zend_ulong)))) { + len = maxlen; + if (len > slen - pos) { + len = slen - pos; + } + while (len >= minlen) { + if ((num_bitset[len / sizeof(zend_ulong)] & (Z_UL(1) << (len % sizeof(zend_ulong))))) { + entry = zend_hash_str_find(pats, key, len); + if (entry != NULL) { + zend_string *s = zval_get_string(entry); + smart_str_appendl(&result, str + old_pos, pos - old_pos); + smart_str_append(&result, s); + old_pos = pos + len; + pos = old_pos - 1; + zend_string_release(s); + break; + } + } + len--; + } + } + pos++; + } + + if (result.s) { + smart_str_appendl(&result, str + old_pos, slen - old_pos); + smart_str_0(&result); + RETVAL_NEW_STR(result.s); + } else { + smart_str_free(&result); + RETVAL_STR_COPY(input); + } + + if (pats == &str_hash) { + zend_hash_destroy(&str_hash); + } + efree(num_bitset); +} +/* }}} */ + +/* {{{ php_char_to_str_ex + */ +static zend_string* php_char_to_str_ex(zend_string *str, char from, char *to, size_t to_len, int case_sensitivity, zend_long *replace_count) +{ + zend_string *result; + size_t char_count = 0; + char lc_from = 0; + char *source, *target, *source_end= ZSTR_VAL(str) + ZSTR_LEN(str); + + if (case_sensitivity) { + char *p = ZSTR_VAL(str), *e = p + ZSTR_LEN(str); + while ((p = memchr(p, from, (e - p)))) { + char_count++; + p++; + } + } else { + lc_from = tolower(from); + for (source = ZSTR_VAL(str); source < source_end; source++) { + if (tolower(*source) == lc_from) { + char_count++; + } + } + } + + if (char_count == 0) { + return zend_string_copy(str); + } + + if (to_len > 0) { + result = zend_string_safe_alloc(char_count, to_len - 1, ZSTR_LEN(str), 0); + } else { + result = zend_string_alloc(ZSTR_LEN(str) - char_count, 0); + } + target = ZSTR_VAL(result); + + if (case_sensitivity) { + char *p = ZSTR_VAL(str), *e = p + ZSTR_LEN(str), *s = ZSTR_VAL(str); + while ((p = memchr(p, from, (e - p)))) { + memcpy(target, s, (p - s)); + target += p - s; + memcpy(target, to, to_len); + target += to_len; + p++; + s = p; + if (replace_count) { + *replace_count += 1; + } + } + if (s < e) { + memcpy(target, s, (e - s)); + target += e - s; + } + } else { + for (source = ZSTR_VAL(str); source < source_end; source++) { + if (tolower(*source) == lc_from) { + if (replace_count) { + *replace_count += 1; + } + memcpy(target, to, to_len); + target += to_len; + } else { + *target = *source; + target++; + } + } + } + *target = 0; + return result; +} +/* }}} */ + +/* {{{ php_str_to_str_ex + */ +static zend_string *php_str_to_str_ex(zend_string *haystack, + char *needle, size_t needle_len, char *str, size_t str_len, zend_long *replace_count) +{ + zend_string *new_str; + + if (needle_len < ZSTR_LEN(haystack)) { + char *end; + char *e, *s, *p, *r; + + if (needle_len == str_len) { + new_str = NULL; + end = ZSTR_VAL(haystack) + ZSTR_LEN(haystack); + for (p = ZSTR_VAL(haystack); (r = (char*)php_memnstr(p, needle, needle_len, end)); p = r + needle_len) { + if (!new_str) { + new_str = zend_string_init(ZSTR_VAL(haystack), ZSTR_LEN(haystack), 0); + } + memcpy(ZSTR_VAL(new_str) + (r - ZSTR_VAL(haystack)), str, str_len); + (*replace_count)++; + } + if (!new_str) { + goto nothing_todo; + } + return new_str; + } else { + size_t count = 0; + char *o = ZSTR_VAL(haystack); + char *n = needle; + char *endp = o + ZSTR_LEN(haystack); + + while ((o = (char*)php_memnstr(o, n, needle_len, endp))) { + o += needle_len; + count++; + } + if (count == 0) { + /* Needle doesn't occur, shortcircuit the actual replacement. */ + goto nothing_todo; + } + if (str_len > needle_len) { + new_str = zend_string_safe_alloc(count, str_len - needle_len, ZSTR_LEN(haystack), 0); + } else { + new_str = zend_string_alloc(count * (str_len - needle_len) + ZSTR_LEN(haystack), 0); + } + + e = s = ZSTR_VAL(new_str); + end = ZSTR_VAL(haystack) + ZSTR_LEN(haystack); + for (p = ZSTR_VAL(haystack); (r = (char*)php_memnstr(p, needle, needle_len, end)); p = r + needle_len) { + memcpy(e, p, r - p); + e += r - p; + memcpy(e, str, str_len); + e += str_len; + (*replace_count)++; + } + + if (p < end) { + memcpy(e, p, end - p); + e += end - p; + } + + *e = '\0'; + return new_str; + } + } else if (needle_len > ZSTR_LEN(haystack) || memcmp(ZSTR_VAL(haystack), needle, ZSTR_LEN(haystack))) { +nothing_todo: + return zend_string_copy(haystack); + } else { + new_str = zend_string_init(str, str_len, 0); + (*replace_count)++; + return new_str; + } +} +/* }}} */ + +/* {{{ php_str_to_str_i_ex + */ +static zend_string *php_str_to_str_i_ex(zend_string *haystack, char *lc_haystack, + zend_string *needle, char *str, size_t str_len, zend_long *replace_count) +{ + zend_string *new_str = NULL; + zend_string *lc_needle; + + if (ZSTR_LEN(needle) < ZSTR_LEN(haystack)) { + char *end; + char *e, *s, *p, *r; + + if (ZSTR_LEN(needle) == str_len) { + lc_needle = php_string_tolower(needle); + end = lc_haystack + ZSTR_LEN(haystack); + for (p = lc_haystack; (r = (char*)php_memnstr(p, ZSTR_VAL(lc_needle), ZSTR_LEN(lc_needle), end)); p = r + ZSTR_LEN(lc_needle)) { + if (!new_str) { + new_str = zend_string_init(ZSTR_VAL(haystack), ZSTR_LEN(haystack), 0); + } + memcpy(ZSTR_VAL(new_str) + (r - lc_haystack), str, str_len); + (*replace_count)++; + } + zend_string_release(lc_needle); + + if (!new_str) { + goto nothing_todo; + } + return new_str; + } else { + size_t count = 0; + char *o = lc_haystack; + char *n; + char *endp = o + ZSTR_LEN(haystack); + + lc_needle = php_string_tolower(needle); + n = ZSTR_VAL(lc_needle); + + while ((o = (char*)php_memnstr(o, n, ZSTR_LEN(lc_needle), endp))) { + o += ZSTR_LEN(lc_needle); + count++; + } + if (count == 0) { + /* Needle doesn't occur, shortcircuit the actual replacement. */ + zend_string_release(lc_needle); + goto nothing_todo; + } + + if (str_len > ZSTR_LEN(lc_needle)) { + new_str = zend_string_safe_alloc(count, str_len - ZSTR_LEN(lc_needle), ZSTR_LEN(haystack), 0); + } else { + new_str = zend_string_alloc(count * (str_len - ZSTR_LEN(lc_needle)) + ZSTR_LEN(haystack), 0); + } + + e = s = ZSTR_VAL(new_str); + end = lc_haystack + ZSTR_LEN(haystack); + + for (p = lc_haystack; (r = (char*)php_memnstr(p, ZSTR_VAL(lc_needle), ZSTR_LEN(lc_needle), end)); p = r + ZSTR_LEN(lc_needle)) { + memcpy(e, ZSTR_VAL(haystack) + (p - lc_haystack), r - p); + e += r - p; + memcpy(e, str, str_len); + e += str_len; + (*replace_count)++; + } + + if (p < end) { + memcpy(e, ZSTR_VAL(haystack) + (p - lc_haystack), end - p); + e += end - p; + } + *e = '\0'; + + zend_string_release(lc_needle); + + return new_str; + } + } else if (ZSTR_LEN(needle) > ZSTR_LEN(haystack)) { +nothing_todo: + return zend_string_copy(haystack); + } else { + lc_needle = php_string_tolower(needle); + + if (memcmp(lc_haystack, ZSTR_VAL(lc_needle), ZSTR_LEN(lc_needle))) { + zend_string_release(lc_needle); + goto nothing_todo; + } + zend_string_release(lc_needle); + + new_str = zend_string_init(str, str_len, 0); + + (*replace_count)++; + return new_str; + } +} +/* }}} */ + +/* {{{ php_str_to_str + */ +PHPAPI zend_string *php_str_to_str(char *haystack, size_t length, char *needle, size_t needle_len, char *str, size_t str_len) +{ + zend_string *new_str; + + if (needle_len < length) { + char *end; + char *e, *s, *p, *r; + + if (needle_len == str_len) { + new_str = zend_string_init(haystack, length, 0); + end = ZSTR_VAL(new_str) + length; + for (p = ZSTR_VAL(new_str); (r = (char*)php_memnstr(p, needle, needle_len, end)); p = r + needle_len) { + memcpy(r, str, str_len); + } + return new_str; + } else { + if (str_len < needle_len) { + new_str = zend_string_alloc(length, 0); + } else { + size_t count = 0; + char *o = haystack; + char *n = needle; + char *endp = o + length; + + while ((o = (char*)php_memnstr(o, n, needle_len, endp))) { + o += needle_len; + count++; + } + if (count == 0) { + /* Needle doesn't occur, shortcircuit the actual replacement. */ + new_str = zend_string_init(haystack, length, 0); + return new_str; + } else { + if (str_len > needle_len) { + new_str = zend_string_safe_alloc(count, str_len - needle_len, length, 0); + } else { + new_str = zend_string_alloc(count * (str_len - needle_len) + length, 0); + } + } + } + + e = s = ZSTR_VAL(new_str); + end = haystack + length; + for (p = haystack; (r = (char*)php_memnstr(p, needle, needle_len, end)); p = r + needle_len) { + memcpy(e, p, r - p); + e += r - p; + memcpy(e, str, str_len); + e += str_len; + } + + if (p < end) { + memcpy(e, p, end - p); + e += end - p; + } + + *e = '\0'; + new_str = zend_string_truncate(new_str, e - s, 0); + return new_str; + } + } else if (needle_len > length || memcmp(haystack, needle, length)) { + new_str = zend_string_init(haystack, length, 0); + return new_str; + } else { + new_str = zend_string_init(str, str_len, 0); + + return new_str; + } +} +/* }}} */ + +/* {{{ proto string strtr(string str, string from[, string to]) + Translates characters in str using given translation tables */ +PHP_FUNCTION(strtr) +{ + zval *from; + zend_string *str; + char *to = NULL; + size_t to_len = 0; + int ac = ZEND_NUM_ARGS(); + + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_STR(str) + Z_PARAM_ZVAL(from) + Z_PARAM_OPTIONAL + Z_PARAM_STRING(to, to_len) + ZEND_PARSE_PARAMETERS_END(); + + if (ac == 2 && Z_TYPE_P(from) != IS_ARRAY) { + php_error_docref(NULL, E_WARNING, "The second argument is not an array"); + RETURN_FALSE; + } + + /* shortcut for empty string */ + if (ZSTR_LEN(str) == 0) { + RETURN_EMPTY_STRING(); + } + + if (ac == 2) { + HashTable *pats = Z_ARRVAL_P(from); + + if (zend_hash_num_elements(pats) < 1) { + RETURN_STR_COPY(str); + } else if (zend_hash_num_elements(pats) == 1) { + zend_long num_key; + zend_string *str_key, *replace; + zval *entry, tmp; + + ZEND_HASH_FOREACH_KEY_VAL(pats, num_key, str_key, entry) { + ZVAL_UNDEF(&tmp); + if (UNEXPECTED(!str_key)) { + ZVAL_LONG(&tmp, num_key); + convert_to_string(&tmp); + str_key = Z_STR(tmp); + } + replace = zval_get_string(entry); + if (ZSTR_LEN(str_key) < 1) { + RETVAL_STR_COPY(str); + } else if (ZSTR_LEN(str_key) == 1) { + RETVAL_STR(php_char_to_str_ex(str, + ZSTR_VAL(str_key)[0], + ZSTR_VAL(replace), + ZSTR_LEN(replace), + 1, + NULL)); + } else { + zend_long dummy; + RETVAL_STR(php_str_to_str_ex(str, + ZSTR_VAL(str_key), ZSTR_LEN(str_key), + ZSTR_VAL(replace), ZSTR_LEN(replace), &dummy)); + } + zend_string_release(replace); + zval_dtor(&tmp); + return; + } ZEND_HASH_FOREACH_END(); + } else { + php_strtr_array(return_value, str, pats); + } + } else { + convert_to_string_ex(from); + + RETURN_STR(php_strtr_ex(str, + Z_STRVAL_P(from), + to, + MIN(Z_STRLEN_P(from), to_len))); + } +} +/* }}} */ + +/* {{{ proto string strrev(string str) + Reverse a string */ +PHP_FUNCTION(strrev) +{ + zend_string *str; + char *e, *p; + zend_string *n; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &str) == FAILURE) { + return; + } + + n = zend_string_alloc(ZSTR_LEN(str), 0); + p = ZSTR_VAL(n); + + e = ZSTR_VAL(str) + ZSTR_LEN(str); + + while (--e >= ZSTR_VAL(str)) { + *p++ = *e; + } + + *p = '\0'; + + RETVAL_NEW_STR(n); +} +/* }}} */ + +/* {{{ php_similar_str + */ +static void php_similar_str(const char *txt1, size_t len1, const char *txt2, size_t len2, size_t *pos1, size_t *pos2, size_t *max) +{ + char *p, *q; + char *end1 = (char *) txt1 + len1; + char *end2 = (char *) txt2 + len2; + size_t l; + + *max = 0; + for (p = (char *) txt1; p < end1; p++) { + for (q = (char *) txt2; q < end2; q++) { + for (l = 0; (p + l < end1) && (q + l < end2) && (p[l] == q[l]); l++); + if (l > *max) { + *max = l; + *pos1 = p - txt1; + *pos2 = q - txt2; + } + } + } +} +/* }}} */ + +/* {{{ php_similar_char + */ +static size_t php_similar_char(const char *txt1, size_t len1, const char *txt2, size_t len2) +{ + size_t sum; + size_t pos1 = 0, pos2 = 0, max; + + php_similar_str(txt1, len1, txt2, len2, &pos1, &pos2, &max); + if ((sum = max)) { + if (pos1 && pos2) { + sum += php_similar_char(txt1, pos1, + txt2, pos2); + } + if ((pos1 + max < len1) && (pos2 + max < len2)) { + sum += php_similar_char(txt1 + pos1 + max, len1 - pos1 - max, + txt2 + pos2 + max, len2 - pos2 - max); + } + } + + return sum; +} +/* }}} */ + +/* {{{ proto int similar_text(string str1, string str2 [, float percent]) + Calculates the similarity between two strings */ +PHP_FUNCTION(similar_text) +{ + zend_string *t1, *t2; + zval *percent = NULL; + int ac = ZEND_NUM_ARGS(); + size_t sim; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS|z/", &t1, &t2, &percent) == FAILURE) { + return; + } + + if (ac > 2) { + convert_to_double_ex(percent); + } + + if (ZSTR_LEN(t1) + ZSTR_LEN(t2) == 0) { + if (ac > 2) { + Z_DVAL_P(percent) = 0; + } + + RETURN_LONG(0); + } + + sim = php_similar_char(ZSTR_VAL(t1), ZSTR_LEN(t1), ZSTR_VAL(t2), ZSTR_LEN(t2)); + + if (ac > 2) { + Z_DVAL_P(percent) = sim * 200.0 / (ZSTR_LEN(t1) + ZSTR_LEN(t2)); + } + + RETURN_LONG(sim); +} +/* }}} */ + +/* {{{ php_stripslashes + * + * be careful, this edits the string in-place */ +PHPAPI void php_stripslashes(zend_string *str) +{ + char *s, *t; + size_t l; + + s = ZSTR_VAL(str); + t = ZSTR_VAL(str); + l = ZSTR_LEN(str); + + while (l > 0) { + if (*t == '\\') { + t++; /* skip the slash */ + ZSTR_LEN(str)--; + l--; + if (l > 0) { + if (*t == '0') { + *s++='\0'; + t++; + } else { + *s++ = *t++; /* preserve the next character */ + } + l--; + } + } else { + *s++ = *t++; + l--; + } + } + if (s != t) { + *s = '\0'; + } +} +/* }}} */ + +/* {{{ proto string addcslashes(string str, string charlist) + Escapes all chars mentioned in charlist with backslash. It creates octal representations if asked to backslash characters with 8th bit set or with ASCII<32 (except '\n', '\r', '\t' etc...) */ +PHP_FUNCTION(addcslashes) +{ + zend_string *str, *what; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS", &str, &what) == FAILURE) { + return; + } + + if (ZSTR_LEN(str) == 0) { + RETURN_EMPTY_STRING(); + } + + if (ZSTR_LEN(what) == 0) { + RETURN_STRINGL(ZSTR_VAL(str), ZSTR_LEN(str)); + } + + RETURN_STR(php_addcslashes(str, 0, ZSTR_VAL(what), ZSTR_LEN(what))); +} +/* }}} */ + +/* {{{ proto string addslashes(string str) + Escapes single quote, double quotes and backslash characters in a string with backslashes */ +PHP_FUNCTION(addslashes) +{ + zend_string *str; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(str) + ZEND_PARSE_PARAMETERS_END(); + + if (ZSTR_LEN(str) == 0) { + RETURN_EMPTY_STRING(); + } + + RETURN_STR(php_addslashes(str, 0)); +} +/* }}} */ + +/* {{{ proto string stripcslashes(string str) + Strips backslashes from a string. Uses C-style conventions */ +PHP_FUNCTION(stripcslashes) +{ + zend_string *str; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &str) == FAILURE) { + return; + } + + ZVAL_STRINGL(return_value, ZSTR_VAL(str), ZSTR_LEN(str)); + php_stripcslashes(Z_STR_P(return_value)); +} +/* }}} */ + +/* {{{ proto string stripslashes(string str) + Strips backslashes from a string */ +PHP_FUNCTION(stripslashes) +{ + zend_string *str; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &str) == FAILURE) { + return; + } + + ZVAL_STRINGL(return_value, ZSTR_VAL(str), ZSTR_LEN(str)); + php_stripslashes(Z_STR_P(return_value)); +} +/* }}} */ + +#ifndef HAVE_STRERROR +/* {{{ php_strerror + */ +char *php_strerror(int errnum) +{ + extern int sys_nerr; + extern char *sys_errlist[]; + + if ((unsigned int) errnum < sys_nerr) { + return(sys_errlist[errnum]); + } + + (void) snprintf(BG(str_ebuf), sizeof(php_basic_globals.str_ebuf), "Unknown error: %d", errnum); + return(BG(str_ebuf)); +} +/* }}} */ +#endif + +/* {{{ php_stripcslashes + */ +PHPAPI void php_stripcslashes(zend_string *str) +{ + char *source, *target, *end; + size_t nlen = ZSTR_LEN(str), i; + char numtmp[4]; + + for (source = (char*)ZSTR_VAL(str), end = source + ZSTR_LEN(str), target = ZSTR_VAL(str); source < end; source++) { + if (*source == '\\' && source + 1 < end) { + source++; + switch (*source) { + case 'n': *target++='\n'; nlen--; break; + case 'r': *target++='\r'; nlen--; break; + case 'a': *target++='\a'; nlen--; break; + case 't': *target++='\t'; nlen--; break; + case 'v': *target++='\v'; nlen--; break; + case 'b': *target++='\b'; nlen--; break; + case 'f': *target++='\f'; nlen--; break; + case '\\': *target++='\\'; nlen--; break; + case 'x': + if (source+1 < end && isxdigit((int)(*(source+1)))) { + numtmp[0] = *++source; + if (source+1 < end && isxdigit((int)(*(source+1)))) { + numtmp[1] = *++source; + numtmp[2] = '\0'; + nlen-=3; + } else { + numtmp[1] = '\0'; + nlen-=2; + } + *target++=(char)strtol(numtmp, NULL, 16); + break; + } + /* break is left intentionally */ + default: + i=0; + while (source < end && *source >= '0' && *source <= '7' && i<3) { + numtmp[i++] = *source++; + } + if (i) { + numtmp[i]='\0'; + *target++=(char)strtol(numtmp, NULL, 8); + nlen-=i; + source--; + } else { + *target++=*source; + nlen--; + } + } + } else { + *target++=*source; + } + } + + if (nlen != 0) { + *target='\0'; + } + + ZSTR_LEN(str) = nlen; +} +/* }}} */ + +/* {{{ php_addcslashes + */ +PHPAPI zend_string *php_addcslashes(zend_string *str, int should_free, char *what, size_t wlength) +{ + char flags[256]; + char *source, *target; + char *end; + char c; + size_t newlen; + zend_string *new_str = zend_string_safe_alloc(4, ZSTR_LEN(str), 0, 0); + + php_charmask((unsigned char *)what, wlength, flags); + + for (source = (char*)ZSTR_VAL(str), end = source + ZSTR_LEN(str), target = ZSTR_VAL(new_str); source < end; source++) { + c = *source; + if (flags[(unsigned char)c]) { + if ((unsigned char) c < 32 || (unsigned char) c > 126) { + *target++ = '\\'; + switch (c) { + case '\n': *target++ = 'n'; break; + case '\t': *target++ = 't'; break; + case '\r': *target++ = 'r'; break; + case '\a': *target++ = 'a'; break; + case '\v': *target++ = 'v'; break; + case '\b': *target++ = 'b'; break; + case '\f': *target++ = 'f'; break; + default: target += sprintf(target, "%03o", (unsigned char) c); + } + continue; + } + *target++ = '\\'; + } + *target++ = c; + } + *target = 0; + newlen = target - ZSTR_VAL(new_str); + if (newlen < ZSTR_LEN(str) * 4) { + new_str = zend_string_truncate(new_str, newlen, 0); + } + if (should_free) { + zend_string_release(str); + } + return new_str; +} +/* }}} */ + +/* {{{ php_addslashes + */ +PHPAPI zend_string *php_addslashes(zend_string *str, int should_free) +{ + /* maximum string length, worst case situation */ + char *source, *target; + char *end; + size_t offset; + zend_string *new_str; + + if (!str) { + return ZSTR_EMPTY_ALLOC(); + } + + source = ZSTR_VAL(str); + end = source + ZSTR_LEN(str); + + while (source < end) { + switch (*source) { + case '\0': + case '\'': + case '\"': + case '\\': + goto do_escape; + default: + source++; + break; + } + } + + if (!should_free) { + return zend_string_copy(str); + } + + return str; + +do_escape: + offset = source - (char *)ZSTR_VAL(str); + new_str = zend_string_safe_alloc(2, ZSTR_LEN(str) - offset, offset, 0); + memcpy(ZSTR_VAL(new_str), ZSTR_VAL(str), offset); + target = ZSTR_VAL(new_str) + offset; + + while (source < end) { + switch (*source) { + case '\0': + *target++ = '\\'; + *target++ = '0'; + break; + case '\'': + case '\"': + case '\\': + *target++ = '\\'; + /* break is missing *intentionally* */ + default: + *target++ = *source; + break; + } + + source++; + } + + *target = 0; + if (should_free) { + zend_string_release(str); + } + + if (ZSTR_LEN(new_str) - (target - ZSTR_VAL(new_str)) > 16) { + new_str = zend_string_truncate(new_str, target - ZSTR_VAL(new_str), 0); + } else { + ZSTR_LEN(new_str) = target - ZSTR_VAL(new_str); + } + + return new_str; +} +/* }}} */ + +#define _HEB_BLOCK_TYPE_ENG 1 +#define _HEB_BLOCK_TYPE_HEB 2 +#define isheb(c) (((((unsigned char) c) >= 224) && (((unsigned char) c) <= 250)) ? 1 : 0) +#define _isblank(c) (((((unsigned char) c) == ' ' || ((unsigned char) c) == '\t')) ? 1 : 0) +#define _isnewline(c) (((((unsigned char) c) == '\n' || ((unsigned char) c) == '\r')) ? 1 : 0) + +/* {{{ php_str_replace_in_subject + */ +static zend_long php_str_replace_in_subject(zval *search, zval *replace, zval *subject, zval *result, int case_sensitivity) +{ + zval *search_entry, + *replace_entry = NULL; + zend_string *tmp_result, + *replace_entry_str = NULL; + char *replace_value = NULL; + size_t replace_len = 0; + zend_long replace_count = 0; + zend_string *subject_str; + zend_string *lc_subject_str = NULL; + uint32_t replace_idx; + + /* Make sure we're dealing with strings. */ + subject_str = zval_get_string(subject); + if (ZSTR_LEN(subject_str) == 0) { + zend_string_release(subject_str); + ZVAL_EMPTY_STRING(result); + return 0; + } + + /* If search is an array */ + if (Z_TYPE_P(search) == IS_ARRAY) { + /* Duplicate subject string for repeated replacement */ + ZVAL_STR_COPY(result, subject_str); + + if (Z_TYPE_P(replace) == IS_ARRAY) { + replace_idx = 0; + } else { + /* Set replacement value to the passed one */ + replace_value = Z_STRVAL_P(replace); + replace_len = Z_STRLEN_P(replace); + } + + /* For each entry in the search array, get the entry */ + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(search), search_entry) { + /* Make sure we're dealing with strings. */ + zend_string *search_str = zval_get_string(search_entry); + if (ZSTR_LEN(search_str) == 0) { + if (Z_TYPE_P(replace) == IS_ARRAY) { + replace_idx++; + } + zend_string_release(search_str); + continue; + } + + /* If replace is an array. */ + if (Z_TYPE_P(replace) == IS_ARRAY) { + /* Get current entry */ + while (replace_idx < Z_ARRVAL_P(replace)->nNumUsed) { + replace_entry = &Z_ARRVAL_P(replace)->arData[replace_idx].val; + if (Z_TYPE_P(replace_entry) != IS_UNDEF) { + break; + } + replace_idx++; + } + if (replace_idx < Z_ARRVAL_P(replace)->nNumUsed) { + /* Make sure we're dealing with strings. */ + replace_entry_str = zval_get_string(replace_entry); + + /* Set replacement value to the one we got from array */ + replace_value = ZSTR_VAL(replace_entry_str); + replace_len = ZSTR_LEN(replace_entry_str); + + replace_idx++; + } else { + /* We've run out of replacement strings, so use an empty one. */ + replace_value = ""; + replace_len = 0; + } + } + + if (ZSTR_LEN(search_str) == 1) { + zend_long old_replace_count = replace_count; + + tmp_result = php_char_to_str_ex(Z_STR_P(result), + ZSTR_VAL(search_str)[0], + replace_value, + replace_len, + case_sensitivity, + &replace_count); + if (lc_subject_str && replace_count != old_replace_count) { + zend_string_release(lc_subject_str); + lc_subject_str = NULL; + } + } else if (ZSTR_LEN(search_str) > 1) { + if (case_sensitivity) { + tmp_result = php_str_to_str_ex(Z_STR_P(result), + ZSTR_VAL(search_str), ZSTR_LEN(search_str), + replace_value, replace_len, &replace_count); + } else { + zend_long old_replace_count = replace_count; + + if (!lc_subject_str) { + lc_subject_str = php_string_tolower(Z_STR_P(result)); + } + tmp_result = php_str_to_str_i_ex(Z_STR_P(result), ZSTR_VAL(lc_subject_str), + search_str, replace_value, replace_len, &replace_count); + if (replace_count != old_replace_count) { + zend_string_release(lc_subject_str); + lc_subject_str = NULL; + } + } + } + + zend_string_release(search_str); + + if (replace_entry_str) { + zend_string_release(replace_entry_str); + replace_entry_str = NULL; + } + zend_string_release(Z_STR_P(result)); + ZVAL_STR(result, tmp_result); + + if (Z_STRLEN_P(result) == 0) { + if (lc_subject_str) { + zend_string_release(lc_subject_str); + } + zend_string_release(subject_str); + return replace_count; + } + } ZEND_HASH_FOREACH_END(); + if (lc_subject_str) { + zend_string_release(lc_subject_str); + } + } else { + ZEND_ASSERT(Z_TYPE_P(search) == IS_STRING); + if (Z_STRLEN_P(search) == 1) { + ZVAL_STR(result, + php_char_to_str_ex(subject_str, + Z_STRVAL_P(search)[0], + Z_STRVAL_P(replace), + Z_STRLEN_P(replace), + case_sensitivity, + &replace_count)); + } else if (Z_STRLEN_P(search) > 1) { + if (case_sensitivity) { + ZVAL_STR(result, php_str_to_str_ex(subject_str, + Z_STRVAL_P(search), Z_STRLEN_P(search), + Z_STRVAL_P(replace), Z_STRLEN_P(replace), &replace_count)); + } else { + lc_subject_str = php_string_tolower(subject_str); + ZVAL_STR(result, php_str_to_str_i_ex(subject_str, ZSTR_VAL(lc_subject_str), + Z_STR_P(search), + Z_STRVAL_P(replace), Z_STRLEN_P(replace), &replace_count)); + zend_string_release(lc_subject_str); + } + } else { + ZVAL_STR_COPY(result, subject_str); + } + } + zend_string_release(subject_str); + return replace_count; +} +/* }}} */ + +/* {{{ php_str_replace_common + */ +static void php_str_replace_common(INTERNAL_FUNCTION_PARAMETERS, int case_sensitivity) +{ + zval *subject, *search, *replace, *subject_entry, *zcount = NULL; + zval result; + zend_string *string_key; + zend_ulong num_key; + zend_long count = 0; + int argc = ZEND_NUM_ARGS(); + + ZEND_PARSE_PARAMETERS_START(3, 4) + Z_PARAM_ZVAL(search) + Z_PARAM_ZVAL(replace) + Z_PARAM_ZVAL(subject) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL_EX(zcount, 0, 1) + ZEND_PARSE_PARAMETERS_END(); + + /* Make sure we're dealing with strings and do the replacement. */ + if (Z_TYPE_P(search) != IS_ARRAY) { + convert_to_string_ex(search); + if (Z_TYPE_P(replace) != IS_STRING) { + convert_to_string_ex(replace); + } + } else if (Z_TYPE_P(replace) != IS_ARRAY) { + convert_to_string_ex(replace); + } + + /* if subject is an array */ + if (Z_TYPE_P(subject) == IS_ARRAY) { + array_init(return_value); + + /* For each subject entry, convert it to string, then perform replacement + and add the result to the return_value array. */ + ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(subject), num_key, string_key, subject_entry) { + ZVAL_DEREF(subject_entry); + if (Z_TYPE_P(subject_entry) != IS_ARRAY && Z_TYPE_P(subject_entry) != IS_OBJECT) { + count += php_str_replace_in_subject(search, replace, subject_entry, &result, case_sensitivity); + } else { + ZVAL_COPY(&result, subject_entry); + } + /* Add to return array */ + if (string_key) { + zend_hash_add_new(Z_ARRVAL_P(return_value), string_key, &result); + } else { + zend_hash_index_add_new(Z_ARRVAL_P(return_value), num_key, &result); + } + } ZEND_HASH_FOREACH_END(); + } else { /* if subject is not an array */ + count = php_str_replace_in_subject(search, replace, subject, return_value, case_sensitivity); + } + if (argc > 3) { + zval_ptr_dtor(zcount); + ZVAL_LONG(zcount, count); + } +} +/* }}} */ + +/* {{{ proto mixed str_replace(mixed search, mixed replace, mixed subject [, int &replace_count]) + Replaces all occurrences of search in haystack with replace */ +PHP_FUNCTION(str_replace) +{ + php_str_replace_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); +} +/* }}} */ + +/* {{{ proto mixed str_ireplace(mixed search, mixed replace, mixed subject [, int &replace_count]) + Replaces all occurrences of search in haystack with replace / case-insensitive */ +PHP_FUNCTION(str_ireplace) +{ + php_str_replace_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); +} +/* }}} */ + +/* {{{ php_hebrev + * + * Converts Logical Hebrew text (Hebrew Windows style) to Visual text + * Cheers/complaints/flames - Zeev Suraski + */ +static void php_hebrev(INTERNAL_FUNCTION_PARAMETERS, int convert_newlines) +{ + char *str; + char *heb_str, *tmp, *target; + size_t block_start, block_end, block_type, block_length, i; + zend_long max_chars=0, char_count; + size_t begin, end, orig_begin; + size_t str_len; + zend_string *broken_str; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &str, &str_len, &max_chars) == FAILURE) { + return; + } + + if (str_len == 0) { + RETURN_FALSE; + } + + tmp = str; + block_start=block_end=0; + + heb_str = (char *) emalloc(str_len+1); + target = heb_str+str_len; + *target = 0; + target--; + + block_length=0; + + if (isheb(*tmp)) { + block_type = _HEB_BLOCK_TYPE_HEB; + } else { + block_type = _HEB_BLOCK_TYPE_ENG; + } + + do { + if (block_type == _HEB_BLOCK_TYPE_HEB) { + while ((isheb((int)*(tmp+1)) || _isblank((int)*(tmp+1)) || ispunct((int)*(tmp+1)) || (int)*(tmp+1)=='\n' ) && block_end': + *target = '<'; + break; + case '\\': + *target = '/'; + break; + case '/': + *target = '\\'; + break; + default: + break; + } + target--; + } + block_type = _HEB_BLOCK_TYPE_ENG; + } else { + while (!isheb(*(tmp+1)) && (int)*(tmp+1)!='\n' && block_end < str_len-1) { + tmp++; + block_end++; + block_length++; + } + while ((_isblank((int)*tmp) || ispunct((int)*tmp)) && *tmp!='/' && *tmp!='-' && block_end > block_start) { + tmp--; + block_end--; + } + for (i = block_end+1; i >= block_start+1; i--) { + *target = str[i-1]; + target--; + } + block_type = _HEB_BLOCK_TYPE_HEB; + } + block_start=block_end+1; + } while (block_end < str_len-1); + + + broken_str = zend_string_alloc(str_len, 0); + begin = end = str_len-1; + target = ZSTR_VAL(broken_str); + + while (1) { + char_count=0; + while ((!max_chars || (max_chars > 0 && char_count < max_chars)) && begin > 0) { + char_count++; + begin--; + if (begin <= 0 || _isnewline(heb_str[begin])) { + while (begin > 0 && _isnewline(heb_str[begin-1])) { + begin--; + char_count++; + } + break; + } + } + if (max_chars >= 0 && char_count == max_chars) { /* try to avoid breaking words */ + size_t new_char_count=char_count, new_begin=begin; + + while (new_char_count > 0) { + if (_isblank(heb_str[new_begin]) || _isnewline(heb_str[new_begin])) { + break; + } + new_begin++; + new_char_count--; + } + if (new_char_count > 0) { + begin=new_begin; + } + } + orig_begin=begin; + + if (_isblank(heb_str[begin])) { + heb_str[begin]='\n'; + } + while (begin <= end && _isnewline(heb_str[begin])) { /* skip leading newlines */ + begin++; + } + for (i = begin; i <= end; i++) { /* copy content */ + *target = heb_str[i]; + target++; + } + for (i = orig_begin; i <= end && _isnewline(heb_str[i]); i++) { + *target = heb_str[i]; + target++; + } + begin=orig_begin; + + if (begin <= 0) { + *target = 0; + break; + } + begin--; + end=begin; + } + efree(heb_str); + + if (convert_newlines) { + RETVAL_STR(php_char_to_str_ex(broken_str, '\n', "
\n", 7, 1, NULL)); + zend_string_release(broken_str); + } else { + RETURN_NEW_STR(broken_str); + } +} +/* }}} */ + +/* {{{ proto string hebrev(string str [, int max_chars_per_line]) + Converts logical Hebrew text to visual text */ +PHP_FUNCTION(hebrev) +{ + php_hebrev(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); +} +/* }}} */ + +/* {{{ proto string hebrevc(string str [, int max_chars_per_line]) + Converts logical Hebrew text to visual text with newline conversion */ +PHP_FUNCTION(hebrevc) +{ + php_hebrev(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); +} +/* }}} */ + +/* {{{ proto string nl2br(string str [, bool is_xhtml]) + Converts newlines to HTML line breaks */ +PHP_FUNCTION(nl2br) +{ + /* in brief this inserts
or
before matched regexp \n\r?|\r\n? */ + char *tmp; + zend_string *str; + char *end, *target; + size_t repl_cnt = 0; + zend_bool is_xhtml = 1; + zend_string *result; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STR(str) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(is_xhtml) + ZEND_PARSE_PARAMETERS_END(); + + tmp = ZSTR_VAL(str); + end = ZSTR_VAL(str) + ZSTR_LEN(str); + + /* it is really faster to scan twice and allocate mem once instead of scanning once + and constantly reallocing */ + while (tmp < end) { + if (*tmp == '\r') { + if (*(tmp+1) == '\n') { + tmp++; + } + repl_cnt++; + } else if (*tmp == '\n') { + if (*(tmp+1) == '\r') { + tmp++; + } + repl_cnt++; + } + + tmp++; + } + + if (repl_cnt == 0) { + RETURN_STR_COPY(str); + } + + { + size_t repl_len = is_xhtml ? (sizeof("
") - 1) : (sizeof("
") - 1); + + result = zend_string_safe_alloc(repl_cnt, repl_len, ZSTR_LEN(str), 0); + target = ZSTR_VAL(result); + } + + tmp = ZSTR_VAL(str); + while (tmp < end) { + switch (*tmp) { + case '\r': + case '\n': + *target++ = '<'; + *target++ = 'b'; + *target++ = 'r'; + + if (is_xhtml) { + *target++ = ' '; + *target++ = '/'; + } + + *target++ = '>'; + + if ((*tmp == '\r' && *(tmp+1) == '\n') || (*tmp == '\n' && *(tmp+1) == '\r')) { + *target++ = *tmp++; + } + /* lack of a break; is intentional */ + default: + *target++ = *tmp; + } + + tmp++; + } + + *target = '\0'; + + RETURN_NEW_STR(result); +} +/* }}} */ + +/* {{{ proto string strip_tags(string str [, string allowable_tags]) + Strips HTML and PHP tags from a string */ +PHP_FUNCTION(strip_tags) +{ + zend_string *buf; + zend_string *str; + zval *allow=NULL; + char *allowed_tags=NULL; + size_t allowed_tags_len=0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|z", &str, &allow) == FAILURE) { + return; + } + + /* To maintain a certain BC, we allow anything for the second parameter and return original string */ + if (allow) { + convert_to_string(allow); + allowed_tags = Z_STRVAL_P(allow); + allowed_tags_len = Z_STRLEN_P(allow); + } + + buf = zend_string_init(ZSTR_VAL(str), ZSTR_LEN(str), 0); + ZSTR_LEN(buf) = php_strip_tags_ex(ZSTR_VAL(buf), ZSTR_LEN(str), NULL, allowed_tags, allowed_tags_len, 0); + RETURN_NEW_STR(buf); +} +/* }}} */ + +/* {{{ proto string setlocale(mixed category, string locale [, string ...]) + Set locale information */ +PHP_FUNCTION(setlocale) +{ + zval *args = NULL; + zval *plocale; + zend_string *loc; + char *retval; + zend_long cat; + int num_args, i = 0; + uint32_t idx; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l+", &cat, &args, &num_args) == FAILURE) { + return; + } + +#ifdef HAVE_SETLOCALE + idx = 0; + while (1) { + if (Z_TYPE(args[0]) == IS_ARRAY) { + while (idx < Z_ARRVAL(args[0])->nNumUsed) { + plocale = &Z_ARRVAL(args[0])->arData[idx].val; + if (Z_TYPE_P(plocale) != IS_UNDEF) { + break; + } + idx++; + } + if (idx >= Z_ARRVAL(args[0])->nNumUsed) { + break; + } + } else { + plocale = &args[i]; + } + + loc = zval_get_string(plocale); + + if (!strcmp("0", ZSTR_VAL(loc))) { + zend_string_release(loc); + loc = NULL; + } else { + if (ZSTR_LEN(loc) >= 255) { + php_error_docref(NULL, E_WARNING, "Specified locale name is too long"); + zend_string_release(loc); + break; + } + } + + retval = php_my_setlocale(cat, loc ? ZSTR_VAL(loc) : NULL); + zend_update_current_locale(); + if (retval) { + if (loc) { + /* Remember if locale was changed */ + size_t len = strlen(retval); + + BG(locale_changed) = 1; + if (cat == LC_CTYPE || cat == LC_ALL) { + if (BG(locale_string)) { + zend_string_release(BG(locale_string)); + } + if (len == ZSTR_LEN(loc) && !memcmp(ZSTR_VAL(loc), retval, len)) { + BG(locale_string) = zend_string_copy(loc); + RETURN_STR(BG(locale_string)); + } else { + BG(locale_string) = zend_string_init(retval, len, 0); + zend_string_release(loc); + RETURN_STR_COPY(BG(locale_string)); + } + } else if (len == ZSTR_LEN(loc) && !memcmp(ZSTR_VAL(loc), retval, len)) { + RETURN_STR(loc); + } + zend_string_release(loc); + } + RETURN_STRING(retval); + } + if (loc) { + zend_string_release(loc); + } + + if (Z_TYPE(args[0]) == IS_ARRAY) { + idx++; + } else { + if (++i >= num_args) break; + } + } + +#endif + RETURN_FALSE; +} +/* }}} */ + +/* {{{ proto void parse_str(string encoded_string [, array result]) + Parses GET/POST/COOKIE data and sets global variables */ +PHP_FUNCTION(parse_str) +{ + char *arg; + zval *arrayArg = NULL; + char *res = NULL; + size_t arglen; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|z/", &arg, &arglen, &arrayArg) == FAILURE) { + return; + } + + res = estrndup(arg, arglen); + + if (arrayArg == NULL) { + zval tmp; + zend_array *symbol_table; + if (zend_forbid_dynamic_call("parse_str() with a single argument") == FAILURE) { + efree(res); + return; + } + + symbol_table = zend_rebuild_symbol_table(); + ZVAL_ARR(&tmp, symbol_table); + sapi_module.treat_data(PARSE_STRING, res, &tmp); + if (UNEXPECTED(zend_hash_del(symbol_table, CG(known_strings)[ZEND_STR_THIS]) == SUCCESS)) { + zend_throw_error(NULL, "Cannot re-assign $this"); + } + } else { + zval ret; + + /* Clear out the array that was passed in. */ + zval_dtor(arrayArg); + array_init(&ret); + sapi_module.treat_data(PARSE_STRING, res, &ret); + ZVAL_COPY_VALUE(arrayArg, &ret); + } +} +/* }}} */ + +#define PHP_TAG_BUF_SIZE 1023 + +/* {{{ php_tag_find + * + * Check if tag is in a set of tags + * + * states: + * + * 0 start tag + * 1 first non-whitespace char seen + */ +int php_tag_find(char *tag, size_t len, const char *set) { + char c, *n, *t; + int state=0, done=0; + char *norm; + + if (len <= 0) { + return 0; + } + + norm = emalloc(len+1); + + n = norm; + t = tag; + c = tolower(*t); + /* + normalize the tag removing leading and trailing whitespace + and turn any into just and any + into + */ + while (!done) { + switch (c) { + case '<': + *(n++) = c; + break; + case '>': + done =1; + break; + default: + if (!isspace((int)c)) { + if (state == 0) { + state=1; + } + if (c != '/') { + *(n++) = c; + } + } else { + if (state == 1) + done=1; + } + break; + } + c = tolower(*(++t)); + } + *(n++) = '>'; + *n = '\0'; + if (strstr(set, norm)) { + done=1; + } else { + done=0; + } + efree(norm); + return done; +} +/* }}} */ + +PHPAPI size_t php_strip_tags(char *rbuf, size_t len, int *stateptr, const char *allow, size_t allow_len) /* {{{ */ +{ + return php_strip_tags_ex(rbuf, len, stateptr, allow, allow_len, 0); +} +/* }}} */ + +/* {{{ php_strip_tags + + A simple little state-machine to strip out html and php tags + + State 0 is the output state, State 1 means we are inside a + normal html tag and state 2 means we are inside a php tag. + + The state variable is passed in to allow a function like fgetss + to maintain state across calls to the function. + + lc holds the last significant character read and br is a bracket + counter. + + When an allow string is passed in we keep track of the string + in state 1 and when the tag is closed check it against the + allow string to see if we should allow it. + + swm: Added ability to strip = PHP_TAG_BUF_SIZE) { + pos = tp - tbuf; + tbuf = erealloc(tbuf, (tp - tbuf) + PHP_TAG_BUF_SIZE + 1); + tp = tbuf + pos; + } + *(tp++) = '<'; + } + } else if (state == 1) { + depth++; + } + break; + + case '(': + if (state == 2) { + if (lc != '"' && lc != '\'') { + lc = '('; + br++; + } + } else if (allow && state == 1) { + if (tp - tbuf >= PHP_TAG_BUF_SIZE) { + pos = tp - tbuf; + tbuf = erealloc(tbuf, (tp - tbuf) + PHP_TAG_BUF_SIZE + 1); + tp = tbuf + pos; + } + *(tp++) = c; + } else if (state == 0) { + *(rp++) = c; + } + break; + + case ')': + if (state == 2) { + if (lc != '"' && lc != '\'') { + lc = ')'; + br--; + } + } else if (allow && state == 1) { + if (tp - tbuf >= PHP_TAG_BUF_SIZE) { + pos = tp - tbuf; + tbuf = erealloc(tbuf, (tp - tbuf) + PHP_TAG_BUF_SIZE + 1); + tp = tbuf + pos; + } + *(tp++) = c; + } else if (state == 0) { + *(rp++) = c; + } + break; + + case '>': + if (depth) { + depth--; + break; + } + + if (in_q) { + break; + } + + switch (state) { + case 1: /* HTML/XML */ + lc = '>'; + if (is_xml && *(p -1) == '-') { + break; + } + in_q = state = is_xml = 0; + if (allow) { + if (tp - tbuf >= PHP_TAG_BUF_SIZE) { + pos = tp - tbuf; + tbuf = erealloc(tbuf, (tp - tbuf) + PHP_TAG_BUF_SIZE + 1); + tp = tbuf + pos; + } + *(tp++) = '>'; + *tp='\0'; + if (php_tag_find(tbuf, tp-tbuf, allow_actual)) { + memcpy(rp, tbuf, tp-tbuf); + rp += tp-tbuf; + } + tp = tbuf; + } + break; + + case 2: /* PHP */ + if (!br && lc != '\"' && *(p-1) == '?') { + in_q = state = 0; + tp = tbuf; + } + break; + + case 3: + in_q = state = 0; + tp = tbuf; + break; + + case 4: /* JavaScript/CSS/etc... */ + if (p >= buf + 2 && *(p-1) == '-' && *(p-2) == '-') { + in_q = state = 0; + tp = tbuf; + } + break; + + default: + *(rp++) = c; + break; + } + break; + + case '"': + case '\'': + if (state == 4) { + /* Inside */ + break; + } else if (state == 2 && *(p-1) != '\\') { + if (lc == c) { + lc = '\0'; + } else if (lc != '\\') { + lc = c; + } + } else if (state == 0) { + *(rp++) = c; + } else if (allow && state == 1) { + if (tp - tbuf >= PHP_TAG_BUF_SIZE) { + pos = tp - tbuf; + tbuf = erealloc(tbuf, (tp - tbuf) + PHP_TAG_BUF_SIZE + 1); + tp = tbuf + pos; + } + *(tp++) = c; + } + if (state && p != buf && (state == 1 || *(p-1) != '\\') && (!in_q || *p == in_q)) { + if (in_q) { + in_q = 0; + } else { + in_q = *p; + } + } + break; + + case '!': + /* JavaScript & Other HTML scripting languages */ + if (state == 1 && *(p-1) == '<') { + state = 3; + lc = c; + } else { + if (state == 0) { + *(rp++) = c; + } else if (allow && state == 1) { + if (tp - tbuf >= PHP_TAG_BUF_SIZE) { + pos = tp - tbuf; + tbuf = erealloc(tbuf, (tp - tbuf) + PHP_TAG_BUF_SIZE + 1); + tp = tbuf + pos; + } + *(tp++) = c; + } + } + break; + + case '-': + if (state == 3 && p >= buf + 2 && *(p-1) == '-' && *(p-2) == '!') { + state = 4; + } else { + goto reg_char; + } + break; + + case '?': + + if (state == 1 && *(p-1) == '<') { + br=0; + state=2; + break; + } + + case 'E': + case 'e': + /* !DOCTYPE exception */ + if (state==3 && p > buf+6 + && tolower(*(p-1)) == 'p' + && tolower(*(p-2)) == 'y' + && tolower(*(p-3)) == 't' + && tolower(*(p-4)) == 'c' + && tolower(*(p-5)) == 'o' + && tolower(*(p-6)) == 'd') { + state = 1; + break; + } + /* fall-through */ + + case 'l': + case 'L': + + /* swm: If we encounter ' buf+4 && strncasecmp(p-4, "= PHP_TAG_BUF_SIZE) { + pos = tp - tbuf; + tbuf = erealloc(tbuf, (tp - tbuf) + PHP_TAG_BUF_SIZE + 1); + tp = tbuf + pos; + } + *(tp++) = c; + } + break; + } + c = *(++p); + i++; + } + if (rp < rbuf + len) { + *rp = '\0'; + } + efree(buf); + if (allow) { + efree(tbuf); + if (allow_free) { + efree(allow_free); + } + } + if (stateptr) + *stateptr = state; + + return (size_t)(rp - rbuf); +} +/* }}} */ + +/* {{{ proto array str_getcsv(string input[, string delimiter[, string enclosure[, string escape]]]) +Parse a CSV string into an array */ +PHP_FUNCTION(str_getcsv) +{ + zend_string *str; + char delim = ',', enc = '"', esc = '\\'; + char *delim_str = NULL, *enc_str = NULL, *esc_str = NULL; + size_t delim_len = 0, enc_len = 0, esc_len = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|sss", &str, &delim_str, &delim_len, + &enc_str, &enc_len, &esc_str, &esc_len) == FAILURE) { + return; + } + + delim = delim_len ? delim_str[0] : delim; + enc = enc_len ? enc_str[0] : enc; + esc = esc_len ? esc_str[0] : esc; + + php_fgetcsv(NULL, delim, enc, esc, ZSTR_LEN(str), ZSTR_VAL(str), return_value); +} +/* }}} */ + +/* {{{ proto string str_repeat(string input, int mult) + Returns the input string repeat mult times */ +PHP_FUNCTION(str_repeat) +{ + zend_string *input_str; /* Input string */ + zend_long mult; /* Multiplier */ + zend_string *result; /* Resulting string */ + size_t result_len; /* Length of the resulting string */ + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sl", &input_str, &mult) == FAILURE) { + return; + } + + if (mult < 0) { + php_error_docref(NULL, E_WARNING, "Second argument has to be greater than or equal to 0"); + return; + } + + /* Don't waste our time if it's empty */ + /* ... or if the multiplier is zero */ + if (ZSTR_LEN(input_str) == 0 || mult == 0) + RETURN_EMPTY_STRING(); + + /* Initialize the result string */ + result = zend_string_safe_alloc(ZSTR_LEN(input_str), mult, 0, 0); + result_len = ZSTR_LEN(input_str) * mult; + + /* Heavy optimization for situations where input string is 1 byte long */ + if (ZSTR_LEN(input_str) == 1) { + memset(ZSTR_VAL(result), *ZSTR_VAL(input_str), mult); + } else { + char *s, *e, *ee; + ptrdiff_t l=0; + memcpy(ZSTR_VAL(result), ZSTR_VAL(input_str), ZSTR_LEN(input_str)); + s = ZSTR_VAL(result); + e = ZSTR_VAL(result) + ZSTR_LEN(input_str); + ee = ZSTR_VAL(result) + result_len; + + while (e 4) { + php_error_docref(NULL, E_WARNING, "Unknown mode"); + RETURN_FALSE; + } + + buf = (unsigned char *) ZSTR_VAL(input); + memset((void*) chars, 0, sizeof(chars)); + + while (tmp < ZSTR_LEN(input)) { + chars[*buf]++; + buf++; + tmp++; + } + + if (mymode < 3) { + array_init(return_value); + } + + for (inx = 0; inx < 256; inx++) { + switch (mymode) { + case 0: + add_index_long(return_value, inx, chars[inx]); + break; + case 1: + if (chars[inx] != 0) { + add_index_long(return_value, inx, chars[inx]); + } + break; + case 2: + if (chars[inx] == 0) { + add_index_long(return_value, inx, chars[inx]); + } + break; + case 3: + if (chars[inx] != 0) { + retstr[retlen++] = inx; + } + break; + case 4: + if (chars[inx] == 0) { + retstr[retlen++] = inx; + } + break; + } + } + + if (mymode >= 3 && mymode <= 4) { + RETURN_STRINGL(retstr, retlen); + } +} +/* }}} */ + +/* {{{ php_strnatcmp + */ +static void php_strnatcmp(INTERNAL_FUNCTION_PARAMETERS, int fold_case) +{ + zend_string *s1, *s2; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS", &s1, &s2) == FAILURE) { + return; + } + + RETURN_LONG(strnatcmp_ex(ZSTR_VAL(s1), ZSTR_LEN(s1), + ZSTR_VAL(s2), ZSTR_LEN(s2), + fold_case)); +} +/* }}} */ + +PHPAPI int string_natural_compare_function_ex(zval *result, zval *op1, zval *op2, zend_bool case_insensitive) /* {{{ */ +{ + zend_string *str1 = zval_get_string(op1); + zend_string *str2 = zval_get_string(op2); + + ZVAL_LONG(result, strnatcmp_ex(ZSTR_VAL(str1), ZSTR_LEN(str1), ZSTR_VAL(str2), ZSTR_LEN(str2), case_insensitive)); + + zend_string_release(str1); + zend_string_release(str2); + return SUCCESS; +} +/* }}} */ + +PHPAPI int string_natural_case_compare_function(zval *result, zval *op1, zval *op2) /* {{{ */ +{ + return string_natural_compare_function_ex(result, op1, op2, 1); +} +/* }}} */ + +PHPAPI int string_natural_compare_function(zval *result, zval *op1, zval *op2) /* {{{ */ +{ + return string_natural_compare_function_ex(result, op1, op2, 0); +} +/* }}} */ + +/* {{{ proto int strnatcmp(string s1, string s2) + Returns the result of string comparison using 'natural' algorithm */ +PHP_FUNCTION(strnatcmp) +{ + php_strnatcmp(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); +} +/* }}} */ + +/* {{{ proto array localeconv(void) + Returns numeric formatting information based on the current locale */ +PHP_FUNCTION(localeconv) +{ + zval grouping, mon_grouping; + int len, i; + + /* We don't need no stinkin' parameters... */ + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + array_init(return_value); + array_init(&grouping); + array_init(&mon_grouping); + +#ifdef HAVE_LOCALECONV + { + struct lconv currlocdata; + + localeconv_r( &currlocdata ); + + /* Grab the grouping data out of the array */ + len = (int)strlen(currlocdata.grouping); + + for (i = 0; i < len; i++) { + add_index_long(&grouping, i, currlocdata.grouping[i]); + } + + /* Grab the monetary grouping data out of the array */ + len = (int)strlen(currlocdata.mon_grouping); + + for (i = 0; i < len; i++) { + add_index_long(&mon_grouping, i, currlocdata.mon_grouping[i]); + } + + add_assoc_string(return_value, "decimal_point", currlocdata.decimal_point); + add_assoc_string(return_value, "thousands_sep", currlocdata.thousands_sep); + add_assoc_string(return_value, "int_curr_symbol", currlocdata.int_curr_symbol); + add_assoc_string(return_value, "currency_symbol", currlocdata.currency_symbol); + add_assoc_string(return_value, "mon_decimal_point", currlocdata.mon_decimal_point); + add_assoc_string(return_value, "mon_thousands_sep", currlocdata.mon_thousands_sep); + add_assoc_string(return_value, "positive_sign", currlocdata.positive_sign); + add_assoc_string(return_value, "negative_sign", currlocdata.negative_sign); + add_assoc_long( return_value, "int_frac_digits", currlocdata.int_frac_digits); + add_assoc_long( return_value, "frac_digits", currlocdata.frac_digits); + add_assoc_long( return_value, "p_cs_precedes", currlocdata.p_cs_precedes); + add_assoc_long( return_value, "p_sep_by_space", currlocdata.p_sep_by_space); + add_assoc_long( return_value, "n_cs_precedes", currlocdata.n_cs_precedes); + add_assoc_long( return_value, "n_sep_by_space", currlocdata.n_sep_by_space); + add_assoc_long( return_value, "p_sign_posn", currlocdata.p_sign_posn); + add_assoc_long( return_value, "n_sign_posn", currlocdata.n_sign_posn); + } +#else + /* Ok, it doesn't look like we have locale info floating around, so I guess it + wouldn't hurt to just go ahead and return the POSIX locale information? */ + + add_index_long(&grouping, 0, -1); + add_index_long(&mon_grouping, 0, -1); + + add_assoc_string(return_value, "decimal_point", "\x2E"); + add_assoc_string(return_value, "thousands_sep", ""); + add_assoc_string(return_value, "int_curr_symbol", ""); + add_assoc_string(return_value, "currency_symbol", ""); + add_assoc_string(return_value, "mon_decimal_point", "\x2E"); + add_assoc_string(return_value, "mon_thousands_sep", ""); + add_assoc_string(return_value, "positive_sign", ""); + add_assoc_string(return_value, "negative_sign", ""); + add_assoc_long( return_value, "int_frac_digits", CHAR_MAX); + add_assoc_long( return_value, "frac_digits", CHAR_MAX); + add_assoc_long( return_value, "p_cs_precedes", CHAR_MAX); + add_assoc_long( return_value, "p_sep_by_space", CHAR_MAX); + add_assoc_long( return_value, "n_cs_precedes", CHAR_MAX); + add_assoc_long( return_value, "n_sep_by_space", CHAR_MAX); + add_assoc_long( return_value, "p_sign_posn", CHAR_MAX); + add_assoc_long( return_value, "n_sign_posn", CHAR_MAX); +#endif + + zend_hash_str_update(Z_ARRVAL_P(return_value), "grouping", sizeof("grouping")-1, &grouping); + zend_hash_str_update(Z_ARRVAL_P(return_value), "mon_grouping", sizeof("mon_grouping")-1, &mon_grouping); +} +/* }}} */ + +/* {{{ proto int strnatcasecmp(string s1, string s2) + Returns the result of case-insensitive string comparison using 'natural' algorithm */ +PHP_FUNCTION(strnatcasecmp) +{ + php_strnatcmp(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); +} +/* }}} */ + +/* {{{ proto int substr_count(string haystack, string needle [, int offset [, int length]]) + Returns the number of times a substring occurs in the string */ +PHP_FUNCTION(substr_count) +{ + char *haystack, *needle; + zend_long offset = 0, length = 0; + int ac = ZEND_NUM_ARGS(); + int count = 0; + size_t haystack_len, needle_len; + char *p, *endp, cmp; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss|ll", &haystack, &haystack_len, &needle, &needle_len, &offset, &length) == FAILURE) { + return; + } + + if (needle_len == 0) { + php_error_docref(NULL, E_WARNING, "Empty substring"); + RETURN_FALSE; + } + + p = haystack; + endp = p + haystack_len; + + if (offset < 0) { + offset += (zend_long)haystack_len; + } + if ((offset < 0) || ((size_t)offset > haystack_len)) { + php_error_docref(NULL, E_WARNING, "Offset not contained in string"); + RETURN_FALSE; + } + p += offset; + + if (ac == 4) { + + if (length < 0) { + length += (haystack_len - offset); + } + if (length < 0 || ((size_t)length > (haystack_len - offset))) { + php_error_docref(NULL, E_WARNING, "Invalid length value"); + RETURN_FALSE; + } + endp = p + length; + } + + if (needle_len == 1) { + cmp = needle[0]; + + while ((p = memchr(p, cmp, endp - p))) { + count++; + p++; + } + } else { + while ((p = (char*)php_memnstr(p, needle, needle_len, endp))) { + p += needle_len; + count++; + } + } + + RETURN_LONG(count); +} +/* }}} */ + +/* {{{ proto string str_pad(string input, int pad_length [, string pad_string [, int pad_type]]) + Returns input string padded on the left or right to specified length with pad_string */ +PHP_FUNCTION(str_pad) +{ + /* Input arguments */ + zend_string *input; /* Input string */ + zend_long pad_length; /* Length to pad to */ + + /* Helper variables */ + size_t num_pad_chars; /* Number of padding characters (total - input size) */ + char *pad_str = " "; /* Pointer to padding string */ + size_t pad_str_len = 1; + zend_long pad_type_val = STR_PAD_RIGHT; /* The padding type value */ + size_t i, left_pad=0, right_pad=0; + zend_string *result = NULL; /* Resulting string */ + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sl|sl", &input, &pad_length, &pad_str, &pad_str_len, &pad_type_val) == FAILURE) { + return; + } + + /* If resulting string turns out to be shorter than input string, + we simply copy the input and return. */ + if (pad_length < 0 || (size_t)pad_length <= ZSTR_LEN(input)) { + RETURN_STRINGL(ZSTR_VAL(input), ZSTR_LEN(input)); + } + + if (pad_str_len == 0) { + php_error_docref(NULL, E_WARNING, "Padding string cannot be empty"); + return; + } + + if (pad_type_val < STR_PAD_LEFT || pad_type_val > STR_PAD_BOTH) { + php_error_docref(NULL, E_WARNING, "Padding type has to be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH"); + return; + } + + num_pad_chars = pad_length - ZSTR_LEN(input); + if (num_pad_chars >= INT_MAX) { + php_error_docref(NULL, E_WARNING, "Padding length is too long"); + return; + } + + result = zend_string_safe_alloc(1, ZSTR_LEN(input), num_pad_chars, 0); + ZSTR_LEN(result) = 0; + + /* We need to figure out the left/right padding lengths. */ + switch (pad_type_val) { + case STR_PAD_RIGHT: + left_pad = 0; + right_pad = num_pad_chars; + break; + + case STR_PAD_LEFT: + left_pad = num_pad_chars; + right_pad = 0; + break; + + case STR_PAD_BOTH: + left_pad = num_pad_chars / 2; + right_pad = num_pad_chars - left_pad; + break; + } + + /* First we pad on the left. */ + for (i = 0; i < left_pad; i++) + ZSTR_VAL(result)[ZSTR_LEN(result)++] = pad_str[i % pad_str_len]; + + /* Then we copy the input string. */ + memcpy(ZSTR_VAL(result) + ZSTR_LEN(result), ZSTR_VAL(input), ZSTR_LEN(input)); + ZSTR_LEN(result) += ZSTR_LEN(input); + + /* Finally, we pad on the right. */ + for (i = 0; i < right_pad; i++) + ZSTR_VAL(result)[ZSTR_LEN(result)++] = pad_str[i % pad_str_len]; + + ZSTR_VAL(result)[ZSTR_LEN(result)] = '\0'; + + RETURN_NEW_STR(result); +} +/* }}} */ + +/* {{{ proto mixed sscanf(string str, string format [, string ...]) + Implements an ANSI C compatible sscanf */ +PHP_FUNCTION(sscanf) +{ + zval *args = NULL; + char *str, *format; + size_t str_len, format_len; + int result, num_args = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss*", &str, &str_len, &format, &format_len, + &args, &num_args) == FAILURE) { + return; + } + + result = php_sscanf_internal(str, format, num_args, args, 0, return_value); + + if (SCAN_ERROR_WRONG_PARAM_COUNT == result) { + WRONG_PARAM_COUNT; + } +} +/* }}} */ + +static char rot13_from[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; +static char rot13_to[] = "nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM"; + +/* {{{ proto string str_rot13(string str) + Perform the rot13 transform on a string */ +PHP_FUNCTION(str_rot13) +{ + zend_string *arg; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &arg) == FAILURE) { + return; + } + + if (ZSTR_LEN(arg) == 0) { + RETURN_EMPTY_STRING(); + } else { + RETURN_STR(php_strtr_ex(arg, rot13_from, rot13_to, 52)); + } +} +/* }}} */ + +static void php_string_shuffle(char *str, zend_long len) /* {{{ */ +{ + zend_long n_elems, rnd_idx, n_left; + char temp; + /* The implementation is stolen from array_data_shuffle */ + /* Thus the characteristics of the randomization are the same */ + n_elems = len; + + if (n_elems <= 1) { + return; + } + + n_left = n_elems; + + while (--n_left) { + rnd_idx = php_rand(); + RAND_RANGE(rnd_idx, 0, n_left, PHP_RAND_MAX); + if (rnd_idx != n_left) { + temp = str[n_left]; + str[n_left] = str[rnd_idx]; + str[rnd_idx] = temp; + } + } +} +/* }}} */ + +/* {{{ proto void str_shuffle(string str) + Shuffles string. One permutation of all possible is created */ +PHP_FUNCTION(str_shuffle) +{ + zend_string *arg; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &arg) == FAILURE) { + return; + } + + RETVAL_STRINGL(ZSTR_VAL(arg), ZSTR_LEN(arg)); + if (Z_STRLEN_P(return_value) > 1) { + php_string_shuffle(Z_STRVAL_P(return_value), (zend_long) Z_STRLEN_P(return_value)); + } +} +/* }}} */ + +/* {{{ proto mixed str_word_count(string str, [int format [, string charlist]]) + Counts the number of words inside a string. If format of 1 is specified, + then the function will return an array containing all the words + found inside the string. If format of 2 is specified, then the function + will return an associated array where the position of the word is the key + and the word itself is the value. + + For the purpose of this function, 'word' is defined as a locale dependent + string containing alphabetic characters, which also may contain, but not start + with "'" and "-" characters. +*/ +PHP_FUNCTION(str_word_count) +{ + zend_string *str; + char *char_list = NULL, *p, *e, *s, ch[256]; + size_t char_list_len = 0, word_count = 0; + zend_long type = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|ls", &str, &type, &char_list, &char_list_len) == FAILURE) { + return; + } + + switch(type) { + case 1: + case 2: + array_init(return_value); + if (!ZSTR_LEN(str)) { + return; + } + break; + case 0: + if (!ZSTR_LEN(str)) { + RETURN_LONG(0); + } + /* nothing to be done */ + break; + default: + php_error_docref(NULL, E_WARNING, "Invalid format value " ZEND_LONG_FMT, type); + RETURN_FALSE; + } + + if (char_list) { + php_charmask((unsigned char *)char_list, char_list_len, ch); + } + + p = ZSTR_VAL(str); + e = ZSTR_VAL(str) + ZSTR_LEN(str); + + /* first character cannot be ' or -, unless explicitly allowed by the user */ + if ((*p == '\'' && (!char_list || !ch['\''])) || (*p == '-' && (!char_list || !ch['-']))) { + p++; + } + /* last character cannot be -, unless explicitly allowed by the user */ + if (*(e - 1) == '-' && (!char_list || !ch['-'])) { + e--; + } + + while (p < e) { + s = p; + while (p < e && (isalpha((unsigned char)*p) || (char_list && ch[(unsigned char)*p]) || *p == '\'' || *p == '-')) { + p++; + } + if (p > s) { + switch (type) + { + case 1: + add_next_index_stringl(return_value, s, p - s); + break; + case 2: + add_index_stringl(return_value, (s - ZSTR_VAL(str)), s, p - s); + break; + default: + word_count++; + break; + } + } + p++; + } + + if (!type) { + RETURN_LONG(word_count); + } +} + +/* }}} */ + +#if HAVE_STRFMON +/* {{{ proto string money_format(string format , float value) + Convert monetary value(s) to string */ +PHP_FUNCTION(money_format) +{ + size_t format_len = 0; + char *format, *p, *e; + double value; + zend_bool check = 0; + zend_string *str; + ssize_t res_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sd", &format, &format_len, &value) == FAILURE) { + return; + } + + p = format; + e = p + format_len; + while ((p = memchr(p, '%', (e - p)))) { + if (*(p + 1) == '%') { + p += 2; + } else if (!check) { + check = 1; + p++; + } else { + php_error_docref(NULL, E_WARNING, "Only a single %%i or %%n token can be used"); + RETURN_FALSE; + } + } + + str = zend_string_safe_alloc(format_len, 1, 1024, 0); + if ((res_len = strfmon(ZSTR_VAL(str), ZSTR_LEN(str), format, value)) < 0) { + zend_string_free(str); + RETURN_FALSE; + } +#ifdef _AIX + /* + On AIX strfmon seems to include the terminating \0 in the length returned by strfmon, + despite the documentation indicating it is not included. + */ + ZSTR_LEN(str) = strlen(ZSTR_VAL(str)); +#else + ZSTR_LEN(str) = (size_t)res_len; +#endif + ZSTR_VAL(str)[ZSTR_LEN(str)] = '\0'; + + RETURN_NEW_STR(zend_string_truncate(str, ZSTR_LEN(str), 0)); +} +/* }}} */ +#endif + +/* {{{ proto array str_split(string str [, int split_length]) + Convert a string to an array. If split_length is specified, break the string down into chunks each split_length characters long. */ +PHP_FUNCTION(str_split) +{ + zend_string *str; + zend_long split_length = 1; + char *p; + size_t n_reg_segments; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|l", &str, &split_length) == FAILURE) { + return; + } + + if (split_length <= 0) { + php_error_docref(NULL, E_WARNING, "The length of each segment must be greater than zero"); + RETURN_FALSE; + } + + + if (0 == ZSTR_LEN(str) || (size_t)split_length >= ZSTR_LEN(str)) { + array_init_size(return_value, 1); + add_next_index_stringl(return_value, ZSTR_VAL(str), ZSTR_LEN(str)); + return; + } + + array_init_size(return_value, (uint32_t)(((ZSTR_LEN(str) - 1) / split_length) + 1)); + + n_reg_segments = ZSTR_LEN(str) / split_length; + p = ZSTR_VAL(str); + + while (n_reg_segments-- > 0) { + add_next_index_stringl(return_value, p, split_length); + p += split_length; + } + + if (p != (ZSTR_VAL(str) + ZSTR_LEN(str))) { + add_next_index_stringl(return_value, p, (ZSTR_VAL(str) + ZSTR_LEN(str) - p)); + } +} +/* }}} */ + +/* {{{ proto array strpbrk(string haystack, string char_list) + Search a string for any of a set of characters */ +PHP_FUNCTION(strpbrk) +{ + zend_string *haystack, *char_list; + char *haystack_ptr, *cl_ptr; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS", &haystack, &char_list) == FAILURE) { + RETURN_FALSE; + } + + if (!ZSTR_LEN(char_list)) { + php_error_docref(NULL, E_WARNING, "The character list cannot be empty"); + RETURN_FALSE; + } + + for (haystack_ptr = ZSTR_VAL(haystack); haystack_ptr < (ZSTR_VAL(haystack) + ZSTR_LEN(haystack)); ++haystack_ptr) { + for (cl_ptr = ZSTR_VAL(char_list); cl_ptr < (ZSTR_VAL(char_list) + ZSTR_LEN(char_list)); ++cl_ptr) { + if (*cl_ptr == *haystack_ptr) { + RETURN_STRINGL(haystack_ptr, (ZSTR_VAL(haystack) + ZSTR_LEN(haystack) - haystack_ptr)); + } + } + } + + RETURN_FALSE; +} +/* }}} */ + +/* {{{ proto int substr_compare(string main_str, string str, int offset [, int length [, bool case_sensitivity]]) + Binary safe optionally case insensitive comparison of 2 strings from an offset, up to length characters */ +PHP_FUNCTION(substr_compare) +{ + zend_string *s1, *s2; + zend_long offset, len=0; + zend_bool len_is_default=1; + zend_bool cs=0; + size_t cmp_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "SSl|l!b", &s1, &s2, &offset, &len, &len_is_default, &cs) == FAILURE) { + RETURN_FALSE; + } + + if (!len_is_default && len <= 0) { + if (len == 0) { + RETURN_LONG(0L); + } else { + php_error_docref(NULL, E_WARNING, "The length must be greater than or equal to zero"); + RETURN_FALSE; + } + } + + if (offset < 0) { + offset = ZSTR_LEN(s1) + offset; + offset = (offset < 0) ? 0 : offset; + } + + if ((size_t)offset >= ZSTR_LEN(s1)) { + php_error_docref(NULL, E_WARNING, "The start position cannot exceed initial string length"); + RETURN_FALSE; + } + + cmp_len = len ? (size_t)len : MAX(ZSTR_LEN(s2), (ZSTR_LEN(s1) - offset)); + + if (!cs) { + RETURN_LONG(zend_binary_strncmp(ZSTR_VAL(s1) + offset, (ZSTR_LEN(s1) - offset), ZSTR_VAL(s2), ZSTR_LEN(s2), cmp_len)); + } else { + RETURN_LONG(zend_binary_strncasecmp_l(ZSTR_VAL(s1) + offset, (ZSTR_LEN(s1) - offset), ZSTR_VAL(s2), ZSTR_LEN(s2), cmp_len)); + } +} +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/ext/standard/tests/crypt/bcrypt_salt_dollar.phpt b/ext/standard/tests/crypt/bcrypt_salt_dollar.phpt new file mode 100644 index 0000000000000..32e335f4b087e --- /dev/null +++ b/ext/standard/tests/crypt/bcrypt_salt_dollar.phpt @@ -0,0 +1,82 @@ +--TEST-- +bcrypt correctly rejects salts containing $ +--FILE-- + +--EXPECT-- +string(8) "$2y$04$$" +string(2) "*0" +bool(false) +string(9) "$2y$04$0$" +string(2) "*0" +bool(false) +string(10) "$2y$04$00$" +string(2) "*0" +bool(false) +string(11) "$2y$04$000$" +string(2) "*0" +bool(false) +string(12) "$2y$04$0000$" +string(2) "*0" +bool(false) +string(13) "$2y$04$00000$" +string(2) "*0" +bool(false) +string(14) "$2y$04$000000$" +string(2) "*0" +bool(false) +string(15) "$2y$04$0000000$" +string(2) "*0" +bool(false) +string(16) "$2y$04$00000000$" +string(2) "*0" +bool(false) +string(17) "$2y$04$000000000$" +string(2) "*0" +bool(false) +string(18) "$2y$04$0000000000$" +string(2) "*0" +bool(false) +string(19) "$2y$04$00000000000$" +string(2) "*0" +bool(false) +string(20) "$2y$04$000000000000$" +string(2) "*0" +bool(false) +string(21) "$2y$04$0000000000000$" +string(2) "*0" +bool(false) +string(22) "$2y$04$00000000000000$" +string(2) "*0" +bool(false) +string(23) "$2y$04$000000000000000$" +string(2) "*0" +bool(false) +string(24) "$2y$04$0000000000000000$" +string(2) "*0" +bool(false) +string(25) "$2y$04$00000000000000000$" +string(2) "*0" +bool(false) +string(26) "$2y$04$000000000000000000$" +string(2) "*0" +bool(false) +string(27) "$2y$04$0000000000000000000$" +string(2) "*0" +bool(false) +string(28) "$2y$04$00000000000000000000$" +string(2) "*0" +bool(false) +string(29) "$2y$04$000000000000000000000$" +string(2) "*0" +bool(false) +string(30) "$2y$04$0000000000000000000000$" +string(60) "$2y$04$000000000000000000000u2a2UpVexIt9k3FMJeAVr3c04F5tcI8K" +bool(false) diff --git a/ext/standard/tests/file/bug79099.phpt b/ext/standard/tests/file/bug79099.phpt new file mode 100644 index 0000000000000..7c842f4654f06 --- /dev/null +++ b/ext/standard/tests/file/bug79099.phpt @@ -0,0 +1,32 @@ +--TEST-- +Bug #79099 (OOB read in php_strip_tags_ex) +--FILE-- + +--EXPECT-- +string(0) "" +string(0) "" +string(0) "" +string(0) "" +string(0) "" +string(0) "" diff --git a/ext/standard/tests/http/bug75981.phpt b/ext/standard/tests/http/bug75981.phpt new file mode 100644 index 0000000000000..d415de66b9007 --- /dev/null +++ b/ext/standard/tests/http/bug75981.phpt @@ -0,0 +1,32 @@ +--TEST-- +Bug #75981 (stack-buffer-overflow while parsing HTTP response) +--INI-- +allow_url_fopen=1 +--SKIPIF-- + +--FILE-- + [ + 'protocol_version' => '1.1', + 'header' => 'Connection: Close' + ], +]; + +$ctx = stream_context_create($options); + +$responses = [ + "data://text/plain,000000000100\xA\xA" +]; +$pid = http_server('tcp://127.0.0.1:12342', $responses); + +echo @file_get_contents('http://127.0.0.1:12342/', false, $ctx); + +http_server_kill($pid); + +?> +DONE +--EXPECT-- +DONE diff --git a/ext/standard/tests/password/password_bcrypt_short.phpt b/ext/standard/tests/password/password_bcrypt_short.phpt new file mode 100644 index 0000000000000..085bc8a239045 --- /dev/null +++ b/ext/standard/tests/password/password_bcrypt_short.phpt @@ -0,0 +1,8 @@ +--TEST-- +Test that password_hash() does not overread buffers when a short hash is passed +--FILE-- + +--EXPECT-- +bool(false) diff --git a/ext/standard/url.c b/ext/standard/url.c index be6b0d6c7bc08..945b87d9aee28 100644 --- a/ext/standard/url.c +++ b/ext/standard/url.c @@ -535,7 +535,7 @@ PHPAPI size_t php_url_decode(char *str, size_t len) #ifndef CHARSET_EBCDIC *dest = (char) php_htoi(data + 1); #else - *dest = os_toebcdic[(char) php_htoi(data + 1)]; + *dest = os_toebcdic[(unsigned char) php_htoi(data + 1)]; #endif data += 2; len -= 2; @@ -627,7 +627,7 @@ PHPAPI size_t php_raw_url_decode(char *str, size_t len) #ifndef CHARSET_EBCDIC *dest = (char) php_htoi(data + 1); #else - *dest = os_toebcdic[(char) php_htoi(data + 1)]; + *dest = os_toebcdic[(unsigned char) php_htoi(data + 1)]; #endif data += 2; len -= 2; diff --git a/ext/standard/url.c.orig b/ext/standard/url.c.orig new file mode 100644 index 0000000000000..be6b0d6c7bc08 --- /dev/null +++ b/ext/standard/url.c.orig @@ -0,0 +1,731 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2017 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Jim Winstead | + +----------------------------------------------------------------------+ + */ +/* $Id$ */ + +#include +#include +#include +#include + +#include "php.h" + +#include "url.h" +#include "file.h" +#ifdef _OSD_POSIX +#ifndef APACHE +#error On this EBCDIC platform, PHP is only supported as an Apache module. +#else /*APACHE*/ +#ifndef CHARSET_EBCDIC +#define CHARSET_EBCDIC /* this machine uses EBCDIC, not ASCII! */ +#endif +#include "ebcdic.h" +#endif /*APACHE*/ +#endif /*_OSD_POSIX*/ + +/* {{{ free_url + */ +PHPAPI void php_url_free(php_url *theurl) +{ + if (theurl->scheme) + efree(theurl->scheme); + if (theurl->user) + efree(theurl->user); + if (theurl->pass) + efree(theurl->pass); + if (theurl->host) + efree(theurl->host); + if (theurl->path) + efree(theurl->path); + if (theurl->query) + efree(theurl->query); + if (theurl->fragment) + efree(theurl->fragment); + efree(theurl); +} +/* }}} */ + +/* {{{ php_replace_controlchars + */ +PHPAPI char *php_replace_controlchars_ex(char *str, size_t len) +{ + unsigned char *s = (unsigned char *)str; + unsigned char *e = (unsigned char *)str + len; + + if (!str) { + return (NULL); + } + + while (s < e) { + + if (iscntrl(*s)) { + *s='_'; + } + s++; + } + + return (str); +} +/* }}} */ + +PHPAPI char *php_replace_controlchars(char *str) +{ + return php_replace_controlchars_ex(str, strlen(str)); +} + +PHPAPI php_url *php_url_parse(char const *str) +{ + return php_url_parse_ex(str, strlen(str)); +} + +/* {{{ php_url_parse + */ +PHPAPI php_url *php_url_parse_ex(char const *str, size_t length) +{ + char port_buf[6]; + php_url *ret = ecalloc(1, sizeof(php_url)); + char const *s, *e, *p, *pp, *ue; + + s = str; + ue = s + length; + + /* parse scheme */ + if ((e = memchr(s, ':', length)) && e != s) { + /* validate scheme */ + p = s; + while (p < e) { + /* scheme = 1*[ lowalpha | digit | "+" | "-" | "." ] */ + if (!isalpha(*p) && !isdigit(*p) && *p != '+' && *p != '.' && *p != '-') { + if (e + 1 < ue && e < s + strcspn(s, "?#")) { + goto parse_port; + } else { + goto just_path; + } + } + p++; + } + + if (e + 1 == ue) { /* only scheme is available */ + ret->scheme = estrndup(s, (e - s)); + php_replace_controlchars_ex(ret->scheme, (e - s)); + return ret; + } + + /* + * certain schemas like mailto: and zlib: may not have any / after them + * this check ensures we support those. + */ + if (*(e+1) != '/') { + /* check if the data we get is a port this allows us to + * correctly parse things like a.com:80 + */ + p = e + 1; + while (p < ue && isdigit(*p)) { + p++; + } + + if ((p == ue || *p == '/') && (p - e) < 7) { + goto parse_port; + } + + ret->scheme = estrndup(s, (e-s)); + php_replace_controlchars_ex(ret->scheme, (e - s)); + + s = e + 1; + goto just_path; + } else { + ret->scheme = estrndup(s, (e-s)); + php_replace_controlchars_ex(ret->scheme, (e - s)); + + if (e + 2 < ue && *(e + 2) == '/') { + s = e + 3; + if (!strncasecmp("file", ret->scheme, sizeof("file"))) { + if (e + 3 < ue && *(e + 3) == '/') { + /* support windows drive letters as in: + file:///c:/somedir/file.txt + */ + if (e + 5 < ue && *(e + 5) == ':') { + s = e + 4; + } + goto just_path; + } + } + } else { + s = e + 1; + goto just_path; + } + } + } else if (e) { /* no scheme; starts with colon: look for port */ + parse_port: + p = e + 1; + pp = p; + + while (pp < ue && pp - p < 6 && isdigit(*pp)) { + pp++; + } + + if (pp - p > 0 && pp - p < 6 && (pp == ue || *pp == '/')) { + zend_long port; + memcpy(port_buf, p, (pp - p)); + port_buf[pp - p] = '\0'; + port = ZEND_STRTOL(port_buf, NULL, 10); + if (port > 0 && port <= 65535) { + ret->port = (unsigned short) port; + if (s + 1 < ue && *s == '/' && *(s + 1) == '/') { /* relative-scheme URL */ + s += 2; + } + } else { + if (ret->scheme) efree(ret->scheme); + efree(ret); + return NULL; + } + } else if (p == pp && pp == ue) { + if (ret->scheme) efree(ret->scheme); + efree(ret); + return NULL; + } else if (s + 1 < ue && *s == '/' && *(s + 1) == '/') { /* relative-scheme URL */ + s += 2; + } else { + goto just_path; + } + } else if (s + 1 < ue && *s == '/' && *(s + 1) == '/') { /* relative-scheme URL */ + s += 2; + } else { + goto just_path; + } + + /* Binary-safe strcspn(s, "/?#") */ + e = ue; + if ((p = memchr(s, '/', e - s))) { + e = p; + } + if ((p = memchr(s, '?', e - s))) { + e = p; + } + if ((p = memchr(s, '#', e - s))) { + e = p; + } + + /* check for login and password */ + if ((p = zend_memrchr(s, '@', (e-s)))) { + if ((pp = memchr(s, ':', (p-s)))) { + ret->user = estrndup(s, (pp-s)); + php_replace_controlchars_ex(ret->user, (pp - s)); + + pp++; + ret->pass = estrndup(pp, (p-pp)); + php_replace_controlchars_ex(ret->pass, (p-pp)); + } else { + ret->user = estrndup(s, (p-s)); + php_replace_controlchars_ex(ret->user, (p-s)); + } + + s = p + 1; + } + + /* check for port */ + if (s < ue && *s == '[' && *(e-1) == ']') { + /* Short circuit portscan, + we're dealing with an + IPv6 embedded address */ + p = NULL; + } else { + p = zend_memrchr(s, ':', (e-s)); + } + + if (p) { + if (!ret->port) { + p++; + if (e-p > 5) { /* port cannot be longer then 5 characters */ + if (ret->scheme) efree(ret->scheme); + if (ret->user) efree(ret->user); + if (ret->pass) efree(ret->pass); + efree(ret); + return NULL; + } else if (e - p > 0) { + zend_long port; + memcpy(port_buf, p, (e - p)); + port_buf[e - p] = '\0'; + port = ZEND_STRTOL(port_buf, NULL, 10); + if (port > 0 && port <= 65535) { + ret->port = (unsigned short)port; + } else { + if (ret->scheme) efree(ret->scheme); + if (ret->user) efree(ret->user); + if (ret->pass) efree(ret->pass); + efree(ret); + return NULL; + } + } + p--; + } + } else { + p = e; + } + + /* check if we have a valid host, if we don't reject the string as url */ + if ((p-s) < 1) { + if (ret->scheme) efree(ret->scheme); + if (ret->user) efree(ret->user); + if (ret->pass) efree(ret->pass); + efree(ret); + return NULL; + } + + ret->host = estrndup(s, (p-s)); + php_replace_controlchars_ex(ret->host, (p - s)); + + if (e == ue) { + return ret; + } + + s = e; + + just_path: + + e = ue; + p = memchr(s, '#', (e - s)); + if (p) { + p++; + if (p < e) { + ret->fragment = estrndup(p, (e - p)); + php_replace_controlchars_ex(ret->fragment, (e - p)); + } + e = p-1; + } + + p = memchr(s, '?', (e - s)); + if (p) { + p++; + if (p < e) { + ret->query = estrndup(p, (e - p)); + php_replace_controlchars_ex(ret->query, (e - p)); + } + e = p-1; + } + + if (s < e || s == ue) { + ret->path = estrndup(s, (e - s)); + php_replace_controlchars_ex(ret->path, (e - s)); + } + + return ret; +} +/* }}} */ + +/* {{{ proto mixed parse_url(string url, [int url_component]) + Parse a URL and return its components */ +PHP_FUNCTION(parse_url) +{ + char *str; + size_t str_len; + php_url *resource; + zend_long key = -1; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &str, &str_len, &key) == FAILURE) { + return; + } + + resource = php_url_parse_ex(str, str_len); + if (resource == NULL) { + /* @todo Find a method to determine why php_url_parse_ex() failed */ + RETURN_FALSE; + } + + if (key > -1) { + switch (key) { + case PHP_URL_SCHEME: + if (resource->scheme != NULL) RETVAL_STRING(resource->scheme); + break; + case PHP_URL_HOST: + if (resource->host != NULL) RETVAL_STRING(resource->host); + break; + case PHP_URL_PORT: + if (resource->port != 0) RETVAL_LONG(resource->port); + break; + case PHP_URL_USER: + if (resource->user != NULL) RETVAL_STRING(resource->user); + break; + case PHP_URL_PASS: + if (resource->pass != NULL) RETVAL_STRING(resource->pass); + break; + case PHP_URL_PATH: + if (resource->path != NULL) RETVAL_STRING(resource->path); + break; + case PHP_URL_QUERY: + if (resource->query != NULL) RETVAL_STRING(resource->query); + break; + case PHP_URL_FRAGMENT: + if (resource->fragment != NULL) RETVAL_STRING(resource->fragment); + break; + default: + php_error_docref(NULL, E_WARNING, "Invalid URL component identifier " ZEND_LONG_FMT, key); + RETVAL_FALSE; + } + goto done; + } + + /* allocate an array for return */ + array_init(return_value); + + /* add the various elements to the array */ + if (resource->scheme != NULL) + add_assoc_string(return_value, "scheme", resource->scheme); + if (resource->host != NULL) + add_assoc_string(return_value, "host", resource->host); + if (resource->port != 0) + add_assoc_long(return_value, "port", resource->port); + if (resource->user != NULL) + add_assoc_string(return_value, "user", resource->user); + if (resource->pass != NULL) + add_assoc_string(return_value, "pass", resource->pass); + if (resource->path != NULL) + add_assoc_string(return_value, "path", resource->path); + if (resource->query != NULL) + add_assoc_string(return_value, "query", resource->query); + if (resource->fragment != NULL) + add_assoc_string(return_value, "fragment", resource->fragment); +done: + php_url_free(resource); +} +/* }}} */ + +/* {{{ php_htoi + */ +static int php_htoi(char *s) +{ + int value; + int c; + + c = ((unsigned char *)s)[0]; + if (isupper(c)) + c = tolower(c); + value = (c >= '0' && c <= '9' ? c - '0' : c - 'a' + 10) * 16; + + c = ((unsigned char *)s)[1]; + if (isupper(c)) + c = tolower(c); + value += c >= '0' && c <= '9' ? c - '0' : c - 'a' + 10; + + return (value); +} +/* }}} */ + +/* rfc1738: + + ...The characters ";", + "/", "?", ":", "@", "=" and "&" are the characters which may be + reserved for special meaning within a scheme... + + ...Thus, only alphanumerics, the special characters "$-_.+!*'(),", and + reserved characters used for their reserved purposes may be used + unencoded within a URL... + + For added safety, we only leave -_. unencoded. + */ + +static unsigned char hexchars[] = "0123456789ABCDEF"; + +/* {{{ php_url_encode + */ +PHPAPI zend_string *php_url_encode(char const *s, size_t len) +{ + register unsigned char c; + unsigned char *to; + unsigned char const *from, *end; + zend_string *start; + + from = (unsigned char *)s; + end = (unsigned char *)s + len; + start = zend_string_safe_alloc(3, len, 0, 0); + to = (unsigned char*)ZSTR_VAL(start); + + while (from < end) { + c = *from++; + + if (c == ' ') { + *to++ = '+'; +#ifndef CHARSET_EBCDIC + } else if ((c < '0' && c != '-' && c != '.') || + (c < 'A' && c > '9') || + (c > 'Z' && c < 'a' && c != '_') || + (c > 'z')) { + to[0] = '%'; + to[1] = hexchars[c >> 4]; + to[2] = hexchars[c & 15]; + to += 3; +#else /*CHARSET_EBCDIC*/ + } else if (!isalnum(c) && strchr("_-.", c) == NULL) { + /* Allow only alphanumeric chars and '_', '-', '.'; escape the rest */ + to[0] = '%'; + to[1] = hexchars[os_toascii[c] >> 4]; + to[2] = hexchars[os_toascii[c] & 15]; + to += 3; +#endif /*CHARSET_EBCDIC*/ + } else { + *to++ = c; + } + } + *to = '\0'; + + start = zend_string_truncate(start, to - (unsigned char*)ZSTR_VAL(start), 0); + + return start; +} +/* }}} */ + +/* {{{ proto string urlencode(string str) + URL-encodes string */ +PHP_FUNCTION(urlencode) +{ + zend_string *in_str; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(in_str) + ZEND_PARSE_PARAMETERS_END(); + + RETURN_STR(php_url_encode(ZSTR_VAL(in_str), ZSTR_LEN(in_str))); +} +/* }}} */ + +/* {{{ proto string urldecode(string str) + Decodes URL-encoded string */ +PHP_FUNCTION(urldecode) +{ + zend_string *in_str, *out_str; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(in_str) + ZEND_PARSE_PARAMETERS_END(); + + out_str = zend_string_init(ZSTR_VAL(in_str), ZSTR_LEN(in_str), 0); + ZSTR_LEN(out_str) = php_url_decode(ZSTR_VAL(out_str), ZSTR_LEN(out_str)); + + RETURN_NEW_STR(out_str); +} +/* }}} */ + +/* {{{ php_url_decode + */ +PHPAPI size_t php_url_decode(char *str, size_t len) +{ + char *dest = str; + char *data = str; + + while (len--) { + if (*data == '+') { + *dest = ' '; + } + else if (*data == '%' && len >= 2 && isxdigit((int) *(data + 1)) + && isxdigit((int) *(data + 2))) { +#ifndef CHARSET_EBCDIC + *dest = (char) php_htoi(data + 1); +#else + *dest = os_toebcdic[(char) php_htoi(data + 1)]; +#endif + data += 2; + len -= 2; + } else { + *dest = *data; + } + data++; + dest++; + } + *dest = '\0'; + return dest - str; +} +/* }}} */ + +/* {{{ php_raw_url_encode + */ +PHPAPI zend_string *php_raw_url_encode(char const *s, size_t len) +{ + register size_t x, y; + zend_string *str; + + str = zend_string_safe_alloc(3, len, 0, 0); + for (x = 0, y = 0; len--; x++, y++) { + ZSTR_VAL(str)[y] = (unsigned char) s[x]; +#ifndef CHARSET_EBCDIC + if ((ZSTR_VAL(str)[y] < '0' && ZSTR_VAL(str)[y] != '-' && ZSTR_VAL(str)[y] != '.') || + (ZSTR_VAL(str)[y] < 'A' && ZSTR_VAL(str)[y] > '9') || + (ZSTR_VAL(str)[y] > 'Z' && ZSTR_VAL(str)[y] < 'a' && ZSTR_VAL(str)[y] != '_') || + (ZSTR_VAL(str)[y] > 'z' && ZSTR_VAL(str)[y] != '~')) { + ZSTR_VAL(str)[y++] = '%'; + ZSTR_VAL(str)[y++] = hexchars[(unsigned char) s[x] >> 4]; + ZSTR_VAL(str)[y] = hexchars[(unsigned char) s[x] & 15]; +#else /*CHARSET_EBCDIC*/ + if (!isalnum(ZSTR_VAL(str)[y]) && strchr("_-.~", ZSTR_VAL(str)[y]) != NULL) { + ZSTR_VAL(str)[y++] = '%'; + ZSTR_VAL(str)[y++] = hexchars[os_toascii[(unsigned char) s[x]] >> 4]; + ZSTR_VAL(str)[y] = hexchars[os_toascii[(unsigned char) s[x]] & 15]; +#endif /*CHARSET_EBCDIC*/ + } + } + ZSTR_VAL(str)[y] = '\0'; + str = zend_string_truncate(str, y, 0); + + return str; +} +/* }}} */ + +/* {{{ proto string rawurlencode(string str) + URL-encodes string */ +PHP_FUNCTION(rawurlencode) +{ + zend_string *in_str; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(in_str) + ZEND_PARSE_PARAMETERS_END(); + + RETURN_STR(php_raw_url_encode(ZSTR_VAL(in_str), ZSTR_LEN(in_str))); +} +/* }}} */ + +/* {{{ proto string rawurldecode(string str) + Decodes URL-encodes string */ +PHP_FUNCTION(rawurldecode) +{ + zend_string *in_str, *out_str; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(in_str) + ZEND_PARSE_PARAMETERS_END(); + + out_str = zend_string_init(ZSTR_VAL(in_str), ZSTR_LEN(in_str), 0); + ZSTR_LEN(out_str) = php_raw_url_decode(ZSTR_VAL(out_str), ZSTR_LEN(out_str)); + + RETURN_NEW_STR(out_str); +} +/* }}} */ + +/* {{{ php_raw_url_decode + */ +PHPAPI size_t php_raw_url_decode(char *str, size_t len) +{ + char *dest = str; + char *data = str; + + while (len--) { + if (*data == '%' && len >= 2 && isxdigit((int) *(data + 1)) + && isxdigit((int) *(data + 2))) { +#ifndef CHARSET_EBCDIC + *dest = (char) php_htoi(data + 1); +#else + *dest = os_toebcdic[(char) php_htoi(data + 1)]; +#endif + data += 2; + len -= 2; + } else { + *dest = *data; + } + data++; + dest++; + } + *dest = '\0'; + return dest - str; +} +/* }}} */ + +/* {{{ proto array get_headers(string url[, int format[, resource context]]) + fetches all the headers sent by the server in response to a HTTP request */ +PHP_FUNCTION(get_headers) +{ + char *url; + size_t url_len; + php_stream *stream; + zval *prev_val, *hdr = NULL, *h; + HashTable *hashT; + zend_long format = 0; + zval *zcontext = NULL; + php_stream_context *context; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|lr!", &url, &url_len, &format, &zcontext) == FAILURE) { + return; + } + + context = php_stream_context_from_zval(zcontext, 0); + + if (!(stream = php_stream_open_wrapper_ex(url, "r", REPORT_ERRORS | STREAM_USE_URL | STREAM_ONLY_GET_HEADERS, NULL, context))) { + RETURN_FALSE; + } + + if (Z_TYPE(stream->wrapperdata) != IS_ARRAY) { + php_stream_close(stream); + RETURN_FALSE; + } + + array_init(return_value); + + /* check for curl-wrappers that provide headers via a special "headers" element */ + if ((h = zend_hash_str_find(HASH_OF(&stream->wrapperdata), "headers", sizeof("headers")-1)) != NULL && Z_TYPE_P(h) == IS_ARRAY) { + /* curl-wrappers don't load data until the 1st read */ + if (!Z_ARRVAL_P(h)->nNumOfElements) { + php_stream_getc(stream); + } + h = zend_hash_str_find(HASH_OF(&stream->wrapperdata), "headers", sizeof("headers")-1); + hashT = Z_ARRVAL_P(h); + } else { + hashT = HASH_OF(&stream->wrapperdata); + } + + ZEND_HASH_FOREACH_VAL(hashT, hdr) { + if (Z_TYPE_P(hdr) != IS_STRING) { + continue; + } + if (!format) { +no_name_header: + add_next_index_str(return_value, zend_string_copy(Z_STR_P(hdr))); + } else { + char c; + char *s, *p; + + if ((p = strchr(Z_STRVAL_P(hdr), ':'))) { + c = *p; + *p = '\0'; + s = p + 1; + while (isspace((int)*(unsigned char *)s)) { + s++; + } + + if ((prev_val = zend_hash_str_find(Z_ARRVAL_P(return_value), Z_STRVAL_P(hdr), (p - Z_STRVAL_P(hdr)))) == NULL) { + add_assoc_stringl_ex(return_value, Z_STRVAL_P(hdr), (p - Z_STRVAL_P(hdr)), s, (Z_STRLEN_P(hdr) - (s - Z_STRVAL_P(hdr)))); + } else { /* some headers may occur more than once, therefor we need to remake the string into an array */ + convert_to_array(prev_val); + add_next_index_stringl(prev_val, s, (Z_STRLEN_P(hdr) - (s - Z_STRVAL_P(hdr)))); + } + + *p = c; + } else { + goto no_name_header; + } + } + } ZEND_HASH_FOREACH_END(); + + php_stream_close(stream); +} +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/ext/xmlrpc/libxmlrpc/base64.c b/ext/xmlrpc/libxmlrpc/base64.c index 5ebdf31f7adeb..a4fa19327b76d 100644 --- a/ext/xmlrpc/libxmlrpc/base64.c +++ b/ext/xmlrpc/libxmlrpc/base64.c @@ -77,7 +77,7 @@ void base64_encode_xmlrpc(struct buffer_st *b, const char *source, int length) while (!hiteof) { unsigned char igroup[3], ogroup[4]; - int c, n; + int c, n; igroup[0] = igroup[1] = igroup[2] = 0; for (n = 0; n < 3; n++) { @@ -169,7 +169,7 @@ void base64_decode_xmlrpc(struct buffer_st *bfr, const char *source, int length) return; } - if (dtable[c] & 0x80) { + if (dtable[(unsigned char)c] & 0x80) { /* fprintf(stderr, "Offset %i length %i\n", offset, length); fprintf(stderr, "character '%c:%x:%c' in input file.\n", c, c, dtable[c]); diff --git a/ext/xmlrpc/libxmlrpc/xml_element.c b/ext/xmlrpc/libxmlrpc/xml_element.c index 6fc6bd397747c..a30b500f2f80e 100644 --- a/ext/xmlrpc/libxmlrpc/xml_element.c +++ b/ext/xmlrpc/libxmlrpc/xml_element.c @@ -723,6 +723,9 @@ xml_element* xml_elem_parse_buf(const char* in_buf, int len, XML_ELEM_INPUT_OPTI long byte_idx = XML_GetCurrentByteIndex(parser); /* int byte_total = XML_GetCurrentByteCount(parser); */ const char * error_str = XML_ErrorString(err_code); + if(byte_idx > len) { + byte_idx = len; + } if(byte_idx >= 0) { snprintf(buf, sizeof(buf), diff --git a/ext/xmlrpc/tests/bug77242.phpt b/ext/xmlrpc/tests/bug77242.phpt new file mode 100644 index 0000000000000..542c06311f745 --- /dev/null +++ b/ext/xmlrpc/tests/bug77242.phpt @@ -0,0 +1,10 @@ +--TEST-- +Bug #77242 (heap out of bounds read in xmlrpc_decode()) +--SKIPIF-- + +--FILE-- + +--EXPECT-- +NULL \ No newline at end of file diff --git a/ext/xmlrpc/tests/bug77380.phpt b/ext/xmlrpc/tests/bug77380.phpt new file mode 100644 index 0000000000000..8559c07a5aea6 --- /dev/null +++ b/ext/xmlrpc/tests/bug77380.phpt @@ -0,0 +1,17 @@ +--TEST-- +Bug #77380 (Global out of bounds read in xmlrpc base64 code) +--SKIPIF-- + +--FILE-- + +--EXPECT-- +object(stdClass)#1 (2) { + ["scalar"]=> + string(0) "" + ["xmlrpc_type"]=> + string(6) "base64" +} diff --git a/main/rfc1867.c b/main/rfc1867.c index 264b234aa31e3..9d52ed5a7cb44 100644 --- a/main/rfc1867.c +++ b/main/rfc1867.c @@ -692,7 +692,8 @@ SAPI_API SAPI_POST_HANDLER_FUNC(rfc1867_post_handler) /* {{{ */ char *boundary, *s = NULL, *boundary_end = NULL, *start_arr = NULL, *array_index = NULL; char *lbuf = NULL, *abuf = NULL; zend_string *temp_filename = NULL; - int boundary_len = 0, cancel_upload = 0, is_arr_upload = 0, array_len = 0; + int boundary_len = 0, cancel_upload = 0, is_arr_upload = 0; + size_t array_len = 0; int64_t total_bytes = 0, max_file_size = 0; int skip_upload = 0, anonindex = 0, is_anonymous; HashTable *uploaded_files = NULL; @@ -1126,7 +1127,7 @@ SAPI_API SAPI_POST_HANDLER_FUNC(rfc1867_post_handler) /* {{{ */ is_arr_upload = (start_arr = strchr(param,'[')) && (param[strlen(param)-1] == ']'); if (is_arr_upload) { - array_len = (int)strlen(start_arr); + array_len = strlen(start_arr); if (array_index) { efree(array_index); } diff --git a/main/streams/plain_wrapper.c b/main/streams/plain_wrapper.c index 25eacceec5e36..b6183292f304c 100644 --- a/main/streams/plain_wrapper.c +++ b/main/streams/plain_wrapper.c @@ -1168,34 +1168,51 @@ static int php_plain_files_rename(php_stream_wrapper *wrapper, const char *url_f # ifdef EXDEV if (errno == EXDEV) { zend_stat_t sb; +# if !defined(ZTS) && !defined(TSRM_WIN32) && !defined(NETWARE) + /* not sure what to do in ZTS case, umask is not thread-safe */ + int oldmask = umask(077); +# endif + int success = 0; if (php_copy_file(url_from, url_to) == SUCCESS) { if (VCWD_STAT(url_from, &sb) == 0) { + success = 1; # if !defined(TSRM_WIN32) && !defined(NETWARE) - if (VCWD_CHMOD(url_to, sb.st_mode)) { - if (errno == EPERM) { - php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno)); - VCWD_UNLINK(url_from); - return 1; - } + /* + * Try to set user and permission info on the target. + * If we're not root, then some of these may fail. + * We try chown first, to set proper group info, relying + * on the system environment to have proper umask to not allow + * access to the file in the meantime. + */ + if (VCWD_CHOWN(url_to, sb.st_uid, sb.st_gid)) { php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno)); - return 0; + if (errno != EPERM) { + success = 0; + } } - if (VCWD_CHOWN(url_to, sb.st_uid, sb.st_gid)) { - if (errno == EPERM) { + + if (success) { + if (VCWD_CHMOD(url_to, sb.st_mode)) { php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno)); - VCWD_UNLINK(url_from); - return 1; + if (errno != EPERM) { + success = 0; + } } - php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno)); - return 0; } # endif - VCWD_UNLINK(url_from); - return 1; + if (success) { + VCWD_UNLINK(url_from); + } + } else { + php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno)); } + } else { + php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno)); } - php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno)); - return 0; +# if !defined(ZTS) && !defined(TSRM_WIN32) && !defined(NETWARE) + umask(oldmask); +# endif + return success; } # endif #endif diff --git a/sapi/apache2handler/sapi_apache2.c b/sapi/apache2handler/sapi_apache2.c index 5ec248a02d27f..9840947fae33c 100644 --- a/sapi/apache2handler/sapi_apache2.c +++ b/sapi/apache2handler/sapi_apache2.c @@ -723,6 +723,7 @@ zend_first_try { if (!parent_req) { php_apache_request_dtor(r); ctx->request_processed = 1; + apr_brigade_cleanup(brigade); bucket = apr_bucket_eos_create(r->connection->bucket_alloc); APR_BRIGADE_INSERT_TAIL(brigade, bucket); diff --git a/sapi/apache2handler/sapi_apache2.c.orig b/sapi/apache2handler/sapi_apache2.c.orig new file mode 100644 index 0000000000000..5ec248a02d27f --- /dev/null +++ b/sapi/apache2handler/sapi_apache2.c.orig @@ -0,0 +1,767 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2017 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Sascha Schumann | + | Parts based on Apache 1.3 SAPI module by | + | Rasmus Lerdorf and Zeev Suraski | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#define ZEND_INCLUDE_FULL_WINDOWS_HEADERS + +#include "php.h" +#include "php_main.h" +#include "php_ini.h" +#include "php_variables.h" +#include "SAPI.h" + +#include + +#include "zend_smart_str.h" +#ifndef NETWARE +#include "ext/standard/php_standard.h" +#else +#include "ext/standard/basic_functions.h" +#endif + +#include "apr_strings.h" +#include "ap_config.h" +#include "util_filter.h" +#include "httpd.h" +#include "http_config.h" +#include "http_request.h" +#include "http_core.h" +#include "http_protocol.h" +#include "http_log.h" +#include "http_main.h" +#include "util_script.h" +#include "http_core.h" +#include "ap_mpm.h" + +#include "php_apache.h" + +/* UnixWare and Netware define shutdown to _shutdown, which causes problems later + * on when using a structure member named shutdown. Since this source + * file does not use the system call shutdown, it is safe to #undef it.K + */ +#undef shutdown + +#define PHP_MAGIC_TYPE "application/x-httpd-php" +#define PHP_SOURCE_MAGIC_TYPE "application/x-httpd-php-source" +#define PHP_SCRIPT "php7-script" + +/* A way to specify the location of the php.ini dir in an apache directive */ +char *apache2_php_ini_path_override = NULL; +#if defined(PHP_WIN32) && defined(ZTS) +ZEND_TSRMLS_CACHE_DEFINE() +#endif + +static size_t +php_apache_sapi_ub_write(const char *str, size_t str_length) +{ + request_rec *r; + php_struct *ctx; + + ctx = SG(server_context); + r = ctx->r; + + if (ap_rwrite(str, str_length, r) < 0) { + php_handle_aborted_connection(); + } + + return str_length; /* we always consume all the data passed to us. */ +} + +static int +php_apache_sapi_header_handler(sapi_header_struct *sapi_header, sapi_header_op_enum op, sapi_headers_struct *sapi_headers) +{ + php_struct *ctx; + char *val, *ptr; + + ctx = SG(server_context); + + switch (op) { + case SAPI_HEADER_DELETE: + apr_table_unset(ctx->r->headers_out, sapi_header->header); + return 0; + + case SAPI_HEADER_DELETE_ALL: + apr_table_clear(ctx->r->headers_out); + return 0; + + case SAPI_HEADER_ADD: + case SAPI_HEADER_REPLACE: + val = strchr(sapi_header->header, ':'); + + if (!val) { + return 0; + } + ptr = val; + + *val = '\0'; + + do { + val++; + } while (*val == ' '); + + if (!strcasecmp(sapi_header->header, "content-type")) { + if (ctx->content_type) { + efree(ctx->content_type); + } + ctx->content_type = estrdup(val); + } else if (!strcasecmp(sapi_header->header, "content-length")) { + apr_off_t clen = 0; + + if (APR_SUCCESS != apr_strtoff(&clen, val, (char **) NULL, 10)) { + /* We'll fall back to strtol, since that's what we used to + * do anyway. */ + clen = (apr_off_t) strtol(val, (char **) NULL, 10); + } + + ap_set_content_length(ctx->r, clen); + } else if (op == SAPI_HEADER_REPLACE) { + apr_table_set(ctx->r->headers_out, sapi_header->header, val); + } else { + apr_table_add(ctx->r->headers_out, sapi_header->header, val); + } + + *ptr = ':'; + + return SAPI_HEADER_ADD; + + default: + return 0; + } +} + +static int +php_apache_sapi_send_headers(sapi_headers_struct *sapi_headers) +{ + php_struct *ctx = SG(server_context); + const char *sline = SG(sapi_headers).http_status_line; + + ctx->r->status = SG(sapi_headers).http_response_code; + + /* httpd requires that r->status_line is set to the first digit of + * the status-code: */ + if (sline && strlen(sline) > 12 && strncmp(sline, "HTTP/1.", 7) == 0 && sline[8] == ' ') { + ctx->r->status_line = apr_pstrdup(ctx->r->pool, sline + 9); + ctx->r->proto_num = 1000 + (sline[7]-'0'); + if ((sline[7]-'0') == 0) { + apr_table_set(ctx->r->subprocess_env, "force-response-1.0", "true"); + } + } + + /* call ap_set_content_type only once, else each time we call it, + configured output filters for that content type will be added */ + if (!ctx->content_type) { + ctx->content_type = sapi_get_default_content_type(); + } + ap_set_content_type(ctx->r, apr_pstrdup(ctx->r->pool, ctx->content_type)); + efree(ctx->content_type); + ctx->content_type = NULL; + + return SAPI_HEADER_SENT_SUCCESSFULLY; +} + +static apr_size_t +php_apache_sapi_read_post(char *buf, size_t count_bytes) +{ + apr_size_t len, tlen=0; + php_struct *ctx = SG(server_context); + request_rec *r; + apr_bucket_brigade *brigade; + + r = ctx->r; + brigade = ctx->brigade; + len = count_bytes; + + /* + * This loop is needed because ap_get_brigade() can return us partial data + * which would cause premature termination of request read. Therefor we + * need to make sure that if data is available we fill the buffer completely. + */ + + while (ap_get_brigade(r->input_filters, brigade, AP_MODE_READBYTES, APR_BLOCK_READ, len) == APR_SUCCESS) { + apr_brigade_flatten(brigade, buf, &len); + apr_brigade_cleanup(brigade); + tlen += len; + if (tlen == count_bytes || !len) { + break; + } + buf += len; + len = count_bytes - tlen; + } + + return tlen; +} + +static zend_stat_t* +php_apache_sapi_get_stat(void) +{ + php_struct *ctx = SG(server_context); + +#ifdef PHP_WIN32 + ctx->finfo.st_uid = 0; + ctx->finfo.st_gid = 0; +#else + ctx->finfo.st_uid = ctx->r->finfo.user; + ctx->finfo.st_gid = ctx->r->finfo.group; +#endif + ctx->finfo.st_dev = ctx->r->finfo.device; + ctx->finfo.st_ino = ctx->r->finfo.inode; +#if defined(NETWARE) && defined(CLIB_STAT_PATCH) + ctx->finfo.st_atime.tv_sec = apr_time_sec(ctx->r->finfo.atime); + ctx->finfo.st_mtime.tv_sec = apr_time_sec(ctx->r->finfo.mtime); + ctx->finfo.st_ctime.tv_sec = apr_time_sec(ctx->r->finfo.ctime); +#else + ctx->finfo.st_atime = apr_time_sec(ctx->r->finfo.atime); + ctx->finfo.st_mtime = apr_time_sec(ctx->r->finfo.mtime); + ctx->finfo.st_ctime = apr_time_sec(ctx->r->finfo.ctime); +#endif + + ctx->finfo.st_size = ctx->r->finfo.size; + ctx->finfo.st_nlink = ctx->r->finfo.nlink; + + return &ctx->finfo; +} + +static char * +php_apache_sapi_read_cookies(void) +{ + php_struct *ctx = SG(server_context); + const char *http_cookie; + + http_cookie = apr_table_get(ctx->r->headers_in, "cookie"); + + /* The SAPI interface should use 'const char *' */ + return (char *) http_cookie; +} + +static char * +php_apache_sapi_getenv(char *name, size_t name_len) +{ + php_struct *ctx = SG(server_context); + const char *env_var; + + if (ctx == NULL) { + return NULL; + } + + env_var = apr_table_get(ctx->r->subprocess_env, name); + + return (char *) env_var; +} + +static void +php_apache_sapi_register_variables(zval *track_vars_array) +{ + php_struct *ctx = SG(server_context); + const apr_array_header_t *arr = apr_table_elts(ctx->r->subprocess_env); + char *key, *val; + size_t new_val_len; + + APR_ARRAY_FOREACH_OPEN(arr, key, val) + if (!val) { + val = ""; + } + if (sapi_module.input_filter(PARSE_SERVER, key, &val, strlen(val), &new_val_len)) { + php_register_variable_safe(key, val, new_val_len, track_vars_array); + } + APR_ARRAY_FOREACH_CLOSE() + + if (sapi_module.input_filter(PARSE_SERVER, "PHP_SELF", &ctx->r->uri, strlen(ctx->r->uri), &new_val_len)) { + php_register_variable_safe("PHP_SELF", ctx->r->uri, new_val_len, track_vars_array); + } +} + +static void +php_apache_sapi_flush(void *server_context) +{ + php_struct *ctx; + request_rec *r; + + ctx = server_context; + + /* If we haven't registered a server_context yet, + * then don't bother flushing. */ + if (!server_context) { + return; + } + + r = ctx->r; + + sapi_send_headers(); + + r->status = SG(sapi_headers).http_response_code; + SG(headers_sent) = 1; + + if (ap_rflush(r) < 0 || r->connection->aborted) { + php_handle_aborted_connection(); + } +} + +static void php_apache_sapi_log_message(char *msg, int syslog_type_int) +{ + php_struct *ctx; + int aplog_type = APLOG_ERR; + + ctx = SG(server_context); + + switch (syslog_type_int) { +#if LOG_EMERG != LOG_CRIT + case LOG_EMERG: + aplog_type = APLOG_EMERG; + break; +#endif +#if LOG_ALERT != LOG_CRIT + case LOG_ALERT: + aplog_type = APLOG_ALERT; + break; +#endif + case LOG_CRIT: + aplog_type = APLOG_CRIT; + break; + case LOG_ERR: + aplog_type = APLOG_ERR; + break; + case LOG_WARNING: + aplog_type = APLOG_WARNING; + break; + case LOG_NOTICE: + aplog_type = APLOG_NOTICE; + break; +#if LOG_INFO != LOG_NOTICE + case LOG_INFO: + aplog_type = APLOG_INFO; + break; +#endif +#if LOG_NOTICE != LOG_DEBUG + case LOG_DEBUG: + aplog_type = APLOG_DEBUG; + break; +#endif + } + + if (ctx == NULL) { /* we haven't initialized our ctx yet, oh well */ + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, 0, NULL, "%s", msg); + } else { + ap_log_rerror(APLOG_MARK, aplog_type, 0, ctx->r, "%s", msg); + } +} + +static void php_apache_sapi_log_message_ex(char *msg, request_rec *r) +{ + if (r) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, msg, r->filename); + } else { + php_apache_sapi_log_message(msg, -1); + } +} + +static double php_apache_sapi_get_request_time(void) +{ + php_struct *ctx = SG(server_context); + return ((double) apr_time_as_msec(ctx->r->request_time)) / 1000.0; +} + +extern zend_module_entry php_apache_module; + +static int php_apache2_startup(sapi_module_struct *sapi_module) +{ + if (php_module_startup(sapi_module, &php_apache_module, 1)==FAILURE) { + return FAILURE; + } + return SUCCESS; +} + +static sapi_module_struct apache2_sapi_module = { + "apache2handler", + "Apache 2.0 Handler", + + php_apache2_startup, /* startup */ + php_module_shutdown_wrapper, /* shutdown */ + + NULL, /* activate */ + NULL, /* deactivate */ + + php_apache_sapi_ub_write, /* unbuffered write */ + php_apache_sapi_flush, /* flush */ + php_apache_sapi_get_stat, /* get uid */ + php_apache_sapi_getenv, /* getenv */ + + php_error, /* error handler */ + + php_apache_sapi_header_handler, /* header handler */ + php_apache_sapi_send_headers, /* send headers handler */ + NULL, /* send header handler */ + + php_apache_sapi_read_post, /* read POST data */ + php_apache_sapi_read_cookies, /* read Cookies */ + + php_apache_sapi_register_variables, + php_apache_sapi_log_message, /* Log message */ + php_apache_sapi_get_request_time, /* Request Time */ + NULL, /* Child Terminate */ + + STANDARD_SAPI_MODULE_PROPERTIES +}; + +static apr_status_t php_apache_server_shutdown(void *tmp) +{ + apache2_sapi_module.shutdown(&apache2_sapi_module); + sapi_shutdown(); +#ifdef ZTS + tsrm_shutdown(); +#endif + return APR_SUCCESS; +} + +static apr_status_t php_apache_child_shutdown(void *tmp) +{ + apache2_sapi_module.shutdown(&apache2_sapi_module); +#if defined(ZTS) && !defined(PHP_WIN32) + tsrm_shutdown(); +#endif + return APR_SUCCESS; +} + +static void php_apache_add_version(apr_pool_t *p) +{ + if (PG(expose_php)) { + ap_add_version_component(p, "PHP/" PHP_VERSION); + } +} + +static int php_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp) +{ +#ifndef ZTS + int threaded_mpm; + + ap_mpm_query(AP_MPMQ_IS_THREADED, &threaded_mpm); + if(threaded_mpm) { + ap_log_error(APLOG_MARK, APLOG_CRIT, 0, 0, "Apache is running a threaded MPM, but your PHP Module is not compiled to be threadsafe. You need to recompile PHP."); + return DONE; + } +#endif + /* When this is NULL, apache won't override the hard-coded default + * php.ini path setting. */ + apache2_php_ini_path_override = NULL; + return OK; +} + +static int +php_apache_server_startup(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) +{ + void *data = NULL; + const char *userdata_key = "apache2hook_post_config"; + + /* Apache will load, unload and then reload a DSO module. This + * prevents us from starting PHP until the second load. */ + apr_pool_userdata_get(&data, userdata_key, s->process->pool); + if (data == NULL) { + /* We must use set() here and *not* setn(), otherwise the + * static string pointed to by userdata_key will be mapped + * to a different location when the DSO is reloaded and the + * pointers won't match, causing get() to return NULL when + * we expected it to return non-NULL. */ + apr_pool_userdata_set((const void *)1, userdata_key, apr_pool_cleanup_null, s->process->pool); + return OK; + } + + /* Set up our overridden path. */ + if (apache2_php_ini_path_override) { + apache2_sapi_module.php_ini_path_override = apache2_php_ini_path_override; + } +#ifdef ZTS + tsrm_startup(1, 1, 0, NULL); + (void)ts_resource(0); + ZEND_TSRMLS_CACHE_UPDATE(); +#endif + + zend_signal_startup(); + + sapi_startup(&apache2_sapi_module); + apache2_sapi_module.startup(&apache2_sapi_module); + apr_pool_cleanup_register(pconf, NULL, php_apache_server_shutdown, apr_pool_cleanup_null); + php_apache_add_version(pconf); + + return OK; +} + +static apr_status_t php_server_context_cleanup(void *data_) +{ + void **data = data_; + *data = NULL; + return APR_SUCCESS; +} + +static int php_apache_request_ctor(request_rec *r, php_struct *ctx) +{ + char *content_length; + const char *auth; + + SG(sapi_headers).http_response_code = !r->status ? HTTP_OK : r->status; + SG(request_info).content_type = apr_table_get(r->headers_in, "Content-Type"); + SG(request_info).query_string = apr_pstrdup(r->pool, r->args); + SG(request_info).request_method = r->method; + SG(request_info).proto_num = r->proto_num; + SG(request_info).request_uri = apr_pstrdup(r->pool, r->uri); + SG(request_info).path_translated = apr_pstrdup(r->pool, r->filename); + r->no_local_copy = 1; + + content_length = (char *) apr_table_get(r->headers_in, "Content-Length"); + if (content_length) { + ZEND_ATOL(SG(request_info).content_length, content_length); + } else { + SG(request_info).content_length = 0; + } + + apr_table_unset(r->headers_out, "Content-Length"); + apr_table_unset(r->headers_out, "Last-Modified"); + apr_table_unset(r->headers_out, "Expires"); + apr_table_unset(r->headers_out, "ETag"); + + auth = apr_table_get(r->headers_in, "Authorization"); + php_handle_auth_data(auth); + + if (SG(request_info).auth_user == NULL && r->user) { + SG(request_info).auth_user = estrdup(r->user); + } + + ctx->r->user = apr_pstrdup(ctx->r->pool, SG(request_info).auth_user); + + return php_request_startup(); +} + +static void php_apache_request_dtor(request_rec *r) +{ + php_request_shutdown(NULL); +} + +static void php_apache_ini_dtor(request_rec *r, request_rec *p) +{ + if (strcmp(r->protocol, "INCLUDED")) { + zend_try { zend_ini_deactivate(); } zend_end_try(); + } else { +typedef struct { + HashTable config; +} php_conf_rec; + zend_string *str; + php_conf_rec *c = ap_get_module_config(r->per_dir_config, &php7_module); + + ZEND_HASH_FOREACH_STR_KEY(&c->config, str) { + zend_restore_ini_entry(str, ZEND_INI_STAGE_SHUTDOWN); + } ZEND_HASH_FOREACH_END(); + } + if (p) { + ((php_struct *)SG(server_context))->r = p; + } else { + apr_pool_cleanup_run(r->pool, (void *)&SG(server_context), php_server_context_cleanup); + } +} + +static int php_handler(request_rec *r) +{ + php_struct * volatile ctx; + void *conf; + apr_bucket_brigade * volatile brigade; + apr_bucket *bucket; + apr_status_t rv; + request_rec * volatile parent_req = NULL; +#ifdef ZTS + /* initial resource fetch */ + (void)ts_resource(0); + ZEND_TSRMLS_CACHE_UPDATE(); +#endif + +#define PHPAP_INI_OFF php_apache_ini_dtor(r, parent_req); + + conf = ap_get_module_config(r->per_dir_config, &php7_module); + + /* apply_config() needs r in some cases, so allocate server_context early */ + ctx = SG(server_context); + if (ctx == NULL || (ctx && ctx->request_processed && !strcmp(r->protocol, "INCLUDED"))) { +normal: + ctx = SG(server_context) = apr_pcalloc(r->pool, sizeof(*ctx)); + /* register a cleanup so we clear out the SG(server_context) + * after each request. Note: We pass in the pointer to the + * server_context in case this is handled by a different thread. + */ + apr_pool_cleanup_register(r->pool, (void *)&SG(server_context), php_server_context_cleanup, apr_pool_cleanup_null); + ctx->r = r; + ctx = NULL; /* May look weird to null it here, but it is to catch the right case in the first_try later on */ + } else { + parent_req = ctx->r; + ctx->r = r; + } + apply_config(conf); + + if (strcmp(r->handler, PHP_MAGIC_TYPE) && strcmp(r->handler, PHP_SOURCE_MAGIC_TYPE) && strcmp(r->handler, PHP_SCRIPT)) { + /* Check for xbithack in this case. */ + if (!AP2(xbithack) || strcmp(r->handler, "text/html") || !(r->finfo.protection & APR_UEXECUTE)) { + PHPAP_INI_OFF; + return DECLINED; + } + } + + /* Give a 404 if PATH_INFO is used but is explicitly disabled in + * the configuration; default behaviour is to accept. */ + if (r->used_path_info == AP_REQ_REJECT_PATH_INFO + && r->path_info && r->path_info[0]) { + PHPAP_INI_OFF; + return HTTP_NOT_FOUND; + } + + /* handle situations where user turns the engine off */ + if (!AP2(engine)) { + PHPAP_INI_OFF; + return DECLINED; + } + + if (r->finfo.filetype == 0) { + php_apache_sapi_log_message_ex("script '%s' not found or unable to stat", r); + PHPAP_INI_OFF; + return HTTP_NOT_FOUND; + } + if (r->finfo.filetype == APR_DIR) { + php_apache_sapi_log_message_ex("attempt to invoke directory '%s' as script", r); + PHPAP_INI_OFF; + return HTTP_FORBIDDEN; + } + + /* Setup the CGI variables if this is the main request */ + if (r->main == NULL || + /* .. or if the sub-request environment differs from the main-request. */ + r->subprocess_env != r->main->subprocess_env + ) { + /* setup standard CGI variables */ + ap_add_common_vars(r); + ap_add_cgi_vars(r); + } + +zend_first_try { + + if (ctx == NULL) { + brigade = apr_brigade_create(r->pool, r->connection->bucket_alloc); + ctx = SG(server_context); + ctx->brigade = brigade; + + if (php_apache_request_ctor(r, ctx)!=SUCCESS) { + zend_bailout(); + } + } else { + if (!parent_req) { + parent_req = ctx->r; + } + if (parent_req && parent_req->handler && + strcmp(parent_req->handler, PHP_MAGIC_TYPE) && + strcmp(parent_req->handler, PHP_SOURCE_MAGIC_TYPE) && + strcmp(parent_req->handler, PHP_SCRIPT)) { + if (php_apache_request_ctor(r, ctx)!=SUCCESS) { + zend_bailout(); + } + } + + /* + * check if coming due to ErrorDocument + * We make a special exception of 413 (Invalid POST request) as the invalidity of the request occurs + * during processing of the request by PHP during POST processing. Therefor we need to re-use the exiting + * PHP instance to handle the request rather then creating a new one. + */ + if (parent_req && parent_req->status != HTTP_OK && parent_req->status != 413 && strcmp(r->protocol, "INCLUDED")) { + parent_req = NULL; + goto normal; + } + ctx->r = r; + brigade = ctx->brigade; + } + + if (AP2(last_modified)) { + ap_update_mtime(r, r->finfo.mtime); + ap_set_last_modified(r); + } + + /* Determine if we need to parse the file or show the source */ + if (strncmp(r->handler, PHP_SOURCE_MAGIC_TYPE, sizeof(PHP_SOURCE_MAGIC_TYPE) - 1) == 0) { + zend_syntax_highlighter_ini syntax_highlighter_ini; + php_get_highlight_struct(&syntax_highlighter_ini); + highlight_file((char *)r->filename, &syntax_highlighter_ini); + } else { + zend_file_handle zfd; + + zfd.type = ZEND_HANDLE_FILENAME; + zfd.filename = (char *) r->filename; + zfd.free_filename = 0; + zfd.opened_path = NULL; + + if (!parent_req) { + php_execute_script(&zfd); + } else { + zend_execute_scripts(ZEND_INCLUDE, NULL, 1, &zfd); + } + + apr_table_set(r->notes, "mod_php_memory_usage", + apr_psprintf(ctx->r->pool, "%" APR_SIZE_T_FMT, zend_memory_peak_usage(1))); + } + +} zend_end_try(); + + if (!parent_req) { + php_apache_request_dtor(r); + ctx->request_processed = 1; + bucket = apr_bucket_eos_create(r->connection->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(brigade, bucket); + + rv = ap_pass_brigade(r->output_filters, brigade); + if (rv != APR_SUCCESS || r->connection->aborted) { +zend_first_try { + php_handle_aborted_connection(); +} zend_end_try(); + } + apr_brigade_cleanup(brigade); + apr_pool_cleanup_run(r->pool, (void *)&SG(server_context), php_server_context_cleanup); + } else { + ctx->r = parent_req; + } + + return OK; +} + +static void php_apache_child_init(apr_pool_t *pchild, server_rec *s) +{ + apr_pool_cleanup_register(pchild, NULL, php_apache_child_shutdown, apr_pool_cleanup_null); +} + +void php_ap2_register_hook(apr_pool_t *p) +{ + ap_hook_pre_config(php_pre_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_post_config(php_apache_server_startup, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_handler(php_handler, NULL, NULL, APR_HOOK_MIDDLE); +#ifdef ZEND_SIGNALS + ap_hook_child_init(zend_signal_init, NULL, NULL, APR_HOOK_MIDDLE); +#endif + ap_hook_child_init(php_apache_child_init, NULL, NULL, APR_HOOK_MIDDLE); +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */