diff --git a/NEWS b/NEWS index 368a0fb898c75..564e85be6c0c8 100644 --- a/NEWS +++ b/NEWS @@ -797,6 +797,9 @@ PHP NEWS . Fixed bug #75453 (Incorrect reflection for ibase_[p]connect). (villfa) . Fixed bug #76443 (php+php_interbase.dll crash on module_shutdown). (Kalle) +- SOAP: + . Fixed bug #80672 (Null Dereference in SoapClient). (CVE-2021-21702) (cmb, Stas) + - intl: . Fixed bug #75317 (UConverter::setDestinationEncoding changes source instead diff --git a/NEWS.orig b/NEWS.orig new file mode 100644 index 0000000000000..368a0fb898c75 --- /dev/null +++ b/NEWS.orig @@ -0,0 +1,1926 @@ +PHP NEWS +||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| +26 Sep 2019, PHP 7.3.10 + +- Core: + . Fixed bug #78220 (Can't access OneDrive folder). (cmb, ab) + . Fixed bug #77922 (Double release of doc comment on inherited shadow + property). (Nikita) + . Fixed bug #78441 (Parse error due to heredoc identifier followed by digit). + (cmb) + . Fixed bug #77812 (Interactive mode does not support PHP 7.3-style heredoc). + (cmb, Nikita) + +- FastCGI: + . Fixed bug #78469 (FastCGI on_accept hook is not called when using named + pipes on Windows). (Sergei Turchanov) + +- FPM: + . Fixed bug #78334 (fpm log prefix message includes wrong stdout/stderr + notation). (Tsuyoshi Sadakata) + +- Intl: + . Ensure IDNA2003 rules are used with idn_to_ascii() and idn_to_utf8() + when requested. (Sara) + +- MBString: + . Fixed bug #78559 (Heap buffer overflow in mb_eregi). (cmb) + +- MySQLnd: + . Fixed connect_attr issues and added the _server_host connection attribute. + (Qianqian Bu) + +- ODBC: + . Fixed bug #78473 (odbc_close() closes arbitrary resources). (cmb) + +- PDO_MySQL: + . Fixed bug #41997 (SP call yields additional empty result set). (cmb) + +- sodium: + . Fixed bug #78510 (Partially uninitialized buffer returned by + sodium_crypto_generichash_init()). (Frank Denis, cmb) + +29 Aug 2019, PHP 7.3.9 + +- Core: + . Fixed bug #78363 (Buffer overflow in zendparse). (Nikita) + . Fixed bug #78379 (Cast to object confuses GC, causes crash). (Dmitry) + . Fixed bug #78412 (Generator incorrectly reports non-releasable $this as GC + child). (Nikita) + +- Curl: + . Fixed bug #77946 (Bad cURL resources returned by curl_multi_info_read()). + (Abyr Valg) + +- Exif: + . Fixed bug #78333 (Exif crash (bus error) due to wrong alignment and + invalid cast). (Nikita) + +- FPM: + . Fixed bug #77185 (Use-after-free in FPM master event handling). + (Maksim Nikulin) + +- Iconv: + . Fixed bug #78342 (Bus error in configure test for iconv //IGNORE). (Rainer + Jung) + +- LiteSpeed: + . Updated to LiteSpeed SAPI V7.5 (Fixed clean shutdown). (George Wang) + +- MBString: + . Fixed bug #78380 (Oniguruma 6.9.3 fixes CVEs). (CVE-2019-13224) (Stas) + +- MySQLnd: + . Fixed bug #78179 (MariaDB server version incorrectly detected). (cmb) + . Fixed bug #78213 (Empty row pocket). (cmb) + +- Opcache: + . Fixed bug #77191 (Assertion failure in dce_live_ranges() when silencing is + used). (Nikita) + +- Standard: + . Fixed bug #69100 (Bus error from stream_copy_to_stream (file -> SSL stream) + with invalid length). (Nikita) + . Fixed bug #78282 (atime and mtime mismatch). (cmb) + . Fixed bug #78326 (improper memory deallocation on stream_get_contents() + with fixed length buffer). (Albert Casademont) + . Fixed bug #78346 (strip_tags no longer handling nested php tags). (cmb) + +01 Aug 2019, PHP 7.3.8 + +- Core: + . Added syslog.filter=raw option. (Erik Lundin) + . Fixed bug #78212 (Segfault in built-in webserver). (cmb) + +- Date: + . Fixed bug #69044 (discrepency between time and microtime). (krakjoe) + . Updated timelib to 2018.02. (Derick) + +- EXIF: + . Fixed bug #78256 (heap-buffer-overflow on exif_process_user_comment). + (CVE-2019-11042) (Stas) + . Fixed bug #78222 (heap-buffer-overflow on exif_scan_thumbnail). + (CVE-2019-11041) (Stas) + +- FTP: + . Fixed bug #78039 (FTP with SSL memory leak). (Nikita) + +- Libxml: + . Fixed bug #78279 (libxml_disable_entity_loader settings is shared between + requests (cgi-fcgi)). (Nikita) + +- LiteSpeed: + . Updated to LiteSpeed SAPI V7.4.3 (increased response header count limit from + 100 to 1000, added crash handler to cleanly shutdown PHP request, added + CloudLinux mod_lsapi mode). (George Wang) + . Fixed bug #76058 (After "POST data can't be buffered", using php://input + makes huge tmp files). (George Wang) + +- Openssl: + . Fixed bug #78231 (Segmentation fault upon stream_socket_accept of exported + socket-to-stream). (Nikita) + +- Opcache: + . Fixed bug #78189 (file cache strips last character of uname hash). (cmb) + . Fixed bug #78202 (Opcache stats for cache hits are capped at 32bit NUM). + (cmb) + . Fixed bug #78271 (Invalid result of if-else). (Nikita) + . Fixed bug #78291 (opcache_get_configuration doesn't list all directives). + (Andrew Collington) + . Fixed bug #78341 (Failure to detect smart branch in DFA pass). (Nikita) + +- PCRE: + . Fixed bug #78197 (PCRE2 version check in configure fails for "##.##-xxx" + version strings). (pgnet, Peter Kokot) + . Fixed bug #78338 (Array cross-border reading in PCRE). (cmb) + +- PDO_Sqlite: + . Fixed bug #78192 (SegFault when reuse statement after schema has changed). + (Vincent Quatrevieux) + +- Phar: + . Fixed bug #77919 (Potential UAF in Phar RSHUTDOWN). (cmb) + +- Phpdbg: + . Fixed bug #78297 (Include unexistent file memory leak). (Nikita) + +- SQLite: + . Upgraded to SQLite 3.28.0. (cmb) + +- Standard: + . Fixed bug #78241 (touch() does not handle dates after 2038 in PHP 64-bit). (cmb) + . Fixed bug #78269 (password_hash uses weak options for argon2). (Remi) + +04 Jul 2019, PHP 7.3.7 + +- Core: + . Fixed bug #76980 (Interface gets skipped if autoloader throws an exception). + (Nikita) + +- DOM: + . Fixed bug #78025 (segfault when accessing properties of DOMDocumentType). + (cmb) + +- MySQLi: + . Fixed bug #77956 (When mysqli.allow_local_infile = Off, use a meaningful + error message). (Sjon Hortensius) + . Fixed bug #38546 (bindParam incorrect processing of bool types). + (camporter) + +- MySQLnd: + . Fixed bug #77955 (Random segmentation fault in mysqlnd from php-fpm). + (Nikita) + +- Opcache: + . Fixed bug #78015 (Incorrect evaluation of expressions involving partials + arrays in SCCP). (Nikita) + . Fixed bug #78106 (Path resolution fails if opcache disabled during request). + (Nikita) + +- OpenSSL: + . Fixed bug #78079 (openssl_encrypt_ccm.phpt fails with OpenSSL 1.1.1c). + (Jakub Zelenka) + +- phpdbg: + . Fixed bug #78050 (SegFault phpdbg + opcache on include file twice). + (Nikita) + +- Sockets: + . Fixed bug #78038 (Socket_select fails when resource array contains + references). (Nikita) + +- Sodium: + . Fixed bug #78114 (segfault when calling sodium_* functions from eval). (cmb) + +- Standard: + . Fixed bug #77135 (Extract with EXTR_SKIP should skip $this). + (Craig Duncan, Dmitry) + . Fixed bug #77937 (preg_match failed). (cmb, Anatol) + +- Zip: + . Fixed bug #76345 (zip.h not found). (Michael Maroszek) + +30 May 2019, PHP 7.3.6 + +- cURL: + . Implemented FR #72189 (Add missing CURL_VERSION_* constants). (Javier + Spagnoletti) + +- EXIF: + . Fixed bug #77988 (heap-buffer-overflow on php_jpg_get16). + (CVE-2019-11040) (Stas) + +- FPM: + . Fixed bug #77934 (php-fpm kill -USR2 not working). (Jakub Zelenka) + . Fixed bug #77921 (static.php.net doesn't work anymore). (Peter Kokot) + +- GD: + . Fixed bug #77943 (imageantialias($image, false); does not work). (cmb) + . Fixed bug #77973 (Uninitialized read in gdImageCreateFromXbm). + (CVE-2019-11038) (cmb) + +- Iconv: + . Fixed bug #78069 (Out-of-bounds read in iconv.c:_php_iconv_mime_decode() + due to integer overflow). (CVE-2019-11039). (maris dot adam) + +- JSON: + . Fixed bug #77843 (Use after free with json serializer). (Nikita) + +- Opcache: + . Fixed possible crashes, because of inconsistent PCRE cache and opcache + SHM reset. (Alexey Kalinin, Dmitry) + +- PDO_MySQL: + . Fixed bug #77944 (Wrong meta pdo_type for bigint on LLP64). (cmb) + +- Reflection: + . Fixed bug #75186 (Inconsistent reflection of Closure:::__invoke()). (Nikita) + +- Session: + . Fixed bug #77911 (Wrong warning for session.sid_bits_per_character). (cmb) + +- SOAP: + . Fixed bug #77945 (Segmentation fault when constructing SoapClient with + WSDL_CACHE_BOTH). (Nikita) + +- SPL: + . Fixed bug #77024 (SplFileObject::__toString() may return array). (Craig + Duncan) + +- SQLite: + . Fixed bug #77967 (Bypassing open_basedir restrictions via file uris). (Stas) + +- Standard: + . Fixed bug #77931 (Warning for array_map mentions wrong type). (Nikita) + . Fixed bug #78003 (strip_tags output change since PHP 7.3). (cmb) + +02 May 2019, PHP 7.3.5 + +- Core: + . Fixed bug #77903 (ArrayIterator stops iterating after offsetSet call). + (Nikita) + +- CLI: + . Fixed bug #77794 (Incorrect Date header format in built-in server). + (kelunik) + +- EXIF + . Fixed bug #77950 (Heap-buffer-overflow in _estrndup via exif_process_IFD_TAG). + (CVE-2019-11036) (Stas) + +- Interbase: + . Fixed bug #72175 (Impossibility of creating multiple connections to + Interbase with php 7.x). (Nikita) + +- Intl: + . Fixed bug #77895 (IntlDateFormatter::create fails in strict mode if $locale + = null). (Nikita) + +- LDAP: + . Fixed bug #77869 (Core dump when using server controls) (mcmic) + +- Mail + . Fixed bug #77821 (Potential heap corruption in TSendMail()). (cmb) + +- mbstring: + . Implemented FR #72777 (Implement regex stack limits for mbregex functions). + (Yasuo Ohgaki, Stas) + +- MySQLi: + . Fixed bug #77773 (Unbuffered queries leak memory - MySQLi / mysqlnd). + (Nikita) + +- PCRE: + . Fixed bug #77827 (preg_match does not ignore \r in regex flags). (requinix, + cmb) + +- PDO: + . Fixed bug #77849 (Disable cloning of PDO handle/connection objects). + (camporter) + +- phpdbg: + . Fixed bug #76801 (too many open files). (alekitto) + . Fixed bug #77800 (phpdbg segfaults on listing some conditional breakpoints). + (krakjoe) + . Fixed bug #77805 (phpdbg build fails when readline is shared). (krakjoe) + +- Reflection: + . Fixed bug #77772 (ReflectionClass::getMethods(null) doesn't work). (Nikita) + . Fixed bug #77882 (Different behavior: always calls destructor). (Nikita) + +- Standard: + . Fixed bug #77793 (Segmentation fault in extract() when overwriting + reference with itself). (Nikita) + . Fixed bug #77844 (Crash due to null pointer in parse_ini_string with + INI_SCANNER_TYPED). (Nikita) + . Fixed bug #77853 (Inconsistent substr_compare behaviour with empty + haystack). (Nikita) + +04 Apr 2019, PHP 7.3.4 + +- Core: + . Fixed bug #77738 (Nullptr deref in zend_compile_expr). (Laruence) + . Fixed bug #77660 (Segmentation fault on break 2147483648). (Laruence) + . Fixed bug #77652 (Anonymous classes can lose their interface information). + (Nikita) + . Fixed bug #77345 (Stack Overflow caused by circular reference in garbage + collection). (Alexandru Patranescu, Nikita, Dmitry) + . Fixed bug #76956 (Wrong value for 'syslog.filter' documented in php.ini). + (cmb) + +- Apache2Handler: + . Fixed bug #77648 (BOM in sapi/apache2handler/php_functions.c). (cmb) + +- Bcmath: + . Fixed bug #77742 (bcpow() implementation related to gcc compiler + optimization). (Nikita) + +- CLI Server: + . Fixed bug #77722 (Incorrect IP set to $_SERVER['REMOTE_ADDR'] on the + localhost). (Nikita) + +- COM: + . Fixed bug #77578 (Crash when php unload). (cmb) + +- EXIF: + . Fixed bug #77753 (Heap-buffer-overflow in php_ifd_get32s). (CVE-2019-11034) + (Stas) + . Fixed bug #77831 (Heap-buffer-overflow in exif_iif_add_value). + (CVE-2019-11035) (Stas) + +- FPM: + . Fixed bug #77677 (FPM fails to build on AIX due to missing WCOREDUMP). + (Kevin Adler) + +- GD: + . Fixed bug #77700 (Writing truecolor images as GIF ignores interlace flag). + (cmb) + +- MySQLi: + . Fixed bug #77597 (mysqli_fetch_field hangs scripts). (Nikita) + +- Opcache: + . Fixed bug #77743 (Incorrect pi node insertion for jmpznz with identical + successors). (Nikita) + +- PCRE: + . Fixed bug #76127 (preg_split does not raise an error on invalid UTF-8). + (Nikita) + +- Phar: + . Fixed bug #77697 (Crash on Big_Endian platform). (Laruence) + +- phpdbg: + . Fixed bug #77767 (phpdbg break cmd aliases listed in help do not match + actual aliases). (Miriam Lauter) + +- sodium: + . Fixed bug #77646 (sign_detached() strings not terminated). (Frank) + +- SQLite3: + . Added sqlite3.defensive INI directive. (BohwaZ) + +- Standard: + . Fixed bug #77664 (Segmentation fault when using undefined constant in + custom wrapper). (Laruence) + . Fixed bug #77669 (Crash in extract() when overwriting extracted array). + (Nikita) + . Fixed bug #76717 (var_export() does not create a parsable value for + PHP_INT_MIN). (Nikita) + . Fixed bug #77765 (FTP stream wrapper should set the directory as + executable). (Vlad Temian) + +07 Mar 2019, PHP 7.3.3 + +- Core: + . Fixed bug #77589 (Core dump using parse_ini_string with numeric sections). + (Laruence) + . Fixed bug #77329 (Buffer Overflow via overly long Error Messages). + (Dmitry) + . Fixed bug #77494 (Disabling class causes segfault on member access). + (Dmitry) + . Fixed bug #77498 (Custom extension Segmentation fault when declare static + property). (Nikita) + . Fixed bug #77530 (PHP crashes when parsing `(2)::class`). (Ekin) + . Fixed bug #77546 (iptcembed broken function). (gdegoulet) + . Fixed bug #77630 (rename() across the device may allow unwanted access + during processing). (Stas) + +- COM: + . Fixed bug #77621 (Already defined constants are not properly reported). + (cmb) + . Fixed bug #77626 (Persistence confusion in php_com_import_typelib()). (cmb) + +- EXIF: + . Fixed bug #77509 (Uninitialized read in exif_process_IFD_in_TIFF). (Stas) + . Fixed bug #77540 (Invalid Read on exif_process_SOFn). (Stas) + . Fixed bug #77563 (Uninitialized read in exif_process_IFD_in_MAKERNOTE). (Stas) + . Fixed bug #77659 (Uninitialized read in exif_process_IFD_in_MAKERNOTE). (Stas) + +- Mbstring: + . Fixed bug #77514 (mb_ereg_replace() with trailing backslash adds null byte). + (Nikita) + +- MySQL + . Disabled LOCAL INFILE by default, can be enabled using php.ini directive + mysqli.allow_local_infile for mysqli, or PDO::MYSQL_ATTR_LOCAL_INFILE + attribute for pdo_mysql. (Darek Slusarczyk) + +- OpenSSL: + . Fixed bug #77390 (feof might hang on TLS streams in case of fragmented TLS + records). (Abyl Valg, Jakub Zelenka) + +- PDO_OCI: + . Support Oracle Database tracing attributes ACTION, MODULE, + CLIENT_INFO, and CLIENT_IDENTIFIER. (Cameron Porter) + +- PHAR: + . Fixed bug #77396 (Null Pointer Dereference in phar_create_or_parse_filename). + (bishop) + . Fixed bug #77586 (phar_tar_writeheaders_int() buffer overflow). (bishop) + +- phpdbg: + . Fixed bug #76596 (phpdbg support for display_errors=stderr). (kabel) + +- SPL: + . Fixed bug #51068 (DirectoryIterator glob:// don't support current path + relative queries). (Ahmed Abdou) + . Fixed bug #77431 (openFile() silently truncates after a null byte). (cmb) + +- Standard: + . Fixed bug #77552 (Unintialized php_stream_statbuf in stat functions). + (John Stevenson) + . Fixed bug #77612 (setcookie() sets incorrect SameSite header if all of its + options filled). (Nikita) + +07 Feb 2019, PHP 7.3.2 + +- Core: + . Fixed bug #77369 (memcpy with negative length via crafted DNS response). (Stas) + . Fixed bug #77387 (Recursion detection broken when printing GLOBALS). + (Laruence) + . Fixed bug #77376 ("undefined function" message no longer includes + namespace). (Laruence) + . Fixed bug #77357 (base64_encode / base64_decode doest not work on nested + VM). (Nikita) + . Fixed bug #77339 (__callStatic may get incorrect arguments). (Dmitry) + . Fixed bug #77317 (__DIR__, __FILE__, realpath() reveal physical path for + subst virtual drive). (Anatol) + . Fixed bug #77263 (Segfault when using 2 RecursiveFilterIterator). (Dmitry) + . Fixed bug #77447 (PHP 7.3 built with ASAN crashes in + zend_cpu_supports_avx2). (Nikita) + . Fixed bug #77484 (Zend engine crashes when calling realpath in invalid + working dir). (Anatol) + +- Curl: + . Fixed bug #76675 (Segfault with H2 server push). (Pedro Magalhães) + +- Fileinfo: + . Fixed bug #77346 (webm files incorrectly detected as + application/octet-stream). (Anatol) + +- FPM: + . Fixed bug #77430 (php-fpm crashes with Main process exited, code=dumped, + status=11/SEGV). (Jakub Zelenka) + +- GD: + . Fixed bug #73281 (imagescale(…, IMG_BILINEAR_FIXED) can cause black border). + (cmb) + . Fixed bug #73614 (gdImageFilledArc() doesn't properly draw pies). (cmb) + . Fixed bug #77272 (imagescale() may return image resource on failure). (cmb) + . Fixed bug #77391 (1bpp BMPs may fail to be loaded). (Romain Déoux, cmb) + . Fixed bug #77479 (imagewbmp() segfaults with very large images). (cmb) + +- ldap: + . Fixed bug #77440 (ldap_bind using ldaps or ldap_start_tls()=exception in + libcrypto-1_1-x64.dll). (Anatol) + +- Mbstring: + . Fixed bug #77428 (mb_ereg_replace() doesn't replace a substitution + variable). (Nikita) + . Fixed bug #77454 (mb_scrub() silently truncates after a null byte). + (64796c6e69 at gmail dot com) + +- MySQLnd: + . Fixed bug #77308 (Unbuffered queries memory leak). (Dmitry) + . Fixed bug #75684 (In mysqlnd_ext_plugin.h the plugin methods family has + no external visibility). (Anatol) + +- Opcache: + . Fixed bug #77266 (Assertion failed in dce_live_ranges). (Laruence) + . Fixed bug #77257 (value of variable assigned in a switch() construct gets + lost). (Nikita) + . Fixed bug #77434 (php-fpm workers are segfaulting in zend_gc_addre). + (Nikita) + . Fixed bug #77361 (configure fails on 64-bit AIX when opcache enabled). + (Kevin Adler) + . Fixed bug #77287 (Opcache literal compaction is incompatible with EXT + opcodes). (Nikita) + +- PCRE: + . Fixed bug #77338 (get_browser with empty string). (Nikita) + +- PDO: + . Fixed bug #77273 (array_walk_recursive corrupts value types leading to PDO + failure). (Nikita) + +- PDO MySQL: + . Fixed bug #77289 (PDO MySQL segfaults with persistent connection). + (Lauri Kenttä) + +- SOAP: + . Fixed bug #77410 (Segmentation Fault when executing method with an empty + parameter). (Nikita) + +- Sockets: + . Fixed bug #76839 (socket_recvfrom may return an invalid 'from' address + on MacOS). (Michael Meyer) + +- SPL: + . Fixed bug #77298 (segfault occurs when add property to unserialized empty + ArrayObject). (jhdxr) + +- Standard: + . Fixed bug #77395 (segfault about array_multisort). (Laruence) + . Fixed bug #77439 (parse_str segfaults when inserting item into existing + array). (Nikita) + +10 Jan 2019, PHP 7.3.1 + +- Core: + . Fixed bug #76654 (Build failure on Mac OS X on 32-bit Intel). (Ryandesign) + . Fixed bug #71041 (zend_signal_startup() needs ZEND_API). + (Valentin V. Bartenev) + . Fixed bug #76046 (PHP generates "FE_FREE" opcode on the wrong line). + (Nikita) + . Fixed bug #77291 (magic methods inherited from a trait may be ignored). + (cmb) + +- CURL: + . Fixed bug #77264 (curl_getinfo returning microseconds, not seconds). + (Pierrick) + +- COM: + . Fixed bug #77177 (Serializing or unserializing COM objects crashes). (cmb) + +- Exif: + . Fixed bug #77184 (Unsigned rational numbers are written out as signed + rationals). (Colin Basnett) + +- GD: + . Fixed bug #77195 (Incorrect error handling of imagecreatefromjpeg()). (cmb) + . Fixed bug #77198 (auto cropping has insufficient precision). (cmb) + . Fixed bug #77200 (imagecropauto(…, GD_CROP_SIDES) crops left but not right). + (cmb) + . Fixed bug #77269 (efree() on uninitialized Heap data in imagescale leads to + use-after-free). (cmb) + . Fixed bug #77270 (imagecolormatch Out Of Bounds Write on Heap). (cmb) + +- MBString: + . Fixed bug #77367 (Negative size parameter in mb_split). (Stas) + . Fixed bug #77370 (Buffer overflow on mb regex functions - fetch_token). + (Stas) + . Fixed bug #77371 (heap buffer overflow in mb regex functions + - compile_string_node). (Stas) + . Fixed bug #77381 (heap buffer overflow in multibyte match_at). (Stas) + . Fixed bug #77382 (heap buffer overflow due to incorrect length in + expand_case_fold_string). (Stas) + . Fixed bug #77385 (buffer overflow in fetch_token). (Stas) + . Fixed bug #77394 (Buffer overflow in multibyte case folding - unicode). + (Stas) + . Fixed bug #77418 (Heap overflow in utf32be_mbc_to_code). (Stas) + +- OCI8: + . Fixed bug #76804 (oci_pconnect with OCI_CRED_EXT not working). (KoenigsKind) + . Added oci_set_call_timeout() for call timeouts. + . Added oci_set_db_operation() for the DBOP end-to-end-tracing attribute. + +- Opcache: + . Fixed bug #77215 (CFG assertion failure on multiple finalizing switch + frees in one block). (Nikita) + . Fixed bug #77275 (OPcache optimization problem for ArrayAccess->offsetGet). + (Nikita) + +- PCRE: + . Fixed bug #77193 (Infinite loop in preg_replace_callback). (Anatol) + +- PDO: + . Handle invalid index passed to PDOStatement::fetchColumn() as error. (Sergei + Morozov) + +- Phar: + . Fixed bug #77247 (heap buffer overflow in phar_detect_phar_fname_ext). (Stas) + +- Soap: + . Fixed bug #77088 (Segfault when using SoapClient with null options). + (Laruence) + +- Sockets: + . Fixed bug #77136 (Unsupported IPV6_RECVPKTINFO constants on macOS). + (Mizunashi Mana) + +- Sodium: + . Fixed bug #77297 (SodiumException segfaults on PHP 7.3). (Nikita, Scott) + +- SPL: + . Fixed bug #77359 (spl_autoload causes segfault). (Lauri Kenttä) + . Fixed bug #77360 (class_uses causes segfault). (Lauri Kenttä) + +- SQLite3: + . Fixed bug #77051 (Issue with re-binding on SQLite3). (BohwaZ) + +- Xmlrpc: + . Fixed bug #77242 (heap out of bounds read in xmlrpc_decode()). (cmb) + . Fixed bug #77380 (Global out of bounds read in xmlrpc base64 code). (Stas) + +06 Dec 2018, PHP 7.3.0 + +- Core: + . Improved PHP GC. (Dmitry, Nikita) + . Redesigned the old ext_skel program written in PHP, run: + 'php ext_skel.php' for all options. This means there are no dependencies, + thus making it work on Windows out of the box. (Kalle) + . Removed support for BeOS. (Kalle) + . Add PHP_VERSION to phpinfo() . (github/MattJeevas) + . Add net_get_interfaces(). (Sara, Joe, Anatol) + . Added gc_status(). (Benjamin Eberlei) + . Implemented flexible heredoc and nowdoc syntax, per + RFC https://wiki.php.net/rfc/flexible_heredoc_nowdoc_syntaxes. + (Thomas Punt) + . Added support for references in list() and array destructuring, per + RFC https://wiki.php.net/rfc/list_reference_assignment. + (David Walker) + . Improved effectiveness of ZEND_SECURE_ZERO for NetBSD and systems + without native similar feature. (devnexen) + . Added syslog.facility and syslog.ident INI entries for customizing syslog + logging. (Philip Prindeville) + . Fixed bug #75683 (Memory leak in zend_register_functions() in ZTS mode). + (Dmitry) + . Fixed bug #75031 (support append mode in temp/memory streams). (adsr) + . Fixed bug #74860 (Uncaught exceptions not being formatted properly when + error_log set to "syslog"). (Philip Prindeville) + . Fixed bug #75220 (Segfault when calling is_callable on parent). + (andrewnester) + . Fixed bug #69954 (broken links and unused config items in distributed ini + files). (petk) + . Fixed bug #74922 (Composed class has fatal error with duplicate, equal const + properties). (pmmaga) + . Fixed bug #63911 (identical trait methods raise errors during composition). + (pmmaga) + . Fixed bug #75677 (Clang ignores fastcall calling convention on variadic + function). (Li-Wen Hsu) + . Fixed bug #54043 (Remove inconsitency of internal exceptions and user + defined exceptions). (Nikita) + . Fixed bug #53033 (Mathematical operations convert objects to integers). + (Nikita) + . Fixed bug #73108 (Internal class cast handler uses integer instead of + float). (Nikita) + . Fixed bug #75765 (Fatal error instead of Error exception when base class is + not found). (Timur Ibragimov) + . Fixed bug #76198 (Wording: "iterable" is not a scalar type). (Levi Morrison) + . Fixed bug #76137 (config.guess/config.sub do not recognize RISC-V). (cmb) + . Fixed bug #76427 (Segfault in zend_objects_store_put). (Laruence) + . Fixed bug #76422 (ftruncate fails on files > 2GB). (Anatol) + . Fixed bug #76509 (Inherited static properties can be desynchronized from + their parent by ref). (Nikita) + . Fixed bug #76439 (Changed behaviour in unclosed HereDoc). (Nikita, tpunt) + . Fixed bug #63217 (Constant numeric strings become integers when used as + ArrayAccess offset). (Rudi Theunissen, Dmitry) + . Fixed bug #33502 (Some nullary functions don't check the number of + arguments). (cmb) + . Fixed bug #76392 (Error relocating sapi/cli/php: unsupported relocation + type 37). (Peter Kokot) + . The declaration and use of case-insensitive constants has been deprecated. + (Nikita) + . Added syslog.filter INI entry for syslog filtering. (Philip Prindeville) + . Fixed bug #76667 (Segfault with divide-assign op and __get + __set). + (Laruence) + . Fixed bug #76030 (RE2C_FLAGS rarely honoured) (Cristian Rodríguez) + . Fixed broken zend_read_static_property (Laruence) + . Fixed bug #76773 (Traits used on the parent are ignored for child classes). + (daverandom) + . Fixed bug #76767 (‘asm’ operand has impossible constraints in zend_operators.h). + (ondrej) + . Fixed bug #76752 (Crash in ZEND_COALESCE_SPEC_TMP_HANDLER - assertion in + _get_zval_ptr_tmp failed). (Laruence) + . Fixed bug #76820 (Z_COPYABLE invalid definition). (mvdwerve, cmb) + . Fixed bug #76510 (file_exists() stopped working for phar://). (cmb) + . Fixed bug #76869 (Incorrect bypassing protected method accessibilty check). + (Dmitry) + . Fixed bug #72635 (Undefined class used by class constant in constexpr + generates fatal error). (Nikita) + . Fixed bug #76947 (file_put_contents() blocks the directory of the file + (__DIR__)). (Anatol) + . Fixed bug #76979 (define() error message does not mention resources as + valid values). (Michael Moravec) + . Fixed bug #76825 (Undefined symbols ___cpuid_count). (Laruence, cmb) + . Fixed bug #77110 (undefined symbol zend_string_equal_val in C++ build). + (Remi) + +- BCMath: + . Implemented FR #67855 (No way to get current scale in use). (Chris Wright, + cmb) + . Fixed bug #66364 (BCMath bcmul ignores scale parameter). (cmb) + . Fixed bug #75164 (split_bc_num() is pointless). (cmb) + . Fixed bug #75169 (BCMath errors/warnings bypass PHP's error handling). (cmb) + +- CLI: + . Fixed bug #44217 (Output after stdout/stderr closed cause immediate exit + with status 0). (Robert Lu) + . Fixed bug #77111 (php-win.exe corrupts unicode symbols from cli + parameters). (Anatol) + +- cURL: + . Expose curl constants from curl 7.50 to 7.61. (Pierrick) + . Fixed bug #74125 (Fixed finding CURL on systems with multiarch support). + (cebe) + +- Date: + . Implemented FR #74668: Add DateTime::createFromImmutable() method. + (majkl578, Rican7) + . Fixed bug #75222 (DateInterval microseconds property always 0). (jhdxr) + . Fixed bug #68406 (calling var_dump on a DateTimeZone object modifies it). + (jhdxr) + . Fixed bug #76131 (mismatch arginfo for date_create). (carusogabriel) + . Updated timelib to 2018.01RC1 to address several bugs: + . Fixed bug #75577 (DateTime::createFromFormat does not accept 'v' format + specifier). (Derick) + . Fixed bug #75642 (Wrap around behaviour for microseconds is not working). + (Derick) + +- DBA: + . Fixed bug #75264 (compiler warnings emitted). (petk) + +- DOM: + . Fixed bug #76285 (DOMDocument::formatOutput attribute sometimes ignored). + (Andrew Nester, Laruence, Anatol) + +- Fileinfo: + . Fixed bug #77095 (slowness regression in 7.2/7.3 (compared to 7.1)). + (Anatol) + +- Filter: + . Added the 'add_slashes' sanitization mode (FILTER_SANITIZE_ADD_SLASHES). + (Kalle) + +- FPM: + . Added fpm_get_status function. (Till Backhaus) + . Fixed bug #62596 (getallheaders() missing with PHP-FPM). (Remi) + . Fixed bug #69031 (Long messages into stdout/stderr are truncated + incorrectly) - added new log related FPM configuration options: + log_limit, log_buffering and decorate_workers_output. (Jakub Zelenka) + +- ftp: + . Fixed bug #77151 (ftp_close(): SSL_read on shutdown). (Remi) + +- GD: + . Added support for WebP in imagecreatefromstring(). (Andreas Treichel, cmb) + +- GMP: + . Export internal structures and accessor helpers for GMP object. (Sara) + . Added gmp_binomial(n, k). (Nikita) + . Added gmp_lcm(a, b). (Nikita) + . Added gmp_perfect_power(a). (Nikita) + . Added gmp_kronecker(a, b). (Nikita) + +- iconv: + . Fixed bug #53891 (iconv_mime_encode() fails to Q-encode UTF-8 string). (cmb) + . Fixed bug #77147 (Fixing 60494 ignored ICONV_MIME_DECODE_CONTINUE_ON_ERROR). + (cmb) + +- IMAP: + . Fixed bug #77020 (null pointer dereference in imap_mail). (cmb) + . Fixed bug #77153 (imap_open allows to run arbitrary shell commands via + mailbox parameter). (Stas) + +- Interbase: + . Fixed bug #75453 (Incorrect reflection for ibase_[p]connect). (villfa) + . Fixed bug #76443 (php+php_interbase.dll crash on module_shutdown). (Kalle) + + +- intl: + . Fixed bug #75317 (UConverter::setDestinationEncoding changes source instead + of destination). (andrewnester) + . Fixed bug #76829 (Incorrect validation of domain on idn_to_utf8() + function). (Anatol) + +- JSON: + . Added JSON_THROW_ON_ERROR flag. (Andrea) + +- LDAP: + . Added ldap_exop_refresh helper for EXOP REFRESH operation with dds overlay. + (Come) + . Added full support for sending and parsing ldap controls. (Come) + . Fixed bug #49876 (Fix LDAP path lookup on 64-bit distros). (dzuelke) + +- libxml2: + . Fixed bug #75871 (use pkg-config where available). (pmmaga) + +- litespeed: + . Fixed bug #75248 (Binary directory doesn't get created when building + only litespeed SAPI). (petk) + . Fixed bug #75251 (Missing program prefix and suffix). (petk) + +- MBstring: + . Updated to Oniguruma 6.9.0. (cmb) + . Fixed bug #65544 (mb title case conversion-first word in quotation isn't + capitalized). (Nikita) + . Fixed bug #71298 (MB_CASE_TITLE misbehaves with curled apostrophe/quote). + (Nikita) + . Fixed bug #73528 (Crash in zif_mb_send_mail). (Nikita) + . Fixed bug #74929 (mbstring functions version 7.1.1 are slow compared to 5.3 + on Windows). (Nikita) + . Fixed bug #76319 (mb_strtolower with invalid UTF-8 causes segmentation + fault). (Nikita) + . Fixed bug #76574 (use of undeclared identifiers INT_MAX and LONG_MAX). (cmb) + . Fixed bug #76594 (Bus Error due to unaligned access in zend_ini.c + OnUpdateLong). (cmb, Nikita) + . Fixed bug #76706 (mbstring.http_output_conv_mimetypes is ignored). (cmb) + . Fixed bug #76958 (Broken UTF7-IMAP conversion). (Nikita) + . Fixed bug #77025 (mb_strpos throws Unknown encoding or conversion error). + (Nikita) + . Fixed bug #77165 (mb_check_encoding crashes when argument given an empty + array). (Nikita) + +- Mysqlnd: + . Fixed bug #76386 (Prepared Statement formatter truncates fractional seconds + from date/time column). (Victor Csiky) + +- ODBC: + . Removed support for ODBCRouter. (Kalle) + . Removed support for Birdstep. (Kalle) + . Fixed bug #77079 (odbc_fetch_object has incorrect type signature). + (Jon Allen) + +- Opcache: + . Fixed bug #76466 (Loop variable confusion). (Dmitry, Laruence, Nikita) + . Fixed bug #76463 (var has array key type but not value type). (Laruence) + . Fixed bug #76446 (zend_variables.c:73: zend_string_destroy: Assertion + `!(zval_gc_flags((str)->gc)). (Nikita, Laruence) + . Fixed bug #76711 (OPcache enabled triggers false-positive "Illegal string + offset"). (Dmitry) + . Fixed bug #77058 (Type inference in opcache causes side effects). (Nikita) + . Fixed bug #77092 (array_diff_key() - segmentation fault). (Nikita) + +- OpenSSL: + . Added openssl_pkey_derive function. (Jim Zubov) + . Add min_proto_version and max_proto_version ssl stream options as well as + related constants for possible TLS protocol values. (Jakub Zelenka) + +- PCRE: + . Implemented https://wiki.php.net/rfc/pcre2-migration. (Anatol, Dmitry) + . Upgrade PCRE2 to 10.32. (Anatol) + . Fixed bug #75355 (preg_quote() does not quote # control character). + (Michael Moravec) + . Fixed bug #76512 (\w no longer includes unicode characters). (cmb) + . Fixed bug #76514 (Regression in preg_match makes it fail with + PREG_JIT_STACKLIMIT_ERROR). (Anatol) + . Fixed bug #76909 (preg_match difference between 7.3 and < 7.3). (Anatol) + +- PDO_DBlib: + . Implemented FR #69592 (allow 0-column rowsets to be skipped automatically). + (fandrieu) + . Expose TDS version as \PDO::DBLIB_ATTR_TDS_VERSION attribute on \PDO + instance. (fandrieu) + . Treat DATETIME2 columns like DATETIME. (fandrieu) + . Fixed bug #74243 (allow locales.conf to drive datetime format). (fandrieu) + +- PDO_Firebird: + . Fixed bug #74462 (PDO_Firebird returns only NULLs for results with boolean + for FIREBIRD >= 3.0). (Dorin Marcoci) + +- PDO_OCI: + . Fixed bug #74631 (PDO_PCO with PHP-FPM: OCI environment initialized + before PHP-FPM sets it up). (Ingmar Runge) + +- PDO SQLite + . Add support for additional open flags + +- pgsql: + . Added new error constants for pg_result_error(): PGSQL_DIAG_SCHEMA_NAME, + PGSQL_DIAG_TABLE_NAME, PGSQL_DIAG_COLUMN_NAME, PGSQL_DIAG_DATATYPE_NAME, + PGSQL_DIAG_CONSTRAINT_NAME and PGSQL_DIAG_SEVERITY_NONLOCALIZED. (Kalle) + . Fixed bug #77047 (pg_convert has a broken regex for the 'TIME WITHOUT + TIMEZONE' data type). (Andy Gajetzki) + +- phar: + . Fixed bug #74991 (include_path has a 4096 char limit in some cases). + (bwbroersma) + . Fixed bug #65414 (deal with leading slash when adding files correctly). + (bishopb) + +- readline: + . Added completion_append_character and completion_suppress_append options + to readline_info() if linked against libreadline. (krageon) + +- Session: + . Fixed bug #74941 (session fails to start after having headers sent). + (morozov) + +- SimpleXML: + . Fixed bug #54973 (SimpleXML casts integers wrong). (Nikita) + . Fixed bug #76712 (Assignment of empty string creates extraneous text node). + (cmb) + +- Sockets: + . Fixed bug #67619 (Validate length on socket_write). (thiagooak) + +- SOAP: + . Fixed bug #75464 (Wrong reflection on SoapClient::__setSoapHeaders). + (villfa) + . Fixed bug #70469 (SoapClient generates E_ERROR even if exceptions=1 is + used). (Anton Artamonov) + . Fixed bug #50675 (SoapClient can't handle object references correctly). + (Cameron Porter) + . Fixed bug #76348 (WSDL_CACHE_MEMORY causes Segmentation fault). (cmb) + . Fixed bug #77141 (Signedness issue in SOAP when precision=-1). (cmb) + +- SPL: + . Fixed bug #74977 (Appending AppendIterator leads to segfault). + (Andrew Nester) + . Fixed bug #75173 (incorrect behavior of AppendIterator::append in foreach + loop). (jhdxr) + . Fixed bug #74372 (autoloading file with syntax error uses next autoloader, + may hide parse error). (Nikita) + . Fixed bug #75878 (RecursiveTreeIterator::setPostfix has wrong signature). + (cmb) + . Fixed bug #74519 (strange behavior of AppendIterator). (jhdxr) + . Fixed bug #76131 (mismatch arginfo for splarray constructor). + (carusogabriel) + +- SQLite3: + . Updated bundled libsqlite to 3.24.0. (cmb) + +- Standard: + . Added is_countable() function. (Gabriel Caruso) + . Added support for the SameSite cookie directive, including an alternative + signature for setcookie(), setrawcookie() and session_set_cookie_params(). + (Frederik Bosch, pmmaga) + . Remove superfluous warnings from inet_ntop()/inet_pton(). (daverandom) + . Fixed bug #75916 (DNS_CAA record results contain garbage). (Mike, + Philip Sharp) + . Fixed unserialize(), to disable creation of unsupported data structures + through manually crafted strings. (Dmitry) + . Fixed bug #75409 (accept EFAULT in addition to ENOSYS as indicator + that getrandom() is missing). (sarciszewski) + . Fixed bug #74719 (fopen() should accept NULL as context). (Alexander Holman) + . Fixed bug #69948 (path/domain are not sanitized in setcookie). (cmb) + . Fixed bug #75996 (incorrect url in header for mt_rand). (tatarbj) + . Added hrtime() function, to get high resolution time. (welting) + . Fixed bug #48016 (stdClass::__setState is not defined although var_export() + uses it). (Andrea) + . Fixed bug #76136 (stream_socket_get_name should enclose IPv6 in brackets). + (seliver) + . Fixed bug #76688 (Disallow excessive parameters after options array). + (pmmaga) + . Fixed bug #76713 (Segmentation fault caused by property corruption). + (Laruence) + . Fixed bug #76755 (setcookie does not accept "double" type for expire time). + (Laruence) + . Fixed bug #76674 (improve array_* failure messages exposing what was passed + instead of an array). (carusogabriel) + . Fixed bug #76803 (ftruncate changes file pointer). (Anatol) + . Fixed bug #76818 (Memory corruption and segfault). (Remi) + . Fixed bug #77081 (ftruncate() changes seek pointer in c mode). (cmb, Anatol) + +- Testing: + . Implemented FR #62055 (Make run-tests.php support --CGI-- sections). (cmb) + +- Tidy: + . Support using tidyp instead of tidy. (devnexen) + . Fixed bug #74707 (Tidy has incorrect ReflectionFunction param counts for + functions taking tidy). (Gabriel Caruso) + . Fixed arginfo for tidy::__construct(). (Tyson Andre) + +- Tokenizer: + . Fixed bug #76437 (token_get_all with TOKEN_PARSE flag fails to recognise + close tag). (Laruence) + . Fixed bug #75218 (Change remaining uncatchable fatal errors for parsing + into ParseError). (Nikita) + . Fixed bug #76538 (token_get_all with TOKEN_PARSE flag fails to recognise + close tag with newline). (Nikita) + . Fixed bug #76991 (Incorrect tokenization of multiple invalid flexible + heredoc strings). (Nikita) + +- XML: + . Fixed bug #71592 (External entity processing never fails). (cmb) + +- Zlib: + . Added zlib/level context option for compress.zlib wrapper. (Sara) + +08 Nov 2018, PHP 7.2.12 + +- Core: + . Fixed bug #76846 (Segfault in shutdown function after memory limit error). + (Nikita) + . Fixed bug #76946 (Cyclic reference in generator not detected). (Nikita) + . Fixed bug #77035 (The phpize and ./configure create redundant .deps file). + (Peter Kokot) + . Fixed bug #77041 (buildconf should output error messages to stderr) + (Mizunashi Mana) + +- Date: + . Upgraded timelib to 2017.08. (Derick) + . Fixed bug #75851 (Year component overflow with date formats "c", "o", "r" + and "y"). (Adam Saponara) + . Fixed bug #77007 (fractions in `diff()` are not correctly normalized). + (Derick) + +- FCGI: + . Fixed #76948 (Failed shutdown/reboot or end session in Windows). (Anatol) + . Fixed bug #76954 (apache_response_headers removes last character from header + name). (stodorovic) + +- FTP: + . Fixed bug #76972 (Data truncation due to forceful ssl socket shutdown). + (Manuel Mausz) + +- intl: + . Fixed bug #76942 (U_ARGUMENT_TYPE_MISMATCH). (anthrax at unixuser dot org) + +- Reflection: + . Fixed bug #76936 (Objects cannot access their private attributes while + handling reflection errors). (Nikita) + . Fixed bug #66430 (ReflectionFunction::invoke does not invoke closure with + object scope). (Nikita) + +- Sodium: + . Some base64 outputs were truncated; this is not the case any more. + (jedisct1) + . block sizes >= 256 bytes are now supposed by sodium_pad() even + when an old version of libsodium has been installed. (jedisct1) + . Fixed bug #77008 (sodium_pad() could read (but not return nor write) + uninitialized memory when trying to pad an empty input). (jedisct1) + +- Standard: + . Fixed bug #76965 (INI_SCANNER_RAW doesn't strip trailing whitespace). + (Pierrick) + +- Tidy: + . Fixed bug #77027 (tidy::getOptDoc() not available on Windows). (cmb) + +- XML: + . Fixed bug #30875 (xml_parse_into_struct() does not resolve entities). (cmb) + . Add support for getting SKIP_TAGSTART and SKIP_WHITE options. (cmb) + +- XMLRPC: + . Fixed bug #75282 (xmlrpc_encode_request() crashes). (cmb) + +11 Oct 2018, PHP 7.2.11 + +- Core: + . Fixed bug #76800 (foreach inconsistent if array modified during loop). + (Dmitry) + . Fixed bug #76901 (method_exists on SPL iterator passthrough method corrupts + memory). (Nikita) + +- CURL: + . Fixed bug #76480 (Use curl_multi_wait() so that timeouts are respected). + (Pierrick) + +- iconv: + . Fixed bug #66828 (iconv_mime_encode Q-encoding longer than it should be). + (cmb) + +- Opcache: + . Fixed bug #76832 (ZendOPcache.MemoryBase periodically deleted by the OS). + (Anatol) + . Fixed bug #76796 (Compile-time evaluation of disabled function in opcache + causes segfault). (Nikita) + +- POSIX: + . Fixed bug #75696 (posix_getgrnam fails to print details of group). (cmb) + +- Reflection: + . Fixed bug #74454 (Wrong exception being thrown when using ReflectionMethod). + (cmb) + +- Standard: + . Fixed bug #73457 (Wrong error message when fopen FTP wrapped fails to open + data connection). (Ville Hukkamäki) + . Fixed bug #74764 (Bindto IPv6 works with file_get_contents but fails with + stream_socket_client). (Ville Hukkamäki) + . Fixed bug #75533 (array_reduce is slow when $carry is large array). + (Manabu Matsui) + +- XMLRPC: + . Fixed bug #76886 (Can't build xmlrpc with expat). (Thomas Petazzoni, cmb) + +- Zlib: + . Fixed bug #75273 (php_zlib_inflate_filter() may not update bytes_consumed). + (Martin Burke, cmb) + +13 Sep 2018, PHP 7.2.10 + +- Core: + . Fixed bug #76754 (parent private constant in extends class memory leak). + (Laruence) + . Fixed bug #72443 (Generate enabled extension). (petk) + . Fixed bug #75797 (Memory leak when using class_alias() in non-debug mode). + (Massimiliano Braglia) + +- Apache2: + . Fixed bug #76582 (Apache bucket brigade sometimes becomes invalid). (stas) + +- Bz2: + . Fixed arginfo for bzcompress. (Tyson Andre) + +- gettext: + . Fixed bug #76517 (incorrect restoring of LDFLAGS). (sji) + +- iconv: + . Fixed bug #68180 (iconv_mime_decode can return extra characters in a + header). (cmb) + . Fixed bug #63839 (iconv_mime_decode_headers function is skipping headers). + (cmb) + . Fixed bug #60494 (iconv_mime_decode does ignore special characters). (cmb) + . Fixed bug #55146 (iconv_mime_decode_headers() skips some headers). (cmb) + +- intl: + . Fixed bug #74484 (MessageFormatter::formatMessage memory corruption with + 11+ named placeholders). (Anatol) + +- libxml: + . Fixed bug #76777 ("public id" parameter of libxml_set_external_entity_loader + callback undefined). (Ville Hukkamäki) + +- mbstring: + . Fixed bug #76704 (mb_detect_order return value varies based on argument + type). (cmb) + +- Opcache: + . Fixed bug #76747 (Opcache treats path containing "test.pharma.tld" as a phar + file). (Laruence) + +- OpenSSL: + . Fixed bug #76705 (unusable ssl => peer_fingerprint in + stream_context_create()). (Jakub Zelenka) + +- phpdbg: + . Fixed bug #76595 (phpdbg man page contains outdated information). + (Kevin Abel) + +- SPL: + . Fixed bug #68825 (Exception in DirectoryIterator::getLinkTarget()). (cmb) + . Fixed bug #68175 (RegexIterator pregFlags are NULL instead of 0). (Tim + Siebels) + +- Standard: + . Fixed bug #76778 (array_reduce leaks memory if callback throws exception). + (cmb) + +- zlib: + . Fixed bug #65988 (Zlib version check fails when an include/zlib/ style dir + is passed to the --with-zlib configure option). (Jay Bonci) + . Fixed bug #76709 (Minimal required zlib library is 1.2.0.4). (petk) + +16 Aug 2018, PHP 7.2.9 + +- Calendar: + . Fixed bug #52974 (jewish.c: compile error under Windows with GBK charset). + (cmb) + +- Filter: + . Fixed bug #76366 (References in sub-array for filtering breaks the filter). + (ZiHang Gao) + +- PDO_Firebird: + . Fixed bug #76488 (Memory leak when fetching a BLOB field). (Simonov Denis) + +- PDO_PgSQL: + . Fixed bug #75402 (Possible Memory Leak using PDO::CURSOR_SCROLL option). + (Anatol) + +- SQLite3: + . Fixed #76665 (SQLite3Stmt::bindValue() with SQLITE3_FLOAT doesn't juggle). + (cmb) + +- Standard: + . Fixed bug #73817 (Incorrect entries in get_html_translation_table). (cmb) + . Fixed bug #68553 (array_column: null values in $index_key become incrementing + keys in result). (Laruence) + . Fixed bug #76643 (Segmentation fault when using `output_add_rewrite_var`). + (cmb) + +- Zip: + . Fixed bug #76524 (ZipArchive memory leak (OVERWRITE flag and empty archive)). + (Timur Ibragimov) + +19 Jul 2018, PHP 7.2.8 + +- Core: + . Fixed bug #76534 (PHP hangs on 'illegal string offset on string references + with an error handler). (Laruence) + . Fixed bug #76520 (Object creation leaks memory when executed over HTTP). + (Nikita) + . Fixed bug #76502 (Chain of mixed exceptions and errors does not serialize + properly). (Nikita) + +- Date: + . Fixed bug #76462 (Undefined property: DateInterval::$f). (Anatol) + +- EXIF: + . Fixed bug #76409 (heap use after free in _php_stream_free). (cmb) + . Fixed bug #76423 (Int Overflow lead to Heap OverFlow in + exif_thumbnail_extract of exif.c). (Stas) + . Fixed bug #76557 (heap-buffer-overflow (READ of size 48) while reading exif + data). (Stas) + +- FPM: + . Fixed bug #73342 (Vulnerability in php-fpm by changing stdin to + non-blocking). (Nikita) + +- GMP: + . Fixed bug #74670 (Integer Underflow when unserializing GMP and possible + other classes). (Nikita) + +- intl: + . Fixed bug #76556 (get_debug_info handler for BreakIterator shows wrong + type). (cmb) + +- mbstring: + . Fixed bug #76532 (Integer overflow and excessive memory usage + in mb_strimwidth). (MarcusSchwarz) + +- Opcache: + . Fixed bug #76477 (Opcache causes empty return value). + (Nikita, Laruence) + +- PGSQL: + . Fixed bug #76548 (pg_fetch_result did not fetch the next row). (Anatol) + +- phpdbg: + . Fix arginfo wrt. optional/required parameters. (cmb) + +- Reflection: + . Fixed bug #76536 (PHP crashes with core dump when throwing exception in + error handler). (Laruence) + . Fixed bug #75231 (ReflectionProperty#getValue() incorrectly works with + inherited classes). (Nikita) + +- Standard: + . Fixed bug #76505 (array_merge_recursive() is duplicating sub-array keys). + (Laruence) + . Fixed bug #71848 (getimagesize with $imageinfo returns false). (cmb) + +- Win32: + . Fixed bug #76459 (windows linkinfo lacks openbasedir check). (Anatol) + +- ZIP: + . Fixed bug #76461 (OPSYS_Z_CPM defined instead of OPSYS_CPM). + (Dennis Birkholz, Remi) + +07 Jun 2018, PHP 7.2.7 + +- Core: + . Fixed bug #76337 (segfault when opcache enabled + extension use + zend_register_class_alias). (xKhorasan) + +- CLI Server: + . Fixed bug #76333 (PHP built-in server does not find files if root path + contains special characters). (Anatol) + +- OpenSSL: + . Fixed bug #76296 (openssl_pkey_get_public does not respect open_basedir). + (Erik Lax, Jakub Zelenka) + . Fixed bug #76174 (openssl extension fails to build with LibreSSL 2.7). + (Jakub Zelenka) + +- SPL: + . Fixed bug #76367 (NoRewindIterator segfault 11). (Laruence) + +- Standard: + . Fixed bug #76410 (SIGV in zend_mm_alloc_small). (Laruence) + . Fixed bug #76335 ("link(): Bad file descriptor" with non-ASCII path). + (Anatol) + +24 May 2018, PHP 7.2.6 + +- EXIF: + . Fixed bug #76164 (exif_read_data zend_mm_heap corrupted). (cmb) + +- FPM: + . Fixed bug #76075 --with-fpm-acl wrongly tries to find libacl on FreeBSD. + (mgorny) + +- intl: + . Fixed bug #74385 (Locale::parseLocale() broken with some arguments). + (Anatol) + +- Opcache: + . Fixed bug #76205 (PHP-FPM sporadic crash when running Infinitewp). (Dmitry) + . Fixed bug #76275 (Assertion failure in file cache when unserializing empty + try_catch_array). (Nikita) + . Fixed bug #76281 (Opcache causes incorrect "undefined variable" errors). + (Nikita) + +- Reflection: + . Fixed arginfo of array_replace(_recursive) and array_merge(_recursive). + (carusogabriel) + +- Session: + . Fixed bug #74892 (Url Rewriting (trans_sid) not working on urls that start + with "#"). (Andrew Nester) + +26 Apr 2018, PHP 7.2.5 + +- Core: + . Fixed bug #75722 (Convert valgrind detection to configure option). + (Michael Heimpold) + +- Date: + . Fixed bug #76131 (mismatch arginfo for date_create). (carusogabriel) + +- Exif: + . Fixed bug #76130 (Heap Buffer Overflow (READ: 1786) in exif_iif_add_value). + (Stas) + +- FPM: + . Fixed bug #68440 (ERROR: failed to reload: execvp() failed: Argument list + too long). (Jacob Hipps) + . Fixed incorrect write to getenv result in FPM reload. (Jakub Zelenka) + +- GD: + . Fixed bug #52070 (imagedashedline() - dashed line sometimes is not visible). + (cmb) + +- iconv: + . Fixed bug #76249 (stream filter convert.iconv leads to infinite loop on + invalid sequence). (Stas) + +- intl: + . Fixed bug #76153 (Intl compilation fails with icu4c 61.1). (Anatol) + +- ldap: + . Fixed bug #76248 (Malicious LDAP-Server Response causes Crash). (Stas) + +- mbstring: + . Fixed bug #75944 (Wrong cp1251 detection). (dmk001) + . Fixed bug #76113 (mbstring does not build with Oniguruma 6.8.1). + (chrullrich, cmb) + +- ODBC: + . Fixed bug #76088 (ODBC functions are not available by default on Windows). + (cmb) + +- Opcache: + . Fixed bug #76094 (Access violation when using opcache). (Laruence) + +- Phar: + . Fixed bug #76129 (fix for CVE-2018-5712 may not be complete). (Stas) + +- phpdbg: + . Fixed bug #76143 (Memory corruption: arbitrary NUL overwrite). (Laruence) + +- SPL: + . Fixed bug #76131 (mismatch arginfo for splarray constructor). + (carusogabriel) + +- standard: + . Fixed bug #74139 (mail.add_x_header default inconsistent with docs). (cmb) + . Fixed bug #75996 (incorrect url in header for mt_rand). (tatarbj) + +29 Mar 2018, PHP 7.2.4 + +- Core: + . Fixed bug #76025 (Segfault while throwing exception in error_handler). + (Dmitry, Laruence) + . Fixed bug #76044 ('date: illegal option -- -' in ./configure on FreeBSD). + (Anatol) + +- FPM: + . Fixed bug #75605 (Dumpable FPM child processes allow bypassing opcache + access controls). (Jakub Zelenka) + +- FTP: + . Fixed ftp_pasv arginfo. (carusogabriel) + +-GD: + . Fixed bug #73957 (signed integer conversion in imagescale()). (cmb) + . Fixed bug #76041 (null pointer access crashed php). (cmb) + . Fixed imagesetinterpolation arginfo. (Gabriel Caruso) + +- iconv: + . Fixed bug #75867 (Freeing uninitialized pointer). (Philip Prindeville) + +- Mbstring: + . Fixed bug #62545 (wrong unicode mapping in some charsets). (cmb) + +- Opcache: + . Fixed bug #75969 (Assertion failure in live range DCE due to block pass + misoptimization). (Nikita) + +- OpenSSL: + . Fixed openssl_* arginfos. (carusogabriel) + +- PCNTL: + . Fixed bug #75873 (pcntl_wexitstatus returns incorrect on Big_Endian platform + (s390x)). (Sam Ding) + +- Phar: + . Fixed bug #76085 (Segmentation fault in buildFromIterator when directory + name contains a \n). (Laruence) + +- Standard: + . Fixed bug #75961 (Strange references behavior). (Laruence) + . Fixed some arginfos. (carusogabriel) + . Fixed bug #76068 (parse_ini_string fails to parse "[foo]\nbar=1|>baz" with + segfault). (Anatol) + +01 Mar 2018, PHP 7.2.3 + +- Core: + . Fixed bug #75864 ("stream_isatty" returns wrong value on s390x). (Sam Ding) + +- Apache2Handler: + . Fixed bug #75882 (a simple way for segfaults in threadsafe php just with + configuration). (Anatol) + +- Date: + . Fixed bug #75857 (Timezone gets truncated when formatted). (carusogabriel) + . Fixed bug #75928 (Argument 2 for `DateTimeZone::listIdentifiers()` should + accept `null`). (Pedro Lacerda) + . Fixed bug #68406 (calling var_dump on a DateTimeZone object modifies it). + (jhdxr) + +- LDAP: + . Fixed bug #49876 (Fix LDAP path lookup on 64-bit distros). (dzuelke) + +- libxml2: + . Fixed bug #75871 (use pkg-config where available). (pmmaga) + +- PGSQL: + . Fixed bug #75838 (Memory leak in pg_escape_bytea()). (ard_1 at mail dot ru) + +- Phar: + . Fixed bug #54289 (Phar::extractTo() does not accept specific directories to + be extracted). (bishop) + . Fixed bug #65414 (deal with leading slash while adding files correctly). + (bishopb) + . Fixed bug #65414 (deal with leading slash when adding files correctly). + (bishopb) + +- ODBC: + . Fixed bug #73725 (Unable to retrieve value of varchar(max) type). (Anatol) + +- Opcache: + . Fixed bug #75729 (opcache segfault when installing Bitrix). (Nikita) + . Fixed bug #75893 (file_get_contents $http_response_header variable bugged + with opcache). (Nikita) + . Fixed bug #75938 (Modulus value not stored in variable). (Nikita) + +- SPL: + . Fixed bug #74519 (strange behavior of AppendIterator). (jhdxr) + +- Standard: + . Fixed bug #75916 (DNS_CAA record results contain garbage). (Mike, + Philip Sharp) + . Fixed bug #75981 (Prevent reading beyond buffer start in http wrapper). + (Stas) + +01 Feb 2018, PHP 7.2.2 + +- Core: + . Fixed bug #75742 (potential memleak in internal classes's static members). + (Laruence) + . Fixed bug #75679 (Path 260 character problem). (Anatol) + . Fixed bug #75614 (Some non-portable == in shell scripts). (jdolecek) + . Fixed bug #75786 (segfault when using spread operator on generator passed + by reference). (Nikita) + . Fixed bug #75799 (arg of get_defined_functions is optional). (carusogabriel) + . Fixed bug #75396 (Exit inside generator finally results in fatal error). + (Nikita) + +- FCGI: + . Fixed bug #75794 (getenv() crashes on Windows 7.2.1 when second parameter is + false). (Anatol) + +- IMAP: + . Fixed bug #75774 (imap_append HeapCorruction). (Anatol) + +- Opcache: + . Fixed bug #75720 (File cache not populated after SHM runs full). (Dmitry) + . Fixed bug #75687 (var 8 (TMP) has array key type but not value type). + (Nikita, Laruence) + . Fixed bug #75698 (Using @ crashes php7.2-fpm). (Nikita) + . Fixed bug #75579 (Interned strings buffer overflow may cause crash). + (Dmitry) + +- PDO: + . Fixed bug #75616 (PDO extension doesn't allow to be built shared on Darwin). + (jdolecek) + +- PDO MySQL: + . Fixed bug #75615 (PDO Mysql module can't be built as module). (jdolecek) + +- PGSQL: + . Fixed bug #75671 (pg_version() crashes when called on a connection to + cockroach). (magicaltux at gmail dot com) + +- Readline: + . Fixed bug #75775 (readline_read_history segfaults with empty file). + (Anatol) + +- SAPI: + . Fixed bug #75735 ([embed SAPI] Segmentation fault in + sapi_register_post_entry). (Laruence) + +- SOAP: + . Fixed bug #70469 (SoapClient generates E_ERROR even if exceptions=1 is + used). (Anton Artamonov) + . Fixed bug #75502 (Segmentation fault in zend_string_release). (Nikita) + +- SPL: + . Fixed bug #75717 (RecursiveArrayIterator does not traverse arrays by + reference). (Nikita) + . Fixed bug #75242 (RecursiveArrayIterator doesn't have constants from parent + class). (Nikita) + . Fixed bug #73209 (RecursiveArrayIterator does not iterate object + properties). (Nikita) + +- Standard: + . Fixed bug #75781 (substr_count incorrect result). (Laruence) + . Fixed bug #75653 (array_values don't work on empty array). (Nikita) + +- Zip: + . Display headers (buildtime) and library (runtime) versions in phpinfo + (with libzip >= 1.3.1). (Remi) + +04 Jan 2018, PHP 7.2.1 + +- Core: + . Fixed bug #75573 (Segmentation fault in 7.1.12 and 7.0.26). (Laruence) + . Fixed bug #75384 (PHP seems incompatible with OneDrive files on demand). + (Anatol) + . Fixed bug #75525 (Access Violation in vcruntime140.dll). (Anatol) + . Fixed bug #74862 (Unable to clone instance when private __clone defined). + (Daniel Ciochiu) + . Fixed bug #75074 (php-process crash when is_file() is used with strings + longer 260 chars). (Anatol) + . Fixed bug #69727 (Remove timestamps from build to make it reproducible). + (jelle van der Waa) + +- CLI server: + . Fixed bug #73830 (Directory does not exist). (Anatol) + +- FPM: + . Fixed bug #64938 (libxml_disable_entity_loader setting is shared between + requests). (Remi) + +- GD: + . Fixed bug #75571 (Potential infinite loop in gdImageCreateFromGifCtx). + (Christoph) + +- Opcache: + . Fixed bug #75608 ("Narrowing occurred during type inference" error). + (Laruence, Dmitry) + . Fixed bug #75579 (Interned strings buffer overflow may cause crash). + (Dmitry) + . Fixed bug #75570 ("Narrowing occurred during type inference" error). + (Dmitry) + . Fixed bug #75681 (Warning: Narrowing occurred during type inference, + specific case). (Nikita) + . Fixed bug #75556 (Invalid opcode 138/1/1). (Laruence) + +- PCRE: + . Fixed bug #74183 (preg_last_error not returning error code after error). + (Andrew Nester) + +- Phar: + . Fixed bug #74782 (remove file name from output to avoid XSS). (stas) + +- Standard: + . Fixed bug #75511 (fread not free unused buffer). (Laruence) + . Fixed bug #75514 (mt_rand returns value outside [$min,$max]+ on 32-bit) + (Remi) + . Fixed bug #75535 (Inappropriately parsing HTTP response leads to PHP + segment fault). (Nikita) + . Fixed bug #75409 (accept EFAULT in addition to ENOSYS as indicator + that getrandom() is missing). (sarciszewski) + . Fixed bug #73124 (php_ini_scanned_files() not reporting correctly). + (John Stevenson) + . Fixed bug #75574 (putenv does not work properly if parameter contains + non-ASCII unicode character). (Anatol) + +- Zip: + . Fixed bug #75540 (Segfault with libzip 1.3.1). (Remi) + +30 Nov 2017, PHP 7.2.0 + +- BCMath: + . Fixed bug #46564 (bcmod truncates fractionals). (liborm85) + +- CLI: + . Fixed bug #74849 (Process is started as interactive shell in PhpStorm). + (Anatol) + . Fixed bug #74979 (Interactive shell opening instead of script execution + with -f flag). (Anatol) + +- CLI server: + . Fixed bug #60471 (Random "Invalid request (unexpected EOF)" using a router + script). (SammyK) + +- Core: + . Added ZEND_COUNT, ZEND_GET_CLASS, ZEND_GET_CALLED_CLASS, ZEND_GET_TYPE, + ZEND_FUNC_NUM_ARGS, ZEND_FUNC_GET_ARGS instructions, to implement + corresponding builtin functions. (Dmitry) + . "Countable" interface is moved from SPL to Core. (Dmitry) + . Added ZEND_IN_ARRAY instruction, implementing optimized in_array() builtin + function, through hash lookup in flipped array. (Dmitry) + . Removed IS_TYPE_IMMUTABLE (it's the same as COPYABLE & !REFCOUNTED). (Dmitry) + . Removed the sql.safe_mode directive. (Kalle) + . Removed support for Netware. (Kalle) + . Renamed ReflectionClass::isIterateable() to ReflectionClass::isIterable() + (alias original name for BC). (Sara) + . Fixed bug #54535 (WSA cleanup executes before MSHUTDOWN). (Kalle) + . Implemented FR #69791 (Disallow mail header injections by extra headers) + (Yasuo) + . Implemented FR #49806 (proc_nice() for Windows). (Kalle) + . Fix pthreads detection when cross-compiling (ffontaine) + . Fixed memory leaks caused by exceptions thrown from destructors. (Bob, + Dmitry). + . Fixed bug #73215 (uniqid() should use better random source). (Yasuo) + . Implemented FR #72768 (Add ENABLE_VIRTUAL_TERMINAL_PROCESSING flag for + php.exe). (Michele Locati) + . Implemented "Convert numeric keys in object/array casts" RFC, fixes + bugs #53838, #61655, #66173, #70925, #72254, etc. (Andrea) + . Implemented "Deprecate and Remove Bareword (Unquoted) Strings" RFC. + (Rowan Collins) + . Raised minimum supported Windows versions to Windows 7/Server 2008 R2. + (Anatol) + . Implemented minor optimization in array_keys/array_values(). (Sara) + . Added PHP_OS_FAMILY constant to determine on which OS we are. (Jan Altensen) + . Fixed bug #73987 (Method compatibility check looks to original + definition and not parent). (pmmaga) + . Fixed bug #73991 (JSON_OBJECT_AS_ARRAY not respected). (Sara) + . Fixed bug #74053 (Corrupted class entries on shutdown when a destructor + spawns another object). (jim at commercebyte dot com) + . Fixed bug #73971 (Filename got limited to MAX_PATH on Win32 when scan + directory). (Anatol) + . Fixed bug #72359, bug #72451, bug #73706, bug #71115 and others related + to interned strings handling in TS builds. (Anatol, Dmitry) + . Implemented "Trailing Commas In List Syntax" RFC for group use lists only. + (Sammy Kaye Powers) + . Fixed bug #74269 (It's possible to override trait property with different + loosely-equal value). (pmmaga) + . Fixed bug #61970 (Restraining __construct() access level in subclass gives + a fatal error). (pmmaga) + . Fixed bug #63384 (Cannot override an abstract method with an abstract + method). (pmmaga, wes) + . Fixed bug #74607 (Traits enforce different inheritance rules). (pmmaga) + . Fixed misparsing of abstract unix domain socket names. (Sara) + . Change PHP_OS_FAMILY value from "OSX" to "Darwin". (Sebastian, Kalle) + . Allow loading PHP/Zend extensions by name in ini files (extension=<name>). + (francois at tekwire dot net) + . Added object type annotation. (brzuchal) + . Fixed bug #74815 (crash with a combination of INI entries at startup). + (Anatol) + . Fixed bug #74836 (isset on zero-prefixed numeric indexes in array broken). + (Dmitry) + . Added new VM instuctions ISSET_ISEMPTY_CV and UNSET_CV. Previously they + were implemented as ISSET_ISEMPTY_VAR and UNSET_VAR variants with + ZEND_QUICK_SET flag. (Nikita, Dmitry) + . Fixed bug #49649 (unserialize() doesn't handle changes in property + visibility). (pmmaga) + . Fixed #74866 (extension_dir = "./ext" now use current directory for base). + (Francois Laupretre) + . Implemented FR #74963 (Improved error message on fetching property of + non-object). (Laruence) + . Fixed Bug #75142 (buildcheck.sh check for autoconf version needs to be updated + for v2.64). (zizzy at zizzy dot net, Remi) + . Fixed bug #74878 (Data race in ZTS builds). (Nikita, Dmitry) + . Fixed bug #75515 ("stream_copy_to_stream" doesn't stream anymore). (Sara) + +- cURL: + . Fixed bug #75093 (OpenSSL support not detected). (Remi) + . Better fix for #74125 (use pkg-config instead of curl-config). (Remi) + +- Date: + . Fixed bug #55407 (Impossible to prototype DateTime::createFromFormat). + (kelunik) + . Implemented FR #71520 (Adding the DateTime constants to the + DateTimeInterface interface). (Majkl578) + . Fixed bug #75149 (redefinition of typedefs ttinfo and t1info). (Remi) + . Fixed bug #75222 (DateInterval microseconds property always 0). (jhdxr) + +- Dba: + . Fixed bug #72885 (flatfile: dba_fetch() fails to read replaced entry). + (Anatol) + +- DOM: + . Implement #74837 (Implement Countable for DomNodeList and DOMNamedNodeMap). + (Andreas Treichel) + +- EXIF: + . Added support for vendor specific tags for the following formats: + Samsung, DJI, Panasonic, Sony, Pentax, Minolta, Sigma/Foveon, AGFA, + Kyocera, Ricoh & Epson. (Kalle) + . Fixed bug #72682 (exif_read_data() fails to read all data for some + images). (Kalle) + . Fixed bug #71534 (Type confusion in exif_read_data() leading to heap + overflow in debug mode). (hlt99 at blinkenshell dot org, Kalle) + . Fixed bug #68547 (Exif Header component value check error). + (sjh21a at gmail dot com, Kalle) + . Fixed bug #66443 (Corrupt EXIF header: maximum directory nesting level + reached for some cameras). (Kalle) + . Fixed Redhat bug #1362571 (PHP not returning full results for + exif_read_data function). (Kalle) + . Implemented #65187 (exif_read_data/thumbnail: add support for stream + resource). (Kalle) + . Deprecated the read_exif_data() alias. (Kalle) + . Fixed bug #74428 (exif_read_data(): "Illegal IFD size" warning occurs with + correct exif format). (bradpiccho at gmail dot com, Kalle) + . Fixed bug #72819 (EXIF thumbnails not read anymore). (Kalle) + . Fixed bug #62523 (php crashes with segfault when exif_read_data called). + (Kalle) + . Fixed bug #50660 (exif_read_data(): Illegal IFD offset (works fine with + other exif readers). (skinny dot bravo at gmail dot com, Kalle) + +- Fileinfo: + . Upgrade bundled libmagic to 5.31. (Anatol) + +- FPM: + . Configuration to limit fpm slow log trace callers. (Sannis) + . Fixed bug #75212 (php_value acts like php_admin_value). (Remi) + +- FTP: + . Implement MLSD for structured listing of directories. (blar) + . Added ftp_append() function. (blar) + +- GD: + . Implemented imageresolution as getter and setter (Christoph) + . Fixed bug #74744 (gd.h: stdarg.h include missing for va_list use in + gdErrorMethod). (rainer dot jung at kippdata dot de, cmb) + . Fixed bug #75111 (Memory disclosure or DoS via crafted .bmp image). (cmb) + +- GMP: + . Fixed bug #70896 (gmp_fact() silently ignores non-integer input). (Sara) + +- Hash: + . Changed HashContext from resource to object. (Rouven Weßling, Sara) + . Disallowed usage of non-cryptographic hash functions with HMAC and PBKDF2. + (Andrey Andreev, Nikita) + . Fixed Bug #75284 (sha3 is not supported on bigendian machine). (Remi) + +- IMAP: + . Fixed bug #72324 (imap_mailboxmsginfo() return wrong size). + (ronaldpoon at udomain dot com dot hk, Kalle) + +- Intl: + . Fixed bug #63790 (test using Spoofchecker which may be unavailable). (Sara) + . Fixed bug #75378 ([REGRESSION] IntlDateFormatter::parse() does not change + $position argument). (Laruence) + +- JSON: + . Add JSON_INVALID_UTF8_IGNORE and JSON_INVALID_UTF8_SUBSTITUTE options for + json_encode and json_decode to ignore or replace invalid UTF-8 byte + sequences - it addresses request #65082. (Jakub Zelenka) + . Fixed bug #75185 (Buffer overflow in json_decode() with + JSON_INVALID_UTF8_IGNORE or JSON_INVALID). (Jakub Zelenka) + . Fixed bug #68567 (JSON_PARTIAL_OUTPUT_ON_ERROR can result in JSON with null + key). (Jakub Zelenka) + +- LDAP: + . Implemented FR #69445 (Support for LDAP EXOP operations) + . Fixed support for LDAP_OPT_SERVER_CONTROLS and LDAP_OPT_CLIENT_CONTROLS in ldap_get_option + . Fixed passing an empty array to ldap_set_option for client or server controls. + +- Mbstring: + . Implemented request #66024 (mb_chr() and mb_ord()). (Masakielastic, Yasuo) + . Implemented request #65081 (mb_scrub()). (Masakielastic, Yasuo) + . Implemented request #69086 (enhancement for mb_convert_encoding() that + handles multibyte replacement char nicely). (Masakielastic, Yasuo) + . Added array input support to mb_convert_encoding(). (Yasuo) + . Added array input support to mb_check_encoding(). (Yasuo) + . Fixed bug #69079 (enhancement for mb_substitute_character). (masakielastic) + . Update to oniguruma version 6.3.0. (Remi) + . Fixed bug #69267 (mb_strtolower fails on titlecase characters). (Nikita) + +- Mcrypt: + . The deprecated mcrypt extension has been moved to PECL. (leigh) + +- Opcache: + . Added global optimisation passes based on data flow analysis using Single + Static Assignment (SSA) form: Sparse Conditional Constant Propagation (SCCP), + Dead Code Elimination (DCE), and removal of unused local variables + (Nikita, Dmitry) + . Fixed incorect constant conditional jump elimination. (Dmitry) + . Fixed bug #75230 (Invalid opcode 49/1/8 using opcache). (Laruence) + . Fixed bug (assertion fails with extended info generated). (Laruence) + . Fixed bug (Phi sources removel). (Laruence) + . Fixed bug #75370 (Webserver hangs on valid PHP text). (Laruence) + . Fixed bug #75357 (segfault loading WordPress wp-admin). (Laruence) + +- OpenSSL: + . Use TLS_ANY for default ssl:// and tls:// negotiation. (kelunik) + . Fix leak in openssl_spki_new(). (jelle at vdwaa dot nl) + . Added openssl_pkcs7_read() and pk7 parameter to openssl_pkcs7_verify(). + (jelle at vdwaa dot nl) + . Add ssl security_level stream option to support OpenSSL security levels. + (Jakub Zelenka). + . Allow setting SNI cert and private key in separate files. (Jakub Zelenka) + . Fixed bug #74903 (openssl_pkcs7_encrypt() uses different EOL than before). + (Anatol) + . Automatically load OpenSSL configuration file. (Jakub Zelenka) + +- PCRE: + . Added support for PCRE JIT fast path API. (dmitry) + . Fixed bug #61780 (Inconsistent PCRE captures in match results). (cmb) + . Fixed bug #74873 (Minor BC break: PCRE_JIT changes output of preg_match()). + (Dmitry) + . Fixed bug #75089 (preg_grep() is not reporting PREG_BAD_UTF8_ERROR after + first input string). (Dmitry) + . Fixed bug #75223 (PCRE JIT broken in 7.2). (Dmitry) + . Fixed bug #75285 (Broken build when system libpcre don't have jit support). + (Remi) + +- phar: + . Fixed bug #74196 (phar does not correctly handle names containing dots). + (mhagstrand) + +- PDO: + . Add "Sent SQL" to debug dump for emulated prepares. (Adam Baratz) + . Add parameter types for national character set strings. (Adam Baratz) + +- PDO_DBlib: + . Fixed bug #73234 (Emulated statements let value dictate parameter type). + (Adam Baratz) + . Fixed bug #73396 (bigint columns are returned as strings). (Adam Baratz) + . Expose DB-Library version as \PDO::DBLIB_ATTR_VERSION attribute on \PDO + instance. (Adam Baratz) + . Add test coverage for bug #72969. (Jeff Farr) + +- PDO_OCI: + . Fixed Bug #74537 (Align --with-pdo-oci configure option with --with-oci8 syntax). + (Tianfang Yang) + +- PDO_Sqlite + . Switch to sqlite3_prepare_v2() and sqlite3_close_v2() functions (rasmus) + +- PHPDBG + . Added extended_value to opcode dump output. (Sara) + +- Session: + . Fixed bug #73461 (Prohibit session save handler recursion). (Yasuo) + . PR #2233 Removed register_globals related code and "!" can be used as $_SESSION key name. (Yasuo) + . Improved bug #73100 fix. 'user' save handler can only be set by session_set_save_handler() + . Fixed bug #74514 (5 session functions incorrectly warn when calling in + read-only/getter mode). (Yasuo) + . Fixed bug #74936 (session_cache_expire/cache_limiter/save_path() trigger a + warning in read mode). (morozov) + . Fixed bug #74941 (session fails to start after having headers sent). + (morozov) + +- Sodium: + . New cryptographic extension + . Added missing bindings for libsodium > 1.0.13. (Frank) + +- SPL: + . Fixed bug #71412 (Incorrect arginfo for ArrayIterator::__construct). + (tysonandre775 at hotmail dot com) + . Added spl_object_id(). (Tyson Andre) + +- SQLite3: + . Implement writing to blobs. (bohwaz at github dot com) + . Update to Sqlite 3.20.1. (cmb) + +- Standard: + . Fixed bug #69442 (closing of fd incorrect when PTS enabled). (jaytaph) + . Fixed bug #74300 (unserialize accepts two plus/minus signs for float number exponent part). + (xKerman) + . Compatibility with libargon2 versions 20161029 and 20160821. + (charlesportwoodii at erianna dot com) + . Fixed Bug #74737 (mysqli_get_client_info reflection info). + (mhagstrand at gmail dot com) + . Add support for extension name as argument to dl(). + (francois at tekwire dot net) + . Fixed bug #74851 (uniqid() without more_entropy performs badly). + (Emmanuel Dreyfus) + . Fixed bug #74103 (heap-use-after-free when unserializing invalid array + size). (Nikita) + . Fixed bug #75054 (A Denial of Service Vulnerability was found when + performing deserialization). (Nikita) + . Fixed bug #75170 (mt_rand() bias on 64-bit machines). (Nikita) + . Fixed bug #75221 (Argon2i always throws NUL at the end). (cmb) + +- Streams: + . Default ssl/single_dh_use and ssl/honor_cipher_order to true. (kelunik) + +- XML: + . Moved utf8_encode() and utf8_decode() to the Standard extension. (Andrea) + +- XMLRPC: + . Use Zend MM for allocation in bundled libxmlrpc (Joe) + +- ZIP: + . Add support for encrypted archives. (Remi) + . Use of bundled libzip is deprecated, --with-libzip option is recommended. (Remi) + . Fixed Bug #73803 (Reflection of ZipArchive does not show public properties). (Remi) + . ZipArchive implements countable, added ZipArchive::count() method. (Remi) + . Fix segfault in php_stream_context_get_option call. (Remi) + . Fixed bug #75143 (new method setEncryptionName() seems not to exist + in ZipArchive). (Anatol) + +- zlib: + . Expose inflate_get_status() and inflate_get_read_len() functions. + (Matthew Trescott) diff --git a/ext/bcmath/libbcmath/src/str2num.c b/ext/bcmath/libbcmath/src/str2num.c index f38d341570f92..03aec15930ba4 100644 --- a/ext/bcmath/libbcmath/src/str2num.c +++ b/ext/bcmath/libbcmath/src/str2num.c @@ -57,9 +57,9 @@ bc_str2num (bc_num *num, char *str, int scale) zero_int = FALSE; if ( (*ptr == '+') || (*ptr == '-')) ptr++; /* Sign */ while (*ptr == '0') ptr++; /* Skip leading zeros. */ - while (isdigit((int)*ptr)) ptr++, digits++; /* digits */ + while (*ptr >= '0' && *ptr <= '9') ptr++, digits++; /* digits */ if (*ptr == '.') ptr++; /* decimal point */ - while (isdigit((int)*ptr)) ptr++, strscale++; /* digits */ + while (*ptr >= '0' && *ptr <= '9') ptr++, strscale++; /* digits */ if ((*ptr != '\0') || (digits+strscale == 0)) { *num = bc_copy_num (BCG(_zero_)); diff --git a/ext/bcmath/tests/bug78878.phpt b/ext/bcmath/tests/bug78878.phpt new file mode 100644 index 0000000000000..2c9d72b94680d --- /dev/null +++ b/ext/bcmath/tests/bug78878.phpt @@ -0,0 +1,13 @@ +--TEST-- +Bug #78878 (Buffer underflow in bc_shift_addsub) +--SKIPIF-- +<?php +if (!extension_loaded('bcmath')) die('skip bcmath extension not available'); +?> +--FILE-- +<?php +print @bcmul("\xB26483605105519922841849335928742092", bcpowmod(2, 65535, -4e-4)); +?> +--EXPECT-- +bc math warning: non-zero scale in modulus +0 diff --git a/ext/dom/domimplementation.c b/ext/dom/domimplementation.c index c6629c85e9658..6f59f9c7c89f3 100644 --- a/ext/dom/domimplementation.c +++ b/ext/dom/domimplementation.c @@ -112,6 +112,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/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-- +<?php +if (!extension_loaded('dom')) die('skip dom extension not available'); +?> +--FILE-- +<?php +$imp = new DOMImplementation; +if (PHP_OS_FAMILY === 'Windows') { + $path = '/' . str_replace('\\', '/', __DIR__); +} else { + $path = __DIR__; +} +$uri = "file://$path/bug79971_2.xml"; +var_dump($imp->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 a337775923700..7a8d292210360 100644 --- a/ext/exif/exif.c +++ b/ext/exif/exif.c @@ -3147,7 +3147,8 @@ static int exif_process_IFD_in_MAKERNOTE(image_info_type *ImageInfo, char * valu /*exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "check (%s)", maker_note->make?maker_note->make:"");*/ if (maker_note->make && (!ImageInfo->make || strcmp(maker_note->make, ImageInfo->make))) 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; } @@ -3221,8 +3222,9 @@ static int exif_process_IFD_in_MAKERNOTE(image_info_type *ImageInfo, char * valu } for (de=0;de<NumDirEntries;de++) { - if (!exif_process_IFD_TAG(ImageInfo, dir_start + 2 + 12 * de, - offset_base, data_len, displacement, section_index, 0, maker_note->tag_table)) { + size_t offset = 2 + 12 * de; + if (!exif_process_IFD_TAG(ImageInfo, dir_start + offset, + offset_base, data_len - offset, displacement, section_index, 0, maker_note->tag_table)) { return FALSE; } } @@ -3649,6 +3651,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; @@ -3801,7 +3808,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. */ diff --git a/ext/exif/exif.c.orig b/ext/exif/exif.c.orig new file mode 100644 index 0000000000000..ff97bf53d34b8 --- /dev/null +++ b/ext/exif/exif.c.orig @@ -0,0 +1,4764 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2018 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 <rasmus@php.net> | + | Marcus Boerger <helly@php.net> | + +----------------------------------------------------------------------+ + */ + +#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 <math.h> +#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 <sys/types.h> + +typedef unsigned char uchar; + +#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 150 + +/* {{{ 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[] + */ +static const zend_function_entry exif_functions[] = { + PHP_FE(exif_read_data, arginfo_exif_read_data) + PHP_DEP_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, "Supported EXIF Version", "0220"); + php_info_print_table_row(2, "Supported filetypes", "JPEG, TIFF"); + + if (zend_hash_str_exists(&module_registry, "mbstring", sizeof("mbstring")-1)) { + php_info_print_table_row(2, "Multibyte decoding support using mbstring", "enabled"); + } else { + php_info_print_table_row(2, "Multibyte decoding support using mbstring", "disabled"); + } + + php_info_print_table_row(2, "Extended EXIF tag formats", "Canon, Casio, Fujifilm, Nikon, Olympus, Samsung, Panasonic, DJI, Sony, Pentax, Minolta, Sigma, Foveon, Kyocera, Ricoh, AGFA, Epson"); + 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; + } + pefree((void *) return_list, 0); + } + 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; + } + pefree((void *) return_list, 0); + } + 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) + */ +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 +}; + +static tag_info_array tag_table_VND_SAMSUNG = { + { 0x0001, "Version"}, + { 0x0021, "PictureWizard"}, + { 0x0030, "LocalLocationName"}, + { 0x0031, "LocationName"}, + { 0x0035, "Preview"}, + { 0x0043, "CameraTemperature"}, + { 0xa001, "FirmwareName"}, + { 0xa003, "LensType"}, + { 0xa004, "LensFirmware"}, + { 0xa010, "SensorAreas"}, + { 0xa011, "ColorSpace"}, + { 0xa012, "SmartRange"}, + { 0xa013, "ExposureBiasValue"}, + { 0xa014, "ISO"}, + { 0xa018, "ExposureTime"}, + { 0xa019, "FNumber"}, + { 0xa01a, "FocalLengthIn35mmFormat"}, + { 0xa020, "EncryptionKey"}, + { 0xa021, "WB_RGGBLevelsUncorrected"}, + { 0xa022, "WB_RGGBLevelsAuto"}, + { 0xa023, "WB_RGGBLevelsIlluminator1"}, + { 0xa024, "WB_RGGBLevelsIlluminator2"}, + { 0xa028, "WB_RGGBLevelsBlack"}, + { 0xa030, "ColorMatrix"}, + { 0xa031, "ColorMatrixSRGB"}, + { 0xa032, "ColorMatrixAdobeRGB"}, + { 0xa040, "ToneCurve1"}, + { 0xa041, "ToneCurve2"}, + { 0xa042, "ToneCurve3"}, + { 0xa043, "ToneCurve4"}, + TAG_TABLE_END +}; + +static tag_info_array tag_table_VND_PANASONIC = { + { 0x0001, "Quality"}, + { 0x0002, "FirmwareVersion"}, + { 0x0003, "WhiteBalance"}, + { 0x0004, "0x0004"}, + { 0x0007, "FocusMode"}, + { 0x000f, "AFMode"}, + { 0x001a, "ImageStabilization"}, + { 0x001c, "Macro"}, + { 0x001f, "ShootingMode"}, + { 0x0020, "Audio"}, + { 0x0021, "DataDump"}, + { 0x0022, "0x0022"}, + { 0x0023, "WhiteBalanceBias"}, + { 0x0024, "FlashBias"}, + { 0x0025, "InternalSerialNumber"}, + { 0x0026, "ExifVersion"}, + { 0x0027, "0x0027"}, + { 0x0028, "ColorEffect"}, + { 0x0029, "TimeSincePowerOn"}, + { 0x002a, "BurstMode"}, + { 0x002b, "SequenceNumber"}, + { 0x002c, "Contrast"}, + { 0x002d, "NoiseReduction"}, + { 0x002e, "SelfTimer"}, + { 0x002f, "0x002f"}, + { 0x0030, "Rotation"}, + { 0x0031, "AFAssistLamp"}, + { 0x0032, "ColorMode"}, + { 0x0033, "BabyAge1"}, + { 0x0034, "OpticalZoomMode"}, + { 0x0035, "ConversionLens"}, + { 0x0036, "TravelDay"}, + { 0x0039, "Contrast"}, + { 0x003a, "WorldTimeLocation"}, + { 0x003b, "TextStamp1"}, + { 0x003c, "ProgramISO"}, + { 0x003d, "AdvancedSceneType"}, + { 0x003e, "TextStamp2"}, + { 0x003f, "FacesDetected"}, + { 0x0040, "Saturation"}, + { 0x0041, "Sharpness"}, + { 0x0042, "FilmMode"}, + { 0x0044, "ColorTempKelvin"}, + { 0x0045, "BracketSettings"}, + { 0x0046, "WBAdjustAB"}, + { 0x0047, "WBAdjustGM"}, + { 0x0048, "FlashCurtain"}, + { 0x0049, "LongShutterNoiseReduction"}, + { 0x004b, "ImageWidth"}, + { 0x004c, "ImageHeight"}, + { 0x004d, "AFPointPosition"}, + { 0x004e, "FaceDetInfo"}, + { 0x0051, "LensType"}, + { 0x0052, "LensSerialNumber"}, + { 0x0053, "AccessoryType"}, + { 0x0054, "AccessorySerialNumber"}, + { 0x0059, "Transform1"}, + { 0x005d, "IntelligentExposure"}, + { 0x0060, "LensFirmwareVersion"}, + { 0x0061, "FaceRecInfo"}, + { 0x0062, "FlashWarning"}, + { 0x0065, "Title"}, + { 0x0066, "BabyName"}, + { 0x0067, "Location"}, + { 0x0069, "Country"}, + { 0x006b, "State"}, + { 0x006d, "City"}, + { 0x006f, "Landmark"}, + { 0x0070, "IntelligentResolution"}, + { 0x0077, "BurstSheed"}, + { 0x0079, "IntelligentDRange"}, + { 0x007c, "ClearRetouch"}, + { 0x0080, "City2"}, + { 0x0086, "ManometerPressure"}, + { 0x0089, "PhotoStyle"}, + { 0x008a, "ShadingCompensation"}, + { 0x008c, "AccelerometerZ"}, + { 0x008d, "AccelerometerX"}, + { 0x008e, "AccelerometerY"}, + { 0x008f, "CameraOrientation"}, + { 0x0090, "RollAngle"}, + { 0x0091, "PitchAngle"}, + { 0x0093, "SweepPanoramaDirection"}, + { 0x0094, "PanoramaFieldOfView"}, + { 0x0096, "TimerRecording"}, + { 0x009d, "InternalNDFilter"}, + { 0x009e, "HDR"}, + { 0x009f, "ShutterType"}, + { 0x00a3, "ClearRetouchValue"}, + { 0x00ab, "TouchAE"}, + { 0x0e00, "PrintIM"}, + { 0x4449, "0x4449"}, + { 0x8000, "MakerNoteVersion"}, + { 0x8001, "SceneMode"}, + { 0x8004, "WBRedLevel"}, + { 0x8005, "WBGreenLevel"}, + { 0x8006, "WBBlueLevel"}, + { 0x8007, "FlashFired"}, + { 0x8008, "TextStamp3"}, + { 0x8009, "TextStamp4"}, + { 0x8010, "BabyAge2"}, + { 0x8012, "Transform2"}, + TAG_TABLE_END +}; + +static tag_info_array tag_table_VND_DJI = { + { 0x0001, "Make"}, + { 0x0003, "SpeedX"}, + { 0x0004, "SpeedY"}, + { 0x0005, "SpeedZ"}, + { 0x0006, "Pitch"}, + { 0x0007, "Yaw"}, + { 0x0008, "Roll"}, + { 0x0009, "CameraPitch"}, + { 0x000a, "CameraYaw"}, + { 0x000b, "CameraRoll"}, + TAG_TABLE_END +}; + +static tag_info_array tag_table_VND_SONY = { + { 0x0102, "Quality"}, + { 0x0104, "FlashExposureComp"}, + { 0x0105, "Teleconverter"}, + { 0x0112, "WhiteBalanceFineTune"}, + { 0x0114, "CameraSettings"}, + { 0x0115, "WhiteBalance"}, + { 0x0116, "0x0116"}, + { 0x0e00, "PrintIM"}, + { 0x1000, "MultiBurstMode"}, + { 0x1001, "MultiBurstImageWidth"}, + { 0x1002, "MultiBurstImageHeight"}, + { 0x1003, "Panorama"}, + { 0x2000, "0x2000"}, + { 0x2001, "PreviewImage"}, + { 0x2002, "0x2002"}, + { 0x2003, "0x2003"}, + { 0x2004, "Contrast"}, + { 0x2005, "Saturation"}, + { 0x2006, "0x2006"}, + { 0x2007, "0x2007"}, + { 0x2008, "0x2008"}, + { 0x2009, "0x2009"}, + { 0x200a, "AutoHDR"}, + { 0x3000, "ShotInfo"}, + { 0xb000, "FileFormat"}, + { 0xb001, "SonyModelID"}, + { 0xb020, "ColorReproduction"}, + { 0xb021, "ColorTemperature"}, + { 0xb022, "ColorCompensationFilter"}, + { 0xb023, "SceneMode"}, + { 0xb024, "ZoneMatching"}, + { 0xb025, "DynamicRangeOptimizer"}, + { 0xb026, "ImageStabilization"}, + { 0xb027, "LensID"}, + { 0xb028, "MinoltaMakerNote"}, + { 0xb029, "ColorMode"}, + { 0xb02b, "FullImageSize"}, + { 0xb02c, "PreviewImageSize"}, + { 0xb040, "Macro"}, + { 0xb041, "ExposureMode"}, + { 0xb042, "FocusMode"}, + { 0xb043, "AFMode"}, + { 0xb044, "AFIlluminator"}, + { 0xb047, "JPEGQuality"}, + { 0xb048, "FlashLevel"}, + { 0xb049, "ReleaseMode"}, + { 0xb04a, "SequenceNumber"}, + { 0xb04b, "AntiBlur"}, + { 0xb04e, "LongExposureNoiseReduction"}, + { 0xb04f, "DynamicRangeOptimizer"}, + { 0xb052, "IntelligentAuto"}, + { 0xb054, "WhiteBalance2"}, + TAG_TABLE_END +}; + +static tag_info_array tag_table_VND_PENTAX = { + { 0x0000, "Version"}, + { 0x0001, "Mode"}, + { 0x0002, "PreviewResolution"}, + { 0x0003, "PreviewLength"}, + { 0x0004, "PreviewOffset"}, + { 0x0005, "ModelID"}, + { 0x0006, "Date"}, + { 0x0007, "Time"}, + { 0x0008, "Quality"}, + { 0x0009, "Size"}, + { 0x000c, "Flash"}, + { 0x000d, "Focus"}, + { 0x000e, "AFPoint"}, + { 0x000f, "AFPointInFocus"}, + { 0x0012, "ExposureTime"}, + { 0x0013, "FNumber"}, + { 0x0014, "ISO"}, + { 0x0016, "ExposureCompensation"}, + { 0x0017, "MeteringMode"}, + { 0x0018, "AutoBracketing"}, + { 0x0019, "WhiteBalance"}, + { 0x001a, "WhiteBalanceMode"}, + { 0x001b, "BlueBalance"}, + { 0x001c, "RedBalance"}, + { 0x001d, "FocalLength"}, + { 0x001e, "DigitalZoom"}, + { 0x001f, "Saturation"}, + { 0x0020, "Contrast"}, + { 0x0021, "Sharpness"}, + { 0x0022, "Location"}, + { 0x0023, "Hometown"}, + { 0x0024, "Destination"}, + { 0x0025, "HometownDST"}, + { 0x0026, "DestinationDST"}, + { 0x0027, "DSPFirmwareVersion"}, + { 0x0028, "CPUFirmwareVersion"}, + { 0x0029, "FrameNumber"}, + { 0x002d, "EffectiveLV"}, + { 0x0032, "ImageProcessing"}, + { 0x0033, "PictureMode"}, + { 0x0034, "DriveMode"}, + { 0x0037, "ColorSpace"}, + { 0x0038, "ImageAreaOffset"}, + { 0x0039, "RawImageSize"}, + { 0x003e, "PreviewImageBorders"}, + { 0x003f, "LensType"}, + { 0x0040, "SensitivityAdjust"}, + { 0x0041, "DigitalFilter"}, + { 0x0047, "Temperature"}, + { 0x0048, "AELock"}, + { 0x0049, "NoiseReduction"}, + { 0x004d, "FlashExposureCompensation"}, + { 0x004f, "ImageTone"}, + { 0x0050, "ColorTemperature"}, + { 0x005c, "ShakeReduction"}, + { 0x005d, "ShutterCount"}, + { 0x0069, "DynamicRangeExpansion"}, + { 0x0071, "HighISONoiseReduction"}, + { 0x0072, "AFAdjustment"}, + { 0x0200, "BlackPoint"}, + { 0x0201, "WhitePoint"}, + { 0x0205, "ShotInfo"}, + { 0x0206, "AEInfo"}, + { 0x0207, "LensInfo"}, + { 0x0208, "FlashInfo"}, + { 0x0209, "AEMeteringSegments"}, + { 0x020a, "FlashADump"}, + { 0x020b, "FlashBDump"}, + { 0x020d, "WB_RGGBLevelsDaylight"}, + { 0x020e, "WB_RGGBLevelsShade"}, + { 0x020f, "WB_RGGBLevelsCloudy"}, + { 0x0210, "WB_RGGBLevelsTungsten"}, + { 0x0211, "WB_RGGBLevelsFluorescentD"}, + { 0x0212, "WB_RGGBLevelsFluorescentN"}, + { 0x0213, "WB_RGGBLevelsFluorescentW"}, + { 0x0214, "WB_RGGBLevelsFlash"}, + { 0x0215, "CameraInfo"}, + { 0x0216, "BatteryInfo"}, + { 0x021f, "AFInfo"}, + { 0x0222, "ColorInfo"}, + { 0x0229, "SerialNumber"}, + TAG_TABLE_END +}; + +static tag_info_array tag_table_VND_MINOLTA = { + { 0x0000, "Version"}, + { 0x0001, "CameraSettingsStdOld"}, + { 0x0003, "CameraSettingsStdNew"}, + { 0x0004, "CameraSettings7D"}, + { 0x0018, "ImageStabilizationData"}, + { 0x0020, "WBInfoA100"}, + { 0x0040, "CompressedImageSize"}, + { 0x0081, "Thumbnail"}, + { 0x0088, "ThumbnailOffset"}, + { 0x0089, "ThumbnailLength"}, + { 0x0100, "SceneMode"}, + { 0x0101, "ColorMode"}, + { 0x0102, "Quality"}, + { 0x0103, "0x0103"}, + { 0x0104, "FlashExposureComp"}, + { 0x0105, "Teleconverter"}, + { 0x0107, "ImageStabilization"}, + { 0x0109, "RawAndJpgRecording"}, + { 0x010a, "ZoneMatching"}, + { 0x010b, "ColorTemperature"}, + { 0x010c, "LensID"}, + { 0x0111, "ColorCompensationFilter"}, + { 0x0112, "WhiteBalanceFineTune"}, + { 0x0113, "ImageStabilizationA100"}, + { 0x0114, "CameraSettings5D"}, + { 0x0115, "WhiteBalance"}, + { 0x0e00, "PrintIM"}, + { 0x0f00, "CameraSettingsZ1"}, + TAG_TABLE_END +}; + +static tag_info_array tag_table_VND_SIGMA = { + { 0x0002, "SerialNumber"}, + { 0x0003, "DriveMode"}, + { 0x0004, "ResolutionMode"}, + { 0x0005, "AutofocusMode"}, + { 0x0006, "FocusSetting"}, + { 0x0007, "WhiteBalance"}, + { 0x0008, "ExposureMode"}, + { 0x0009, "MeteringMode"}, + { 0x000a, "LensRange"}, + { 0x000b, "ColorSpace"}, + { 0x000c, "Exposure"}, + { 0x000d, "Contrast"}, + { 0x000e, "Shadow"}, + { 0x000f, "Highlight"}, + { 0x0010, "Saturation"}, + { 0x0011, "Sharpness"}, + { 0x0012, "FillLight"}, + { 0x0014, "ColorAdjustment"}, + { 0x0015, "AdjustmentMode"}, + { 0x0016, "Quality"}, + { 0x0017, "Firmware"}, + { 0x0018, "Software"}, + { 0x0019, "AutoBracket"}, + TAG_TABLE_END +}; + +static tag_info_array tag_table_VND_KYOCERA = { + { 0x0001, "FormatThumbnail"}, + { 0x0E00, "PrintImageMatchingInfo"}, + TAG_TABLE_END +}; + +static tag_info_array tag_table_VND_RICOH = { + { 0x0001, "MakerNoteDataType"}, + { 0x0002, "Version"}, + { 0x0E00, "PrintImageMatchingInfo"}, + { 0x2001, "RicohCameraInfoMakerNoteSubIFD"}, + 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 +#ifdef KALLE_0 + , MN_OFFSET_GUESS +#endif +} mn_offset_mode_t; + +typedef struct { + tag_table_type tag_table; + char * make; + char * id_string; + int id_string_len; + int offset; + mn_byte_order_t byte_order; + mn_offset_mode_t offset_mode; +} maker_note_type; + +/* Remember to update PHP_MINFO if updated */ +static const maker_note_type maker_note_array[] = { + { tag_table_VND_CANON, "Canon", NULL, 0, 0, MN_ORDER_INTEL, MN_OFFSET_NORMAL}, + { tag_table_VND_CASIO, "CASIO", NULL, 0, 0, MN_ORDER_MOTOROLA, MN_OFFSET_NORMAL}, + { tag_table_VND_FUJI, "FUJIFILM", "FUJIFILM\x0C\x00\x00\x00", 12, 12, MN_ORDER_INTEL, MN_OFFSET_MAKER}, + { tag_table_VND_NIKON, "NIKON", "Nikon\x00\x01\x00", 8, 8, MN_ORDER_NORMAL, MN_OFFSET_NORMAL}, + { tag_table_VND_NIKON_990, "NIKON", NULL, 0, 0, MN_ORDER_NORMAL, MN_OFFSET_NORMAL}, + { tag_table_VND_OLYMPUS, "OLYMPUS OPTICAL CO.,LTD", "OLYMP\x00\x01\x00", 8, 8, MN_ORDER_NORMAL, MN_OFFSET_NORMAL}, + { tag_table_VND_SAMSUNG, "SAMSUNG", NULL, 0, 0, MN_ORDER_NORMAL, MN_OFFSET_NORMAL}, + { tag_table_VND_PANASONIC, "Panasonic", "Panasonic\x00\x00\x00", 12, 12, MN_ORDER_NORMAL, MN_OFFSET_NORMAL}, + { tag_table_VND_DJI, "DJI", NULL, 0, 0, MN_ORDER_NORMAL, MN_OFFSET_NORMAL}, + { tag_table_VND_SONY, "SONY", "SONY DSC \x00\x00\x00", 12, 12, MN_ORDER_NORMAL, MN_OFFSET_NORMAL}, + { tag_table_VND_PENTAX, "PENTAX", "AOC\x00", 6, 6, MN_ORDER_NORMAL, MN_OFFSET_NORMAL}, + { tag_table_VND_MINOLTA, "Minolta, KONICA MINOLTA", NULL, 0, 0, MN_ORDER_NORMAL, MN_OFFSET_NORMAL}, + { tag_table_VND_SIGMA, "SIGMA, FOVEON", "SIGMA\x00\x00\x00", 10, 10, MN_ORDER_NORMAL, MN_OFFSET_NORMAL}, + { tag_table_VND_SIGMA, "SIGMA, FOVEON", "FOVEON\x00\x00\x00", 10, 10, MN_ORDER_NORMAL, MN_OFFSET_NORMAL}, + { tag_table_VND_KYOCERA, "KYOCERA, CONTAX", "KYOCERA \x00\x00\x00", 22, 22, MN_ORDER_NORMAL, MN_OFFSET_MAKER}, + { tag_table_VND_RICOH, "RICOH", "Ricoh", 5, 5, MN_ORDER_MOTOROLA, MN_OFFSET_NORMAL}, + { tag_table_VND_RICOH, "RICOH", "RICOH", 5, 5, MN_ORDER_MOTOROLA, MN_OFFSET_NORMAL}, + + /* These re-uses existing formats */ + { tag_table_VND_OLYMPUS, "AGFA", "AGFA \x00\x01", 8, 8, MN_ORDER_NORMAL, MN_OFFSET_NORMAL}, + { tag_table_VND_OLYMPUS, "EPSON", "EPSON\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<len+15 && p+n<=sizeof(buf); i++) { + if (i%16==0) { + p += slprintf(buf+p, sizeof(buf)-p, "\n%08X: ", i+offset); + } + if (i<len) { + c = *addr++; + p += slprintf(buf+p, sizeof(buf)-p, "%02X ", c); + tmp[i%16] = c>=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] = (char) ((value & 0x00FF0000) >> 16); + data[2] = (value & 0x0000FF00) >> 8; + data[3] = (value & 0x000000FF); + } else { + data[3] = (value & 0xFF000000) >> 24; + data[2] = (char) ((value & 0x00FF0000) >> 16); + data[1] = (value & 0x0000FF00) >> 8; + data[0] = (value & 0x000000FF); + } +} +/* }}} */ + +static float php_ifd_get_float(char *data) { + /* Copy to avoid alignment issues */ + float f; + memcpy(&f, data, sizeof(float)); + return f; +} + +static double php_ifd_get_double(char *data) { + /* Copy to avoid alignment issues */ + double f; + memcpy(&f, data, sizeof(double)); + return f; +} + +#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 : "<no data>"; + } + if (format == TAG_FMT_UNDEFINED) { + return "<undefined>"; + } + if (format == TAG_FMT_IFD) { + return ""; + } + if (format == TAG_FMT_SINGLE || format == TAG_FMT_DOUBLE) { + return "<not implemented>"; + } + *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) php_ifd_get_float(value); + case TAG_FMT_DOUBLE: +#ifdef EXIF_DEBUG + php_error_docref(NULL, E_NOTICE, "Found value of type double"); +#endif + return php_ifd_get_double(value); + } + return 0; +} +/* }}} */ + +/* {{{ exif_rewrite_tag_format_to_unsigned + * Rewrite format tag so that it specifies an unsigned type for a tag */ +static int exif_rewrite_tag_format_to_unsigned(int format) +{ + switch(format) { + case TAG_FMT_SBYTE: return TAG_FMT_BYTE; + case TAG_FMT_SRATIONAL: return TAG_FMT_URATIONAL; + case TAG_FMT_SSHORT: return TAG_FMT_USHORT; + case TAG_FMT_SLONG: return TAG_FMT_ULONG; + } + return format; +} +/* }}} */ + +/* {{{ 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) php_ifd_get_float(value); + case TAG_FMT_DOUBLE: +#ifdef EXIF_DEBUG + php_error_docref(NULL, E_NOTICE, "Found value of type double"); +#endif + return (size_t) php_ifd_get_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<<SECTION_FILE) +#define FOUND_COMPUTED (1<<SECTION_COMPUTED) +#define FOUND_ANY_TAG (1<<SECTION_ANY_TAG) +#define FOUND_IFD0 (1<<SECTION_IFD0) +#define FOUND_THUMBNAIL (1<<SECTION_THUMBNAIL) +#define FOUND_COMMENT (1<<SECTION_COMMENT) +#define FOUND_APP0 (1<<SECTION_APP0) +#define FOUND_EXIF (1<<SECTION_EXIF) +#define FOUND_FPIX (1<<SECTION_FPIX) +#define FOUND_GPS (1<<SECTION_GPS) +#define FOUND_INTEROP (1<<SECTION_INTEROP) +#define FOUND_APP12 (1<<SECTION_APP12) +#define FOUND_WINXP (1<<SECTION_WINXP) +#define FOUND_MAKERNOTE (1<<SECTION_MAKERNOTE) + +static char *exif_get_sectionname(int section) +{ + switch(section) { + case SECTION_FILE: return "FILE"; + case SECTION_COMPUTED: return "COMPUTED"; + case SECTION_ANY_TAG: return "ANY_TAG"; + case SECTION_IFD0: return "IFD0"; + case SECTION_THUMBNAIL: return "THUMBNAIL"; + case SECTION_COMMENT: return "COMMENT"; + case SECTION_APP0: return "APP0"; + case SECTION_EXIF: return "EXIF"; + case SECTION_FPIX: return "FPIX"; + case SECTION_GPS: return "GPS"; + case SECTION_INTEROP: return "INTEROP"; + case SECTION_APP12: return "APP12"; + case SECTION_WINXP: return "WINXP"; + case SECTION_MAKERNOTE: return "MAKERNOTE"; + } + return ""; +} + +static tag_table_type exif_get_tag_table(int section) +{ + switch(section) { + case SECTION_FILE: return &tag_table_IFD[0]; + case SECTION_COMPUTED: return &tag_table_IFD[0]; + case SECTION_ANY_TAG: return &tag_table_IFD[0]; + case SECTION_IFD0: return &tag_table_IFD[0]; + case SECTION_THUMBNAIL: return &tag_table_IFD[0]; + case SECTION_COMMENT: return &tag_table_IFD[0]; + case SECTION_APP0: return &tag_table_IFD[0]; + case SECTION_EXIF: return &tag_table_IFD[0]; + case SECTION_FPIX: return &tag_table_IFD[0]; + case SECTION_GPS: return &tag_table_GPS[0]; + case SECTION_INTEROP: return &tag_table_IOP[0]; + case SECTION_APP12: return &tag_table_IFD[0]; + case SECTION_WINXP: return &tag_table_IFD[0]; + } + return &tag_table_IFD[0]; +} +/* }}} */ + +/* {{{ exif_get_sectionlist + Return list of sectionnames specified by sectionlist. Return value must be freed +*/ +static char *exif_get_sectionlist(int sectionlist) +{ + int i, len, ml = 0; + char *sections; + + for(i=0; i<SECTION_COUNT; i++) { + ml += strlen(exif_get_sectionname(i))+2; + } + sections = safe_emalloc(ml, 1, 1); + sections[0] = '\0'; + len = 0; + for(i=0; i<SECTION_COUNT; i++) { + if (sectionlist&(1<<i)) { + snprintf(sections+len, ml-len, "%s, ", exif_get_sectionname(i)); + len = strlen(sections); + } + } + if (len>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 "<Photographer> [ '\0' <Editor> ] ['\0']" */ + char *CopyrightPhotographer; + char *CopyrightEditor; + + xp_field_list xp_fields; + + thumbnail_data Thumbnail; + /* other */ + int sections_found; /* FOUND_<marker> */ + 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 && ImageInfo->FileName ? ImageInfo->FileName:"", type, buf, args); + efree(buf); + } +#else + php_verror(docref, ImageInfo && 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; i<ImageInfo->file.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, size_t value_len, int motorola_intel) +{ + size_t idex; + void *vptr, *vptr_end; + 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 (length > value_len) { + exif_error_docref("exif_iif_add_value" EXIFERR_CC, image_info, E_WARNING, "length > value_len: %d > %zu", length, value_len); + value = NULL; + } + if (value) { + length = (int)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 (length > value_len) { + exif_error_docref("exif_iif_add_value" EXIFERR_CC, image_info, E_WARNING, "length > value_len: %d > %zu", length, value_len); + value = NULL; + } + if (value) { + if (tag == TAG_MAKER_NOTE) { + length = (int) php_strnlen(value, length); + } + + /* 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; + } + vptr_end = (char *) value + value_len; + for (idex=0,vptr=value; idex<(size_t)length; idex++,vptr=(char *) vptr + php_tiff_bytes_per_format[format]) { + if ((char *) vptr_end - (char *) vptr < php_tiff_bytes_per_format[format]) { + exif_error_docref("exif_iif_add_value" EXIFERR_CC, image_info, E_WARNING, "Value too short"); + break; + } + 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 = php_ifd_get_float(value); + break; + case TAG_FMT_DOUBLE: +#ifdef EXIF_DEBUG + php_error_docref(NULL, E_WARNING, "Found value of type double"); +#endif + info_value->d = php_ifd_get_double(value); + break; + } + } + } + image_info->sections_found |= 1<<section_index; + image_info->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, size_t value_len) +{ + exif_iif_add_value(image_info, section_index, name, tag, format, (int)length, value, value_len, 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<<section_index; + image_info->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<<section_index; + image_info->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<<section_index; + image_info->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; i<image_info->info_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:"<no data>"):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; ap<l; ap++) { + if (l>1) { + 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;b<l;b++) { + add_index_long(&array, b, (int)(info_value->s[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), "%u/%u", 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;b<l;b++) { + add_index_long(&array, ap, (int)info_value->s[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_<xx> */ +/* 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 <text>" 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, length-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, length); + break; + case 1: + exif_iif_add_tag(image_info, SECTION_COMMENT, "Comment", TAG_COMPUTED_VALUE, TAG_FMT_STRING, length, value, length); + 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, 0); + 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, int tag); +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; i<info_data->length; 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; i<info_list->count; 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; i<info_list->count; 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.size > length + || (ImageInfo->Thumbnail.offset + ImageInfo->Thumbnail.size) > length + || ImageInfo->Thumbnail.offset > length - ImageInfo->Thumbnail.size + ) { + 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 (ByteCount >=2 && !memcmp(szValuePtr, "\xFE\xFF", 2)) { + decode = "UCS-2BE"; + szValuePtr = szValuePtr+2; + ByteCount -= 2; + } else if (ByteCount >=2 && !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; +#ifdef KALLE_0 + int offset_diff; +#endif + const maker_note_type *maker_note; + char *dir_start; + int data_len; + + 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)", maker_note->make?maker_note->make:"");*/ + if (maker_note->make && (!ImageInfo->make || strcmp(maker_note->make, ImageInfo->make))) + 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 (value_len < 2 || maker_note->offset >= value_len - 1) { + /* 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; + data_len = value_len; + break; +#ifdef KALLE_0 + 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; + data_len = value_len - offset_diff; + break; +#endif + default: + case MN_OFFSET_NORMAL: + data_len = value_len; + 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; + } + if ((dir_start - value_ptr) > value_len - (2+NumDirEntries*12)) { + exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "Illegal IFD size: 0x%04X > 0x%04X", (dir_start - value_ptr) + (2+NumDirEntries*12), value_len); + return FALSE; + } + + for (de=0;de<NumDirEntries;de++) { + size_t offset = 2 + 12 * de; + if (!exif_process_IFD_TAG(ImageInfo, dir_start + offset, + offset_base, data_len - offset, displacement, section_index, 0, maker_note->tag_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; + unsigned 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) || 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 (fgot<byte_count) { + EFREE_IF(outside); + EXIF_ERRLOG_FILEEOF(ImageInfo) + return FALSE; + } + } + } else { + /* 4 bytes or less and value is in the dir entry itself */ + value_ptr = dir_entry+8; + offset_val= value_ptr-offset_base; + } + + ImageInfo->sections_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, exif_rewrite_tag_format_to_unsigned(format), ImageInfo->motorola_intel); + break; + + case TAG_IMAGEHEIGHT: + case TAG_COMP_IMAGE_HEIGHT: + ImageInfo->Thumbnail.height = exif_convert_any_to_int(value_ptr, exif_rewrite_tag_format_to_unsigned(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, exif_rewrite_tag_format_to_unsigned(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, exif_rewrite_tag_format_to_unsigned(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, exif_rewrite_tag_format_to_unsigned(format), ImageInfo->motorola_intel); + } + break; + } + } + } else { + if (section_index==SECTION_IFD0 || section_index==SECTION_EXIF) + switch(tag) { + case TAG_COPYRIGHT: + /* check for "<photographer> NUL <editor> NUL" */ + if (byte_count>1 && (length=php_strnlen(value_ptr, byte_count)) > 0) { + if (length<byte_count-1) { + /* When there are any characters after the first NUL */ + ImageInfo->CopyrightPhotographer = 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: + EFREE_IF(ImageInfo->UserComment); + ImageInfo->UserComment = NULL; + EFREE_IF(ImageInfo->UserCommentEncoding); + ImageInfo->UserCommentEncoding = NULL; + 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, exif_rewrite_tag_format_to_unsigned(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, tag)) { + 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, byte_count); + 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 tag) +{ + int de; + int NumDirEntries; + int NextDirOffset = 0; + +#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<NumDirEntries;de++) { + if (!exif_process_IFD_TAG(ImageInfo, dir_start + 2 + 12 * de, + offset_base, IFDlength, displacement, section_index, 1, exif_get_tag_table(section_index))) { + return FALSE; + } + } + /* + * Ignore IFD2 if it purportedly exists + */ + if (section_index == SECTION_THUMBNAIL) { + return TRUE; + } + /* + * Hack to make it process IDF1 I hope + * There are 2 IDFs, the second one holds the keys (0x0201 and 0x0202) to the thumbnail + */ + if ((dir_start+2+12*de + 4) > (offset_base+IFDlength)) { + exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "Illegal IFD size"); + return FALSE; + } + + if (tag != TAG_EXIF_IFD_POINTER && tag != TAG_GPS_IFD_POINTER) { + 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, 0)) { +#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, 0); + +#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, l1); + 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, l2); + } + } +#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 || ImageInfo->Thumbnail.size < 4) { + 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; + if (pos>=ImageInfo->Thumbnail.size) + return FALSE; + length = php_jpg_get16(data+pos); + if (length > ImageInfo->Thumbnail.size || pos >= ImageInfo->Thumbnail.size - length) { + 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 */ + if (length < 8 || ImageInfo->Thumbnail.size - 8 < pos) { + /* exif_process_SOFn needs 8 bytes */ + return FALSE; + } + 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;i<num_entries;i++) { + dir_entry = ImageInfo->file.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;i<num_entries;i++) { + dir_entry = ImageInfo->file.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; i<ImageInfo->xp_fields.count; i++) { + EFREE_IF(ImageInfo->xp_fields.list[i].value); + } + EFREE_IF(ImageInfo->xp_fields.list); + for (i=0; i<SECTION_COUNT; i++) { + exif_iif_free(ImageInfo, i); + } + exif_file_sections_free(ImageInfo); + memset(ImageInfo, 0, sizeof(*ImageInfo)); + return TRUE; +} +/* }}} */ + +/* {{{ exif_read_from_impl + */ +static int exif_read_from_impl(image_info_type *ImageInfo, php_stream *stream, int read_thumbnail, int read_all) +{ + int ret; + zend_stat_t st; + + /* Start with an empty image information structure. */ + memset(ImageInfo, 0, sizeof(*ImageInfo)); + + ImageInfo->motorola_intel = -1; /* flag as unknown */ + ImageInfo->infile = stream; + ImageInfo->FileName = NULL; + + if (php_stream_is(ImageInfo->infile, PHP_STREAM_IS_STDIO)) { + if (VCWD_STAT(stream->orig_path, &st) >= 0) { + zend_string *base; + if ((st.st_mode & S_IFMT) != S_IFREG) { + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Not a file"); + ImageInfo->infile = NULL; + return FALSE; + } + + /* Store file name */ + base = php_basename(stream->orig_path, strlen(stream->orig_path), NULL, 0); + ImageInfo->FileName = estrndup(ZSTR_VAL(base), ZSTR_LEN(base)); + + zend_string_release_ex(base, 0); + + /* 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); + } + } + + 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 headers */ + ret = exif_scan_FILE_header(ImageInfo); + + return ret; +} +/* }}} */ + +/* {{{ exif_read_from_stream + */ +static int exif_read_from_stream(image_info_type *ImageInfo, php_stream *stream, int read_thumbnail, int read_all) +{ + int ret; + off_t old_pos = php_stream_tell(stream); + + if (old_pos) { + php_stream_seek(stream, 0, SEEK_SET); + } + + ret = exif_read_from_impl(ImageInfo, stream, read_thumbnail, read_all); + + if (old_pos) { + php_stream_seek(stream, old_pos, SEEK_SET); + } + + return ret; +} +/* }}} */ + +/* {{{ exif_read_from_file + */ +static int exif_read_from_file(image_info_type *ImageInfo, char *FileName, int read_thumbnail, int read_all) +{ + int ret; + php_stream *stream; + + stream = php_stream_open_wrapper(FileName, "rb", STREAM_MUST_SEEK | IGNORE_PATH, NULL); + + if (!stream) { + memset(&ImageInfo, 0, sizeof(ImageInfo)); + + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Unable to open file"); + + return FALSE; + } + + ret = exif_read_from_stream(ImageInfo, stream, read_thumbnail, read_all); + + php_stream_close(stream); + + return ret; +} +/* }}} */ + +/* {{{ proto array exif_read_data(mixed stream [, string sections_needed [, bool sub_arrays[, bool read_thumbnail]]]) + Reads header data from an image and optionally reads the internal thumbnails */ +PHP_FUNCTION(exif_read_data) +{ + zend_string *z_sections_needed = NULL; + zend_bool sub_arrays = 0, read_thumbnail = 0, read_all = 0; + zval *stream; + int i, ret, sections_needed = 0; + image_info_type ImageInfo; + char tmp[64], *sections_str, *s; + + /* Parse arguments */ + ZEND_PARSE_PARAMETERS_START(1, 4) + Z_PARAM_ZVAL(stream) + Z_PARAM_OPTIONAL + Z_PARAM_STR(z_sections_needed) + Z_PARAM_BOOL(sub_arrays) + Z_PARAM_BOOL(read_thumbnail) + ZEND_PARSE_PARAMETERS_END(); + + memset(&ImageInfo, 0, sizeof(ImageInfo)); + + if (z_sections_needed) { + spprintf(§ions_str, 0, ",%s,", ZSTR_VAL(z_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<<i; + } + } + EFREE_IF(sections_str); + /* now see what we need */ +#ifdef EXIF_DEBUG + sections_str = exif_get_sectionlist(sections_needed); + if (!sections_str) { + RETURN_FALSE; + } + exif_error_docref(NULL EXIFERR_CC, &ImageInfo, E_NOTICE, "Sections needed: %s", sections_str[0] ? sections_str : "None"); + EFREE_IF(sections_str); +#endif + } + + if (Z_TYPE_P(stream) == IS_RESOURCE) { + php_stream *p_stream = NULL; + + php_stream_from_res(p_stream, Z_RES_P(stream)); + + ret = exif_read_from_stream(&ImageInfo, p_stream, read_thumbnail, read_all); + } else { + convert_to_string(stream); + + if (!Z_STRLEN_P(stream)) { + exif_error_docref(NULL EXIFERR_CC, &ImageInfo, E_WARNING, "Filename cannot be empty"); + + RETURN_FALSE; + } + + ret = exif_read_from_file(&ImageInfo, Z_STRVAL_P(stream), read_thumbnail, read_all); + } + + sections_str = exif_get_sectionlist(ImageInfo.sections_found); + +#ifdef EXIF_DEBUG + if (sections_str) { + exif_error_docref(NULL EXIFERR_CC, &ImageInfo, E_NOTICE, "Sections found: %s", sections_str[0] ? sections_str : "None"); + } +#endif + + ImageInfo.sections_found |= FOUND_COMPUTED|FOUND_FILE;/* do not inform about in debug*/ + + if (ret == FALSE || (sections_needed && !(sections_needed&ImageInfo.sections_found))) { + /* array_init must be checked at last! otherwise the array must be freed if a later test fails. */ + exif_discard_imageinfo(&ImageInfo); + EFREE_IF(sections_str); + RETURN_FALSE; + } + + array_init(return_value); + +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, &ImageInfo, E_NOTICE, "Generate section FILE"); +#endif + + /* now we can add our information */ + exif_iif_add_str(&ImageInfo, SECTION_FILE, "FileName", ImageInfo.FileName); + exif_iif_add_int(&ImageInfo, SECTION_FILE, "FileDateTime", ImageInfo.FileDateTime); + exif_iif_add_int(&ImageInfo, SECTION_FILE, "FileSize", ImageInfo.FileSize); + exif_iif_add_int(&ImageInfo, SECTION_FILE, "FileType", ImageInfo.FileType); + exif_iif_add_str(&ImageInfo, SECTION_FILE, "MimeType", (char*)php_image_type_to_mime_type(ImageInfo.FileType)); + exif_iif_add_str(&ImageInfo, SECTION_FILE, "SectionsFound", sections_str ? sections_str : "NONE"); + +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, &ImageInfo, E_NOTICE, "Generate section COMPUTED"); +#endif + + if (ImageInfo.Width>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<ImageInfo.xp_fields.count; i++) { + exif_iif_add_str(&ImageInfo, SECTION_WINXP, exif_get_tagname(ImageInfo.xp_fields.list[i].tag, NULL, 0, exif_get_tag_table(SECTION_WINXP)), ImageInfo.xp_fields.list[i].value); + } + if (ImageInfo.Thumbnail.size) { + if (read_thumbnail) { + /* not exif_iif_add_str : this is a buffer */ + exif_iif_add_tag(&ImageInfo, SECTION_THUMBNAIL, "THUMBNAIL", TAG_NONE, TAG_FMT_UNDEFINED, ImageInfo.Thumbnail.size, ImageInfo.Thumbnail.data, ImageInfo.Thumbnail.size); + } + if (!ImageInfo.Thumbnail.width || !ImageInfo.Thumbnail.height) { + /* try to evaluate if thumbnail data is present */ + exif_scan_thumbnail(&ImageInfo); + } + exif_iif_add_int(&ImageInfo, SECTION_COMPUTED, "Thumbnail.FileType", ImageInfo.Thumbnail.filetype); + exif_iif_add_str(&ImageInfo, SECTION_COMPUTED, "Thumbnail.MimeType", (char*)php_image_type_to_mime_type(ImageInfo.Thumbnail.filetype)); + } + if (ImageInfo.Thumbnail.width && ImageInfo.Thumbnail.height) { + exif_iif_add_int(&ImageInfo, SECTION_COMPUTED, "Thumbnail.Height", ImageInfo.Thumbnail.height); + exif_iif_add_int(&ImageInfo, SECTION_COMPUTED, "Thumbnail.Width", ImageInfo.Thumbnail.width); + } + EFREE_IF(sections_str); + +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, &ImageInfo, E_NOTICE, "Adding image infos"); +#endif + + add_assoc_image_info(return_value, sub_arrays, &ImageInfo, SECTION_FILE ); + add_assoc_image_info(return_value, 1, &ImageInfo, SECTION_COMPUTED ); + add_assoc_image_info(return_value, sub_arrays, &ImageInfo, SECTION_ANY_TAG ); + add_assoc_image_info(return_value, sub_arrays, &ImageInfo, SECTION_IFD0 ); + add_assoc_image_info(return_value, 1, &ImageInfo, SECTION_THUMBNAIL ); + add_assoc_image_info(return_value, 1, &ImageInfo, SECTION_COMMENT ); + add_assoc_image_info(return_value, sub_arrays, &ImageInfo, SECTION_EXIF ); + add_assoc_image_info(return_value, sub_arrays, &ImageInfo, SECTION_GPS ); + add_assoc_image_info(return_value, sub_arrays, &ImageInfo, SECTION_INTEROP ); + add_assoc_image_info(return_value, sub_arrays, &ImageInfo, SECTION_FPIX ); + add_assoc_image_info(return_value, sub_arrays, &ImageInfo, SECTION_APP12 ); + add_assoc_image_info(return_value, sub_arrays, &ImageInfo, SECTION_WINXP ); + add_assoc_image_info(return_value, sub_arrays, &ImageInfo, SECTION_MAKERNOTE ); + +#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, (Z_TYPE_P(stream) == IS_RESOURCE ? "<stream>" : Z_STRVAL_P(stream)), E_NOTICE, "Done"); +#endif +} +/* }}} */ + +/* {{{ proto string exif_thumbnail(string filename [, &width, &height [, &imagetype]]) + Reads the embedded thumbnail */ +PHP_FUNCTION(exif_thumbnail) +{ + int ret, arg_c = ZEND_NUM_ARGS(); + image_info_type ImageInfo; + zval *stream; + zval *z_width = NULL, *z_height = NULL, *z_imagetype = NULL; + + /* Parse arguments */ + ZEND_PARSE_PARAMETERS_START(1, 4) + Z_PARAM_ZVAL(stream) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL_DEREF(z_width) + Z_PARAM_ZVAL_DEREF(z_height) + Z_PARAM_ZVAL_DEREF(z_imagetype) + ZEND_PARSE_PARAMETERS_END(); + + memset(&ImageInfo, 0, sizeof(ImageInfo)); + + if (Z_TYPE_P(stream) == IS_RESOURCE) { + php_stream *p_stream = NULL; + + php_stream_from_res(p_stream, Z_RES_P(stream)); + + ret = exif_read_from_stream(&ImageInfo, p_stream, 1, 0); + } else { + convert_to_string(stream); + + if (!Z_STRLEN_P(stream)) { + exif_error_docref(NULL EXIFERR_CC, &ImageInfo, E_WARNING, "Filename cannot be empty"); + + RETURN_FALSE; + } + + ret = exif_read_from_file(&ImageInfo, Z_STRVAL_P(stream), 1, 0); + } + + if (ret == FALSE) { + exif_discard_imageinfo(&ImageInfo); + RETURN_FALSE; + } + +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, &ImageInfo, E_NOTICE, "Thumbnail data %d %d %d, %d x %d", ImageInfo.Thumbnail.data, ImageInfo.Thumbnail.size, ImageInfo.Thumbnail.filetype, ImageInfo.Thumbnail.width, ImageInfo.Thumbnail.height); +#endif + if (!ImageInfo.Thumbnail.data || !ImageInfo.Thumbnail.size) { + exif_discard_imageinfo(&ImageInfo); + RETURN_FALSE; + } + +#ifdef EXIF_DEBUG + exif_error_docref(NULL EXIFERR_CC, &ImageInfo, E_NOTICE, "Returning thumbnail(%d)", ImageInfo.Thumbnail.size); +#endif + + ZVAL_STRINGL(return_value, ImageInfo.Thumbnail.data, ImageInfo.Thumbnail.size); + if (arg_c >= 3) { + if (!ImageInfo.Thumbnail.width || !ImageInfo.Thumbnail.height) { + if (!exif_scan_thumbnail(&ImageInfo)) { + ImageInfo.Thumbnail.width = ImageInfo.Thumbnail.height = 0; + } + } + zval_ptr_dtor(z_width); + zval_ptr_dtor(z_height); + ZVAL_LONG(z_width, ImageInfo.Thumbnail.width); + ZVAL_LONG(z_height, ImageInfo.Thumbnail.height); + } + if (arg_c >= 4) { + zval_ptr_dtor(z_imagetype); + ZVAL_LONG(z_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, (Z_TYPE_P(stream) == IS_RESOURCE ? "<stream>" : Z_STRVAL_P(stream)), 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/bug78793.phpt b/ext/exif/tests/bug78793.phpt new file mode 100644 index 0000000000000..033f255ace038 --- /dev/null +++ b/ext/exif/tests/bug78793.phpt @@ -0,0 +1,12 @@ +--TEST-- +Bug #78793: Use-after-free in exif parsing under memory sanitizer +--FILE-- +<?php +$f = "ext/exif/tests/bug77950.tiff"; +for ($i = 0; $i < 10; $i++) { + @exif_read_data($f); +} +?> +===DONE=== +--EXPECT-- +===DONE=== 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-- +<?php + +var_dump(exif_read_data('data:image/jpg;base64,TU0AKgAAAAwgICAgAAIBDwAEAAAAAgAAACKSfCAgAAAAAEZVSklGSUxN')); + +?> +--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-- +<?php + +var_dump(exif_read_data('data://image/jpeg;base64,/9jhAAlFeGlmAAAg')); + +?> +--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/libxml/libxml.c b/ext/libxml/libxml.c index b0b94b7c3a71c..b428ff5bccda2 100644 --- a/ext/libxml/libxml.c +++ b/ext/libxml/libxml.c @@ -306,6 +306,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 || @@ -436,6 +440,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..b0b94b7c3a71c --- /dev/null +++ b/ext/libxml/libxml.c.orig @@ -0,0 +1,1365 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2018 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 <shane@php.net> | + | Wez Furlong <wez@thebrainroom.com> | + +----------------------------------------------------------------------+ + */ + +#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 <libxml/parser.h> +#include <libxml/parserInternals.h> +#include <libxml/tree.h> +#include <libxml/uri.h> +#include <libxml/xmlerror.h> +#include <libxml/xmlsave.h> +#ifdef LIBXML_SCHEMAS_ENABLED +#include <libxml/relaxng.h> +#include <libxml/xmlschemas.h> +#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_NULL(¶ms[0]); + } + if (URL != NULL) { + ZVAL_STRING(¶ms[1], URL); + } else { + ZVAL_NULL(¶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_ADDREF(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", + "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; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_RESOURCE(arg) + ZEND_PARSE_PARAMETERS_END(); + + 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; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(use_errors) + ZEND_PARSE_PARAMETERS_END(); + + 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 (LIBXML(error_list)) { + + array_init(return_value); + 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)); + } + } else { + ZVAL_EMPTY_ARRAY(return_value); + } +} +/* }}} */ + +/* {{{ 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; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(disable) + ZEND_PARSE_PARAMETERS_END(); + + 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; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_FUNC_EX(fci, fcc, 1, 0) + ZEND_PARSE_PARAMETERS_END(); + + _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/php_unicode.c b/ext/mbstring/php_unicode.c index ac452b6a20776..acb16bf06e4cc 100644 --- a/ext/mbstring/php_unicode.c +++ b/ext/mbstring/php_unicode.c @@ -315,7 +315,7 @@ static int convert_case_filter(int c, void *void_data) /* Handle invalid characters early, as we assign special meaning to * codepoints above 0xffffff. */ - if (UNEXPECTED(c > 0xffffff)) { + if (UNEXPECTED((unsigned) c > 0xffffff)) { (*data->next_filter->filter_function)(c, data->next_filter); return 0; } diff --git a/ext/mbstring/tests/bug79371.phpt b/ext/mbstring/tests/bug79371.phpt new file mode 100644 index 0000000000000..3014feba5369f --- /dev/null +++ b/ext/mbstring/tests/bug79371.phpt @@ -0,0 +1,14 @@ +--TEST-- +Bug #79371 (mb_strtolower (UTF-32LE): stack-buffer-overflow) +--SKIPIF-- +<?php +if (!extension_loaded('mbstring')) die('skip mbstring extension not available'); +?> +--FILE-- +<?php +$bytes = array(0xef, 0xbf, 0xbd, 0xef); +$str = implode(array_map("chr", $bytes)); +var_dump(bin2hex(mb_strtolower($str, "UTF-32LE"))); +?> +--EXPECT-- +string(8) "3f000000" diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index 4cc3bd544675a..4a621883c055f 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -6443,11 +6443,6 @@ static int php_openssl_validate_iv(char **piv, size_t *piv_len, size_t iv_requir { char *iv_new; - /* Best case scenario, user behaved */ - if (*piv_len == iv_required_len) { - return SUCCESS; - } - if (mode->is_aead) { if (EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_ivlen_flag, *piv_len, NULL) != 1) { php_error_docref(NULL, E_WARNING, "Setting of IV length for AEAD mode failed"); @@ -6456,6 +6451,11 @@ static int php_openssl_validate_iv(char **piv, size_t *piv_len, size_t iv_requir return SUCCESS; } + /* Best case scenario, user behaved */ + if (*piv_len == iv_required_len) { + return SUCCESS; + } + iv_new = ecalloc(1, iv_required_len + 1); if (*piv_len == 0) { diff --git a/ext/openssl/openssl.c.orig b/ext/openssl/openssl.c.orig new file mode 100644 index 0000000000000..4cc3bd544675a --- /dev/null +++ b/ext/openssl/openssl.c.orig @@ -0,0 +1,6886 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2018 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 Venaas <venaas@php.net> | + | Wez Furlong <wez@thebrainroom.com> | + | Sascha Kettler <kettler@gmx.net> | + | Pierre-Alain Joye <pierre@php.net> | + | Marc Delling <delling@silpion.de> (PKCS12 functions) | + | Jakub Zelenka <bukka@php.net> | + +----------------------------------------------------------------------+ + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "php_openssl.h" + +/* PHP Includes */ +#include "ext/standard/file.h" +#include "ext/standard/info.h" +#include "ext/standard/php_fopen_wrappers.h" +#include "ext/standard/md5.h" +#include "ext/standard/base64.h" +#ifdef PHP_WIN32 +# include "win32/winutil.h" +#endif + +/* OpenSSL includes */ +#include <openssl/evp.h> +#include <openssl/bn.h> +#include <openssl/rsa.h> +#include <openssl/dsa.h> +#include <openssl/dh.h> +#include <openssl/x509.h> +#include <openssl/x509v3.h> +#include <openssl/crypto.h> +#include <openssl/pem.h> +#include <openssl/err.h> +#include <openssl/conf.h> +#include <openssl/rand.h> +#include <openssl/ssl.h> +#include <openssl/pkcs12.h> + +/* Common */ +#include <time.h> + +#if (defined(PHP_WIN32) && defined(_MSC_VER) && _MSC_VER >= 1900) +#define timezone _timezone /* timezone is called _timezone in LibC */ +#endif + +#define MIN_KEY_LENGTH 384 + +#define OPENSSL_ALGO_SHA1 1 +#define OPENSSL_ALGO_MD5 2 +#define OPENSSL_ALGO_MD4 3 +#ifdef HAVE_OPENSSL_MD2_H +#define OPENSSL_ALGO_MD2 4 +#endif +#if PHP_OPENSSL_API_VERSION < 0x10100 +#define OPENSSL_ALGO_DSS1 5 +#endif +#define OPENSSL_ALGO_SHA224 6 +#define OPENSSL_ALGO_SHA256 7 +#define OPENSSL_ALGO_SHA384 8 +#define OPENSSL_ALGO_SHA512 9 +#define OPENSSL_ALGO_RMD160 10 +#define DEBUG_SMIME 0 + +#if !defined(OPENSSL_NO_EC) && defined(EVP_PKEY_EC) +#define HAVE_EVP_PKEY_EC 1 +#endif + +ZEND_DECLARE_MODULE_GLOBALS(openssl) + +/* FIXME: Use the openssl constants instead of + * enum. It is now impossible to match real values + * against php constants. Also sorry to break the + * enum principles here, BC... + */ +enum php_openssl_key_type { + OPENSSL_KEYTYPE_RSA, + OPENSSL_KEYTYPE_DSA, + OPENSSL_KEYTYPE_DH, + OPENSSL_KEYTYPE_DEFAULT = OPENSSL_KEYTYPE_RSA, +#ifdef HAVE_EVP_PKEY_EC + OPENSSL_KEYTYPE_EC = OPENSSL_KEYTYPE_DH +1 +#endif +}; + +enum php_openssl_cipher_type { + PHP_OPENSSL_CIPHER_RC2_40, + PHP_OPENSSL_CIPHER_RC2_128, + PHP_OPENSSL_CIPHER_RC2_64, + PHP_OPENSSL_CIPHER_DES, + PHP_OPENSSL_CIPHER_3DES, + PHP_OPENSSL_CIPHER_AES_128_CBC, + PHP_OPENSSL_CIPHER_AES_192_CBC, + PHP_OPENSSL_CIPHER_AES_256_CBC, + + PHP_OPENSSL_CIPHER_DEFAULT = PHP_OPENSSL_CIPHER_RC2_40 +}; + +PHP_FUNCTION(openssl_get_md_methods); +PHP_FUNCTION(openssl_get_cipher_methods); +#ifdef HAVE_EVP_PKEY_EC +PHP_FUNCTION(openssl_get_curve_names); +#endif + +PHP_FUNCTION(openssl_digest); +PHP_FUNCTION(openssl_encrypt); +PHP_FUNCTION(openssl_decrypt); +PHP_FUNCTION(openssl_cipher_iv_length); + +PHP_FUNCTION(openssl_dh_compute_key); +PHP_FUNCTION(openssl_pkey_derive); +PHP_FUNCTION(openssl_random_pseudo_bytes); + +/* {{{ arginfo */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_x509_export_to_file, 0, 0, 2) + ZEND_ARG_INFO(0, x509) + ZEND_ARG_INFO(0, outfilename) + ZEND_ARG_INFO(0, notext) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_x509_export, 0, 0, 2) + ZEND_ARG_INFO(0, x509) + ZEND_ARG_INFO(1, out) + ZEND_ARG_INFO(0, notext) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_x509_fingerprint, 0, 0, 1) + ZEND_ARG_INFO(0, x509) + ZEND_ARG_INFO(0, method) + ZEND_ARG_INFO(0, raw_output) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_openssl_x509_check_private_key, 0) + ZEND_ARG_INFO(0, cert) + ZEND_ARG_INFO(0, key) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_x509_parse, 0, 0, 1) + ZEND_ARG_INFO(0, x509) + ZEND_ARG_INFO(0, shortname) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_x509_checkpurpose, 0, 0, 2) + ZEND_ARG_INFO(0, x509cert) + ZEND_ARG_INFO(0, purpose) + ZEND_ARG_INFO(0, cainfo) /* array */ + ZEND_ARG_INFO(0, untrustedfile) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_openssl_x509_read, 0) + ZEND_ARG_INFO(0, cert) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_openssl_x509_free, 0) + ZEND_ARG_INFO(0, x509) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_pkcs12_export_to_file, 0, 0, 4) + ZEND_ARG_INFO(0, x509) + ZEND_ARG_INFO(0, filename) + ZEND_ARG_INFO(0, priv_key) + ZEND_ARG_INFO(0, pass) + ZEND_ARG_INFO(0, args) /* array */ +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_pkcs12_export, 0, 0, 4) + ZEND_ARG_INFO(0, x509) + ZEND_ARG_INFO(1, out) + ZEND_ARG_INFO(0, priv_key) + ZEND_ARG_INFO(0, pass) + ZEND_ARG_INFO(0, args) /* array */ +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_openssl_pkcs12_read, 0) + ZEND_ARG_INFO(0, PKCS12) + ZEND_ARG_INFO(1, certs) /* array */ + ZEND_ARG_INFO(0, pass) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_csr_export_to_file, 0, 0, 2) + ZEND_ARG_INFO(0, csr) + ZEND_ARG_INFO(0, outfilename) + ZEND_ARG_INFO(0, notext) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_csr_export, 0, 0, 2) + ZEND_ARG_INFO(0, csr) + ZEND_ARG_INFO(1, out) + ZEND_ARG_INFO(0, notext) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_csr_sign, 0, 0, 4) + ZEND_ARG_INFO(0, csr) + ZEND_ARG_INFO(0, x509) + ZEND_ARG_INFO(0, priv_key) + ZEND_ARG_INFO(0, days) + ZEND_ARG_INFO(0, config_args) /* array */ + ZEND_ARG_INFO(0, serial) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_csr_new, 0, 0, 2) + ZEND_ARG_INFO(0, dn) /* array */ + ZEND_ARG_INFO(1, privkey) + ZEND_ARG_INFO(0, configargs) + ZEND_ARG_INFO(0, extraattribs) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_csr_get_subject, 0, 0, 1) + ZEND_ARG_INFO(0, csr) + ZEND_ARG_INFO(0, use_shortnames) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_csr_get_public_key, 0, 0, 1) + ZEND_ARG_INFO(0, csr) + ZEND_ARG_INFO(0, use_shortnames) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_pkey_new, 0, 0, 0) + ZEND_ARG_INFO(0, configargs) /* array */ +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_pkey_export_to_file, 0, 0, 2) + ZEND_ARG_INFO(0, key) + ZEND_ARG_INFO(0, outfilename) + ZEND_ARG_INFO(0, passphrase) + ZEND_ARG_INFO(0, config_args) /* array */ +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_pkey_export, 0, 0, 2) + ZEND_ARG_INFO(0, key) + ZEND_ARG_INFO(1, out) + ZEND_ARG_INFO(0, passphrase) + ZEND_ARG_INFO(0, config_args) /* array */ +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_openssl_pkey_get_public, 0) + ZEND_ARG_INFO(0, cert) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_openssl_pkey_free, 0) + ZEND_ARG_INFO(0, key) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_pkey_get_private, 0, 0, 1) + ZEND_ARG_INFO(0, key) + ZEND_ARG_INFO(0, passphrase) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_openssl_pkey_get_details, 0) + ZEND_ARG_INFO(0, key) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_pbkdf2, 0, 0, 4) + ZEND_ARG_INFO(0, password) + ZEND_ARG_INFO(0, salt) + ZEND_ARG_INFO(0, key_length) + ZEND_ARG_INFO(0, iterations) + ZEND_ARG_INFO(0, digest_algorithm) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_pkcs7_verify, 0, 0, 2) + ZEND_ARG_INFO(0, filename) + ZEND_ARG_INFO(0, flags) + ZEND_ARG_INFO(0, signerscerts) + ZEND_ARG_INFO(0, cainfo) /* array */ + ZEND_ARG_INFO(0, extracerts) + ZEND_ARG_INFO(0, content) + ZEND_ARG_INFO(0, pk7) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_pkcs7_encrypt, 0, 0, 4) + ZEND_ARG_INFO(0, infile) + ZEND_ARG_INFO(0, outfile) + ZEND_ARG_INFO(0, recipcerts) + ZEND_ARG_INFO(0, headers) /* array */ + ZEND_ARG_INFO(0, flags) + ZEND_ARG_INFO(0, cipher) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_pkcs7_sign, 0, 0, 5) + ZEND_ARG_INFO(0, infile) + ZEND_ARG_INFO(0, outfile) + ZEND_ARG_INFO(0, signcert) + ZEND_ARG_INFO(0, signkey) + ZEND_ARG_INFO(0, headers) /* array */ + ZEND_ARG_INFO(0, flags) + ZEND_ARG_INFO(0, extracertsfilename) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_pkcs7_decrypt, 0, 0, 3) + ZEND_ARG_INFO(0, infilename) + ZEND_ARG_INFO(0, outfilename) + ZEND_ARG_INFO(0, recipcert) + ZEND_ARG_INFO(0, recipkey) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_pkcs7_read, 0, 0, 2) + ZEND_ARG_INFO(0, infilename) + ZEND_ARG_INFO(1, certs) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_private_encrypt, 0, 0, 3) + ZEND_ARG_INFO(0, data) + ZEND_ARG_INFO(1, crypted) + ZEND_ARG_INFO(0, key) + ZEND_ARG_INFO(0, padding) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_private_decrypt, 0, 0, 3) + ZEND_ARG_INFO(0, data) + ZEND_ARG_INFO(1, crypted) + ZEND_ARG_INFO(0, key) + ZEND_ARG_INFO(0, padding) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_public_encrypt, 0, 0, 3) + ZEND_ARG_INFO(0, data) + ZEND_ARG_INFO(1, crypted) + ZEND_ARG_INFO(0, key) + ZEND_ARG_INFO(0, padding) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_public_decrypt, 0, 0, 3) + ZEND_ARG_INFO(0, data) + ZEND_ARG_INFO(1, crypted) + ZEND_ARG_INFO(0, key) + ZEND_ARG_INFO(0, padding) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_openssl_error_string, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_sign, 0, 0, 3) + ZEND_ARG_INFO(0, data) + ZEND_ARG_INFO(1, signature) + ZEND_ARG_INFO(0, key) + ZEND_ARG_INFO(0, method) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_verify, 0, 0, 3) + ZEND_ARG_INFO(0, data) + ZEND_ARG_INFO(0, signature) + ZEND_ARG_INFO(0, key) + ZEND_ARG_INFO(0, method) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_seal, 0, 0, 4) + ZEND_ARG_INFO(0, data) + ZEND_ARG_INFO(1, sealdata) + ZEND_ARG_INFO(1, ekeys) /* array */ + ZEND_ARG_INFO(0, pubkeys) /* array */ + ZEND_ARG_INFO(0, method) + ZEND_ARG_INFO(1, iv) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_open, 0, 0, 4) + ZEND_ARG_INFO(0, data) + ZEND_ARG_INFO(1, opendata) + ZEND_ARG_INFO(0, ekey) + ZEND_ARG_INFO(0, privkey) + ZEND_ARG_INFO(0, method) + ZEND_ARG_INFO(0, iv) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_get_md_methods, 0, 0, 0) + ZEND_ARG_INFO(0, aliases) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_get_cipher_methods, 0, 0, 0) + ZEND_ARG_INFO(0, aliases) +ZEND_END_ARG_INFO() + +#ifdef HAVE_EVP_PKEY_EC +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_get_curve_names, 0, 0, 0) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_digest, 0, 0, 2) + ZEND_ARG_INFO(0, data) + ZEND_ARG_INFO(0, method) + ZEND_ARG_INFO(0, raw_output) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_encrypt, 0, 0, 3) + ZEND_ARG_INFO(0, data) + ZEND_ARG_INFO(0, method) + ZEND_ARG_INFO(0, password) + ZEND_ARG_INFO(0, options) + ZEND_ARG_INFO(0, iv) + ZEND_ARG_INFO(1, tag) + ZEND_ARG_INFO(0, aad) + ZEND_ARG_INFO(0, tag_length) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_decrypt, 0, 0, 3) + ZEND_ARG_INFO(0, data) + ZEND_ARG_INFO(0, method) + ZEND_ARG_INFO(0, password) + ZEND_ARG_INFO(0, options) + ZEND_ARG_INFO(0, iv) + ZEND_ARG_INFO(0, tag) + ZEND_ARG_INFO(0, aad) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_openssl_cipher_iv_length, 0) + ZEND_ARG_INFO(0, method) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_openssl_dh_compute_key, 0) + ZEND_ARG_INFO(0, pub_key) + ZEND_ARG_INFO(0, dh_key) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_pkey_derive, 0, 0, 2) + ZEND_ARG_INFO(0, peer_pub_key) + ZEND_ARG_INFO(0, priv_key) + ZEND_ARG_INFO(0, keylen) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_random_pseudo_bytes, 0, 0, 1) + ZEND_ARG_INFO(0, length) + ZEND_ARG_INFO(1, result_is_strong) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_spki_new, 0, 0, 2) + ZEND_ARG_INFO(0, privkey) + ZEND_ARG_INFO(0, challenge) + ZEND_ARG_INFO(0, algo) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_openssl_spki_verify, 0) + ZEND_ARG_INFO(0, spki) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_openssl_spki_export, 0) + ZEND_ARG_INFO(0, spki) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_openssl_spki_export_challenge, 0) + ZEND_ARG_INFO(0, spki) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_openssl_get_cert_locations, 0) +ZEND_END_ARG_INFO() +/* }}} */ + +/* {{{ openssl_functions[] + */ +static const zend_function_entry openssl_functions[] = { + PHP_FE(openssl_get_cert_locations, arginfo_openssl_get_cert_locations) + +/* spki functions */ + PHP_FE(openssl_spki_new, arginfo_openssl_spki_new) + PHP_FE(openssl_spki_verify, arginfo_openssl_spki_verify) + PHP_FE(openssl_spki_export, arginfo_openssl_spki_export) + PHP_FE(openssl_spki_export_challenge, arginfo_openssl_spki_export_challenge) + +/* public/private key functions */ + PHP_FE(openssl_pkey_free, arginfo_openssl_pkey_free) + PHP_FE(openssl_pkey_new, arginfo_openssl_pkey_new) + PHP_FE(openssl_pkey_export, arginfo_openssl_pkey_export) + PHP_FE(openssl_pkey_export_to_file, arginfo_openssl_pkey_export_to_file) + PHP_FE(openssl_pkey_get_private, arginfo_openssl_pkey_get_private) + PHP_FE(openssl_pkey_get_public, arginfo_openssl_pkey_get_public) + PHP_FE(openssl_pkey_get_details, arginfo_openssl_pkey_get_details) + + PHP_FALIAS(openssl_free_key, openssl_pkey_free, arginfo_openssl_pkey_free) + PHP_FALIAS(openssl_get_privatekey, openssl_pkey_get_private, arginfo_openssl_pkey_get_private) + PHP_FALIAS(openssl_get_publickey, openssl_pkey_get_public, arginfo_openssl_pkey_get_public) + +/* x.509 cert funcs */ + PHP_FE(openssl_x509_read, arginfo_openssl_x509_read) + PHP_FE(openssl_x509_free, arginfo_openssl_x509_free) + PHP_FE(openssl_x509_parse, arginfo_openssl_x509_parse) + PHP_FE(openssl_x509_checkpurpose, arginfo_openssl_x509_checkpurpose) + PHP_FE(openssl_x509_check_private_key, arginfo_openssl_x509_check_private_key) + PHP_FE(openssl_x509_export, arginfo_openssl_x509_export) + PHP_FE(openssl_x509_fingerprint, arginfo_openssl_x509_fingerprint) + PHP_FE(openssl_x509_export_to_file, arginfo_openssl_x509_export_to_file) + +/* PKCS12 funcs */ + PHP_FE(openssl_pkcs12_export, arginfo_openssl_pkcs12_export) + PHP_FE(openssl_pkcs12_export_to_file, arginfo_openssl_pkcs12_export_to_file) + PHP_FE(openssl_pkcs12_read, arginfo_openssl_pkcs12_read) + +/* CSR funcs */ + PHP_FE(openssl_csr_new, arginfo_openssl_csr_new) + PHP_FE(openssl_csr_export, arginfo_openssl_csr_export) + PHP_FE(openssl_csr_export_to_file, arginfo_openssl_csr_export_to_file) + PHP_FE(openssl_csr_sign, arginfo_openssl_csr_sign) + PHP_FE(openssl_csr_get_subject, arginfo_openssl_csr_get_subject) + PHP_FE(openssl_csr_get_public_key, arginfo_openssl_csr_get_public_key) + + PHP_FE(openssl_digest, arginfo_openssl_digest) + PHP_FE(openssl_encrypt, arginfo_openssl_encrypt) + PHP_FE(openssl_decrypt, arginfo_openssl_decrypt) + PHP_FE(openssl_cipher_iv_length, arginfo_openssl_cipher_iv_length) + PHP_FE(openssl_sign, arginfo_openssl_sign) + PHP_FE(openssl_verify, arginfo_openssl_verify) + PHP_FE(openssl_seal, arginfo_openssl_seal) + PHP_FE(openssl_open, arginfo_openssl_open) + + PHP_FE(openssl_pbkdf2, arginfo_openssl_pbkdf2) + +/* for S/MIME handling */ + PHP_FE(openssl_pkcs7_verify, arginfo_openssl_pkcs7_verify) + PHP_FE(openssl_pkcs7_decrypt, arginfo_openssl_pkcs7_decrypt) + PHP_FE(openssl_pkcs7_sign, arginfo_openssl_pkcs7_sign) + PHP_FE(openssl_pkcs7_encrypt, arginfo_openssl_pkcs7_encrypt) + PHP_FE(openssl_pkcs7_read, arginfo_openssl_pkcs7_read) + + PHP_FE(openssl_private_encrypt, arginfo_openssl_private_encrypt) + PHP_FE(openssl_private_decrypt, arginfo_openssl_private_decrypt) + PHP_FE(openssl_public_encrypt, arginfo_openssl_public_encrypt) + PHP_FE(openssl_public_decrypt, arginfo_openssl_public_decrypt) + + PHP_FE(openssl_get_md_methods, arginfo_openssl_get_md_methods) + PHP_FE(openssl_get_cipher_methods, arginfo_openssl_get_cipher_methods) +#ifdef HAVE_EVP_PKEY_EC + PHP_FE(openssl_get_curve_names, arginfo_openssl_get_curve_names) +#endif + + PHP_FE(openssl_dh_compute_key, arginfo_openssl_dh_compute_key) + PHP_FE(openssl_pkey_derive, arginfo_openssl_pkey_derive) + + PHP_FE(openssl_random_pseudo_bytes, arginfo_openssl_random_pseudo_bytes) + PHP_FE(openssl_error_string, arginfo_openssl_error_string) + PHP_FE_END +}; +/* }}} */ + +/* {{{ openssl_module_entry + */ +zend_module_entry openssl_module_entry = { + STANDARD_MODULE_HEADER, + "openssl", + openssl_functions, + PHP_MINIT(openssl), + PHP_MSHUTDOWN(openssl), + NULL, + NULL, + PHP_MINFO(openssl), + PHP_OPENSSL_VERSION, + PHP_MODULE_GLOBALS(openssl), + PHP_GINIT(openssl), + PHP_GSHUTDOWN(openssl), + NULL, + STANDARD_MODULE_PROPERTIES_EX +}; +/* }}} */ + +#ifdef COMPILE_DL_OPENSSL +ZEND_GET_MODULE(openssl) +#endif + +/* {{{ OpenSSL compatibility functions and macros */ +#if PHP_OPENSSL_API_VERSION < 0x10100 +#define EVP_PKEY_get0_RSA(_pkey) _pkey->pkey.rsa +#define EVP_PKEY_get0_DH(_pkey) _pkey->pkey.dh +#define EVP_PKEY_get0_DSA(_pkey) _pkey->pkey.dsa +#define EVP_PKEY_get0_EC_KEY(_pkey) _pkey->pkey.ec + +static int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d) +{ + r->n = n; + r->e = e; + r->d = d; + + return 1; +} + +static int RSA_set0_factors(RSA *r, BIGNUM *p, BIGNUM *q) +{ + r->p = p; + r->q = q; + + return 1; +} + +static int RSA_set0_crt_params(RSA *r, BIGNUM *dmp1, BIGNUM *dmq1, BIGNUM *iqmp) +{ + r->dmp1 = dmp1; + r->dmq1 = dmq1; + r->iqmp = iqmp; + + return 1; +} + +static void RSA_get0_key(const RSA *r, const BIGNUM **n, const BIGNUM **e, const BIGNUM **d) +{ + *n = r->n; + *e = r->e; + *d = r->d; +} + +static void RSA_get0_factors(const RSA *r, const BIGNUM **p, const BIGNUM **q) +{ + *p = r->p; + *q = r->q; +} + +static void RSA_get0_crt_params(const RSA *r, const BIGNUM **dmp1, const BIGNUM **dmq1, const BIGNUM **iqmp) +{ + *dmp1 = r->dmp1; + *dmq1 = r->dmq1; + *iqmp = r->iqmp; +} + +static void DH_get0_pqg(const DH *dh, const BIGNUM **p, const BIGNUM **q, const BIGNUM **g) +{ + *p = dh->p; + *q = dh->q; + *g = dh->g; +} + +static int DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g) +{ + dh->p = p; + dh->q = q; + dh->g = g; + + return 1; +} + +static void DH_get0_key(const DH *dh, const BIGNUM **pub_key, const BIGNUM **priv_key) +{ + *pub_key = dh->pub_key; + *priv_key = dh->priv_key; +} + +static int DH_set0_key(DH *dh, BIGNUM *pub_key, BIGNUM *priv_key) +{ + dh->pub_key = pub_key; + dh->priv_key = priv_key; + + return 1; +} + +static void DSA_get0_pqg(const DSA *d, const BIGNUM **p, const BIGNUM **q, const BIGNUM **g) +{ + *p = d->p; + *q = d->q; + *g = d->g; +} + +int DSA_set0_pqg(DSA *d, BIGNUM *p, BIGNUM *q, BIGNUM *g) +{ + d->p = p; + d->q = q; + d->g = g; + + return 1; +} + +static void DSA_get0_key(const DSA *d, const BIGNUM **pub_key, const BIGNUM **priv_key) +{ + *pub_key = d->pub_key; + *priv_key = d->priv_key; +} + +int DSA_set0_key(DSA *d, BIGNUM *pub_key, BIGNUM *priv_key) +{ + d->pub_key = pub_key; + d->priv_key = priv_key; + + return 1; +} + +static const unsigned char *ASN1_STRING_get0_data(const ASN1_STRING *asn1) +{ + return M_ASN1_STRING_data(asn1); +} + +#if PHP_OPENSSL_API_VERSION < 0x10002 + +static int X509_get_signature_nid(const X509 *x) +{ + return OBJ_obj2nid(x->sig_alg->algorithm); +} + +#endif + +#endif +/* }}} */ + +/* number conversion flags checks */ +#define PHP_OPENSSL_CHECK_NUMBER_CONVERSION(_cond, _name) \ + do { \ + if (_cond) { \ + php_error_docref(NULL, E_WARNING, #_name" is too long"); \ + RETURN_FALSE; \ + } \ + } while(0) +/* check if size_t can be safely casted to int */ +#define PHP_OPENSSL_CHECK_SIZE_T_TO_INT(_var, _name) \ + PHP_OPENSSL_CHECK_NUMBER_CONVERSION(ZEND_SIZE_T_INT_OVFL(_var), _name) +/* check if size_t can be safely casted to unsigned int */ +#define PHP_OPENSSL_CHECK_SIZE_T_TO_UINT(_var, _name) \ + PHP_OPENSSL_CHECK_NUMBER_CONVERSION(ZEND_SIZE_T_UINT_OVFL(_var), _name) +/* check if long can be safely casted to int */ +#define PHP_OPENSSL_CHECK_LONG_TO_INT(_var, _name) \ + PHP_OPENSSL_CHECK_NUMBER_CONVERSION(ZEND_LONG_EXCEEDS_INT(_var), _name) + +/* {{{ php_openssl_store_errors */ +void php_openssl_store_errors() +{ + struct php_openssl_errors *errors; + int error_code = ERR_get_error(); + + if (!error_code) { + return; + } + + if (!OPENSSL_G(errors)) { + OPENSSL_G(errors) = pecalloc(1, sizeof(struct php_openssl_errors), 1); + } + + errors = OPENSSL_G(errors); + + do { + errors->top = (errors->top + 1) % ERR_NUM_ERRORS; + if (errors->top == errors->bottom) { + errors->bottom = (errors->bottom + 1) % ERR_NUM_ERRORS; + } + errors->buffer[errors->top] = error_code; + } while ((error_code = ERR_get_error())); + +} +/* }}} */ + +static int le_key; +static int le_x509; +static int le_csr; +static int ssl_stream_data_index; + +int php_openssl_get_x509_list_id(void) /* {{{ */ +{ + return le_x509; +} +/* }}} */ + +/* {{{ resource destructors */ +static void php_openssl_pkey_free(zend_resource *rsrc) +{ + EVP_PKEY *pkey = (EVP_PKEY *)rsrc->ptr; + + assert(pkey != NULL); + + EVP_PKEY_free(pkey); +} + +static void php_openssl_x509_free(zend_resource *rsrc) +{ + X509 *x509 = (X509 *)rsrc->ptr; + X509_free(x509); +} + +static void php_openssl_csr_free(zend_resource *rsrc) +{ + X509_REQ * csr = (X509_REQ*)rsrc->ptr; + X509_REQ_free(csr); +} +/* }}} */ + +/* {{{ openssl open_basedir check */ +inline static int php_openssl_open_base_dir_chk(char *filename) +{ + if (php_check_open_basedir(filename)) { + return -1; + } + + return 0; +} +/* }}} */ + +php_stream* php_openssl_get_stream_from_ssl_handle(const SSL *ssl) +{ + return (php_stream*)SSL_get_ex_data(ssl, ssl_stream_data_index); +} + +int php_openssl_get_ssl_stream_data_index() +{ + return ssl_stream_data_index; +} + +/* openssl -> PHP "bridging" */ +/* true global; readonly after module startup */ +static char default_ssl_conf_filename[MAXPATHLEN]; + +struct php_x509_request { /* {{{ */ + LHASH_OF(CONF_VALUE) * global_config; /* Global SSL config */ + LHASH_OF(CONF_VALUE) * req_config; /* SSL config for this request */ + const EVP_MD * md_alg; + const EVP_MD * digest; + char * section_name, + * config_filename, + * digest_name, + * extensions_section, + * request_extensions_section; + int priv_key_bits; + int priv_key_type; + + int priv_key_encrypt; + +#ifdef HAVE_EVP_PKEY_EC + int curve_name; +#endif + + EVP_PKEY * priv_key; + + const EVP_CIPHER * priv_key_encrypt_cipher; +}; +/* }}} */ + +static X509 * php_openssl_x509_from_zval(zval * val, int makeresource, zend_resource **resourceval); +static EVP_PKEY * php_openssl_evp_from_zval( + zval * val, int public_key, char *passphrase, size_t passphrase_len, + int makeresource, zend_resource **resourceval); +static int php_openssl_is_private_key(EVP_PKEY* pkey); +static X509_STORE * php_openssl_setup_verify(zval * calist); +static STACK_OF(X509) * php_openssl_load_all_certs_from_file(char *certfile); +static X509_REQ * php_openssl_csr_from_zval(zval * val, int makeresource, zend_resource ** resourceval); +static EVP_PKEY * php_openssl_generate_private_key(struct php_x509_request * req); + +static void php_openssl_add_assoc_name_entry(zval * val, char * key, X509_NAME * name, int shortname) /* {{{ */ +{ + zval *data; + zval subitem, tmp; + int i; + char *sname; + int nid; + X509_NAME_ENTRY * ne; + ASN1_STRING * str = NULL; + ASN1_OBJECT * obj; + + if (key != NULL) { + array_init(&subitem); + } else { + ZVAL_COPY_VALUE(&subitem, val); + } + + for (i = 0; i < X509_NAME_entry_count(name); i++) { + const unsigned char *to_add = NULL; + int to_add_len = 0; + unsigned char *to_add_buf = NULL; + + ne = X509_NAME_get_entry(name, i); + obj = X509_NAME_ENTRY_get_object(ne); + nid = OBJ_obj2nid(obj); + + if (shortname) { + sname = (char *) OBJ_nid2sn(nid); + } else { + sname = (char *) OBJ_nid2ln(nid); + } + + str = X509_NAME_ENTRY_get_data(ne); + if (ASN1_STRING_type(str) != V_ASN1_UTF8STRING) { + /* ASN1_STRING_to_UTF8(3): The converted data is copied into a newly allocated buffer */ + to_add_len = ASN1_STRING_to_UTF8(&to_add_buf, str); + to_add = to_add_buf; + } else { + /* ASN1_STRING_get0_data(3): Since this is an internal pointer it should not be freed or modified in any way */ + to_add = ASN1_STRING_get0_data(str); + to_add_len = ASN1_STRING_length(str); + } + + if (to_add_len != -1) { + if ((data = zend_hash_str_find(Z_ARRVAL(subitem), sname, strlen(sname))) != NULL) { + if (Z_TYPE_P(data) == IS_ARRAY) { + add_next_index_stringl(data, (const char *)to_add, to_add_len); + } else if (Z_TYPE_P(data) == IS_STRING) { + array_init(&tmp); + add_next_index_str(&tmp, zend_string_copy(Z_STR_P(data))); + add_next_index_stringl(&tmp, (const char *)to_add, to_add_len); + zend_hash_str_update(Z_ARRVAL(subitem), sname, strlen(sname), &tmp); + } + } else { + /* it might be better to expand it and pass zval from ZVAL_STRING + * to zend_symtable_str_update so we do not silently drop const + * but we need a test to cover this part first */ + add_assoc_stringl(&subitem, sname, (char *)to_add, to_add_len); + } + } else { + php_openssl_store_errors(); + } + + if (to_add_buf != NULL) { + OPENSSL_free(to_add_buf); + } + } + + if (key != NULL) { + zend_hash_str_update(Z_ARRVAL_P(val), key, strlen(key), &subitem); + } +} +/* }}} */ + +static void php_openssl_add_assoc_asn1_string(zval * val, char * key, ASN1_STRING * str) /* {{{ */ +{ + add_assoc_stringl(val, key, (char *)str->data, str->length); +} +/* }}} */ + +static time_t php_openssl_asn1_time_to_time_t(ASN1_UTCTIME * timestr) /* {{{ */ +{ +/* + This is how the time string is formatted: + + snprintf(p, sizeof(p), "%02d%02d%02d%02d%02d%02dZ",ts->tm_year%100, + ts->tm_mon+1,ts->tm_mday,ts->tm_hour,ts->tm_min,ts->tm_sec); +*/ + + time_t ret; + struct tm thetime; + char * strbuf; + char * thestr; + long gmadjust = 0; + size_t timestr_len; + + if (ASN1_STRING_type(timestr) != V_ASN1_UTCTIME && ASN1_STRING_type(timestr) != V_ASN1_GENERALIZEDTIME) { + php_error_docref(NULL, E_WARNING, "illegal ASN1 data type for timestamp"); + return (time_t)-1; + } + + timestr_len = (size_t)ASN1_STRING_length(timestr); + + if (timestr_len != strlen((const char *)ASN1_STRING_get0_data(timestr))) { + php_error_docref(NULL, E_WARNING, "illegal length in timestamp"); + return (time_t)-1; + } + + if (timestr_len < 13 && timestr_len != 11) { + php_error_docref(NULL, E_WARNING, "unable to parse time string %s correctly", timestr->data); + return (time_t)-1; + } + + if (ASN1_STRING_type(timestr) == V_ASN1_GENERALIZEDTIME && timestr_len < 15) { + php_error_docref(NULL, E_WARNING, "unable to parse time string %s correctly", timestr->data); + return (time_t)-1; + } + + strbuf = estrdup((const char *)ASN1_STRING_get0_data(timestr)); + + memset(&thetime, 0, sizeof(thetime)); + + /* we work backwards so that we can use atoi more easily */ + + thestr = strbuf + timestr_len - 3; + + if (timestr_len == 11) { + thetime.tm_sec = 0; + } else { + thetime.tm_sec = atoi(thestr); + *thestr = '\0'; + thestr -= 2; + } + thetime.tm_min = atoi(thestr); + *thestr = '\0'; + thestr -= 2; + thetime.tm_hour = atoi(thestr); + *thestr = '\0'; + thestr -= 2; + thetime.tm_mday = atoi(thestr); + *thestr = '\0'; + thestr -= 2; + thetime.tm_mon = atoi(thestr)-1; + + *thestr = '\0'; + if( ASN1_STRING_type(timestr) == V_ASN1_UTCTIME ) { + thestr -= 2; + thetime.tm_year = atoi(thestr); + + if (thetime.tm_year < 68) { + thetime.tm_year += 100; + } + } else if( ASN1_STRING_type(timestr) == V_ASN1_GENERALIZEDTIME ) { + thestr -= 4; + thetime.tm_year = atoi(thestr) - 1900; + } + + + thetime.tm_isdst = -1; + ret = mktime(&thetime); + +#if HAVE_TM_GMTOFF + gmadjust = thetime.tm_gmtoff; +#else + /* + ** If correcting for daylight savings time, we set the adjustment to + ** the value of timezone - 3600 seconds. Otherwise, we need to overcorrect and + ** set the adjustment to the main timezone + 3600 seconds. + */ + gmadjust = -(thetime.tm_isdst ? (long)timezone - 3600 : (long)timezone); +#endif + ret += gmadjust; + + efree(strbuf); + + return ret; +} +/* }}} */ + +static inline int php_openssl_config_check_syntax(const char * section_label, const char * config_filename, const char * section, LHASH_OF(CONF_VALUE) * config) /* {{{ */ +{ + X509V3_CTX ctx; + + X509V3_set_ctx_test(&ctx); + X509V3_set_conf_lhash(&ctx, config); + if (!X509V3_EXT_add_conf(config, &ctx, (char *)section, NULL)) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "Error loading %s section %s of %s", + section_label, + section, + config_filename); + return FAILURE; + } + return SUCCESS; +} +/* }}} */ + +static int php_openssl_add_oid_section(struct php_x509_request * req) /* {{{ */ +{ + char * str; + STACK_OF(CONF_VALUE) * sktmp; + CONF_VALUE * cnf; + int i; + + str = CONF_get_string(req->req_config, NULL, "oid_section"); + if (str == NULL) { + php_openssl_store_errors(); + return SUCCESS; + } + sktmp = CONF_get_section(req->req_config, str); + if (sktmp == NULL) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "problem loading oid section %s", str); + return FAILURE; + } + for (i = 0; i < sk_CONF_VALUE_num(sktmp); i++) { + cnf = sk_CONF_VALUE_value(sktmp, i); + if (OBJ_sn2nid(cnf->name) == NID_undef && OBJ_ln2nid(cnf->name) == NID_undef && + OBJ_create(cnf->value, cnf->name, cnf->name) == NID_undef) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "problem creating object %s=%s", cnf->name, cnf->value); + return FAILURE; + } + } + return SUCCESS; +} +/* }}} */ + +#define PHP_SSL_REQ_INIT(req) memset(req, 0, sizeof(*req)) +#define PHP_SSL_REQ_DISPOSE(req) php_openssl_dispose_config(req) +#define PHP_SSL_REQ_PARSE(req, zval) php_openssl_parse_config(req, zval) + +#define PHP_SSL_CONFIG_SYNTAX_CHECK(var) if (req->var && php_openssl_config_check_syntax(#var, \ + req->config_filename, req->var, req->req_config) == FAILURE) return FAILURE + +#define SET_OPTIONAL_STRING_ARG(key, varname, defval) \ + do { \ + if (optional_args && (item = zend_hash_str_find(Z_ARRVAL_P(optional_args), key, sizeof(key)-1)) != NULL && Z_TYPE_P(item) == IS_STRING) { \ + varname = Z_STRVAL_P(item); \ + } else { \ + varname = defval; \ + if (varname == NULL) { \ + php_openssl_store_errors(); \ + } \ + } \ + } while(0) + +#define SET_OPTIONAL_LONG_ARG(key, varname, defval) \ + if (optional_args && (item = zend_hash_str_find(Z_ARRVAL_P(optional_args), key, sizeof(key)-1)) != NULL && Z_TYPE_P(item) == IS_LONG) \ + varname = (int)Z_LVAL_P(item); \ + else \ + varname = defval + +static const EVP_CIPHER * php_openssl_get_evp_cipher_from_algo(zend_long algo); + +/* {{{ strip line endings from spkac */ +static int php_openssl_spki_cleanup(const char *src, char *dest) +{ + int removed = 0; + + while (*src) { + if (*src != '\n' && *src != '\r') { + *dest++ = *src; + } else { + ++removed; + } + ++src; + } + *dest = 0; + return removed; +} +/* }}} */ + +static int php_openssl_parse_config(struct php_x509_request * req, zval * optional_args) /* {{{ */ +{ + char * str; + zval * item; + + SET_OPTIONAL_STRING_ARG("config", req->config_filename, default_ssl_conf_filename); + SET_OPTIONAL_STRING_ARG("config_section_name", req->section_name, "req"); + req->global_config = CONF_load(NULL, default_ssl_conf_filename, NULL); + if (req->global_config == NULL) { + php_openssl_store_errors(); + } + req->req_config = CONF_load(NULL, req->config_filename, NULL); + if (req->req_config == NULL) { + php_openssl_store_errors(); + return FAILURE; + } + + /* read in the oids */ + str = CONF_get_string(req->req_config, NULL, "oid_file"); + if (str == NULL) { + php_openssl_store_errors(); + } else if (!php_openssl_open_base_dir_chk(str)) { + BIO *oid_bio = BIO_new_file(str, PHP_OPENSSL_BIO_MODE_R(PKCS7_BINARY)); + if (oid_bio) { + OBJ_create_objects(oid_bio); + BIO_free(oid_bio); + php_openssl_store_errors(); + } + } + if (php_openssl_add_oid_section(req) == FAILURE) { + return FAILURE; + } + SET_OPTIONAL_STRING_ARG("digest_alg", req->digest_name, + CONF_get_string(req->req_config, req->section_name, "default_md")); + SET_OPTIONAL_STRING_ARG("x509_extensions", req->extensions_section, + CONF_get_string(req->req_config, req->section_name, "x509_extensions")); + SET_OPTIONAL_STRING_ARG("req_extensions", req->request_extensions_section, + CONF_get_string(req->req_config, req->section_name, "req_extensions")); + SET_OPTIONAL_LONG_ARG("private_key_bits", req->priv_key_bits, + CONF_get_number(req->req_config, req->section_name, "default_bits")); + + SET_OPTIONAL_LONG_ARG("private_key_type", req->priv_key_type, OPENSSL_KEYTYPE_DEFAULT); + + if (optional_args && (item = zend_hash_str_find(Z_ARRVAL_P(optional_args), "encrypt_key", sizeof("encrypt_key")-1)) != NULL) { + req->priv_key_encrypt = Z_TYPE_P(item) == IS_TRUE ? 1 : 0; + } else { + str = CONF_get_string(req->req_config, req->section_name, "encrypt_rsa_key"); + if (str == NULL) { + str = CONF_get_string(req->req_config, req->section_name, "encrypt_key"); + /* it is sure that there are some errrors as str was NULL for encrypt_rsa_key */ + php_openssl_store_errors(); + } + if (str != NULL && strcmp(str, "no") == 0) { + req->priv_key_encrypt = 0; + } else { + req->priv_key_encrypt = 1; + } + } + + if (req->priv_key_encrypt && + optional_args && + (item = zend_hash_str_find(Z_ARRVAL_P(optional_args), "encrypt_key_cipher", sizeof("encrypt_key_cipher")-1)) != NULL && + Z_TYPE_P(item) == IS_LONG + ) { + zend_long cipher_algo = Z_LVAL_P(item); + const EVP_CIPHER* cipher = php_openssl_get_evp_cipher_from_algo(cipher_algo); + if (cipher == NULL) { + php_error_docref(NULL, E_WARNING, "Unknown cipher algorithm for private key."); + return FAILURE; + } else { + req->priv_key_encrypt_cipher = cipher; + } + } else { + req->priv_key_encrypt_cipher = NULL; + } + + /* digest alg */ + if (req->digest_name == NULL) { + req->digest_name = CONF_get_string(req->req_config, req->section_name, "default_md"); + } + if (req->digest_name != NULL) { + req->digest = req->md_alg = EVP_get_digestbyname(req->digest_name); + } else { + php_openssl_store_errors(); + } + if (req->md_alg == NULL) { + req->md_alg = req->digest = EVP_sha1(); + php_openssl_store_errors(); + } + + PHP_SSL_CONFIG_SYNTAX_CHECK(extensions_section); +#ifdef HAVE_EVP_PKEY_EC + /* set the ec group curve name */ + req->curve_name = NID_undef; + if (optional_args && (item = zend_hash_str_find(Z_ARRVAL_P(optional_args), "curve_name", sizeof("curve_name")-1)) != NULL + && Z_TYPE_P(item) == IS_STRING) { + req->curve_name = OBJ_sn2nid(Z_STRVAL_P(item)); + if (req->curve_name == NID_undef) { + php_error_docref(NULL, E_WARNING, "Unknown elliptic curve (short) name %s", Z_STRVAL_P(item)); + return FAILURE; + } + } +#endif + + /* set the string mask */ + str = CONF_get_string(req->req_config, req->section_name, "string_mask"); + if (str == NULL) { + php_openssl_store_errors(); + } else if (!ASN1_STRING_set_default_mask_asc(str)) { + php_error_docref(NULL, E_WARNING, "Invalid global string mask setting %s", str); + return FAILURE; + } + + PHP_SSL_CONFIG_SYNTAX_CHECK(request_extensions_section); + + return SUCCESS; +} +/* }}} */ + +static void php_openssl_dispose_config(struct php_x509_request * req) /* {{{ */ +{ + if (req->priv_key) { + EVP_PKEY_free(req->priv_key); + req->priv_key = NULL; + } + if (req->global_config) { + CONF_free(req->global_config); + req->global_config = NULL; + } + if (req->req_config) { + CONF_free(req->req_config); + req->req_config = NULL; + } +} +/* }}} */ + +#if defined(PHP_WIN32) || PHP_OPENSSL_API_VERSION >= 0x10100 +#define PHP_OPENSSL_RAND_ADD_TIME() ((void) 0) +#else +#define PHP_OPENSSL_RAND_ADD_TIME() php_openssl_rand_add_timeval() + +static inline void php_openssl_rand_add_timeval() /* {{{ */ +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + RAND_add(&tv, sizeof(tv), 0.0); +} +/* }}} */ + +#endif + +static int php_openssl_load_rand_file(const char * file, int *egdsocket, int *seeded) /* {{{ */ +{ + char buffer[MAXPATHLEN]; + + *egdsocket = 0; + *seeded = 0; + + if (file == NULL) { + file = RAND_file_name(buffer, sizeof(buffer)); +#ifdef HAVE_RAND_EGD + } else if (RAND_egd(file) > 0) { + /* if the given filename is an EGD socket, don't + * write anything back to it */ + *egdsocket = 1; + return SUCCESS; +#endif + } + if (file == NULL || !RAND_load_file(file, -1)) { + if (RAND_status() == 0) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "unable to load random state; not enough random data!"); + return FAILURE; + } + return FAILURE; + } + *seeded = 1; + return SUCCESS; +} +/* }}} */ + +static int php_openssl_write_rand_file(const char * file, int egdsocket, int seeded) /* {{{ */ +{ + char buffer[MAXPATHLEN]; + + + if (egdsocket || !seeded) { + /* if we did not manage to read the seed file, we should not write + * a low-entropy seed file back */ + return FAILURE; + } + if (file == NULL) { + file = RAND_file_name(buffer, sizeof(buffer)); + } + PHP_OPENSSL_RAND_ADD_TIME(); + if (file == NULL || !RAND_write_file(file)) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "unable to write random state"); + return FAILURE; + } + return SUCCESS; +} +/* }}} */ + +static EVP_MD * php_openssl_get_evp_md_from_algo(zend_long algo) { /* {{{ */ + EVP_MD *mdtype; + + switch (algo) { + case OPENSSL_ALGO_SHA1: + mdtype = (EVP_MD *) EVP_sha1(); + break; + case OPENSSL_ALGO_MD5: + mdtype = (EVP_MD *) EVP_md5(); + break; + case OPENSSL_ALGO_MD4: + mdtype = (EVP_MD *) EVP_md4(); + break; +#ifdef HAVE_OPENSSL_MD2_H + case OPENSSL_ALGO_MD2: + mdtype = (EVP_MD *) EVP_md2(); + break; +#endif +#if PHP_OPENSSL_API_VERSION < 0x10100 + case OPENSSL_ALGO_DSS1: + mdtype = (EVP_MD *) EVP_dss1(); + break; +#endif + case OPENSSL_ALGO_SHA224: + mdtype = (EVP_MD *) EVP_sha224(); + break; + case OPENSSL_ALGO_SHA256: + mdtype = (EVP_MD *) EVP_sha256(); + break; + case OPENSSL_ALGO_SHA384: + mdtype = (EVP_MD *) EVP_sha384(); + break; + case OPENSSL_ALGO_SHA512: + mdtype = (EVP_MD *) EVP_sha512(); + break; + case OPENSSL_ALGO_RMD160: + mdtype = (EVP_MD *) EVP_ripemd160(); + break; + default: + return NULL; + break; + } + return mdtype; +} +/* }}} */ + +static const EVP_CIPHER * php_openssl_get_evp_cipher_from_algo(zend_long algo) { /* {{{ */ + switch (algo) { +#ifndef OPENSSL_NO_RC2 + case PHP_OPENSSL_CIPHER_RC2_40: + return EVP_rc2_40_cbc(); + break; + case PHP_OPENSSL_CIPHER_RC2_64: + return EVP_rc2_64_cbc(); + break; + case PHP_OPENSSL_CIPHER_RC2_128: + return EVP_rc2_cbc(); + break; +#endif + +#ifndef OPENSSL_NO_DES + case PHP_OPENSSL_CIPHER_DES: + return EVP_des_cbc(); + break; + case PHP_OPENSSL_CIPHER_3DES: + return EVP_des_ede3_cbc(); + break; +#endif + +#ifndef OPENSSL_NO_AES + case PHP_OPENSSL_CIPHER_AES_128_CBC: + return EVP_aes_128_cbc(); + break; + case PHP_OPENSSL_CIPHER_AES_192_CBC: + return EVP_aes_192_cbc(); + break; + case PHP_OPENSSL_CIPHER_AES_256_CBC: + return EVP_aes_256_cbc(); + break; +#endif + + + default: + return NULL; + break; + } +} +/* }}} */ + +/* {{{ INI Settings */ +PHP_INI_BEGIN() + PHP_INI_ENTRY("openssl.cafile", NULL, PHP_INI_PERDIR, NULL) + PHP_INI_ENTRY("openssl.capath", NULL, PHP_INI_PERDIR, NULL) +PHP_INI_END() +/* }}} */ + +/* {{{ PHP_MINIT_FUNCTION + */ +PHP_MINIT_FUNCTION(openssl) +{ + char * config_filename; + + le_key = zend_register_list_destructors_ex(php_openssl_pkey_free, NULL, "OpenSSL key", module_number); + le_x509 = zend_register_list_destructors_ex(php_openssl_x509_free, NULL, "OpenSSL X.509", module_number); + le_csr = zend_register_list_destructors_ex(php_openssl_csr_free, NULL, "OpenSSL X.509 CSR", module_number); + +#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined (LIBRESSL_VERSION_NUMBER) + OPENSSL_config(NULL); + SSL_library_init(); + OpenSSL_add_all_ciphers(); + OpenSSL_add_all_digests(); + OpenSSL_add_all_algorithms(); + +#if !defined(OPENSSL_NO_AES) && defined(EVP_CIPH_CCM_MODE) && OPENSSL_VERSION_NUMBER < 0x100020000 + EVP_add_cipher(EVP_aes_128_ccm()); + EVP_add_cipher(EVP_aes_192_ccm()); + EVP_add_cipher(EVP_aes_256_ccm()); +#endif + + SSL_load_error_strings(); +#else + OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, NULL); +#endif + + /* register a resource id number with OpenSSL so that we can map SSL -> stream structures in + * OpenSSL callbacks */ + ssl_stream_data_index = SSL_get_ex_new_index(0, "PHP stream index", NULL, NULL, NULL); + + REGISTER_STRING_CONSTANT("OPENSSL_VERSION_TEXT", OPENSSL_VERSION_TEXT, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("OPENSSL_VERSION_NUMBER", OPENSSL_VERSION_NUMBER, CONST_CS|CONST_PERSISTENT); + + /* purposes for cert purpose checking */ + REGISTER_LONG_CONSTANT("X509_PURPOSE_SSL_CLIENT", X509_PURPOSE_SSL_CLIENT, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("X509_PURPOSE_SSL_SERVER", X509_PURPOSE_SSL_SERVER, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("X509_PURPOSE_NS_SSL_SERVER", X509_PURPOSE_NS_SSL_SERVER, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("X509_PURPOSE_SMIME_SIGN", X509_PURPOSE_SMIME_SIGN, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("X509_PURPOSE_SMIME_ENCRYPT", X509_PURPOSE_SMIME_ENCRYPT, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("X509_PURPOSE_CRL_SIGN", X509_PURPOSE_CRL_SIGN, CONST_CS|CONST_PERSISTENT); +#ifdef X509_PURPOSE_ANY + REGISTER_LONG_CONSTANT("X509_PURPOSE_ANY", X509_PURPOSE_ANY, CONST_CS|CONST_PERSISTENT); +#endif + + /* signature algorithm constants */ + REGISTER_LONG_CONSTANT("OPENSSL_ALGO_SHA1", OPENSSL_ALGO_SHA1, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("OPENSSL_ALGO_MD5", OPENSSL_ALGO_MD5, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("OPENSSL_ALGO_MD4", OPENSSL_ALGO_MD4, CONST_CS|CONST_PERSISTENT); +#ifdef HAVE_OPENSSL_MD2_H + REGISTER_LONG_CONSTANT("OPENSSL_ALGO_MD2", OPENSSL_ALGO_MD2, CONST_CS|CONST_PERSISTENT); +#endif +#if PHP_OPENSSL_API_VERSION < 0x10100 + REGISTER_LONG_CONSTANT("OPENSSL_ALGO_DSS1", OPENSSL_ALGO_DSS1, CONST_CS|CONST_PERSISTENT); +#endif + REGISTER_LONG_CONSTANT("OPENSSL_ALGO_SHA224", OPENSSL_ALGO_SHA224, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("OPENSSL_ALGO_SHA256", OPENSSL_ALGO_SHA256, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("OPENSSL_ALGO_SHA384", OPENSSL_ALGO_SHA384, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("OPENSSL_ALGO_SHA512", OPENSSL_ALGO_SHA512, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("OPENSSL_ALGO_RMD160", OPENSSL_ALGO_RMD160, CONST_CS|CONST_PERSISTENT); + + /* flags for S/MIME */ + REGISTER_LONG_CONSTANT("PKCS7_DETACHED", PKCS7_DETACHED, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PKCS7_TEXT", PKCS7_TEXT, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PKCS7_NOINTERN", PKCS7_NOINTERN, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PKCS7_NOVERIFY", PKCS7_NOVERIFY, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PKCS7_NOCHAIN", PKCS7_NOCHAIN, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PKCS7_NOCERTS", PKCS7_NOCERTS, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PKCS7_NOATTR", PKCS7_NOATTR, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PKCS7_BINARY", PKCS7_BINARY, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PKCS7_NOSIGS", PKCS7_NOSIGS, CONST_CS|CONST_PERSISTENT); + + REGISTER_LONG_CONSTANT("OPENSSL_PKCS1_PADDING", RSA_PKCS1_PADDING, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("OPENSSL_SSLV23_PADDING", RSA_SSLV23_PADDING, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("OPENSSL_NO_PADDING", RSA_NO_PADDING, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("OPENSSL_PKCS1_OAEP_PADDING", RSA_PKCS1_OAEP_PADDING, CONST_CS|CONST_PERSISTENT); + + /* Informational stream wrapper constants */ + REGISTER_STRING_CONSTANT("OPENSSL_DEFAULT_STREAM_CIPHERS", OPENSSL_DEFAULT_STREAM_CIPHERS, CONST_CS|CONST_PERSISTENT); + + /* Ciphers */ +#ifndef OPENSSL_NO_RC2 + REGISTER_LONG_CONSTANT("OPENSSL_CIPHER_RC2_40", PHP_OPENSSL_CIPHER_RC2_40, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("OPENSSL_CIPHER_RC2_128", PHP_OPENSSL_CIPHER_RC2_128, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("OPENSSL_CIPHER_RC2_64", PHP_OPENSSL_CIPHER_RC2_64, CONST_CS|CONST_PERSISTENT); +#endif +#ifndef OPENSSL_NO_DES + REGISTER_LONG_CONSTANT("OPENSSL_CIPHER_DES", PHP_OPENSSL_CIPHER_DES, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("OPENSSL_CIPHER_3DES", PHP_OPENSSL_CIPHER_3DES, CONST_CS|CONST_PERSISTENT); +#endif +#ifndef OPENSSL_NO_AES + REGISTER_LONG_CONSTANT("OPENSSL_CIPHER_AES_128_CBC", PHP_OPENSSL_CIPHER_AES_128_CBC, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("OPENSSL_CIPHER_AES_192_CBC", PHP_OPENSSL_CIPHER_AES_192_CBC, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("OPENSSL_CIPHER_AES_256_CBC", PHP_OPENSSL_CIPHER_AES_256_CBC, CONST_CS|CONST_PERSISTENT); +#endif + + /* Values for key types */ + REGISTER_LONG_CONSTANT("OPENSSL_KEYTYPE_RSA", OPENSSL_KEYTYPE_RSA, CONST_CS|CONST_PERSISTENT); +#ifndef NO_DSA + REGISTER_LONG_CONSTANT("OPENSSL_KEYTYPE_DSA", OPENSSL_KEYTYPE_DSA, CONST_CS|CONST_PERSISTENT); +#endif + REGISTER_LONG_CONSTANT("OPENSSL_KEYTYPE_DH", OPENSSL_KEYTYPE_DH, CONST_CS|CONST_PERSISTENT); +#ifdef HAVE_EVP_PKEY_EC + REGISTER_LONG_CONSTANT("OPENSSL_KEYTYPE_EC", OPENSSL_KEYTYPE_EC, CONST_CS|CONST_PERSISTENT); +#endif + + REGISTER_LONG_CONSTANT("OPENSSL_RAW_DATA", OPENSSL_RAW_DATA, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("OPENSSL_ZERO_PADDING", OPENSSL_ZERO_PADDING, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("OPENSSL_DONT_ZERO_PAD_KEY", OPENSSL_DONT_ZERO_PAD_KEY, CONST_CS|CONST_PERSISTENT); + +#ifndef OPENSSL_NO_TLSEXT + /* SNI support included */ + REGISTER_LONG_CONSTANT("OPENSSL_TLSEXT_SERVER_NAME", 1, CONST_CS|CONST_PERSISTENT); +#endif + + /* Determine default SSL configuration file */ + config_filename = getenv("OPENSSL_CONF"); + if (config_filename == NULL) { + config_filename = getenv("SSLEAY_CONF"); + } + + /* default to 'openssl.cnf' if no environment variable is set */ + if (config_filename == NULL) { + snprintf(default_ssl_conf_filename, sizeof(default_ssl_conf_filename), "%s/%s", + X509_get_default_cert_area(), + "openssl.cnf"); + } else { + strlcpy(default_ssl_conf_filename, config_filename, sizeof(default_ssl_conf_filename)); + } + + php_stream_xport_register("ssl", php_openssl_ssl_socket_factory); +#ifndef OPENSSL_NO_SSL3 + php_stream_xport_register("sslv3", php_openssl_ssl_socket_factory); +#endif + php_stream_xport_register("tls", php_openssl_ssl_socket_factory); + php_stream_xport_register("tlsv1.0", php_openssl_ssl_socket_factory); + php_stream_xport_register("tlsv1.1", php_openssl_ssl_socket_factory); + php_stream_xport_register("tlsv1.2", php_openssl_ssl_socket_factory); + + /* override the default tcp socket provider */ + php_stream_xport_register("tcp", php_openssl_ssl_socket_factory); + + php_register_url_stream_wrapper("https", &php_stream_http_wrapper); + php_register_url_stream_wrapper("ftps", &php_stream_ftp_wrapper); + + REGISTER_INI_ENTRIES(); + + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_GINIT_FUNCTION +*/ +PHP_GINIT_FUNCTION(openssl) +{ +#if defined(COMPILE_DL_OPENSSL) && defined(ZTS) + ZEND_TSRMLS_CACHE_UPDATE(); +#endif + openssl_globals->errors = NULL; +} +/* }}} */ + +/* {{{ PHP_GSHUTDOWN_FUNCTION +*/ +PHP_GSHUTDOWN_FUNCTION(openssl) +{ + if (openssl_globals->errors) { + pefree(openssl_globals->errors, 1); + } +} +/* }}} */ + +/* {{{ PHP_MINFO_FUNCTION + */ +PHP_MINFO_FUNCTION(openssl) +{ + php_info_print_table_start(); + php_info_print_table_row(2, "OpenSSL support", "enabled"); + php_info_print_table_row(2, "OpenSSL Library Version", SSLeay_version(SSLEAY_VERSION)); + php_info_print_table_row(2, "OpenSSL Header Version", OPENSSL_VERSION_TEXT); + php_info_print_table_row(2, "Openssl default config", default_ssl_conf_filename); + php_info_print_table_end(); + DISPLAY_INI_ENTRIES(); +} +/* }}} */ + +/* {{{ PHP_MSHUTDOWN_FUNCTION + */ +PHP_MSHUTDOWN_FUNCTION(openssl) +{ +#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined (LIBRESSL_VERSION_NUMBER) + EVP_cleanup(); + + /* prevent accessing locking callback from unloaded extension */ + CRYPTO_set_locking_callback(NULL); + /* free allocated error strings */ + ERR_free_strings(); + CONF_modules_free(); +#endif + + php_unregister_url_stream_wrapper("https"); + php_unregister_url_stream_wrapper("ftps"); + + php_stream_xport_unregister("ssl"); +#ifndef OPENSSL_NO_SSL3 + php_stream_xport_unregister("sslv3"); +#endif + php_stream_xport_unregister("tls"); + php_stream_xport_unregister("tlsv1.0"); + php_stream_xport_unregister("tlsv1.1"); + php_stream_xport_unregister("tlsv1.2"); + + /* reinstate the default tcp handler */ + php_stream_xport_register("tcp", php_stream_generic_socket_factory); + + UNREGISTER_INI_ENTRIES(); + + return SUCCESS; +} +/* }}} */ + +/* {{{ x509 cert functions */ + +/* {{{ proto array openssl_get_cert_locations(void) + Retrieve an array mapping available certificate locations */ +PHP_FUNCTION(openssl_get_cert_locations) +{ + array_init(return_value); + + add_assoc_string(return_value, "default_cert_file", (char *) X509_get_default_cert_file()); + add_assoc_string(return_value, "default_cert_file_env", (char *) X509_get_default_cert_file_env()); + add_assoc_string(return_value, "default_cert_dir", (char *) X509_get_default_cert_dir()); + add_assoc_string(return_value, "default_cert_dir_env", (char *) X509_get_default_cert_dir_env()); + add_assoc_string(return_value, "default_private_dir", (char *) X509_get_default_private_dir()); + add_assoc_string(return_value, "default_default_cert_area", (char *) X509_get_default_cert_area()); + add_assoc_string(return_value, "ini_cafile", + zend_ini_string("openssl.cafile", sizeof("openssl.cafile")-1, 0)); + add_assoc_string(return_value, "ini_capath", + zend_ini_string("openssl.capath", sizeof("openssl.capath")-1, 0)); +} +/* }}} */ + + +/* {{{ php_openssl_x509_from_zval + Given a zval, coerce it into an X509 object. + The zval can be: + . X509 resource created using openssl_read_x509() + . if it starts with file:// then it will be interpreted as the path to that cert + . it will be interpreted as the cert data + If you supply makeresource, the result will be registered as an x509 resource and + it's value returned in makeresource. +*/ +static X509 * php_openssl_x509_from_zval(zval * val, int makeresource, zend_resource **resourceval) +{ + X509 *cert = NULL; + BIO *in; + + if (resourceval) { + *resourceval = NULL; + } + if (Z_TYPE_P(val) == IS_RESOURCE) { + /* is it an x509 resource ? */ + void * what; + zend_resource *res = Z_RES_P(val); + + what = zend_fetch_resource(res, "OpenSSL X.509", le_x509); + if (!what) { + return NULL; + } + if (resourceval) { + *resourceval = res; + if (makeresource) { + Z_ADDREF_P(val); + } + } + return (X509*)what; + } + + if (!(Z_TYPE_P(val) == IS_STRING || Z_TYPE_P(val) == IS_OBJECT)) { + return NULL; + } + + /* force it to be a string and check if it refers to a file */ + convert_to_string_ex(val); + + if (Z_STRLEN_P(val) > 7 && memcmp(Z_STRVAL_P(val), "file://", sizeof("file://") - 1) == 0) { + + if (php_openssl_open_base_dir_chk(Z_STRVAL_P(val) + (sizeof("file://") - 1))) { + return NULL; + } + + in = BIO_new_file(Z_STRVAL_P(val) + (sizeof("file://") - 1), PHP_OPENSSL_BIO_MODE_R(PKCS7_BINARY)); + if (in == NULL) { + php_openssl_store_errors(); + return NULL; + } + cert = PEM_read_bio_X509(in, NULL, NULL, NULL); + + } else { + + in = BIO_new_mem_buf(Z_STRVAL_P(val), (int)Z_STRLEN_P(val)); + if (in == NULL) { + php_openssl_store_errors(); + return NULL; + } +#ifdef TYPEDEF_D2I_OF + cert = (X509 *) PEM_ASN1_read_bio((d2i_of_void *)d2i_X509, PEM_STRING_X509, in, NULL, NULL, NULL); +#else + cert = (X509 *) PEM_ASN1_read_bio((char *(*)())d2i_X509, PEM_STRING_X509, in, NULL, NULL, NULL); +#endif + } + + if (!BIO_free(in)) { + php_openssl_store_errors(); + } + + if (cert == NULL) { + php_openssl_store_errors(); + return NULL; + } + + if (makeresource && resourceval) { + *resourceval = zend_register_resource(cert, le_x509); + } + return cert; +} + +/* }}} */ + +/* {{{ proto bool openssl_x509_export_to_file(mixed x509, string outfilename [, bool notext = true]) + Exports a CERT to file or a var */ +PHP_FUNCTION(openssl_x509_export_to_file) +{ + X509 * cert; + zval * zcert; + zend_bool notext = 1; + BIO * bio_out; + char * filename; + size_t filename_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zp|b", &zcert, &filename, &filename_len, ¬ext) == FAILURE) { + return; + } + RETVAL_FALSE; + + cert = php_openssl_x509_from_zval(zcert, 0, NULL); + if (cert == NULL) { + php_error_docref(NULL, E_WARNING, "cannot get cert from parameter 1"); + return; + } + + if (php_openssl_open_base_dir_chk(filename)) { + return; + } + + bio_out = BIO_new_file(filename, PHP_OPENSSL_BIO_MODE_W(PKCS7_BINARY)); + if (bio_out) { + if (!notext && !X509_print(bio_out, cert)) { + php_openssl_store_errors(); + } + if (!PEM_write_bio_X509(bio_out, cert)) { + php_openssl_store_errors(); + } + + RETVAL_TRUE; + } else { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "error opening file %s", filename); + } + if (Z_TYPE_P(zcert) != IS_RESOURCE) { + X509_free(cert); + } + + if (!BIO_free(bio_out)) { + php_openssl_store_errors(); + } +} +/* }}} */ + +/* {{{ proto string openssl_spki_new(mixed zpkey, string challenge [, mixed method]) + Creates new private key (or uses existing) and creates a new spki cert + outputting results to var */ +PHP_FUNCTION(openssl_spki_new) +{ + size_t challenge_len; + char * challenge = NULL, * spkstr = NULL; + zend_string * s = NULL; + zend_resource *keyresource = NULL; + const char *spkac = "SPKAC="; + zend_long algo = OPENSSL_ALGO_MD5; + + zval *method = NULL; + zval * zpkey = NULL; + EVP_PKEY * pkey = NULL; + NETSCAPE_SPKI *spki=NULL; + const EVP_MD *mdtype; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rs|z", &zpkey, &challenge, &challenge_len, &method) == FAILURE) { + return; + } + RETVAL_FALSE; + + PHP_OPENSSL_CHECK_SIZE_T_TO_INT(challenge_len, challenge); + pkey = php_openssl_evp_from_zval(zpkey, 0, challenge, challenge_len, 1, &keyresource); + + if (pkey == NULL) { + php_error_docref(NULL, E_WARNING, "Unable to use supplied private key"); + goto cleanup; + } + + if (method != NULL) { + if (Z_TYPE_P(method) == IS_LONG) { + algo = Z_LVAL_P(method); + } else { + php_error_docref(NULL, E_WARNING, "Algorithm must be of supported type"); + goto cleanup; + } + } + mdtype = php_openssl_get_evp_md_from_algo(algo); + + if (!mdtype) { + php_error_docref(NULL, E_WARNING, "Unknown signature algorithm"); + goto cleanup; + } + + if ((spki = NETSCAPE_SPKI_new()) == NULL) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "Unable to create new SPKAC"); + goto cleanup; + } + + if (challenge) { + if (!ASN1_STRING_set(spki->spkac->challenge, challenge, (int)challenge_len)) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "Unable to set challenge data"); + goto cleanup; + } + } + + if (!NETSCAPE_SPKI_set_pubkey(spki, pkey)) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "Unable to embed public key"); + goto cleanup; + } + + if (!NETSCAPE_SPKI_sign(spki, pkey, mdtype)) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "Unable to sign with specified algorithm"); + goto cleanup; + } + + spkstr = NETSCAPE_SPKI_b64_encode(spki); + if (!spkstr){ + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "Unable to encode SPKAC"); + goto cleanup; + } + + s = zend_string_alloc(strlen(spkac) + strlen(spkstr), 0); + sprintf(ZSTR_VAL(s), "%s%s", spkac, spkstr); + ZSTR_LEN(s) = strlen(ZSTR_VAL(s)); + OPENSSL_free(spkstr); + + RETVAL_STR(s); + goto cleanup; + +cleanup: + + if (spki != NULL) { + NETSCAPE_SPKI_free(spki); + } + if (keyresource == NULL && pkey != NULL) { + EVP_PKEY_free(pkey); + } + + if (s && ZSTR_LEN(s) <= 0) { + RETVAL_FALSE; + } + + if (keyresource == NULL && s != NULL) { + zend_string_release_ex(s, 0); + } +} +/* }}} */ + +/* {{{ proto bool openssl_spki_verify(string spki) + Verifies spki returns boolean */ +PHP_FUNCTION(openssl_spki_verify) +{ + size_t spkstr_len; + int i = 0, spkstr_cleaned_len = 0; + char *spkstr = NULL, * spkstr_cleaned = NULL; + + EVP_PKEY *pkey = NULL; + NETSCAPE_SPKI *spki = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &spkstr, &spkstr_len) == FAILURE) { + return; + } + RETVAL_FALSE; + + if (spkstr == NULL) { + php_error_docref(NULL, E_WARNING, "Unable to use supplied SPKAC"); + goto cleanup; + } + + spkstr_cleaned = emalloc(spkstr_len + 1); + spkstr_cleaned_len = (int)(spkstr_len - php_openssl_spki_cleanup(spkstr, spkstr_cleaned)); + + if (spkstr_cleaned_len == 0) { + php_error_docref(NULL, E_WARNING, "Invalid SPKAC"); + goto cleanup; + } + + spki = NETSCAPE_SPKI_b64_decode(spkstr_cleaned, spkstr_cleaned_len); + if (spki == NULL) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "Unable to decode supplied SPKAC"); + goto cleanup; + } + + pkey = X509_PUBKEY_get(spki->spkac->pubkey); + if (pkey == NULL) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "Unable to acquire signed public key"); + goto cleanup; + } + + i = NETSCAPE_SPKI_verify(spki, pkey); + goto cleanup; + +cleanup: + if (spki != NULL) { + NETSCAPE_SPKI_free(spki); + } + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } + if (spkstr_cleaned != NULL) { + efree(spkstr_cleaned); + } + + if (i > 0) { + RETVAL_TRUE; + } else { + php_openssl_store_errors(); + } +} +/* }}} */ + +/* {{{ proto string openssl_spki_export(string spki) + Exports public key from existing spki to var */ +PHP_FUNCTION(openssl_spki_export) +{ + size_t spkstr_len; + char *spkstr = NULL, * spkstr_cleaned = NULL, * s = NULL; + int spkstr_cleaned_len; + + EVP_PKEY *pkey = NULL; + NETSCAPE_SPKI *spki = NULL; + BIO *out = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &spkstr, &spkstr_len) == FAILURE) { + return; + } + RETVAL_FALSE; + + if (spkstr == NULL) { + php_error_docref(NULL, E_WARNING, "Unable to use supplied SPKAC"); + goto cleanup; + } + + spkstr_cleaned = emalloc(spkstr_len + 1); + spkstr_cleaned_len = (int)(spkstr_len - php_openssl_spki_cleanup(spkstr, spkstr_cleaned)); + + if (spkstr_cleaned_len == 0) { + php_error_docref(NULL, E_WARNING, "Invalid SPKAC"); + goto cleanup; + } + + spki = NETSCAPE_SPKI_b64_decode(spkstr_cleaned, spkstr_cleaned_len); + if (spki == NULL) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "Unable to decode supplied SPKAC"); + goto cleanup; + } + + pkey = X509_PUBKEY_get(spki->spkac->pubkey); + if (pkey == NULL) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "Unable to acquire signed public key"); + goto cleanup; + } + + out = BIO_new(BIO_s_mem()); + if (out && PEM_write_bio_PUBKEY(out, pkey)) { + BUF_MEM *bio_buf; + + BIO_get_mem_ptr(out, &bio_buf); + RETVAL_STRINGL((char *)bio_buf->data, bio_buf->length); + } else { + php_openssl_store_errors(); + } + goto cleanup; + +cleanup: + + if (spki != NULL) { + NETSCAPE_SPKI_free(spki); + } + if (out != NULL) { + BIO_free_all(out); + } + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } + if (spkstr_cleaned != NULL) { + efree(spkstr_cleaned); + } + if (s != NULL) { + efree(s); + } +} +/* }}} */ + +/* {{{ proto string openssl_spki_export_challenge(string spki) + Exports spkac challenge from existing spki to var */ +PHP_FUNCTION(openssl_spki_export_challenge) +{ + size_t spkstr_len; + char *spkstr = NULL, * spkstr_cleaned = NULL; + int spkstr_cleaned_len; + + NETSCAPE_SPKI *spki = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &spkstr, &spkstr_len) == FAILURE) { + return; + } + RETVAL_FALSE; + + if (spkstr == NULL) { + php_error_docref(NULL, E_WARNING, "Unable to use supplied SPKAC"); + goto cleanup; + } + + spkstr_cleaned = emalloc(spkstr_len + 1); + spkstr_cleaned_len = (int)(spkstr_len - php_openssl_spki_cleanup(spkstr, spkstr_cleaned)); + + if (spkstr_cleaned_len == 0) { + php_error_docref(NULL, E_WARNING, "Invalid SPKAC"); + goto cleanup; + } + + spki = NETSCAPE_SPKI_b64_decode(spkstr_cleaned, spkstr_cleaned_len); + if (spki == NULL) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "Unable to decode SPKAC"); + goto cleanup; + } + + RETVAL_STRING((const char *)ASN1_STRING_get0_data(spki->spkac->challenge)); + goto cleanup; + +cleanup: + if (spkstr_cleaned != NULL) { + efree(spkstr_cleaned); + } + if (spki) { + NETSCAPE_SPKI_free(spki); + } +} +/* }}} */ + +/* {{{ proto bool openssl_x509_export(mixed x509, string &out [, bool notext = true]) + Exports a CERT to file or a var */ +PHP_FUNCTION(openssl_x509_export) +{ + X509 * cert; + zval * zcert, *zout; + zend_bool notext = 1; + BIO * bio_out; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zz/|b", &zcert, &zout, ¬ext) == FAILURE) { + return; + } + RETVAL_FALSE; + + cert = php_openssl_x509_from_zval(zcert, 0, NULL); + if (cert == NULL) { + php_error_docref(NULL, E_WARNING, "cannot get cert from parameter 1"); + return; + } + + bio_out = BIO_new(BIO_s_mem()); + if (!bio_out) { + php_openssl_store_errors(); + goto cleanup; + } + if (!notext && !X509_print(bio_out, cert)) { + php_openssl_store_errors(); + } + if (PEM_write_bio_X509(bio_out, cert)) { + BUF_MEM *bio_buf; + + zval_ptr_dtor(zout); + BIO_get_mem_ptr(bio_out, &bio_buf); + ZVAL_STRINGL(zout, bio_buf->data, bio_buf->length); + + RETVAL_TRUE; + } else { + php_openssl_store_errors(); + } + + BIO_free(bio_out); + +cleanup: + if (Z_TYPE_P(zcert) != IS_RESOURCE) { + X509_free(cert); + } +} +/* }}} */ + +zend_string* php_openssl_x509_fingerprint(X509 *peer, const char *method, zend_bool raw) +{ + unsigned char md[EVP_MAX_MD_SIZE]; + const EVP_MD *mdtype; + unsigned int n; + zend_string *ret; + + if (!(mdtype = EVP_get_digestbyname(method))) { + php_error_docref(NULL, E_WARNING, "Unknown signature algorithm"); + return NULL; + } else if (!X509_digest(peer, mdtype, md, &n)) { + php_openssl_store_errors(); + php_error_docref(NULL, E_ERROR, "Could not generate signature"); + return NULL; + } + + if (raw) { + ret = zend_string_init((char*)md, n, 0); + } else { + ret = zend_string_alloc(n * 2, 0); + make_digest_ex(ZSTR_VAL(ret), md, n); + ZSTR_VAL(ret)[n * 2] = '\0'; + } + + return ret; +} + +PHP_FUNCTION(openssl_x509_fingerprint) +{ + X509 *cert; + zval *zcert; + zend_bool raw_output = 0; + char *method = "sha1"; + size_t method_len; + zend_string *fingerprint; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "z|sb", &zcert, &method, &method_len, &raw_output) == FAILURE) { + return; + } + + cert = php_openssl_x509_from_zval(zcert, 0, NULL); + if (cert == NULL) { + php_error_docref(NULL, E_WARNING, "cannot get cert from parameter 1"); + RETURN_FALSE; + } + + fingerprint = php_openssl_x509_fingerprint(cert, method, raw_output); + if (fingerprint) { + RETVAL_STR(fingerprint); + } else { + RETVAL_FALSE; + } + + if (Z_TYPE_P(zcert) != IS_RESOURCE) { + X509_free(cert); + } +} + +/* {{{ proto bool openssl_x509_check_private_key(mixed cert, mixed key) + Checks if a private key corresponds to a CERT */ +PHP_FUNCTION(openssl_x509_check_private_key) +{ + zval * zcert, *zkey; + X509 * cert = NULL; + EVP_PKEY * key = NULL; + zend_resource *keyresource = NULL; + + RETVAL_FALSE; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zz", &zcert, &zkey) == FAILURE) { + return; + } + cert = php_openssl_x509_from_zval(zcert, 0, NULL); + if (cert == NULL) { + RETURN_FALSE; + } + key = php_openssl_evp_from_zval(zkey, 0, "", 0, 1, &keyresource); + if (key) { + RETVAL_BOOL(X509_check_private_key(cert, key)); + } + + if (keyresource == NULL && key) { + EVP_PKEY_free(key); + } + if (Z_TYPE_P(zcert) != IS_RESOURCE) { + X509_free(cert); + } +} +/* }}} */ + +/* Special handling of subjectAltName, see CVE-2013-4073 + * Christian Heimes + */ + +static int openssl_x509v3_subjectAltName(BIO *bio, X509_EXTENSION *extension) +{ + GENERAL_NAMES *names; + const X509V3_EXT_METHOD *method = NULL; + ASN1_OCTET_STRING *extension_data; + long i, length, num; + const unsigned char *p; + + method = X509V3_EXT_get(extension); + if (method == NULL) { + return -1; + } + + extension_data = X509_EXTENSION_get_data(extension); + p = extension_data->data; + length = extension_data->length; + if (method->it) { + names = (GENERAL_NAMES*) (ASN1_item_d2i(NULL, &p, length, + ASN1_ITEM_ptr(method->it))); + } else { + names = (GENERAL_NAMES*) (method->d2i(NULL, &p, length)); + } + if (names == NULL) { + php_openssl_store_errors(); + return -1; + } + + num = sk_GENERAL_NAME_num(names); + for (i = 0; i < num; i++) { + GENERAL_NAME *name; + ASN1_STRING *as; + name = sk_GENERAL_NAME_value(names, i); + switch (name->type) { + case GEN_EMAIL: + BIO_puts(bio, "email:"); + as = name->d.rfc822Name; + BIO_write(bio, ASN1_STRING_get0_data(as), + ASN1_STRING_length(as)); + break; + case GEN_DNS: + BIO_puts(bio, "DNS:"); + as = name->d.dNSName; + BIO_write(bio, ASN1_STRING_get0_data(as), + ASN1_STRING_length(as)); + break; + case GEN_URI: + BIO_puts(bio, "URI:"); + as = name->d.uniformResourceIdentifier; + BIO_write(bio, ASN1_STRING_get0_data(as), + ASN1_STRING_length(as)); + break; + default: + /* use builtin print for GEN_OTHERNAME, GEN_X400, + * GEN_EDIPARTY, GEN_DIRNAME, GEN_IPADD and GEN_RID + */ + GENERAL_NAME_print(bio, name); + } + /* trailing ', ' except for last element */ + if (i < (num - 1)) { + BIO_puts(bio, ", "); + } + } + sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free); + + return 0; +} + +/* {{{ proto array openssl_x509_parse(mixed x509 [, bool shortnames=true]) + Returns an array of the fields/values of the CERT */ +PHP_FUNCTION(openssl_x509_parse) +{ + zval * zcert; + X509 * cert = NULL; + int i, sig_nid; + zend_bool useshortnames = 1; + char * tmpstr; + zval subitem; + X509_EXTENSION *extension; + X509_NAME *subject_name; + char *cert_name; + char *extname; + BIO *bio_out; + BUF_MEM *bio_buf; + ASN1_INTEGER *asn1_serial; + BIGNUM *bn_serial; + char *str_serial; + char *hex_serial; + char buf[256]; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "z|b", &zcert, &useshortnames) == FAILURE) { + return; + } + cert = php_openssl_x509_from_zval(zcert, 0, NULL); + if (cert == NULL) { + RETURN_FALSE; + } + array_init(return_value); + + subject_name = X509_get_subject_name(cert); + cert_name = X509_NAME_oneline(subject_name, NULL, 0); + add_assoc_string(return_value, "name", cert_name); + OPENSSL_free(cert_name); + + php_openssl_add_assoc_name_entry(return_value, "subject", subject_name, useshortnames); + /* hash as used in CA directories to lookup cert by subject name */ + { + char buf[32]; + snprintf(buf, sizeof(buf), "%08lx", X509_subject_name_hash(cert)); + add_assoc_string(return_value, "hash", buf); + } + + php_openssl_add_assoc_name_entry(return_value, "issuer", X509_get_issuer_name(cert), useshortnames); + add_assoc_long(return_value, "version", X509_get_version(cert)); + + asn1_serial = X509_get_serialNumber(cert); + + bn_serial = ASN1_INTEGER_to_BN(asn1_serial, NULL); + /* Can return NULL on error or memory allocation failure */ + if (!bn_serial) { + php_openssl_store_errors(); + RETURN_FALSE; + } + + hex_serial = BN_bn2hex(bn_serial); + BN_free(bn_serial); + /* Can return NULL on error or memory allocation failure */ + if (!hex_serial) { + php_openssl_store_errors(); + RETURN_FALSE; + } + + str_serial = i2s_ASN1_INTEGER(NULL, asn1_serial); + add_assoc_string(return_value, "serialNumber", str_serial); + OPENSSL_free(str_serial); + + /* Return the hex representation of the serial number, as defined by OpenSSL */ + add_assoc_string(return_value, "serialNumberHex", hex_serial); + OPENSSL_free(hex_serial); + + php_openssl_add_assoc_asn1_string(return_value, "validFrom", X509_get_notBefore(cert)); + php_openssl_add_assoc_asn1_string(return_value, "validTo", X509_get_notAfter(cert)); + + add_assoc_long(return_value, "validFrom_time_t", php_openssl_asn1_time_to_time_t(X509_get_notBefore(cert))); + add_assoc_long(return_value, "validTo_time_t", php_openssl_asn1_time_to_time_t(X509_get_notAfter(cert))); + + tmpstr = (char *)X509_alias_get0(cert, NULL); + if (tmpstr) { + add_assoc_string(return_value, "alias", tmpstr); + } + + sig_nid = X509_get_signature_nid(cert); + add_assoc_string(return_value, "signatureTypeSN", (char*)OBJ_nid2sn(sig_nid)); + add_assoc_string(return_value, "signatureTypeLN", (char*)OBJ_nid2ln(sig_nid)); + add_assoc_long(return_value, "signatureTypeNID", sig_nid); + array_init(&subitem); + + /* NOTE: the purposes are added as integer keys - the keys match up to the X509_PURPOSE_SSL_XXX defines + in x509v3.h */ + for (i = 0; i < X509_PURPOSE_get_count(); i++) { + int id, purpset; + char * pname; + X509_PURPOSE * purp; + zval subsub; + + array_init(&subsub); + + purp = X509_PURPOSE_get0(i); + id = X509_PURPOSE_get_id(purp); + + purpset = X509_check_purpose(cert, id, 0); + add_index_bool(&subsub, 0, purpset); + + purpset = X509_check_purpose(cert, id, 1); + add_index_bool(&subsub, 1, purpset); + + pname = useshortnames ? X509_PURPOSE_get0_sname(purp) : X509_PURPOSE_get0_name(purp); + add_index_string(&subsub, 2, pname); + + /* NOTE: if purpset > 1 then it's a warning - we should mention it ? */ + + add_index_zval(&subitem, id, &subsub); + } + add_assoc_zval(return_value, "purposes", &subitem); + + array_init(&subitem); + + + for (i = 0; i < X509_get_ext_count(cert); i++) { + int nid; + extension = X509_get_ext(cert, i); + nid = OBJ_obj2nid(X509_EXTENSION_get_object(extension)); + if (nid != NID_undef) { + extname = (char *)OBJ_nid2sn(OBJ_obj2nid(X509_EXTENSION_get_object(extension))); + } else { + OBJ_obj2txt(buf, sizeof(buf)-1, X509_EXTENSION_get_object(extension), 1); + extname = buf; + } + bio_out = BIO_new(BIO_s_mem()); + if (bio_out == NULL) { + php_openssl_store_errors(); + RETURN_FALSE; + } + if (nid == NID_subject_alt_name) { + if (openssl_x509v3_subjectAltName(bio_out, extension) == 0) { + BIO_get_mem_ptr(bio_out, &bio_buf); + add_assoc_stringl(&subitem, extname, bio_buf->data, bio_buf->length); + } else { + zend_array_destroy(Z_ARR_P(return_value)); + BIO_free(bio_out); + if (Z_TYPE_P(zcert) != IS_RESOURCE) { + X509_free(cert); + } + RETURN_FALSE; + } + } + else if (X509V3_EXT_print(bio_out, extension, 0, 0)) { + BIO_get_mem_ptr(bio_out, &bio_buf); + add_assoc_stringl(&subitem, extname, bio_buf->data, bio_buf->length); + } else { + php_openssl_add_assoc_asn1_string(&subitem, extname, X509_EXTENSION_get_data(extension)); + } + BIO_free(bio_out); + } + add_assoc_zval(return_value, "extensions", &subitem); + if (Z_TYPE_P(zcert) != IS_RESOURCE) { + X509_free(cert); + } +} +/* }}} */ + +/* {{{ php_openssl_load_all_certs_from_file */ +static STACK_OF(X509) *php_openssl_load_all_certs_from_file(char *certfile) +{ + STACK_OF(X509_INFO) *sk=NULL; + STACK_OF(X509) *stack=NULL, *ret=NULL; + BIO *in=NULL; + X509_INFO *xi; + + if(!(stack = sk_X509_new_null())) { + php_openssl_store_errors(); + php_error_docref(NULL, E_ERROR, "memory allocation failure"); + goto end; + } + + if (php_openssl_open_base_dir_chk(certfile)) { + sk_X509_free(stack); + goto end; + } + + if (!(in=BIO_new_file(certfile, PHP_OPENSSL_BIO_MODE_R(PKCS7_BINARY)))) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "error opening the file, %s", certfile); + sk_X509_free(stack); + goto end; + } + + /* This loads from a file, a stack of x509/crl/pkey sets */ + if (!(sk=PEM_X509_INFO_read_bio(in, NULL, NULL, NULL))) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "error reading the file, %s", certfile); + sk_X509_free(stack); + goto end; + } + + /* scan over it and pull out the certs */ + while (sk_X509_INFO_num(sk)) { + xi=sk_X509_INFO_shift(sk); + if (xi->x509 != NULL) { + sk_X509_push(stack,xi->x509); + xi->x509=NULL; + } + X509_INFO_free(xi); + } + if (!sk_X509_num(stack)) { + php_error_docref(NULL, E_WARNING, "no certificates in file, %s", certfile); + sk_X509_free(stack); + goto end; + } + ret = stack; +end: + BIO_free(in); + sk_X509_INFO_free(sk); + + return ret; +} +/* }}} */ + +/* {{{ check_cert */ +static int check_cert(X509_STORE *ctx, X509 *x, STACK_OF(X509) *untrustedchain, int purpose) +{ + int ret=0; + X509_STORE_CTX *csc; + + csc = X509_STORE_CTX_new(); + if (csc == NULL) { + php_openssl_store_errors(); + php_error_docref(NULL, E_ERROR, "memory allocation failure"); + return 0; + } + if (!X509_STORE_CTX_init(csc, ctx, x, untrustedchain)) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "cert store initialization failed"); + return 0; + } + if (purpose >= 0 && !X509_STORE_CTX_set_purpose(csc, purpose)) { + php_openssl_store_errors(); + } + ret = X509_verify_cert(csc); + if (ret < 0) { + php_openssl_store_errors(); + } + X509_STORE_CTX_free(csc); + + return ret; +} +/* }}} */ + +/* {{{ proto int openssl_x509_checkpurpose(mixed x509cert, int purpose, array cainfo [, string untrustedfile]) + Checks the CERT to see if it can be used for the purpose in purpose. cainfo holds information about trusted CAs */ +PHP_FUNCTION(openssl_x509_checkpurpose) +{ + zval * zcert, * zcainfo = NULL; + X509_STORE * cainfo = NULL; + X509 * cert = NULL; + STACK_OF(X509) * untrustedchain = NULL; + zend_long purpose; + char * untrusted = NULL; + size_t untrusted_len = 0; + int ret; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zl|a!s", &zcert, &purpose, &zcainfo, &untrusted, &untrusted_len) == FAILURE) { + return; + } + + RETVAL_LONG(-1); + + if (untrusted) { + untrustedchain = php_openssl_load_all_certs_from_file(untrusted); + if (untrustedchain == NULL) { + goto clean_exit; + } + } + + cainfo = php_openssl_setup_verify(zcainfo); + if (cainfo == NULL) { + goto clean_exit; + } + cert = php_openssl_x509_from_zval(zcert, 0, NULL); + if (cert == NULL) { + goto clean_exit; + } + + ret = check_cert(cainfo, cert, untrustedchain, (int)purpose); + if (ret != 0 && ret != 1) { + RETVAL_LONG(ret); + } else { + RETVAL_BOOL(ret); + } + if (Z_TYPE_P(zcert) != IS_RESOURCE) { + X509_free(cert); + } +clean_exit: + if (cainfo) { + X509_STORE_free(cainfo); + } + if (untrustedchain) { + sk_X509_pop_free(untrustedchain, X509_free); + } +} +/* }}} */ + +/* {{{ php_openssl_setup_verify + * calist is an array containing file and directory names. create a + * certificate store and add those certs to it for use in verification. +*/ +static X509_STORE *php_openssl_setup_verify(zval *calist) +{ + X509_STORE *store; + X509_LOOKUP * dir_lookup, * file_lookup; + int ndirs = 0, nfiles = 0; + zval * item; + zend_stat_t sb; + + store = X509_STORE_new(); + + if (store == NULL) { + php_openssl_store_errors(); + return NULL; + } + + if (calist && (Z_TYPE_P(calist) == IS_ARRAY)) { + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(calist), item) { + convert_to_string_ex(item); + + if (VCWD_STAT(Z_STRVAL_P(item), &sb) == -1) { + php_error_docref(NULL, E_WARNING, "unable to stat %s", Z_STRVAL_P(item)); + continue; + } + + if ((sb.st_mode & S_IFREG) == S_IFREG) { + file_lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file()); + if (file_lookup == NULL || !X509_LOOKUP_load_file(file_lookup, Z_STRVAL_P(item), X509_FILETYPE_PEM)) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "error loading file %s", Z_STRVAL_P(item)); + } else { + nfiles++; + } + file_lookup = NULL; + } else { + dir_lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir()); + if (dir_lookup == NULL || !X509_LOOKUP_add_dir(dir_lookup, Z_STRVAL_P(item), X509_FILETYPE_PEM)) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "error loading directory %s", Z_STRVAL_P(item)); + } else { + ndirs++; + } + dir_lookup = NULL; + } + } ZEND_HASH_FOREACH_END(); + } + if (nfiles == 0) { + file_lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file()); + if (file_lookup == NULL || !X509_LOOKUP_load_file(file_lookup, NULL, X509_FILETYPE_DEFAULT)) { + php_openssl_store_errors(); + } + } + if (ndirs == 0) { + dir_lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir()); + if (dir_lookup == NULL || !X509_LOOKUP_add_dir(dir_lookup, NULL, X509_FILETYPE_DEFAULT)) { + php_openssl_store_errors(); + } + } + return store; +} +/* }}} */ + +/* {{{ proto resource openssl_x509_read(mixed cert) + Reads X.509 certificates */ +PHP_FUNCTION(openssl_x509_read) +{ + zval *cert; + X509 *x509; + zend_resource *res; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &cert) == FAILURE) { + return; + } + x509 = php_openssl_x509_from_zval(cert, 1, &res); + ZVAL_RES(return_value, res); + + if (x509 == NULL) { + php_error_docref(NULL, E_WARNING, "supplied parameter cannot be coerced into an X509 certificate!"); + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto void openssl_x509_free(resource x509) + Frees X.509 certificates */ +PHP_FUNCTION(openssl_x509_free) +{ + zval *x509; + X509 *cert; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &x509) == FAILURE) { + return; + } + if ((cert = (X509 *)zend_fetch_resource(Z_RES_P(x509), "OpenSSL X.509", le_x509)) == NULL) { + RETURN_FALSE; + } + zend_list_close(Z_RES_P(x509)); +} +/* }}} */ + +/* }}} */ + +/* Pop all X509 from Stack and free them, free the stack afterwards */ +static void php_sk_X509_free(STACK_OF(X509) * sk) /* {{{ */ +{ + for (;;) { + X509* x = sk_X509_pop(sk); + if (!x) break; + X509_free(x); + } + sk_X509_free(sk); +} +/* }}} */ + +static STACK_OF(X509) * php_array_to_X509_sk(zval * zcerts) /* {{{ */ +{ + zval * zcertval; + STACK_OF(X509) * sk = NULL; + X509 * cert; + zend_resource *certresource; + + sk = sk_X509_new_null(); + + /* get certs */ + if (Z_TYPE_P(zcerts) == IS_ARRAY) { + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(zcerts), zcertval) { + cert = php_openssl_x509_from_zval(zcertval, 0, &certresource); + if (cert == NULL) { + goto clean_exit; + } + + if (certresource != NULL) { + cert = X509_dup(cert); + + if (cert == NULL) { + php_openssl_store_errors(); + goto clean_exit; + } + + } + sk_X509_push(sk, cert); + } ZEND_HASH_FOREACH_END(); + } else { + /* a single certificate */ + cert = php_openssl_x509_from_zval(zcerts, 0, &certresource); + + if (cert == NULL) { + goto clean_exit; + } + + if (certresource != NULL) { + cert = X509_dup(cert); + if (cert == NULL) { + php_openssl_store_errors(); + goto clean_exit; + } + } + sk_X509_push(sk, cert); + } + +clean_exit: + return sk; +} +/* }}} */ + +/* {{{ proto bool openssl_pkcs12_export_to_file(mixed x509, string filename, mixed priv_key, string pass[, array args]) + Creates and exports a PKCS to file */ +PHP_FUNCTION(openssl_pkcs12_export_to_file) +{ + X509 * cert = NULL; + BIO * bio_out = NULL; + PKCS12 * p12 = NULL; + char * filename; + char * friendly_name = NULL; + size_t filename_len; + char * pass; + size_t pass_len; + zval *zcert = NULL, *zpkey = NULL, *args = NULL; + EVP_PKEY *priv_key = NULL; + zend_resource *keyresource; + zval * item; + STACK_OF(X509) *ca = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zpzs|a", &zcert, &filename, &filename_len, &zpkey, &pass, &pass_len, &args) == FAILURE) + return; + + RETVAL_FALSE; + + cert = php_openssl_x509_from_zval(zcert, 0, NULL); + if (cert == NULL) { + php_error_docref(NULL, E_WARNING, "cannot get cert from parameter 1"); + return; + } + priv_key = php_openssl_evp_from_zval(zpkey, 0, "", 0, 1, &keyresource); + if (priv_key == NULL) { + php_error_docref(NULL, E_WARNING, "cannot get private key from parameter 3"); + goto cleanup; + } + if (!X509_check_private_key(cert, priv_key)) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "private key does not correspond to cert"); + goto cleanup; + } + if (php_openssl_open_base_dir_chk(filename)) { + goto cleanup; + } + + /* parse extra config from args array, promote this to an extra function */ + if (args && + (item = zend_hash_str_find(Z_ARRVAL_P(args), "friendly_name", sizeof("friendly_name")-1)) != NULL && + Z_TYPE_P(item) == IS_STRING + ) { + friendly_name = Z_STRVAL_P(item); + } + /* certpbe (default RC2-40) + keypbe (default 3DES) + friendly_caname + */ + + if (args && (item = zend_hash_str_find(Z_ARRVAL_P(args), "extracerts", sizeof("extracerts")-1)) != NULL) { + ca = php_array_to_X509_sk(item); + } + /* end parse extra config */ + + /*PKCS12 *PKCS12_create(char *pass, char *name, EVP_PKEY *pkey, X509 *cert, STACK_OF(X509) *ca, + int nid_key, int nid_cert, int iter, int mac_iter, int keytype);*/ + + p12 = PKCS12_create(pass, friendly_name, priv_key, cert, ca, 0, 0, 0, 0, 0); + if (p12 != NULL) { + bio_out = BIO_new_file(filename, PHP_OPENSSL_BIO_MODE_W(PKCS7_BINARY)); + if (bio_out != NULL) { + + i2d_PKCS12_bio(bio_out, p12); + BIO_free(bio_out); + + RETVAL_TRUE; + } else { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "error opening file %s", filename); + } + + PKCS12_free(p12); + } else { + php_openssl_store_errors(); + } + + php_sk_X509_free(ca); + +cleanup: + + if (keyresource == NULL && priv_key) { + EVP_PKEY_free(priv_key); + } + + if (Z_TYPE_P(zcert) != IS_RESOURCE) { + X509_free(cert); + } +} +/* }}} */ + +/* {{{ proto bool openssl_pkcs12_export(mixed x509, string &out, mixed priv_key, string pass[, array args]) + Creates and exports a PKCS12 to a var */ +PHP_FUNCTION(openssl_pkcs12_export) +{ + X509 * cert = NULL; + BIO * bio_out; + PKCS12 * p12 = NULL; + zval * zcert = NULL, *zout = NULL, *zpkey, *args = NULL; + EVP_PKEY *priv_key = NULL; + zend_resource *keyresource; + char * pass; + size_t pass_len; + char * friendly_name = NULL; + zval * item; + STACK_OF(X509) *ca = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zz/zs|a", &zcert, &zout, &zpkey, &pass, &pass_len, &args) == FAILURE) + return; + + RETVAL_FALSE; + + cert = php_openssl_x509_from_zval(zcert, 0, NULL); + if (cert == NULL) { + php_error_docref(NULL, E_WARNING, "cannot get cert from parameter 1"); + return; + } + priv_key = php_openssl_evp_from_zval(zpkey, 0, "", 0, 1, &keyresource); + if (priv_key == NULL) { + php_error_docref(NULL, E_WARNING, "cannot get private key from parameter 3"); + goto cleanup; + } + if (!X509_check_private_key(cert, priv_key)) { + php_error_docref(NULL, E_WARNING, "private key does not correspond to cert"); + goto cleanup; + } + + /* parse extra config from args array, promote this to an extra function */ + if (args && + (item = zend_hash_str_find(Z_ARRVAL_P(args), "friendly_name", sizeof("friendly_name")-1)) != NULL && + Z_TYPE_P(item) == IS_STRING + ) { + friendly_name = Z_STRVAL_P(item); + } + + if (args && (item = zend_hash_str_find(Z_ARRVAL_P(args), "extracerts", sizeof("extracerts")-1)) != NULL) { + ca = php_array_to_X509_sk(item); + } + /* end parse extra config */ + + p12 = PKCS12_create(pass, friendly_name, priv_key, cert, ca, 0, 0, 0, 0, 0); + + if (p12 != NULL) { + bio_out = BIO_new(BIO_s_mem()); + if (i2d_PKCS12_bio(bio_out, p12)) { + BUF_MEM *bio_buf; + + zval_ptr_dtor(zout); + BIO_get_mem_ptr(bio_out, &bio_buf); + ZVAL_STRINGL(zout, bio_buf->data, bio_buf->length); + + RETVAL_TRUE; + } else { + php_openssl_store_errors(); + } + + BIO_free(bio_out); + PKCS12_free(p12); + } else { + php_openssl_store_errors(); + } + php_sk_X509_free(ca); + +cleanup: + + if (keyresource == NULL && priv_key) { + EVP_PKEY_free(priv_key); + } + if (Z_TYPE_P(zcert) != IS_RESOURCE) { + X509_free(cert); + } +} +/* }}} */ + +/* {{{ proto bool openssl_pkcs12_read(string PKCS12, array &certs, string pass) + Parses a PKCS12 to an array */ +PHP_FUNCTION(openssl_pkcs12_read) +{ + zval *zout = NULL, zextracerts, zcert, zpkey; + char *pass, *zp12; + size_t pass_len, zp12_len; + PKCS12 * p12 = NULL; + EVP_PKEY * pkey = NULL; + X509 * cert = NULL; + STACK_OF(X509) * ca = NULL; + BIO * bio_in = NULL; + int i; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sz/s", &zp12, &zp12_len, &zout, &pass, &pass_len) == FAILURE) + return; + + RETVAL_FALSE; + + PHP_OPENSSL_CHECK_SIZE_T_TO_INT(zp12_len, pkcs12); + + bio_in = BIO_new(BIO_s_mem()); + + if (0 >= BIO_write(bio_in, zp12, (int)zp12_len)) { + php_openssl_store_errors(); + goto cleanup; + } + + if (d2i_PKCS12_bio(bio_in, &p12) && PKCS12_parse(p12, pass, &pkey, &cert, &ca)) { + BIO * bio_out; + int cert_num; + + zval_ptr_dtor(zout); + array_init(zout); + + if (cert) { + bio_out = BIO_new(BIO_s_mem()); + if (PEM_write_bio_X509(bio_out, cert)) { + BUF_MEM *bio_buf; + BIO_get_mem_ptr(bio_out, &bio_buf); + ZVAL_STRINGL(&zcert, bio_buf->data, bio_buf->length); + add_assoc_zval(zout, "cert", &zcert); + } else { + php_openssl_store_errors(); + } + BIO_free(bio_out); + } + + if (pkey) { + bio_out = BIO_new(BIO_s_mem()); + if (PEM_write_bio_PrivateKey(bio_out, pkey, NULL, NULL, 0, 0, NULL)) { + BUF_MEM *bio_buf; + BIO_get_mem_ptr(bio_out, &bio_buf); + ZVAL_STRINGL(&zpkey, bio_buf->data, bio_buf->length); + add_assoc_zval(zout, "pkey", &zpkey); + } else { + php_openssl_store_errors(); + } + BIO_free(bio_out); + } + + cert_num = sk_X509_num(ca); + if (ca && cert_num) { + array_init(&zextracerts); + + for (i = 0; i < cert_num; i++) { + zval zextracert; + X509* aCA = sk_X509_pop(ca); + if (!aCA) break; + + bio_out = BIO_new(BIO_s_mem()); + if (PEM_write_bio_X509(bio_out, aCA)) { + BUF_MEM *bio_buf; + BIO_get_mem_ptr(bio_out, &bio_buf); + ZVAL_STRINGL(&zextracert, bio_buf->data, bio_buf->length); + add_index_zval(&zextracerts, i, &zextracert); + } + + X509_free(aCA); + BIO_free(bio_out); + } + + sk_X509_free(ca); + add_assoc_zval(zout, "extracerts", &zextracerts); + } + + RETVAL_TRUE; + } else { + php_openssl_store_errors(); + } + + cleanup: + if (bio_in) { + BIO_free(bio_in); + } + if (pkey) { + EVP_PKEY_free(pkey); + } + if (cert) { + X509_free(cert); + } + if (p12) { + PKCS12_free(p12); + } +} +/* }}} */ + +/* {{{ x509 CSR functions */ + +/* {{{ php_openssl_make_REQ */ +static int php_openssl_make_REQ(struct php_x509_request * req, X509_REQ * csr, zval * dn, zval * attribs) +{ + STACK_OF(CONF_VALUE) * dn_sk, *attr_sk = NULL; + char * str, *dn_sect, *attr_sect; + + dn_sect = CONF_get_string(req->req_config, req->section_name, "distinguished_name"); + if (dn_sect == NULL) { + php_openssl_store_errors(); + return FAILURE; + } + dn_sk = CONF_get_section(req->req_config, dn_sect); + if (dn_sk == NULL) { + php_openssl_store_errors(); + return FAILURE; + } + attr_sect = CONF_get_string(req->req_config, req->section_name, "attributes"); + if (attr_sect == NULL) { + php_openssl_store_errors(); + attr_sk = NULL; + } else { + attr_sk = CONF_get_section(req->req_config, attr_sect); + if (attr_sk == NULL) { + php_openssl_store_errors(); + return FAILURE; + } + } + /* setup the version number: version 1 */ + if (X509_REQ_set_version(csr, 0L)) { + int i, nid; + char * type; + CONF_VALUE * v; + X509_NAME * subj; + zval * item; + zend_string * strindex = NULL; + + subj = X509_REQ_get_subject_name(csr); + /* apply values from the dn hash */ + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(dn), strindex, item) { + if (strindex) { + int nid; + + convert_to_string_ex(item); + + nid = OBJ_txt2nid(ZSTR_VAL(strindex)); + if (nid != NID_undef) { + if (!X509_NAME_add_entry_by_NID(subj, nid, MBSTRING_UTF8, + (unsigned char*)Z_STRVAL_P(item), -1, -1, 0)) + { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, + "dn: add_entry_by_NID %d -> %s (failed; check error" + " queue and value of string_mask OpenSSL option " + "if illegal characters are reported)", + nid, Z_STRVAL_P(item)); + return FAILURE; + } + } else { + php_error_docref(NULL, E_WARNING, "dn: %s is not a recognized name", ZSTR_VAL(strindex)); + } + } + } ZEND_HASH_FOREACH_END(); + + /* Finally apply defaults from config file */ + for(i = 0; i < sk_CONF_VALUE_num(dn_sk); i++) { + size_t len; + char buffer[200 + 1]; /*200 + \0 !*/ + + v = sk_CONF_VALUE_value(dn_sk, i); + type = v->name; + + len = strlen(type); + if (len < sizeof("_default")) { + continue; + } + len -= sizeof("_default") - 1; + if (strcmp("_default", type + len) != 0) { + continue; + } + if (len > 200) { + len = 200; + } + memcpy(buffer, type, len); + buffer[len] = '\0'; + type = buffer; + + /* Skip past any leading X. X: X, etc to allow for multiple + * instances */ + for (str = type; *str; str++) { + if (*str == ':' || *str == ',' || *str == '.') { + str++; + if (*str) { + type = str; + } + break; + } + } + /* if it is already set, skip this */ + nid = OBJ_txt2nid(type); + if (X509_NAME_get_index_by_NID(subj, nid, -1) >= 0) { + continue; + } + if (!X509_NAME_add_entry_by_txt(subj, type, MBSTRING_UTF8, (unsigned char*)v->value, -1, -1, 0)) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "add_entry_by_txt %s -> %s (failed)", type, v->value); + return FAILURE; + } + if (!X509_NAME_entry_count(subj)) { + php_error_docref(NULL, E_WARNING, "no objects specified in config file"); + return FAILURE; + } + } + if (attribs) { + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(attribs), strindex, item) { + int nid; + + if (NULL == strindex) { + php_error_docref(NULL, E_WARNING, "dn: numeric fild names are not supported"); + continue; + } + + convert_to_string_ex(item); + + nid = OBJ_txt2nid(ZSTR_VAL(strindex)); + if (nid != NID_undef) { + if (!X509_NAME_add_entry_by_NID(subj, nid, MBSTRING_UTF8, (unsigned char*)Z_STRVAL_P(item), -1, -1, 0)) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "attribs: add_entry_by_NID %d -> %s (failed)", nid, Z_STRVAL_P(item)); + return FAILURE; + } + } else { + php_error_docref(NULL, E_WARNING, "dn: %s is not a recognized name", ZSTR_VAL(strindex)); + } + } ZEND_HASH_FOREACH_END(); + for (i = 0; i < sk_CONF_VALUE_num(attr_sk); i++) { + v = sk_CONF_VALUE_value(attr_sk, i); + /* if it is already set, skip this */ + nid = OBJ_txt2nid(v->name); + if (X509_REQ_get_attr_by_NID(csr, nid, -1) >= 0) { + continue; + } + if (!X509_REQ_add1_attr_by_txt(csr, v->name, MBSTRING_UTF8, (unsigned char*)v->value, -1)) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, + "add1_attr_by_txt %s -> %s (failed; check error queue " + "and value of string_mask OpenSSL option if illegal " + "characters are reported)", + v->name, v->value); + return FAILURE; + } + } + } + } else { + php_openssl_store_errors(); + } + + if (!X509_REQ_set_pubkey(csr, req->priv_key)) { + php_openssl_store_errors(); + } + return SUCCESS; +} +/* }}} */ + +/* {{{ php_openssl_csr_from_zval */ +static X509_REQ * php_openssl_csr_from_zval(zval * val, int makeresource, zend_resource **resourceval) +{ + X509_REQ * csr = NULL; + char * filename = NULL; + BIO * in; + + if (resourceval) { + *resourceval = NULL; + } + if (Z_TYPE_P(val) == IS_RESOURCE) { + void * what; + zend_resource *res = Z_RES_P(val); + + what = zend_fetch_resource(res, "OpenSSL X.509 CSR", le_csr); + if (what) { + if (resourceval) { + *resourceval = res; + if (makeresource) { + Z_ADDREF_P(val); + } + } + return (X509_REQ*)what; + } + return NULL; + } else if (Z_TYPE_P(val) != IS_STRING) { + return NULL; + } + + if (Z_STRLEN_P(val) > 7 && memcmp(Z_STRVAL_P(val), "file://", sizeof("file://") - 1) == 0) { + filename = Z_STRVAL_P(val) + (sizeof("file://") - 1); + } + if (filename) { + if (php_openssl_open_base_dir_chk(filename)) { + return NULL; + } + in = BIO_new_file(filename, PHP_OPENSSL_BIO_MODE_R(PKCS7_BINARY)); + } else { + in = BIO_new_mem_buf(Z_STRVAL_P(val), (int)Z_STRLEN_P(val)); + } + + if (in == NULL) { + php_openssl_store_errors(); + return NULL; + } + + csr = PEM_read_bio_X509_REQ(in, NULL,NULL,NULL); + if (csr == NULL) { + php_openssl_store_errors(); + } + + BIO_free(in); + + return csr; +} +/* }}} */ + +/* {{{ proto bool openssl_csr_export_to_file(resource csr, string outfilename [, bool notext=true]) + Exports a CSR to file */ +PHP_FUNCTION(openssl_csr_export_to_file) +{ + X509_REQ * csr; + zval * zcsr = NULL; + zend_bool notext = 1; + char * filename = NULL; + size_t filename_len; + BIO * bio_out; + zend_resource *csr_resource; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rp|b", &zcsr, &filename, &filename_len, ¬ext) == FAILURE) { + return; + } + RETVAL_FALSE; + + csr = php_openssl_csr_from_zval(zcsr, 0, &csr_resource); + if (csr == NULL) { + php_error_docref(NULL, E_WARNING, "cannot get CSR from parameter 1"); + return; + } + + if (php_openssl_open_base_dir_chk(filename)) { + return; + } + + bio_out = BIO_new_file(filename, PHP_OPENSSL_BIO_MODE_W(PKCS7_BINARY)); + if (bio_out != NULL) { + if (!notext && !X509_REQ_print(bio_out, csr)) { + php_openssl_store_errors(); + } + if (!PEM_write_bio_X509_REQ(bio_out, csr)) { + php_error_docref(NULL, E_WARNING, "error writing PEM to file %s", filename); + php_openssl_store_errors(); + } else { + RETVAL_TRUE; + } + BIO_free(bio_out); + } else { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "error opening file %s", filename); + } + + if (csr_resource == NULL && csr != NULL) { + X509_REQ_free(csr); + } +} +/* }}} */ + +/* {{{ proto bool openssl_csr_export(resource csr, string &out [, bool notext=true]) + Exports a CSR to file or a var */ +PHP_FUNCTION(openssl_csr_export) +{ + X509_REQ * csr; + zval * zcsr = NULL, *zout=NULL; + zend_bool notext = 1; + BIO * bio_out; + zend_resource *csr_resource; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rz/|b", &zcsr, &zout, ¬ext) == FAILURE) { + return; + } + + RETVAL_FALSE; + + csr = php_openssl_csr_from_zval(zcsr, 0, &csr_resource); + if (csr == NULL) { + php_error_docref(NULL, E_WARNING, "cannot get CSR from parameter 1"); + return; + } + + /* export to a var */ + + bio_out = BIO_new(BIO_s_mem()); + if (!notext && !X509_REQ_print(bio_out, csr)) { + php_openssl_store_errors(); + } + + if (PEM_write_bio_X509_REQ(bio_out, csr)) { + BUF_MEM *bio_buf; + + BIO_get_mem_ptr(bio_out, &bio_buf); + zval_ptr_dtor(zout); + ZVAL_STRINGL(zout, bio_buf->data, bio_buf->length); + + RETVAL_TRUE; + } else { + php_openssl_store_errors(); + } + + if (csr_resource == NULL && csr) { + X509_REQ_free(csr); + } + BIO_free(bio_out); +} +/* }}} */ + +/* {{{ proto resource openssl_csr_sign(mixed csr, mixed x509, mixed priv_key, int days [, array config_args [, int serial]]) + Signs a cert with another CERT */ +PHP_FUNCTION(openssl_csr_sign) +{ + zval * zcert = NULL, *zcsr, *zpkey, *args = NULL; + zend_long num_days; + zend_long serial = Z_L(0); + X509 * cert = NULL, *new_cert = NULL; + X509_REQ * csr; + EVP_PKEY * key = NULL, *priv_key = NULL; + zend_resource *csr_resource, *certresource = NULL, *keyresource = NULL; + int i; + struct php_x509_request req; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zz!zl|a!l", &zcsr, &zcert, &zpkey, &num_days, &args, &serial) == FAILURE) + return; + + RETVAL_FALSE; + PHP_SSL_REQ_INIT(&req); + + csr = php_openssl_csr_from_zval(zcsr, 0, &csr_resource); + if (csr == NULL) { + php_error_docref(NULL, E_WARNING, "cannot get CSR from parameter 1"); + return; + } + if (zcert) { + cert = php_openssl_x509_from_zval(zcert, 0, &certresource); + if (cert == NULL) { + php_error_docref(NULL, E_WARNING, "cannot get cert from parameter 2"); + goto cleanup; + } + } + priv_key = php_openssl_evp_from_zval(zpkey, 0, "", 0, 1, &keyresource); + if (priv_key == NULL) { + php_error_docref(NULL, E_WARNING, "cannot get private key from parameter 3"); + goto cleanup; + } + if (cert && !X509_check_private_key(cert, priv_key)) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "private key does not correspond to signing cert"); + goto cleanup; + } + + if (PHP_SSL_REQ_PARSE(&req, args) == FAILURE) { + goto cleanup; + } + /* Check that the request matches the signature */ + key = X509_REQ_get_pubkey(csr); + if (key == NULL) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "error unpacking public key"); + goto cleanup; + } + i = X509_REQ_verify(csr, key); + + if (i < 0) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "Signature verification problems"); + goto cleanup; + } + else if (i == 0) { + php_error_docref(NULL, E_WARNING, "Signature did not match the certificate request"); + goto cleanup; + } + + /* Now we can get on with it */ + + new_cert = X509_new(); + if (new_cert == NULL) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "No memory"); + goto cleanup; + } + /* Version 3 cert */ + if (!X509_set_version(new_cert, 2)) { + goto cleanup; + } + + + ASN1_INTEGER_set(X509_get_serialNumber(new_cert), (long)serial); + + X509_set_subject_name(new_cert, X509_REQ_get_subject_name(csr)); + + if (cert == NULL) { + cert = new_cert; + } + if (!X509_set_issuer_name(new_cert, X509_get_subject_name(cert))) { + php_openssl_store_errors(); + goto cleanup; + } + X509_gmtime_adj(X509_get_notBefore(new_cert), 0); + X509_gmtime_adj(X509_get_notAfter(new_cert), 60*60*24*(long)num_days); + i = X509_set_pubkey(new_cert, key); + if (!i) { + php_openssl_store_errors(); + goto cleanup; + } + if (req.extensions_section) { + X509V3_CTX ctx; + + X509V3_set_ctx(&ctx, cert, new_cert, csr, NULL, 0); + X509V3_set_conf_lhash(&ctx, req.req_config); + if (!X509V3_EXT_add_conf(req.req_config, &ctx, req.extensions_section, new_cert)) { + php_openssl_store_errors(); + goto cleanup; + } + } + + /* Now sign it */ + if (!X509_sign(new_cert, priv_key, req.digest)) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "failed to sign it"); + goto cleanup; + } + + /* Succeeded; lets return the cert */ + ZVAL_RES(return_value, zend_register_resource(new_cert, le_x509)); + new_cert = NULL; + +cleanup: + + if (cert == new_cert) { + cert = NULL; + } + PHP_SSL_REQ_DISPOSE(&req); + + if (keyresource == NULL && priv_key) { + EVP_PKEY_free(priv_key); + } + if (key) { + EVP_PKEY_free(key); + } + if (csr_resource == NULL && csr) { + X509_REQ_free(csr); + } + if (zcert && certresource == NULL && cert) { + X509_free(cert); + } + if (new_cert) { + X509_free(new_cert); + } +} +/* }}} */ + +/* {{{ proto bool openssl_csr_new(array dn, resource &privkey [, array configargs [, array extraattribs]]) + Generates a privkey and CSR */ +PHP_FUNCTION(openssl_csr_new) +{ + struct php_x509_request req; + zval * args = NULL, * dn, *attribs = NULL; + zval * out_pkey; + X509_REQ * csr = NULL; + int we_made_the_key = 1; + zend_resource *key_resource; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "az/|a!a!", &dn, &out_pkey, &args, &attribs) == FAILURE) { + return; + } + RETVAL_FALSE; + + PHP_SSL_REQ_INIT(&req); + + if (PHP_SSL_REQ_PARSE(&req, args) == SUCCESS) { + /* Generate or use a private key */ + if (Z_TYPE_P(out_pkey) != IS_NULL) { + req.priv_key = php_openssl_evp_from_zval(out_pkey, 0, NULL, 0, 0, &key_resource); + if (req.priv_key != NULL) { + we_made_the_key = 0; + } + } + if (req.priv_key == NULL) { + php_openssl_generate_private_key(&req); + } + if (req.priv_key == NULL) { + php_error_docref(NULL, E_WARNING, "Unable to generate a private key"); + } else { + csr = X509_REQ_new(); + if (csr) { + if (php_openssl_make_REQ(&req, csr, dn, attribs) == SUCCESS) { + X509V3_CTX ext_ctx; + + X509V3_set_ctx(&ext_ctx, NULL, NULL, csr, NULL, 0); + X509V3_set_conf_lhash(&ext_ctx, req.req_config); + + /* Add extensions */ + if (req.request_extensions_section && !X509V3_EXT_REQ_add_conf(req.req_config, + &ext_ctx, req.request_extensions_section, csr)) + { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "Error loading extension section %s", req.request_extensions_section); + } else { + RETVAL_TRUE; + + if (X509_REQ_sign(csr, req.priv_key, req.digest)) { + ZVAL_RES(return_value, zend_register_resource(csr, le_csr)); + csr = NULL; + } else { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "Error signing request"); + } + + if (we_made_the_key) { + /* and a resource for the private key */ + zval_ptr_dtor(out_pkey); + ZVAL_RES(out_pkey, zend_register_resource(req.priv_key, le_key)); + req.priv_key = NULL; /* make sure the cleanup code doesn't zap it! */ + } else if (key_resource != NULL) { + req.priv_key = NULL; /* make sure the cleanup code doesn't zap it! */ + } + } + } + else { + if (!we_made_the_key) { + /* if we have not made the key we are not supposed to zap it by calling dispose! */ + req.priv_key = NULL; + } + } + } else { + php_openssl_store_errors(); + } + + } + } + if (csr) { + X509_REQ_free(csr); + } + PHP_SSL_REQ_DISPOSE(&req); +} +/* }}} */ + +/* {{{ proto mixed openssl_csr_get_subject(mixed csr) + Returns the subject of a CERT or FALSE on error */ +PHP_FUNCTION(openssl_csr_get_subject) +{ + zval * zcsr; + zend_bool use_shortnames = 1; + zend_resource *csr_resource; + X509_NAME * subject; + X509_REQ * csr; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "z|b", &zcsr, &use_shortnames) == FAILURE) { + return; + } + + csr = php_openssl_csr_from_zval(zcsr, 0, &csr_resource); + + if (csr == NULL) { + RETURN_FALSE; + } + + subject = X509_REQ_get_subject_name(csr); + + array_init(return_value); + php_openssl_add_assoc_name_entry(return_value, NULL, subject, use_shortnames); + + if (!csr_resource) { + X509_REQ_free(csr); + } +} +/* }}} */ + +/* {{{ proto mixed openssl_csr_get_public_key(mixed csr) + Returns the subject of a CERT or FALSE on error */ +PHP_FUNCTION(openssl_csr_get_public_key) +{ + zval * zcsr; + zend_bool use_shortnames = 1; + zend_resource *csr_resource; + + X509_REQ *orig_csr, *csr; + EVP_PKEY *tpubkey; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "z|b", &zcsr, &use_shortnames) == FAILURE) { + return; + } + + orig_csr = php_openssl_csr_from_zval(zcsr, 0, &csr_resource); + + if (orig_csr == NULL) { + RETURN_FALSE; + } + +#if PHP_OPENSSL_API_VERSION >= 0x10100 + /* Due to changes in OpenSSL 1.1 related to locking when decoding CSR, + * the pub key is not changed after assigning. It means if we pass + * a private key, it will be returned including the private part. + * If we duplicate it, then we get just the public part which is + * the same behavior as for OpenSSL 1.0 */ + csr = X509_REQ_dup(orig_csr); +#else + csr = orig_csr; +#endif + + /* Retrieve the public key from the CSR */ + tpubkey = X509_REQ_get_pubkey(csr); + + if (csr != orig_csr) { + /* We need to free the duplicated CSR */ + X509_REQ_free(csr); + } + + if (!csr_resource) { + /* We also need to free the original CSR if it was freshly created */ + X509_REQ_free(orig_csr); + } + + if (tpubkey == NULL) { + php_openssl_store_errors(); + RETURN_FALSE; + } + + RETURN_RES(zend_register_resource(tpubkey, le_key)); +} +/* }}} */ + +/* }}} */ + +/* {{{ EVP Public/Private key functions */ + +struct php_openssl_pem_password { + char *key; + int len; +}; + +/* {{{ php_openssl_pem_password_cb */ +static int php_openssl_pem_password_cb(char *buf, int size, int rwflag, void *userdata) +{ + struct php_openssl_pem_password *password = userdata; + + if (password == NULL || password->key == NULL) { + return -1; + } + + size = (password->len > size) ? size : password->len; + memcpy(buf, password->key, size); + + return size; +} +/* }}} */ + +/* {{{ php_openssl_evp_from_zval + Given a zval, coerce it into a EVP_PKEY object. + It can be: + 1. private key resource from openssl_get_privatekey() + 2. X509 resource -> public key will be extracted from it + 3. if it starts with file:// interpreted as path to key file + 4. interpreted as the data from the cert/key file and interpreted in same way as openssl_get_privatekey() + 5. an array(0 => [items 2..4], 1 => passphrase) + 6. if val is a string (possibly starting with file:///) and it is not an X509 certificate, then interpret as public key + NOTE: If you are requesting a private key but have not specified a passphrase, you should use an + empty string rather than NULL for the passphrase - NULL causes a passphrase prompt to be emitted in + the Apache error log! +*/ +static EVP_PKEY * php_openssl_evp_from_zval( + zval * val, int public_key, char *passphrase, size_t passphrase_len, + int makeresource, zend_resource **resourceval) +{ + EVP_PKEY * key = NULL; + X509 * cert = NULL; + int free_cert = 0; + zend_resource *cert_res = NULL; + char * filename = NULL; + zval tmp; + + ZVAL_NULL(&tmp); + +#define TMP_CLEAN \ + if (Z_TYPE(tmp) == IS_STRING) {\ + zval_ptr_dtor_str(&tmp); \ + } \ + return NULL; + + if (resourceval) { + *resourceval = NULL; + } + if (Z_TYPE_P(val) == IS_ARRAY) { + zval * zphrase; + + /* get passphrase */ + + if ((zphrase = zend_hash_index_find(Z_ARRVAL_P(val), 1)) == NULL) { + php_error_docref(NULL, E_WARNING, "key array must be of the form array(0 => key, 1 => phrase)"); + return NULL; + } + + if (Z_TYPE_P(zphrase) == IS_STRING) { + passphrase = Z_STRVAL_P(zphrase); + passphrase_len = Z_STRLEN_P(zphrase); + } else { + ZVAL_COPY(&tmp, zphrase); + convert_to_string(&tmp); + passphrase = Z_STRVAL(tmp); + passphrase_len = Z_STRLEN(tmp); + } + + /* now set val to be the key param and continue */ + if ((val = zend_hash_index_find(Z_ARRVAL_P(val), 0)) == NULL) { + php_error_docref(NULL, E_WARNING, "key array must be of the form array(0 => key, 1 => phrase)"); + TMP_CLEAN; + } + } + + if (Z_TYPE_P(val) == IS_RESOURCE) { + void * what; + zend_resource * res = Z_RES_P(val); + + what = zend_fetch_resource2(res, "OpenSSL X.509/key", le_x509, le_key); + if (!what) { + TMP_CLEAN; + } + if (resourceval) { + *resourceval = res; + Z_ADDREF_P(val); + } + if (res->type == le_x509) { + /* extract key from cert, depending on public_key param */ + cert = (X509*)what; + free_cert = 0; + } else if (res->type == le_key) { + int is_priv; + + is_priv = php_openssl_is_private_key((EVP_PKEY*)what); + + /* check whether it is actually a private key if requested */ + if (!public_key && !is_priv) { + php_error_docref(NULL, E_WARNING, "supplied key param is a public key"); + TMP_CLEAN; + } + + if (public_key && is_priv) { + php_error_docref(NULL, E_WARNING, "Don't know how to get public key from this private key"); + TMP_CLEAN; + } else { + if (Z_TYPE(tmp) == IS_STRING) { + zval_ptr_dtor_str(&tmp); + } + /* got the key - return it */ + return (EVP_PKEY*)what; + } + } else { + /* other types could be used here - eg: file pointers and read in the data from them */ + TMP_CLEAN; + } + } else { + /* force it to be a string and check if it refers to a file */ + /* passing non string values leaks, object uses toString, it returns NULL + * See bug38255.phpt + */ + if (!(Z_TYPE_P(val) == IS_STRING || Z_TYPE_P(val) == IS_OBJECT)) { + TMP_CLEAN; + } + convert_to_string_ex(val); + + if (Z_STRLEN_P(val) > 7 && memcmp(Z_STRVAL_P(val), "file://", sizeof("file://") - 1) == 0) { + filename = Z_STRVAL_P(val) + (sizeof("file://") - 1); + if (php_openssl_open_base_dir_chk(filename)) { + TMP_CLEAN; + } + } + /* it's an X509 file/cert of some kind, and we need to extract the data from that */ + if (public_key) { + cert = php_openssl_x509_from_zval(val, 0, &cert_res); + free_cert = (cert_res == NULL); + /* actual extraction done later */ + if (!cert) { + /* not a X509 certificate, try to retrieve public key */ + BIO* in; + if (filename) { + in = BIO_new_file(filename, PHP_OPENSSL_BIO_MODE_R(PKCS7_BINARY)); + } else { + in = BIO_new_mem_buf(Z_STRVAL_P(val), (int)Z_STRLEN_P(val)); + } + if (in == NULL) { + php_openssl_store_errors(); + TMP_CLEAN; + } + key = PEM_read_bio_PUBKEY(in, NULL,NULL, NULL); + BIO_free(in); + } + } else { + /* we want the private key */ + BIO *in; + + if (filename) { + in = BIO_new_file(filename, PHP_OPENSSL_BIO_MODE_R(PKCS7_BINARY)); + } else { + in = BIO_new_mem_buf(Z_STRVAL_P(val), (int)Z_STRLEN_P(val)); + } + + if (in == NULL) { + TMP_CLEAN; + } + if (passphrase == NULL) { + key = PEM_read_bio_PrivateKey(in, NULL, NULL, NULL); + } else { + struct php_openssl_pem_password password; + password.key = passphrase; + password.len = passphrase_len; + key = PEM_read_bio_PrivateKey(in, NULL, php_openssl_pem_password_cb, &password); + } + BIO_free(in); + } + } + + if (key == NULL) { + php_openssl_store_errors(); + + if (public_key && cert) { + /* extract public key from X509 cert */ + key = (EVP_PKEY *) X509_get_pubkey(cert); + if (key == NULL) { + php_openssl_store_errors(); + } + } + } + + if (free_cert && cert) { + X509_free(cert); + } + if (key && makeresource && resourceval) { + *resourceval = zend_register_resource(key, le_key); + } + if (Z_TYPE(tmp) == IS_STRING) { + zval_ptr_dtor_str(&tmp); + } + return key; +} +/* }}} */ + +/* {{{ php_openssl_generate_private_key */ +static EVP_PKEY * php_openssl_generate_private_key(struct php_x509_request * req) +{ + char * randfile = NULL; + int egdsocket, seeded; + EVP_PKEY * return_val = NULL; + + if (req->priv_key_bits < MIN_KEY_LENGTH) { + php_error_docref(NULL, E_WARNING, "private key length is too short; it needs to be at least %d bits, not %d", + MIN_KEY_LENGTH, req->priv_key_bits); + return NULL; + } + + randfile = CONF_get_string(req->req_config, req->section_name, "RANDFILE"); + if (randfile == NULL) { + php_openssl_store_errors(); + } + php_openssl_load_rand_file(randfile, &egdsocket, &seeded); + + if ((req->priv_key = EVP_PKEY_new()) != NULL) { + switch(req->priv_key_type) { + case OPENSSL_KEYTYPE_RSA: + { + RSA* rsaparam; +#if OPENSSL_VERSION_NUMBER < 0x10002000L + /* OpenSSL 1.0.2 deprecates RSA_generate_key */ + PHP_OPENSSL_RAND_ADD_TIME(); + rsaparam = (RSA*)RSA_generate_key(req->priv_key_bits, RSA_F4, NULL, NULL); +#else + { + BIGNUM *bne = (BIGNUM *)BN_new(); + if (BN_set_word(bne, RSA_F4) != 1) { + BN_free(bne); + php_error_docref(NULL, E_WARNING, "failed setting exponent"); + return NULL; + } + rsaparam = RSA_new(); + PHP_OPENSSL_RAND_ADD_TIME(); + if (rsaparam == NULL || !RSA_generate_key_ex(rsaparam, req->priv_key_bits, bne, NULL)) { + php_openssl_store_errors(); + } + BN_free(bne); + } +#endif + if (rsaparam && EVP_PKEY_assign_RSA(req->priv_key, rsaparam)) { + return_val = req->priv_key; + } else { + php_openssl_store_errors(); + } + } + break; +#if !defined(NO_DSA) + case OPENSSL_KEYTYPE_DSA: + PHP_OPENSSL_RAND_ADD_TIME(); + { + DSA *dsaparam = DSA_new(); + if (dsaparam && DSA_generate_parameters_ex(dsaparam, req->priv_key_bits, NULL, 0, NULL, NULL, NULL)) { + DSA_set_method(dsaparam, DSA_get_default_method()); + if (DSA_generate_key(dsaparam)) { + if (EVP_PKEY_assign_DSA(req->priv_key, dsaparam)) { + return_val = req->priv_key; + } else { + php_openssl_store_errors(); + } + } else { + php_openssl_store_errors(); + DSA_free(dsaparam); + } + } else { + php_openssl_store_errors(); + } + } + break; +#endif +#if !defined(NO_DH) + case OPENSSL_KEYTYPE_DH: + PHP_OPENSSL_RAND_ADD_TIME(); + { + int codes = 0; + DH *dhparam = DH_new(); + if (dhparam && DH_generate_parameters_ex(dhparam, req->priv_key_bits, 2, NULL)) { + DH_set_method(dhparam, DH_get_default_method()); + if (DH_check(dhparam, &codes) && codes == 0 && DH_generate_key(dhparam)) { + if (EVP_PKEY_assign_DH(req->priv_key, dhparam)) { + return_val = req->priv_key; + } else { + php_openssl_store_errors(); + } + } else { + php_openssl_store_errors(); + DH_free(dhparam); + } + } else { + php_openssl_store_errors(); + } + } + break; +#endif +#ifdef HAVE_EVP_PKEY_EC + case OPENSSL_KEYTYPE_EC: + { + EC_KEY *eckey; + if (req->curve_name == NID_undef) { + php_error_docref(NULL, E_WARNING, "Missing configuration value: 'curve_name' not set"); + return NULL; + } + eckey = EC_KEY_new_by_curve_name(req->curve_name); + if (eckey) { + EC_KEY_set_asn1_flag(eckey, OPENSSL_EC_NAMED_CURVE); + if (EC_KEY_generate_key(eckey) && + EVP_PKEY_assign_EC_KEY(req->priv_key, eckey)) { + return_val = req->priv_key; + } else { + EC_KEY_free(eckey); + } + } + } + break; +#endif + default: + php_error_docref(NULL, E_WARNING, "Unsupported private key type"); + } + } else { + php_openssl_store_errors(); + } + + php_openssl_write_rand_file(randfile, egdsocket, seeded); + + if (return_val == NULL) { + EVP_PKEY_free(req->priv_key); + req->priv_key = NULL; + return NULL; + } + + return return_val; +} +/* }}} */ + +/* {{{ php_openssl_is_private_key + Check whether the supplied key is a private key by checking if the secret prime factors are set */ +static int php_openssl_is_private_key(EVP_PKEY* pkey) +{ + assert(pkey != NULL); + + switch (EVP_PKEY_id(pkey)) { + case EVP_PKEY_RSA: + case EVP_PKEY_RSA2: + { + RSA *rsa = EVP_PKEY_get0_RSA(pkey); + if (rsa != NULL) { + const BIGNUM *p, *q; + + RSA_get0_factors(rsa, &p, &q); + if (p == NULL || q == NULL) { + return 0; + } + } + } + break; + case EVP_PKEY_DSA: + case EVP_PKEY_DSA1: + case EVP_PKEY_DSA2: + case EVP_PKEY_DSA3: + case EVP_PKEY_DSA4: + { + DSA *dsa = EVP_PKEY_get0_DSA(pkey); + if (dsa != NULL) { + const BIGNUM *p, *q, *g, *pub_key, *priv_key; + + DSA_get0_pqg(dsa, &p, &q, &g); + if (p == NULL || q == NULL) { + return 0; + } + + DSA_get0_key(dsa, &pub_key, &priv_key); + if (priv_key == NULL) { + return 0; + } + } + } + break; + case EVP_PKEY_DH: + { + DH *dh = EVP_PKEY_get0_DH(pkey); + if (dh != NULL) { + const BIGNUM *p, *q, *g, *pub_key, *priv_key; + + DH_get0_pqg(dh, &p, &q, &g); + if (p == NULL) { + return 0; + } + + DH_get0_key(dh, &pub_key, &priv_key); + if (priv_key == NULL) { + return 0; + } + } + } + break; +#ifdef HAVE_EVP_PKEY_EC + case EVP_PKEY_EC: + { + EC_KEY *ec = EVP_PKEY_get0_EC_KEY(pkey); + if (ec != NULL && NULL == EC_KEY_get0_private_key(ec)) { + return 0; + } + } + break; +#endif + default: + php_error_docref(NULL, E_WARNING, "key type not supported in this PHP build!"); + break; + } + return 1; +} +/* }}} */ + +#define OPENSSL_GET_BN(_array, _bn, _name) do { \ + if (_bn != NULL) { \ + int len = BN_num_bytes(_bn); \ + zend_string *str = zend_string_alloc(len, 0); \ + BN_bn2bin(_bn, (unsigned char*)ZSTR_VAL(str)); \ + ZSTR_VAL(str)[len] = 0; \ + add_assoc_str(&_array, #_name, str); \ + } \ + } while (0); + +#define OPENSSL_PKEY_GET_BN(_type, _name) OPENSSL_GET_BN(_type, _name, _name) + +#define OPENSSL_PKEY_SET_BN(_data, _name) do { \ + zval *bn; \ + if ((bn = zend_hash_str_find(Z_ARRVAL_P(_data), #_name, sizeof(#_name)-1)) != NULL && \ + Z_TYPE_P(bn) == IS_STRING) { \ + _name = BN_bin2bn( \ + (unsigned char*)Z_STRVAL_P(bn), \ + (int)Z_STRLEN_P(bn), NULL); \ + } else { \ + _name = NULL; \ + } \ + } while (0); + +/* {{{ php_openssl_pkey_init_rsa */ +static zend_bool php_openssl_pkey_init_and_assign_rsa(EVP_PKEY *pkey, RSA *rsa, zval *data) +{ + BIGNUM *n, *e, *d, *p, *q, *dmp1, *dmq1, *iqmp; + + OPENSSL_PKEY_SET_BN(data, n); + OPENSSL_PKEY_SET_BN(data, e); + OPENSSL_PKEY_SET_BN(data, d); + if (!n || !d || !RSA_set0_key(rsa, n, e, d)) { + return 0; + } + + OPENSSL_PKEY_SET_BN(data, p); + OPENSSL_PKEY_SET_BN(data, q); + if ((p || q) && !RSA_set0_factors(rsa, p, q)) { + return 0; + } + + OPENSSL_PKEY_SET_BN(data, dmp1); + OPENSSL_PKEY_SET_BN(data, dmq1); + OPENSSL_PKEY_SET_BN(data, iqmp); + if ((dmp1 || dmq1 || iqmp) && !RSA_set0_crt_params(rsa, dmp1, dmq1, iqmp)) { + return 0; + } + + if (!EVP_PKEY_assign_RSA(pkey, rsa)) { + php_openssl_store_errors(); + return 0; + } + + return 1; +} + +/* {{{ php_openssl_pkey_init_dsa */ +static zend_bool php_openssl_pkey_init_dsa(DSA *dsa, zval *data) +{ + BIGNUM *p, *q, *g, *priv_key, *pub_key; + const BIGNUM *priv_key_const, *pub_key_const; + + OPENSSL_PKEY_SET_BN(data, p); + OPENSSL_PKEY_SET_BN(data, q); + OPENSSL_PKEY_SET_BN(data, g); + if (!p || !q || !g || !DSA_set0_pqg(dsa, p, q, g)) { + return 0; + } + + OPENSSL_PKEY_SET_BN(data, pub_key); + OPENSSL_PKEY_SET_BN(data, priv_key); + if (pub_key) { + return DSA_set0_key(dsa, pub_key, priv_key); + } + + /* generate key */ + PHP_OPENSSL_RAND_ADD_TIME(); + if (!DSA_generate_key(dsa)) { + php_openssl_store_errors(); + return 0; + } + + /* if BN_mod_exp return -1, then DSA_generate_key succeed for failed key + * so we need to double check that public key is created */ + DSA_get0_key(dsa, &pub_key_const, &priv_key_const); + if (!pub_key_const || BN_is_zero(pub_key_const)) { + return 0; + } + /* all good */ + return 1; +} +/* }}} */ + +/* {{{ php_openssl_dh_pub_from_priv */ +static BIGNUM *php_openssl_dh_pub_from_priv(BIGNUM *priv_key, BIGNUM *g, BIGNUM *p) +{ + BIGNUM *pub_key, *priv_key_const_time; + BN_CTX *ctx; + + pub_key = BN_new(); + if (pub_key == NULL) { + php_openssl_store_errors(); + return NULL; + } + + priv_key_const_time = BN_new(); + if (priv_key_const_time == NULL) { + BN_free(pub_key); + php_openssl_store_errors(); + return NULL; + } + ctx = BN_CTX_new(); + if (ctx == NULL) { + BN_free(pub_key); + BN_free(priv_key_const_time); + php_openssl_store_errors(); + return NULL; + } + + BN_with_flags(priv_key_const_time, priv_key, BN_FLG_CONSTTIME); + + if (!BN_mod_exp_mont(pub_key, g, priv_key_const_time, p, ctx, NULL)) { + BN_free(pub_key); + php_openssl_store_errors(); + pub_key = NULL; + } + + BN_free(priv_key_const_time); + BN_CTX_free(ctx); + + return pub_key; +} +/* }}} */ + +/* {{{ php_openssl_pkey_init_dh */ +static zend_bool php_openssl_pkey_init_dh(DH *dh, zval *data) +{ + BIGNUM *p, *q, *g, *priv_key, *pub_key; + + OPENSSL_PKEY_SET_BN(data, p); + OPENSSL_PKEY_SET_BN(data, q); + OPENSSL_PKEY_SET_BN(data, g); + if (!p || !g || !DH_set0_pqg(dh, p, q, g)) { + return 0; + } + + OPENSSL_PKEY_SET_BN(data, priv_key); + OPENSSL_PKEY_SET_BN(data, pub_key); + if (pub_key) { + return DH_set0_key(dh, pub_key, priv_key); + } + if (priv_key) { + pub_key = php_openssl_dh_pub_from_priv(priv_key, g, p); + if (pub_key == NULL) { + return 0; + } + return DH_set0_key(dh, pub_key, priv_key); + } + + /* generate key */ + PHP_OPENSSL_RAND_ADD_TIME(); + if (!DH_generate_key(dh)) { + php_openssl_store_errors(); + return 0; + } + /* all good */ + return 1; +} +/* }}} */ + +/* {{{ proto resource openssl_pkey_new([array configargs]) + Generates a new private key */ +PHP_FUNCTION(openssl_pkey_new) +{ + struct php_x509_request req; + zval * args = NULL; + zval *data; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|a!", &args) == FAILURE) { + return; + } + RETVAL_FALSE; + + if (args && Z_TYPE_P(args) == IS_ARRAY) { + EVP_PKEY *pkey; + + if ((data = zend_hash_str_find(Z_ARRVAL_P(args), "rsa", sizeof("rsa")-1)) != NULL && + Z_TYPE_P(data) == IS_ARRAY) { + pkey = EVP_PKEY_new(); + if (pkey) { + RSA *rsa = RSA_new(); + if (rsa) { + if (php_openssl_pkey_init_and_assign_rsa(pkey, rsa, data)) { + RETURN_RES(zend_register_resource(pkey, le_key)); + } + RSA_free(rsa); + } else { + php_openssl_store_errors(); + } + EVP_PKEY_free(pkey); + } else { + php_openssl_store_errors(); + } + RETURN_FALSE; + } else if ((data = zend_hash_str_find(Z_ARRVAL_P(args), "dsa", sizeof("dsa") - 1)) != NULL && + Z_TYPE_P(data) == IS_ARRAY) { + pkey = EVP_PKEY_new(); + if (pkey) { + DSA *dsa = DSA_new(); + if (dsa) { + if (php_openssl_pkey_init_dsa(dsa, data)) { + if (EVP_PKEY_assign_DSA(pkey, dsa)) { + RETURN_RES(zend_register_resource(pkey, le_key)); + } else { + php_openssl_store_errors(); + } + } + DSA_free(dsa); + } else { + php_openssl_store_errors(); + } + EVP_PKEY_free(pkey); + } else { + php_openssl_store_errors(); + } + RETURN_FALSE; + } else if ((data = zend_hash_str_find(Z_ARRVAL_P(args), "dh", sizeof("dh") - 1)) != NULL && + Z_TYPE_P(data) == IS_ARRAY) { + pkey = EVP_PKEY_new(); + if (pkey) { + DH *dh = DH_new(); + if (dh) { + if (php_openssl_pkey_init_dh(dh, data)) { + if (EVP_PKEY_assign_DH(pkey, dh)) { + ZVAL_COPY_VALUE(return_value, zend_list_insert(pkey, le_key)); + return; + } else { + php_openssl_store_errors(); + } + } + DH_free(dh); + } else { + php_openssl_store_errors(); + } + EVP_PKEY_free(pkey); + } else { + php_openssl_store_errors(); + } + RETURN_FALSE; +#ifdef HAVE_EVP_PKEY_EC + } else if ((data = zend_hash_str_find(Z_ARRVAL_P(args), "ec", sizeof("ec") - 1)) != NULL && + Z_TYPE_P(data) == IS_ARRAY) { + EC_KEY *eckey = NULL; + EC_GROUP *group = NULL; + EC_POINT *pnt = NULL; + BIGNUM *d = NULL; + pkey = EVP_PKEY_new(); + if (pkey) { + eckey = EC_KEY_new(); + if (eckey) { + EC_GROUP *group = NULL; + zval *bn; + zval *x; + zval *y; + + if ((bn = zend_hash_str_find(Z_ARRVAL_P(data), "curve_name", sizeof("curve_name") - 1)) != NULL && + Z_TYPE_P(bn) == IS_STRING) { + int nid = OBJ_sn2nid(Z_STRVAL_P(bn)); + if (nid != NID_undef) { + group = EC_GROUP_new_by_curve_name(nid); + if (!group) { + php_openssl_store_errors(); + goto clean_exit; + } + EC_GROUP_set_asn1_flag(group, OPENSSL_EC_NAMED_CURVE); + EC_GROUP_set_point_conversion_form(group, POINT_CONVERSION_UNCOMPRESSED); + if (!EC_KEY_set_group(eckey, group)) { + php_openssl_store_errors(); + goto clean_exit; + } + } + } + + if (group == NULL) { + php_error_docref(NULL, E_WARNING, "Unknown curve_name"); + goto clean_exit; + } + + // The public key 'pnt' can be calculated from 'd' or is defined by 'x' and 'y' + if ((bn = zend_hash_str_find(Z_ARRVAL_P(data), "d", sizeof("d") - 1)) != NULL && + Z_TYPE_P(bn) == IS_STRING) { + d = BN_bin2bn((unsigned char*) Z_STRVAL_P(bn), Z_STRLEN_P(bn), NULL); + if (!EC_KEY_set_private_key(eckey, d)) { + php_openssl_store_errors(); + goto clean_exit; + } + // Calculate the public key by multiplying the Point Q with the public key + // P = d * Q + pnt = EC_POINT_new(group); + if (!pnt || !EC_POINT_mul(group, pnt, d, NULL, NULL, NULL)) { + php_openssl_store_errors(); + goto clean_exit; + } + + BN_free(d); + } else if ((x = zend_hash_str_find(Z_ARRVAL_P(data), "x", sizeof("x") - 1)) != NULL && + Z_TYPE_P(x) == IS_STRING && + (y = zend_hash_str_find(Z_ARRVAL_P(data), "y", sizeof("y") - 1)) != NULL && + Z_TYPE_P(y) == IS_STRING) { + pnt = EC_POINT_new(group); + if (pnt == NULL) { + php_openssl_store_errors(); + goto clean_exit; + } + if (!EC_POINT_set_affine_coordinates_GFp( + group, pnt, BN_bin2bn((unsigned char*) Z_STRVAL_P(x), Z_STRLEN_P(x), NULL), + BN_bin2bn((unsigned char*) Z_STRVAL_P(y), Z_STRLEN_P(y), NULL), NULL)) { + php_openssl_store_errors(); + goto clean_exit; + } + } + + if (pnt != NULL) { + if (!EC_KEY_set_public_key(eckey, pnt)) { + php_openssl_store_errors(); + goto clean_exit; + } + EC_POINT_free(pnt); + pnt = NULL; + } + + if (!EC_KEY_check_key(eckey)) { + PHP_OPENSSL_RAND_ADD_TIME(); + EC_KEY_generate_key(eckey); + php_openssl_store_errors(); + } + if (EC_KEY_check_key(eckey) && EVP_PKEY_assign_EC_KEY(pkey, eckey)) { + EC_GROUP_free(group); + RETURN_RES(zend_register_resource(pkey, le_key)); + } else { + php_openssl_store_errors(); + } + } else { + php_openssl_store_errors(); + } + } else { + php_openssl_store_errors(); + } +clean_exit: + if (d != NULL) { + BN_free(d); + } + if (pnt != NULL) { + EC_POINT_free(pnt); + } + if (group != NULL) { + EC_GROUP_free(group); + } + if (eckey != NULL) { + EC_KEY_free(eckey); + } + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } + RETURN_FALSE; +#endif + } + } + + PHP_SSL_REQ_INIT(&req); + + if (PHP_SSL_REQ_PARSE(&req, args) == SUCCESS) { + if (php_openssl_generate_private_key(&req)) { + /* pass back a key resource */ + RETVAL_RES(zend_register_resource(req.priv_key, le_key)); + /* make sure the cleanup code doesn't zap it! */ + req.priv_key = NULL; + } + } + PHP_SSL_REQ_DISPOSE(&req); +} +/* }}} */ + +/* {{{ proto bool openssl_pkey_export_to_file(mixed key, string outfilename [, string passphrase, array config_args) + Gets an exportable representation of a key into a file */ +PHP_FUNCTION(openssl_pkey_export_to_file) +{ + struct php_x509_request req; + zval * zpkey, * args = NULL; + char * passphrase = NULL; + size_t passphrase_len = 0; + char * filename = NULL; + size_t filename_len = 0; + zend_resource *key_resource = NULL; + int pem_write = 0; + EVP_PKEY * key; + BIO * bio_out = NULL; + const EVP_CIPHER * cipher; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zp|s!a!", &zpkey, &filename, &filename_len, &passphrase, &passphrase_len, &args) == FAILURE) { + return; + } + RETVAL_FALSE; + + PHP_OPENSSL_CHECK_SIZE_T_TO_INT(passphrase_len, passphrase); + key = php_openssl_evp_from_zval(zpkey, 0, passphrase, passphrase_len, 0, &key_resource); + + if (key == NULL) { + php_error_docref(NULL, E_WARNING, "cannot get key from parameter 1"); + RETURN_FALSE; + } + + if (php_openssl_open_base_dir_chk(filename)) { + RETURN_FALSE; + } + + PHP_SSL_REQ_INIT(&req); + + if (PHP_SSL_REQ_PARSE(&req, args) == SUCCESS) { + bio_out = BIO_new_file(filename, PHP_OPENSSL_BIO_MODE_W(PKCS7_BINARY)); + if (bio_out == NULL) { + php_openssl_store_errors(); + goto clean_exit; + } + + if (passphrase && req.priv_key_encrypt) { + if (req.priv_key_encrypt_cipher) { + cipher = req.priv_key_encrypt_cipher; + } else { + cipher = (EVP_CIPHER *) EVP_des_ede3_cbc(); + } + } else { + cipher = NULL; + } + + switch (EVP_PKEY_base_id(key)) { +#ifdef HAVE_EVP_PKEY_EC + case EVP_PKEY_EC: + pem_write = PEM_write_bio_ECPrivateKey( + bio_out, EVP_PKEY_get0_EC_KEY(key), cipher, + (unsigned char *)passphrase, (int)passphrase_len, NULL, NULL); + break; +#endif + default: + pem_write = PEM_write_bio_PrivateKey( + bio_out, key, cipher, + (unsigned char *)passphrase, (int)passphrase_len, NULL, NULL); + break; + } + + if (pem_write) { + /* Success! + * If returning the output as a string, do so now */ + RETVAL_TRUE; + } else { + php_openssl_store_errors(); + } + } + +clean_exit: + PHP_SSL_REQ_DISPOSE(&req); + + if (key_resource == NULL && key) { + EVP_PKEY_free(key); + } + if (bio_out) { + BIO_free(bio_out); + } +} +/* }}} */ + +/* {{{ proto bool openssl_pkey_export(mixed key, &mixed out [, string passphrase [, array config_args]]) + Gets an exportable representation of a key into a string or file */ +PHP_FUNCTION(openssl_pkey_export) +{ + struct php_x509_request req; + zval * zpkey, * args = NULL, *out; + char * passphrase = NULL; size_t passphrase_len = 0; + int pem_write = 0; + zend_resource *key_resource = NULL; + EVP_PKEY * key; + BIO * bio_out = NULL; + const EVP_CIPHER * cipher; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zz/|s!a!", &zpkey, &out, &passphrase, &passphrase_len, &args) == FAILURE) { + return; + } + RETVAL_FALSE; + + PHP_OPENSSL_CHECK_SIZE_T_TO_INT(passphrase_len, passphrase); + key = php_openssl_evp_from_zval(zpkey, 0, passphrase, passphrase_len, 0, &key_resource); + + if (key == NULL) { + php_error_docref(NULL, E_WARNING, "cannot get key from parameter 1"); + RETURN_FALSE; + } + + PHP_SSL_REQ_INIT(&req); + + if (PHP_SSL_REQ_PARSE(&req, args) == SUCCESS) { + bio_out = BIO_new(BIO_s_mem()); + + if (passphrase && req.priv_key_encrypt) { + if (req.priv_key_encrypt_cipher) { + cipher = req.priv_key_encrypt_cipher; + } else { + cipher = (EVP_CIPHER *) EVP_des_ede3_cbc(); + } + } else { + cipher = NULL; + } + + switch (EVP_PKEY_base_id(key)) { +#ifdef HAVE_EVP_PKEY_EC + case EVP_PKEY_EC: + pem_write = PEM_write_bio_ECPrivateKey( + bio_out, EVP_PKEY_get0_EC_KEY(key), cipher, + (unsigned char *)passphrase, (int)passphrase_len, NULL, NULL); + break; +#endif + default: + pem_write = PEM_write_bio_PrivateKey( + bio_out, key, cipher, + (unsigned char *)passphrase, (int)passphrase_len, NULL, NULL); + break; + } + + if (pem_write) { + /* Success! + * If returning the output as a string, do so now */ + + char * bio_mem_ptr; + long bio_mem_len; + RETVAL_TRUE; + + bio_mem_len = BIO_get_mem_data(bio_out, &bio_mem_ptr); + zval_ptr_dtor(out); + ZVAL_STRINGL(out, bio_mem_ptr, bio_mem_len); + } else { + php_openssl_store_errors(); + } + } + PHP_SSL_REQ_DISPOSE(&req); + + if (key_resource == NULL && key) { + EVP_PKEY_free(key); + } + if (bio_out) { + BIO_free(bio_out); + } +} +/* }}} */ + +/* {{{ proto int openssl_pkey_get_public(mixed cert) + Gets public key from X.509 certificate */ +PHP_FUNCTION(openssl_pkey_get_public) +{ + zval *cert; + EVP_PKEY *pkey; + zend_resource *res; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &cert) == FAILURE) { + return; + } + pkey = php_openssl_evp_from_zval(cert, 1, NULL, 0, 1, &res); + if (pkey == NULL) { + RETURN_FALSE; + } + ZVAL_RES(return_value, res); + Z_ADDREF_P(return_value); +} +/* }}} */ + +/* {{{ proto void openssl_pkey_free(int key) + Frees a key */ +PHP_FUNCTION(openssl_pkey_free) +{ + zval *key; + EVP_PKEY *pkey; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &key) == FAILURE) { + return; + } + if ((pkey = (EVP_PKEY *)zend_fetch_resource(Z_RES_P(key), "OpenSSL key", le_key)) == NULL) { + RETURN_FALSE; + } + zend_list_close(Z_RES_P(key)); +} +/* }}} */ + +/* {{{ proto int openssl_pkey_get_private(string key [, string passphrase]) + Gets private keys */ +PHP_FUNCTION(openssl_pkey_get_private) +{ + zval *cert; + EVP_PKEY *pkey; + char * passphrase = ""; + size_t passphrase_len = sizeof("")-1; + zend_resource *res; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "z|s", &cert, &passphrase, &passphrase_len) == FAILURE) { + return; + } + + PHP_OPENSSL_CHECK_SIZE_T_TO_INT(passphrase_len, passphrase); + pkey = php_openssl_evp_from_zval(cert, 0, passphrase, passphrase_len, 1, &res); + + if (pkey == NULL) { + RETURN_FALSE; + } + ZVAL_RES(return_value, res); + Z_ADDREF_P(return_value); +} + +/* }}} */ + +/* {{{ proto resource openssl_pkey_get_details(resource key) + returns an array with the key details (bits, pkey, type)*/ +PHP_FUNCTION(openssl_pkey_get_details) +{ + zval *key; + EVP_PKEY *pkey; + BIO *out; + unsigned int pbio_len; + char *pbio; + zend_long ktype; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &key) == FAILURE) { + return; + } + if ((pkey = (EVP_PKEY *)zend_fetch_resource(Z_RES_P(key), "OpenSSL key", le_key)) == NULL) { + RETURN_FALSE; + } + out = BIO_new(BIO_s_mem()); + if (!PEM_write_bio_PUBKEY(out, pkey)) { + BIO_free(out); + php_openssl_store_errors(); + RETURN_FALSE; + } + pbio_len = BIO_get_mem_data(out, &pbio); + + array_init(return_value); + add_assoc_long(return_value, "bits", EVP_PKEY_bits(pkey)); + add_assoc_stringl(return_value, "key", pbio, pbio_len); + /*TODO: Use the real values once the openssl constants are used + * See the enum at the top of this file + */ + switch (EVP_PKEY_base_id(pkey)) { + case EVP_PKEY_RSA: + case EVP_PKEY_RSA2: + { + RSA *rsa = EVP_PKEY_get0_RSA(pkey); + ktype = OPENSSL_KEYTYPE_RSA; + + if (rsa != NULL) { + zval z_rsa; + const BIGNUM *n, *e, *d, *p, *q, *dmp1, *dmq1, *iqmp; + + RSA_get0_key(rsa, &n, &e, &d); + RSA_get0_factors(rsa, &p, &q); + RSA_get0_crt_params(rsa, &dmp1, &dmq1, &iqmp); + + array_init(&z_rsa); + OPENSSL_PKEY_GET_BN(z_rsa, n); + OPENSSL_PKEY_GET_BN(z_rsa, e); + OPENSSL_PKEY_GET_BN(z_rsa, d); + OPENSSL_PKEY_GET_BN(z_rsa, p); + OPENSSL_PKEY_GET_BN(z_rsa, q); + OPENSSL_PKEY_GET_BN(z_rsa, dmp1); + OPENSSL_PKEY_GET_BN(z_rsa, dmq1); + OPENSSL_PKEY_GET_BN(z_rsa, iqmp); + add_assoc_zval(return_value, "rsa", &z_rsa); + } + } + break; + case EVP_PKEY_DSA: + case EVP_PKEY_DSA2: + case EVP_PKEY_DSA3: + case EVP_PKEY_DSA4: + { + DSA *dsa = EVP_PKEY_get0_DSA(pkey); + ktype = OPENSSL_KEYTYPE_DSA; + + if (dsa != NULL) { + zval z_dsa; + const BIGNUM *p, *q, *g, *priv_key, *pub_key; + + DSA_get0_pqg(dsa, &p, &q, &g); + DSA_get0_key(dsa, &pub_key, &priv_key); + + array_init(&z_dsa); + OPENSSL_PKEY_GET_BN(z_dsa, p); + OPENSSL_PKEY_GET_BN(z_dsa, q); + OPENSSL_PKEY_GET_BN(z_dsa, g); + OPENSSL_PKEY_GET_BN(z_dsa, priv_key); + OPENSSL_PKEY_GET_BN(z_dsa, pub_key); + add_assoc_zval(return_value, "dsa", &z_dsa); + } + } + break; + case EVP_PKEY_DH: + { + DH *dh = EVP_PKEY_get0_DH(pkey); + ktype = OPENSSL_KEYTYPE_DH; + + if (dh != NULL) { + zval z_dh; + const BIGNUM *p, *q, *g, *priv_key, *pub_key; + + DH_get0_pqg(dh, &p, &q, &g); + DH_get0_key(dh, &pub_key, &priv_key); + + array_init(&z_dh); + OPENSSL_PKEY_GET_BN(z_dh, p); + OPENSSL_PKEY_GET_BN(z_dh, g); + OPENSSL_PKEY_GET_BN(z_dh, priv_key); + OPENSSL_PKEY_GET_BN(z_dh, pub_key); + add_assoc_zval(return_value, "dh", &z_dh); + } + } + break; +#ifdef HAVE_EVP_PKEY_EC + case EVP_PKEY_EC: + ktype = OPENSSL_KEYTYPE_EC; + if (EVP_PKEY_get0_EC_KEY(pkey) != NULL) { + zval ec; + const EC_GROUP *ec_group; + const EC_POINT *pub; + int nid; + char *crv_sn; + ASN1_OBJECT *obj; + // openssl recommends a buffer length of 80 + char oir_buf[80]; + const EC_KEY *ec_key = EVP_PKEY_get0_EC_KEY(pkey); + BIGNUM *x = BN_new(); + BIGNUM *y = BN_new(); + const BIGNUM *d; + + ec_group = EC_KEY_get0_group(ec_key); + + // Curve nid (numerical identifier) used for ASN1 mapping + nid = EC_GROUP_get_curve_name(ec_group); + if (nid == NID_undef) { + break; + } + array_init(&ec); + + // Short object name + crv_sn = (char*) OBJ_nid2sn(nid); + if (crv_sn != NULL) { + add_assoc_string(&ec, "curve_name", crv_sn); + } + + obj = OBJ_nid2obj(nid); + if (obj != NULL) { + int oir_len = OBJ_obj2txt(oir_buf, sizeof(oir_buf), obj, 1); + add_assoc_stringl(&ec, "curve_oid", (char*) oir_buf, oir_len); + ASN1_OBJECT_free(obj); + } + + pub = EC_KEY_get0_public_key(ec_key); + + if (EC_POINT_get_affine_coordinates_GFp(ec_group, pub, x, y, NULL)) { + OPENSSL_GET_BN(ec, x, x); + OPENSSL_GET_BN(ec, y, y); + } else { + php_openssl_store_errors(); + } + + if ((d = EC_KEY_get0_private_key(EVP_PKEY_get0_EC_KEY(pkey))) != NULL) { + OPENSSL_GET_BN(ec, d, d); + } + + add_assoc_zval(return_value, "ec", &ec); + + BN_free(x); + BN_free(y); + } + break; +#endif + default: + ktype = -1; + break; + } + add_assoc_long(return_value, "type", ktype); + + BIO_free(out); +} +/* }}} */ + +/* {{{ proto string openssl_dh_compute_key(string pub_key, resource dh_key) + Computes shared secret for public value of remote DH key and local DH key */ +PHP_FUNCTION(openssl_dh_compute_key) +{ + zval *key; + char *pub_str; + size_t pub_len; + DH *dh; + EVP_PKEY *pkey; + BIGNUM *pub; + zend_string *data; + int len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sr", &pub_str, &pub_len, &key) == FAILURE) { + return; + } + if ((pkey = (EVP_PKEY *)zend_fetch_resource(Z_RES_P(key), "OpenSSL key", le_key)) == NULL) { + RETURN_FALSE; + } + if (EVP_PKEY_base_id(pkey) != EVP_PKEY_DH) { + RETURN_FALSE; + } + dh = EVP_PKEY_get0_DH(pkey); + if (dh == NULL) { + RETURN_FALSE; + } + + PHP_OPENSSL_CHECK_SIZE_T_TO_INT(pub_len, pub_key); + pub = BN_bin2bn((unsigned char*)pub_str, (int)pub_len, NULL); + + data = zend_string_alloc(DH_size(dh), 0); + len = DH_compute_key((unsigned char*)ZSTR_VAL(data), pub, dh); + + if (len >= 0) { + ZSTR_LEN(data) = len; + ZSTR_VAL(data)[len] = 0; + RETVAL_NEW_STR(data); + } else { + php_openssl_store_errors(); + zend_string_release_ex(data, 0); + RETVAL_FALSE; + } + + BN_free(pub); +} +/* }}} */ + +/* {{{ proto string openssl_pkey_derive(peer_pub_key, priv_key, int keylen=NULL) + Computes shared secret for public value of remote and local DH or ECDH key */ +PHP_FUNCTION(openssl_pkey_derive) +{ + zval *priv_key; + zval *peer_pub_key; + EVP_PKEY *pkey; + EVP_PKEY *peer_key; + size_t key_size; + zend_long key_len = 0; + zend_string *result; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zz|l", &peer_pub_key, &priv_key, &key_len) == FAILURE) { + RETURN_FALSE; + } + if (key_len < 0) { + php_error_docref(NULL, E_WARNING, "keylen < 0, assuming NULL"); + } + key_size = key_len; + if ((pkey = php_openssl_evp_from_zval(priv_key, 0, "", 0, 0, NULL)) == NULL + || (peer_key = php_openssl_evp_from_zval(peer_pub_key, 1, NULL, 0, 0, NULL)) == NULL) { + RETURN_FALSE; + } + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, NULL); + if (!ctx) { + RETURN_FALSE; + } + if (EVP_PKEY_derive_init(ctx) > 0 + && EVP_PKEY_derive_set_peer(ctx, peer_key) > 0 + && (key_size > 0 || EVP_PKEY_derive(ctx, NULL, &key_size) > 0) + && (result = zend_string_alloc(key_size, 0)) != NULL) { + if (EVP_PKEY_derive(ctx, (unsigned char*)ZSTR_VAL(result), &key_size) > 0) { + ZSTR_LEN(result) = key_size; + ZSTR_VAL(result)[key_size] = 0; + RETVAL_NEW_STR(result); + } else { + php_openssl_store_errors(); + zend_string_release_ex(result, 0); + RETVAL_FALSE; + } + } else { + RETVAL_FALSE; + } + EVP_PKEY_CTX_free(ctx); +} +/* }}} */ + + +/* {{{ proto string openssl_pbkdf2(string password, string salt, int key_length, int iterations [, string digest_method = "sha1"]) + Generates a PKCS5 v2 PBKDF2 string, defaults to sha1 */ +PHP_FUNCTION(openssl_pbkdf2) +{ + zend_long key_length = 0, iterations = 0; + char *password; + size_t password_len; + char *salt; + size_t salt_len; + char *method; + size_t method_len = 0; + zend_string *out_buffer; + + const EVP_MD *digest; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssll|s", + &password, &password_len, + &salt, &salt_len, + &key_length, &iterations, + &method, &method_len) == FAILURE) { + return; + } + + if (key_length <= 0) { + RETURN_FALSE; + } + + if (method_len) { + digest = EVP_get_digestbyname(method); + } else { + digest = EVP_sha1(); + } + + if (!digest) { + php_error_docref(NULL, E_WARNING, "Unknown signature algorithm"); + RETURN_FALSE; + } + + PHP_OPENSSL_CHECK_LONG_TO_INT(key_length, key); + PHP_OPENSSL_CHECK_LONG_TO_INT(iterations, iterations); + PHP_OPENSSL_CHECK_SIZE_T_TO_INT(password_len, password); + PHP_OPENSSL_CHECK_SIZE_T_TO_INT(salt_len, salt); + + out_buffer = zend_string_alloc(key_length, 0); + + if (PKCS5_PBKDF2_HMAC(password, (int)password_len, (unsigned char *)salt, (int)salt_len, (int)iterations, digest, (int)key_length, (unsigned char*)ZSTR_VAL(out_buffer)) == 1) { + ZSTR_VAL(out_buffer)[key_length] = 0; + RETURN_NEW_STR(out_buffer); + } else { + php_openssl_store_errors(); + zend_string_release_ex(out_buffer, 0); + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ PKCS7 S/MIME functions */ + +/* {{{ proto bool openssl_pkcs7_verify(string filename, int flags [, string signerscerts [, array cainfo [, string extracerts [, string content [, string pk7]]]]]) + Verifys that the data block is intact, the signer is who they say they are, and returns the CERTs of the signers */ +PHP_FUNCTION(openssl_pkcs7_verify) +{ + X509_STORE * store = NULL; + zval * cainfo = NULL; + STACK_OF(X509) *signers= NULL; + STACK_OF(X509) *others = NULL; + PKCS7 * p7 = NULL; + BIO * in = NULL, * datain = NULL, * dataout = NULL, * p7bout = NULL; + zend_long flags = 0; + char * filename; + size_t filename_len; + char * extracerts = NULL; + size_t extracerts_len = 0; + char * signersfilename = NULL; + size_t signersfilename_len = 0; + char * datafilename = NULL; + size_t datafilename_len = 0; + char * p7bfilename = NULL; + size_t p7bfilename_len = 0; + + RETVAL_LONG(-1); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "pl|pappp", &filename, &filename_len, + &flags, &signersfilename, &signersfilename_len, &cainfo, + &extracerts, &extracerts_len, &datafilename, &datafilename_len, &p7bfilename, &p7bfilename_len) == FAILURE) { + return; + } + + if (extracerts) { + others = php_openssl_load_all_certs_from_file(extracerts); + if (others == NULL) { + goto clean_exit; + } + } + + flags = flags & ~PKCS7_DETACHED; + + store = php_openssl_setup_verify(cainfo); + + if (!store) { + goto clean_exit; + } + if (php_openssl_open_base_dir_chk(filename)) { + goto clean_exit; + } + + in = BIO_new_file(filename, PHP_OPENSSL_BIO_MODE_R(flags)); + if (in == NULL) { + php_openssl_store_errors(); + goto clean_exit; + } + p7 = SMIME_read_PKCS7(in, &datain); + if (p7 == NULL) { +#if DEBUG_SMIME + zend_printf("SMIME_read_PKCS7 failed\n"); +#endif + php_openssl_store_errors(); + goto clean_exit; + } + + if (datafilename) { + + if (php_openssl_open_base_dir_chk(datafilename)) { + goto clean_exit; + } + + dataout = BIO_new_file(datafilename, PHP_OPENSSL_BIO_MODE_W(PKCS7_BINARY)); + if (dataout == NULL) { + php_openssl_store_errors(); + goto clean_exit; + } + } + + if (p7bfilename) { + + if (php_openssl_open_base_dir_chk(p7bfilename)) { + goto clean_exit; + } + + p7bout = BIO_new_file(p7bfilename, PHP_OPENSSL_BIO_MODE_W(PKCS7_BINARY)); + if (p7bout == NULL) { + php_openssl_store_errors(); + goto clean_exit; + } + } +#if DEBUG_SMIME + zend_printf("Calling PKCS7 verify\n"); +#endif + + if (PKCS7_verify(p7, others, store, datain, dataout, (int)flags)) { + + RETVAL_TRUE; + + if (signersfilename) { + BIO *certout; + + if (php_openssl_open_base_dir_chk(signersfilename)) { + goto clean_exit; + } + + certout = BIO_new_file(signersfilename, PHP_OPENSSL_BIO_MODE_W(PKCS7_BINARY)); + if (certout) { + int i; + signers = PKCS7_get0_signers(p7, NULL, (int)flags); + if (signers != NULL) { + + for (i = 0; i < sk_X509_num(signers); i++) { + if (!PEM_write_bio_X509(certout, sk_X509_value(signers, i))) { + php_openssl_store_errors(); + RETVAL_LONG(-1); + php_error_docref(NULL, E_WARNING, "failed to write signer %d", i); + } + } + + sk_X509_free(signers); + } else { + RETVAL_LONG(-1); + php_openssl_store_errors(); + } + + BIO_free(certout); + } else { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "signature OK, but cannot open %s for writing", signersfilename); + RETVAL_LONG(-1); + } + + if (p7bout) { + PEM_write_bio_PKCS7(p7bout, p7); + } + } + } else { + php_openssl_store_errors(); + RETVAL_FALSE; + } +clean_exit: + if (p7bout) { + BIO_free(p7bout); + } + X509_STORE_free(store); + BIO_free(datain); + BIO_free(in); + BIO_free(dataout); + PKCS7_free(p7); + sk_X509_pop_free(others, X509_free); +} +/* }}} */ + +/* {{{ proto bool openssl_pkcs7_encrypt(string infile, string outfile, mixed recipcerts, array headers [, int flags [, int cipher]]) + Encrypts the message in the file named infile with the certificates in recipcerts and output the result to the file named outfile */ +PHP_FUNCTION(openssl_pkcs7_encrypt) +{ + zval * zrecipcerts, * zheaders = NULL; + STACK_OF(X509) * recipcerts = NULL; + BIO * infile = NULL, * outfile = NULL; + zend_long flags = 0; + PKCS7 * p7 = NULL; + zval * zcertval; + X509 * cert; + const EVP_CIPHER *cipher = NULL; + zend_long cipherid = PHP_OPENSSL_CIPHER_DEFAULT; + zend_string * strindex; + char * infilename = NULL; + size_t infilename_len; + char * outfilename = NULL; + size_t outfilename_len; + + RETVAL_FALSE; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ppza!|ll", &infilename, &infilename_len, + &outfilename, &outfilename_len, &zrecipcerts, &zheaders, &flags, &cipherid) == FAILURE) + return; + + + if (php_openssl_open_base_dir_chk(infilename) || php_openssl_open_base_dir_chk(outfilename)) { + return; + } + + infile = BIO_new_file(infilename, PHP_OPENSSL_BIO_MODE_R(flags)); + if (infile == NULL) { + php_openssl_store_errors(); + goto clean_exit; + } + + outfile = BIO_new_file(outfilename, PHP_OPENSSL_BIO_MODE_W(flags)); + if (outfile == NULL) { + php_openssl_store_errors(); + goto clean_exit; + } + + recipcerts = sk_X509_new_null(); + + /* get certs */ + if (Z_TYPE_P(zrecipcerts) == IS_ARRAY) { + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(zrecipcerts), zcertval) { + zend_resource *certresource; + + cert = php_openssl_x509_from_zval(zcertval, 0, &certresource); + if (cert == NULL) { + goto clean_exit; + } + + if (certresource != NULL) { + /* we shouldn't free this particular cert, as it is a resource. + make a copy and push that on the stack instead */ + cert = X509_dup(cert); + if (cert == NULL) { + php_openssl_store_errors(); + goto clean_exit; + } + } + sk_X509_push(recipcerts, cert); + } ZEND_HASH_FOREACH_END(); + } else { + /* a single certificate */ + zend_resource *certresource; + + cert = php_openssl_x509_from_zval(zrecipcerts, 0, &certresource); + if (cert == NULL) { + goto clean_exit; + } + + if (certresource != NULL) { + /* we shouldn't free this particular cert, as it is a resource. + make a copy and push that on the stack instead */ + cert = X509_dup(cert); + if (cert == NULL) { + php_openssl_store_errors(); + goto clean_exit; + } + } + sk_X509_push(recipcerts, cert); + } + + /* sanity check the cipher */ + cipher = php_openssl_get_evp_cipher_from_algo(cipherid); + if (cipher == NULL) { + /* shouldn't happen */ + php_error_docref(NULL, E_WARNING, "Failed to get cipher"); + goto clean_exit; + } + + p7 = PKCS7_encrypt(recipcerts, infile, (EVP_CIPHER*)cipher, (int)flags); + + if (p7 == NULL) { + php_openssl_store_errors(); + goto clean_exit; + } + + /* tack on extra headers */ + if (zheaders) { + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(zheaders), strindex, zcertval) { + convert_to_string_ex(zcertval); + + if (strindex) { + BIO_printf(outfile, "%s: %s\n", ZSTR_VAL(strindex), Z_STRVAL_P(zcertval)); + } else { + BIO_printf(outfile, "%s\n", Z_STRVAL_P(zcertval)); + } + } ZEND_HASH_FOREACH_END(); + } + + (void)BIO_reset(infile); + + /* write the encrypted data */ + if (!SMIME_write_PKCS7(outfile, p7, infile, (int)flags)) { + php_openssl_store_errors(); + goto clean_exit; + } + + RETVAL_TRUE; + +clean_exit: + PKCS7_free(p7); + BIO_free(infile); + BIO_free(outfile); + if (recipcerts) { + sk_X509_pop_free(recipcerts, X509_free); + } +} +/* }}} */ + +/* {{{ proto bool openssl_pkcs7_read(string P7B, array &certs) + Exports the PKCS7 file to an array of PEM certificates */ +PHP_FUNCTION(openssl_pkcs7_read) +{ + zval * zout = NULL, zcert; + char *p7b; + size_t p7b_len; + STACK_OF(X509) *certs = NULL; + STACK_OF(X509_CRL) *crls = NULL; + BIO * bio_in = NULL, * bio_out = NULL; + PKCS7 * p7 = NULL; + int i; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sz/", &p7b, &p7b_len, + &zout) == FAILURE) { + return; + } + + RETVAL_FALSE; + + PHP_OPENSSL_CHECK_SIZE_T_TO_INT(p7b_len, p7b); + + bio_in = BIO_new(BIO_s_mem()); + if (bio_in == NULL) { + goto clean_exit; + } + + if (0 >= BIO_write(bio_in, p7b, (int)p7b_len)) { + php_openssl_store_errors(); + goto clean_exit; + } + + p7 = PEM_read_bio_PKCS7(bio_in, NULL, NULL, NULL); + if (p7 == NULL) { + php_openssl_store_errors(); + goto clean_exit; + } + + switch (OBJ_obj2nid(p7->type)) { + case NID_pkcs7_signed: + if (p7->d.sign != NULL) { + certs = p7->d.sign->cert; + crls = p7->d.sign->crl; + } + break; + case NID_pkcs7_signedAndEnveloped: + if (p7->d.signed_and_enveloped != NULL) { + certs = p7->d.signed_and_enveloped->cert; + crls = p7->d.signed_and_enveloped->crl; + } + break; + default: + break; + } + + zval_ptr_dtor(zout); + array_init(zout); + + if (certs != NULL) { + for (i = 0; i < sk_X509_num(certs); i++) { + X509* ca = sk_X509_value(certs, i); + + bio_out = BIO_new(BIO_s_mem()); + if (bio_out && PEM_write_bio_X509(bio_out, ca)) { + BUF_MEM *bio_buf; + BIO_get_mem_ptr(bio_out, &bio_buf); + ZVAL_STRINGL(&zcert, bio_buf->data, bio_buf->length); + add_index_zval(zout, i, &zcert); + BIO_free(bio_out); + } + } + } + + if (crls != NULL) { + for (i = 0; i < sk_X509_CRL_num(crls); i++) { + X509_CRL* crl = sk_X509_CRL_value(crls, i); + + bio_out = BIO_new(BIO_s_mem()); + if (bio_out && PEM_write_bio_X509_CRL(bio_out, crl)) { + BUF_MEM *bio_buf; + BIO_get_mem_ptr(bio_out, &bio_buf); + ZVAL_STRINGL(&zcert, bio_buf->data, bio_buf->length); + add_index_zval(zout, i, &zcert); + BIO_free(bio_out); + } + } + } + + RETVAL_TRUE; + +clean_exit: + if (bio_in != NULL) { + BIO_free(bio_in); + } + + if (p7 != NULL) { + PKCS7_free(p7); + } +} +/* }}} */ + +/* {{{ proto bool openssl_pkcs7_sign(string infile, string outfile, mixed signcert, mixed signkey, array headers [, int flags [, string extracertsfilename]]) + Signs the MIME message in the file named infile with signcert/signkey and output the result to file name outfile. headers lists plain text headers to exclude from the signed portion of the message, and should include to, from and subject as a minimum */ + +PHP_FUNCTION(openssl_pkcs7_sign) +{ + zval * zcert, * zprivkey, * zheaders; + zval * hval; + X509 * cert = NULL; + EVP_PKEY * privkey = NULL; + zend_long flags = PKCS7_DETACHED; + PKCS7 * p7 = NULL; + BIO * infile = NULL, * outfile = NULL; + STACK_OF(X509) *others = NULL; + zend_resource *certresource = NULL, *keyresource = NULL; + zend_string * strindex; + char * infilename; + size_t infilename_len; + char * outfilename; + size_t outfilename_len; + char * extracertsfilename = NULL; + size_t extracertsfilename_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ppzza!|lp!", + &infilename, &infilename_len, &outfilename, &outfilename_len, + &zcert, &zprivkey, &zheaders, &flags, &extracertsfilename, + &extracertsfilename_len) == FAILURE) { + return; + } + + RETVAL_FALSE; + + if (extracertsfilename) { + others = php_openssl_load_all_certs_from_file(extracertsfilename); + if (others == NULL) { + goto clean_exit; + } + } + + privkey = php_openssl_evp_from_zval(zprivkey, 0, "", 0, 0, &keyresource); + if (privkey == NULL) { + php_error_docref(NULL, E_WARNING, "error getting private key"); + goto clean_exit; + } + + cert = php_openssl_x509_from_zval(zcert, 0, &certresource); + if (cert == NULL) { + php_error_docref(NULL, E_WARNING, "error getting cert"); + goto clean_exit; + } + + if (php_openssl_open_base_dir_chk(infilename) || php_openssl_open_base_dir_chk(outfilename)) { + goto clean_exit; + } + + infile = BIO_new_file(infilename, PHP_OPENSSL_BIO_MODE_R(flags)); + if (infile == NULL) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "error opening input file %s!", infilename); + goto clean_exit; + } + + outfile = BIO_new_file(outfilename, PHP_OPENSSL_BIO_MODE_W(PKCS7_BINARY)); + if (outfile == NULL) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "error opening output file %s!", outfilename); + goto clean_exit; + } + + p7 = PKCS7_sign(cert, privkey, others, infile, (int)flags); + if (p7 == NULL) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "error creating PKCS7 structure!"); + goto clean_exit; + } + + (void)BIO_reset(infile); + + /* tack on extra headers */ + if (zheaders) { + int ret; + + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(zheaders), strindex, hval) { + convert_to_string_ex(hval); + + if (strindex) { + ret = BIO_printf(outfile, "%s: %s\n", ZSTR_VAL(strindex), Z_STRVAL_P(hval)); + } else { + ret = BIO_printf(outfile, "%s\n", Z_STRVAL_P(hval)); + } + if (ret < 0) { + php_openssl_store_errors(); + } + } ZEND_HASH_FOREACH_END(); + } + /* write the signed data */ + if (!SMIME_write_PKCS7(outfile, p7, infile, (int)flags)) { + php_openssl_store_errors(); + goto clean_exit; + } + + RETVAL_TRUE; + +clean_exit: + PKCS7_free(p7); + BIO_free(infile); + BIO_free(outfile); + if (others) { + sk_X509_pop_free(others, X509_free); + } + if (privkey && keyresource == NULL) { + EVP_PKEY_free(privkey); + } + if (cert && certresource == NULL) { + X509_free(cert); + } +} +/* }}} */ + +/* {{{ proto bool openssl_pkcs7_decrypt(string infilename, string outfilename, mixed recipcert [, mixed recipkey]) + Decrypts the S/MIME message in the file name infilename and output the results to the file name outfilename. recipcert is a CERT for one of the recipients. recipkey specifies the private key matching recipcert, if recipcert does not include the key */ + +PHP_FUNCTION(openssl_pkcs7_decrypt) +{ + zval * recipcert, * recipkey = NULL; + X509 * cert = NULL; + EVP_PKEY * key = NULL; + zend_resource *certresval, *keyresval; + BIO * in = NULL, * out = NULL, * datain = NULL; + PKCS7 * p7 = NULL; + char * infilename; + size_t infilename_len; + char * outfilename; + size_t outfilename_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ppz|z", &infilename, &infilename_len, + &outfilename, &outfilename_len, &recipcert, &recipkey) == FAILURE) { + return; + } + + RETVAL_FALSE; + + cert = php_openssl_x509_from_zval(recipcert, 0, &certresval); + if (cert == NULL) { + php_error_docref(NULL, E_WARNING, "unable to coerce parameter 3 to x509 cert"); + goto clean_exit; + } + + key = php_openssl_evp_from_zval(recipkey ? recipkey : recipcert, 0, "", 0, 0, &keyresval); + if (key == NULL) { + php_error_docref(NULL, E_WARNING, "unable to get private key"); + goto clean_exit; + } + + if (php_openssl_open_base_dir_chk(infilename) || php_openssl_open_base_dir_chk(outfilename)) { + goto clean_exit; + } + + in = BIO_new_file(infilename, PHP_OPENSSL_BIO_MODE_R(PKCS7_BINARY)); + if (in == NULL) { + php_openssl_store_errors(); + goto clean_exit; + } + out = BIO_new_file(outfilename, PHP_OPENSSL_BIO_MODE_W(PKCS7_BINARY)); + if (out == NULL) { + php_openssl_store_errors(); + goto clean_exit; + } + + p7 = SMIME_read_PKCS7(in, &datain); + + if (p7 == NULL) { + php_openssl_store_errors(); + goto clean_exit; + } + if (PKCS7_decrypt(p7, key, cert, out, PKCS7_DETACHED)) { + RETVAL_TRUE; + } else { + php_openssl_store_errors(); + } +clean_exit: + PKCS7_free(p7); + BIO_free(datain); + BIO_free(in); + BIO_free(out); + if (cert && certresval == NULL) { + X509_free(cert); + } + if (key && keyresval == NULL) { + EVP_PKEY_free(key); + } +} +/* }}} */ + +/* }}} */ + +/* {{{ proto bool openssl_private_encrypt(string data, string &crypted, mixed key [, int padding]) + Encrypts data with private key */ +PHP_FUNCTION(openssl_private_encrypt) +{ + zval *key, *crypted; + EVP_PKEY *pkey; + int cryptedlen; + zend_string *cryptedbuf = NULL; + int successful = 0; + zend_resource *keyresource = NULL; + char * data; + size_t data_len; + zend_long padding = RSA_PKCS1_PADDING; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sz/z|l", &data, &data_len, &crypted, &key, &padding) == FAILURE) { + return; + } + RETVAL_FALSE; + + pkey = php_openssl_evp_from_zval(key, 0, "", 0, 0, &keyresource); + + if (pkey == NULL) { + php_error_docref(NULL, E_WARNING, "key param is not a valid private key"); + RETURN_FALSE; + } + + PHP_OPENSSL_CHECK_SIZE_T_TO_INT(data_len, data); + + cryptedlen = EVP_PKEY_size(pkey); + cryptedbuf = zend_string_alloc(cryptedlen, 0); + + switch (EVP_PKEY_id(pkey)) { + case EVP_PKEY_RSA: + case EVP_PKEY_RSA2: + successful = (RSA_private_encrypt((int)data_len, + (unsigned char *)data, + (unsigned char *)ZSTR_VAL(cryptedbuf), + EVP_PKEY_get0_RSA(pkey), + (int)padding) == cryptedlen); + break; + default: + php_error_docref(NULL, E_WARNING, "key type not supported in this PHP build!"); + } + + if (successful) { + zval_ptr_dtor(crypted); + ZSTR_VAL(cryptedbuf)[cryptedlen] = '\0'; + ZVAL_NEW_STR(crypted, cryptedbuf); + cryptedbuf = NULL; + RETVAL_TRUE; + } else { + php_openssl_store_errors(); + } + if (cryptedbuf) { + zend_string_release_ex(cryptedbuf, 0); + } + if (keyresource == NULL) { + EVP_PKEY_free(pkey); + } +} +/* }}} */ + +/* {{{ proto bool openssl_private_decrypt(string data, string &decrypted, mixed key [, int padding]) + Decrypts data with private key */ +PHP_FUNCTION(openssl_private_decrypt) +{ + zval *key, *crypted; + EVP_PKEY *pkey; + int cryptedlen; + zend_string *cryptedbuf = NULL; + unsigned char *crypttemp; + int successful = 0; + zend_long padding = RSA_PKCS1_PADDING; + zend_resource *keyresource = NULL; + char * data; + size_t data_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sz/z|l", &data, &data_len, &crypted, &key, &padding) == FAILURE) { + return; + } + RETVAL_FALSE; + + pkey = php_openssl_evp_from_zval(key, 0, "", 0, 0, &keyresource); + if (pkey == NULL) { + php_error_docref(NULL, E_WARNING, "key parameter is not a valid private key"); + RETURN_FALSE; + } + + PHP_OPENSSL_CHECK_SIZE_T_TO_INT(data_len, data); + + cryptedlen = EVP_PKEY_size(pkey); + crypttemp = emalloc(cryptedlen + 1); + + switch (EVP_PKEY_id(pkey)) { + case EVP_PKEY_RSA: + case EVP_PKEY_RSA2: + cryptedlen = RSA_private_decrypt((int)data_len, + (unsigned char *)data, + crypttemp, + EVP_PKEY_get0_RSA(pkey), + (int)padding); + if (cryptedlen != -1) { + cryptedbuf = zend_string_alloc(cryptedlen, 0); + memcpy(ZSTR_VAL(cryptedbuf), crypttemp, cryptedlen); + successful = 1; + } + break; + default: + php_error_docref(NULL, E_WARNING, "key type not supported in this PHP build!"); + } + + efree(crypttemp); + + if (successful) { + zval_ptr_dtor(crypted); + ZSTR_VAL(cryptedbuf)[cryptedlen] = '\0'; + ZVAL_NEW_STR(crypted, cryptedbuf); + cryptedbuf = NULL; + RETVAL_TRUE; + } else { + php_openssl_store_errors(); + } + + if (keyresource == NULL) { + EVP_PKEY_free(pkey); + } + if (cryptedbuf) { + zend_string_release_ex(cryptedbuf, 0); + } +} +/* }}} */ + +/* {{{ proto bool openssl_public_encrypt(string data, string &crypted, mixed key [, int padding]) + Encrypts data with public key */ +PHP_FUNCTION(openssl_public_encrypt) +{ + zval *key, *crypted; + EVP_PKEY *pkey; + int cryptedlen; + zend_string *cryptedbuf; + int successful = 0; + zend_resource *keyresource = NULL; + zend_long padding = RSA_PKCS1_PADDING; + char * data; + size_t data_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sz/z|l", &data, &data_len, &crypted, &key, &padding) == FAILURE) + return; + RETVAL_FALSE; + + pkey = php_openssl_evp_from_zval(key, 1, NULL, 0, 0, &keyresource); + if (pkey == NULL) { + php_error_docref(NULL, E_WARNING, "key parameter is not a valid public key"); + RETURN_FALSE; + } + + PHP_OPENSSL_CHECK_SIZE_T_TO_INT(data_len, data); + + cryptedlen = EVP_PKEY_size(pkey); + cryptedbuf = zend_string_alloc(cryptedlen, 0); + + switch (EVP_PKEY_id(pkey)) { + case EVP_PKEY_RSA: + case EVP_PKEY_RSA2: + successful = (RSA_public_encrypt((int)data_len, + (unsigned char *)data, + (unsigned char *)ZSTR_VAL(cryptedbuf), + EVP_PKEY_get0_RSA(pkey), + (int)padding) == cryptedlen); + break; + default: + php_error_docref(NULL, E_WARNING, "key type not supported in this PHP build!"); + + } + + if (successful) { + zval_ptr_dtor(crypted); + ZSTR_VAL(cryptedbuf)[cryptedlen] = '\0'; + ZVAL_NEW_STR(crypted, cryptedbuf); + cryptedbuf = NULL; + RETVAL_TRUE; + } else { + php_openssl_store_errors(); + } + if (keyresource == NULL) { + EVP_PKEY_free(pkey); + } + if (cryptedbuf) { + zend_string_release_ex(cryptedbuf, 0); + } +} +/* }}} */ + +/* {{{ proto bool openssl_public_decrypt(string data, string &crypted, resource key [, int padding]) + Decrypts data with public key */ +PHP_FUNCTION(openssl_public_decrypt) +{ + zval *key, *crypted; + EVP_PKEY *pkey; + int cryptedlen; + zend_string *cryptedbuf = NULL; + unsigned char *crypttemp; + int successful = 0; + zend_resource *keyresource = NULL; + zend_long padding = RSA_PKCS1_PADDING; + char * data; + size_t data_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sz/z|l", &data, &data_len, &crypted, &key, &padding) == FAILURE) { + return; + } + RETVAL_FALSE; + + pkey = php_openssl_evp_from_zval(key, 1, NULL, 0, 0, &keyresource); + if (pkey == NULL) { + php_error_docref(NULL, E_WARNING, "key parameter is not a valid public key"); + RETURN_FALSE; + } + + PHP_OPENSSL_CHECK_SIZE_T_TO_INT(data_len, data); + + cryptedlen = EVP_PKEY_size(pkey); + crypttemp = emalloc(cryptedlen + 1); + + switch (EVP_PKEY_id(pkey)) { + case EVP_PKEY_RSA: + case EVP_PKEY_RSA2: + cryptedlen = RSA_public_decrypt((int)data_len, + (unsigned char *)data, + crypttemp, + EVP_PKEY_get0_RSA(pkey), + (int)padding); + if (cryptedlen != -1) { + cryptedbuf = zend_string_alloc(cryptedlen, 0); + memcpy(ZSTR_VAL(cryptedbuf), crypttemp, cryptedlen); + successful = 1; + } + break; + + default: + php_error_docref(NULL, E_WARNING, "key type not supported in this PHP build!"); + + } + + efree(crypttemp); + + if (successful) { + zval_ptr_dtor(crypted); + ZSTR_VAL(cryptedbuf)[cryptedlen] = '\0'; + ZVAL_NEW_STR(crypted, cryptedbuf); + cryptedbuf = NULL; + RETVAL_TRUE; + } else { + php_openssl_store_errors(); + } + + if (cryptedbuf) { + zend_string_release_ex(cryptedbuf, 0); + } + if (keyresource == NULL) { + EVP_PKEY_free(pkey); + } +} +/* }}} */ + +/* {{{ proto mixed openssl_error_string(void) + Returns a description of the last error, and alters the index of the error messages. Returns false when the are no more messages */ +PHP_FUNCTION(openssl_error_string) +{ + char buf[256]; + unsigned long val; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + php_openssl_store_errors(); + + if (OPENSSL_G(errors) == NULL || OPENSSL_G(errors)->top == OPENSSL_G(errors)->bottom) { + RETURN_FALSE; + } + + OPENSSL_G(errors)->bottom = (OPENSSL_G(errors)->bottom + 1) % ERR_NUM_ERRORS; + val = OPENSSL_G(errors)->buffer[OPENSSL_G(errors)->bottom]; + + if (val) { + ERR_error_string_n(val, buf, 256); + RETURN_STRING(buf); + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto bool openssl_sign(string data, &string signature, mixed key[, mixed method]) + Signs data */ +PHP_FUNCTION(openssl_sign) +{ + zval *key, *signature; + EVP_PKEY *pkey; + unsigned int siglen; + zend_string *sigbuf; + zend_resource *keyresource = NULL; + char * data; + size_t data_len; + EVP_MD_CTX *md_ctx; + zval *method = NULL; + zend_long signature_algo = OPENSSL_ALGO_SHA1; + const EVP_MD *mdtype; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sz/z|z", &data, &data_len, &signature, &key, &method) == FAILURE) { + return; + } + pkey = php_openssl_evp_from_zval(key, 0, "", 0, 0, &keyresource); + if (pkey == NULL) { + php_error_docref(NULL, E_WARNING, "supplied key param cannot be coerced into a private key"); + RETURN_FALSE; + } + + if (method == NULL || Z_TYPE_P(method) == IS_LONG) { + if (method != NULL) { + signature_algo = Z_LVAL_P(method); + } + mdtype = php_openssl_get_evp_md_from_algo(signature_algo); + } else if (Z_TYPE_P(method) == IS_STRING) { + mdtype = EVP_get_digestbyname(Z_STRVAL_P(method)); + } else { + php_error_docref(NULL, E_WARNING, "Unknown signature algorithm."); + RETURN_FALSE; + } + if (!mdtype) { + php_error_docref(NULL, E_WARNING, "Unknown signature algorithm."); + RETURN_FALSE; + } + + siglen = EVP_PKEY_size(pkey); + sigbuf = zend_string_alloc(siglen, 0); + + md_ctx = EVP_MD_CTX_create(); + if (md_ctx != NULL && + EVP_SignInit(md_ctx, mdtype) && + EVP_SignUpdate(md_ctx, data, data_len) && + EVP_SignFinal(md_ctx, (unsigned char*)ZSTR_VAL(sigbuf), &siglen, pkey)) { + zval_ptr_dtor(signature); + ZSTR_VAL(sigbuf)[siglen] = '\0'; + ZSTR_LEN(sigbuf) = siglen; + ZVAL_NEW_STR(signature, sigbuf); + RETVAL_TRUE; + } else { + php_openssl_store_errors(); + efree(sigbuf); + RETVAL_FALSE; + } + EVP_MD_CTX_destroy(md_ctx); + if (keyresource == NULL) { + EVP_PKEY_free(pkey); + } +} +/* }}} */ + +/* {{{ proto int openssl_verify(string data, string signature, mixed key[, mixed method]) + Verifys data */ +PHP_FUNCTION(openssl_verify) +{ + zval *key; + EVP_PKEY *pkey; + int err = 0; + EVP_MD_CTX *md_ctx; + const EVP_MD *mdtype; + zend_resource *keyresource = NULL; + char * data; + size_t data_len; + char * signature; + size_t signature_len; + zval *method = NULL; + zend_long signature_algo = OPENSSL_ALGO_SHA1; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssz|z", &data, &data_len, &signature, &signature_len, &key, &method) == FAILURE) { + return; + } + + PHP_OPENSSL_CHECK_SIZE_T_TO_UINT(signature_len, signature); + + if (method == NULL || Z_TYPE_P(method) == IS_LONG) { + if (method != NULL) { + signature_algo = Z_LVAL_P(method); + } + mdtype = php_openssl_get_evp_md_from_algo(signature_algo); + } else if (Z_TYPE_P(method) == IS_STRING) { + mdtype = EVP_get_digestbyname(Z_STRVAL_P(method)); + } else { + php_error_docref(NULL, E_WARNING, "Unknown signature algorithm."); + RETURN_FALSE; + } + if (!mdtype) { + php_error_docref(NULL, E_WARNING, "Unknown signature algorithm."); + RETURN_FALSE; + } + + pkey = php_openssl_evp_from_zval(key, 1, NULL, 0, 0, &keyresource); + if (pkey == NULL) { + php_error_docref(NULL, E_WARNING, "supplied key param cannot be coerced into a public key"); + RETURN_FALSE; + } + + md_ctx = EVP_MD_CTX_create(); + if (md_ctx == NULL || + !EVP_VerifyInit (md_ctx, mdtype) || + !EVP_VerifyUpdate (md_ctx, data, data_len) || + (err = EVP_VerifyFinal(md_ctx, (unsigned char *)signature, (unsigned int)signature_len, pkey)) < 0) { + php_openssl_store_errors(); + } + EVP_MD_CTX_destroy(md_ctx); + + if (keyresource == NULL) { + EVP_PKEY_free(pkey); + } + RETURN_LONG(err); +} +/* }}} */ + +/* {{{ proto int openssl_seal(string data, &string sealdata, &array ekeys, array pubkeys [, string method [, &string iv]])) + Seals data */ +PHP_FUNCTION(openssl_seal) +{ + zval *pubkeys, *pubkey, *sealdata, *ekeys, *iv = NULL; + HashTable *pubkeysht; + EVP_PKEY **pkeys; + zend_resource ** key_resources; /* so we know what to cleanup */ + int i, len1, len2, *eksl, nkeys, iv_len; + unsigned char iv_buf[EVP_MAX_IV_LENGTH + 1], *buf = NULL, **eks; + char * data; + size_t data_len; + char *method =NULL; + size_t method_len = 0; + const EVP_CIPHER *cipher; + EVP_CIPHER_CTX *ctx; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sz/z/a|sz/", &data, &data_len, + &sealdata, &ekeys, &pubkeys, &method, &method_len, &iv) == FAILURE) { + return; + } + pubkeysht = Z_ARRVAL_P(pubkeys); + nkeys = pubkeysht ? zend_hash_num_elements(pubkeysht) : 0; + if (!nkeys) { + php_error_docref(NULL, E_WARNING, "Fourth argument to openssl_seal() must be a non-empty array"); + RETURN_FALSE; + } + + PHP_OPENSSL_CHECK_SIZE_T_TO_INT(data_len, data); + + if (method) { + cipher = EVP_get_cipherbyname(method); + if (!cipher) { + php_error_docref(NULL, E_WARNING, "Unknown signature algorithm."); + RETURN_FALSE; + } + } else { + cipher = EVP_rc4(); + } + + iv_len = EVP_CIPHER_iv_length(cipher); + if (!iv && iv_len > 0) { + php_error_docref(NULL, E_WARNING, + "Cipher algorithm requires an IV to be supplied as a sixth parameter"); + RETURN_FALSE; + } + + pkeys = safe_emalloc(nkeys, sizeof(*pkeys), 0); + eksl = safe_emalloc(nkeys, sizeof(*eksl), 0); + eks = safe_emalloc(nkeys, sizeof(*eks), 0); + memset(eks, 0, sizeof(*eks) * nkeys); + key_resources = safe_emalloc(nkeys, sizeof(zend_resource*), 0); + memset(key_resources, 0, sizeof(zend_resource*) * nkeys); + memset(pkeys, 0, sizeof(*pkeys) * nkeys); + + /* get the public keys we are using to seal this data */ + i = 0; + ZEND_HASH_FOREACH_VAL(pubkeysht, pubkey) { + pkeys[i] = php_openssl_evp_from_zval(pubkey, 1, NULL, 0, 0, &key_resources[i]); + if (pkeys[i] == NULL) { + php_error_docref(NULL, E_WARNING, "not a public key (%dth member of pubkeys)", i+1); + RETVAL_FALSE; + goto clean_exit; + } + eks[i] = emalloc(EVP_PKEY_size(pkeys[i]) + 1); + i++; + } ZEND_HASH_FOREACH_END(); + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL || !EVP_EncryptInit(ctx,cipher,NULL,NULL)) { + EVP_CIPHER_CTX_free(ctx); + php_openssl_store_errors(); + RETVAL_FALSE; + goto clean_exit; + } + + /* allocate one byte extra to make room for \0 */ + buf = emalloc(data_len + EVP_CIPHER_CTX_block_size(ctx)); + EVP_CIPHER_CTX_cleanup(ctx); + + if (EVP_SealInit(ctx, cipher, eks, eksl, &iv_buf[0], pkeys, nkeys) <= 0 || + !EVP_SealUpdate(ctx, buf, &len1, (unsigned char *)data, (int)data_len) || + !EVP_SealFinal(ctx, buf + len1, &len2)) { + efree(buf); + EVP_CIPHER_CTX_free(ctx); + php_openssl_store_errors(); + RETVAL_FALSE; + goto clean_exit; + } + + if (len1 + len2 > 0) { + zval_ptr_dtor(sealdata); + ZVAL_NEW_STR(sealdata, zend_string_init((char*)buf, len1 + len2, 0)); + efree(buf); + + zval_ptr_dtor(ekeys); + array_init(ekeys); + for (i=0; i<nkeys; i++) { + eks[i][eksl[i]] = '\0'; + add_next_index_stringl(ekeys, (const char*)eks[i], eksl[i]); + efree(eks[i]); + eks[i] = NULL; + } + + if (iv) { + zval_ptr_dtor(iv); + iv_buf[iv_len] = '\0'; + ZVAL_NEW_STR(iv, zend_string_init((char*)iv_buf, iv_len, 0)); + } + } else { + efree(buf); + } + RETVAL_LONG(len1 + len2); + EVP_CIPHER_CTX_free(ctx); + +clean_exit: + for (i=0; i<nkeys; i++) { + if (key_resources[i] == NULL && pkeys[i] != NULL) { + EVP_PKEY_free(pkeys[i]); + } + if (eks[i]) { + efree(eks[i]); + } + } + efree(eks); + efree(eksl); + efree(pkeys); + efree(key_resources); +} +/* }}} */ + +/* {{{ proto bool openssl_open(string data, &string opendata, string ekey, mixed privkey [, string method [, string iv]]) + Opens data */ +PHP_FUNCTION(openssl_open) +{ + zval *privkey, *opendata; + EVP_PKEY *pkey; + int len1, len2, cipher_iv_len; + unsigned char *buf, *iv_buf; + zend_resource *keyresource = NULL; + EVP_CIPHER_CTX *ctx; + char * data; + size_t data_len; + char * ekey; + size_t ekey_len; + char *method = NULL, *iv = NULL; + size_t method_len = 0, iv_len = 0; + const EVP_CIPHER *cipher; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sz/sz|ss", &data, &data_len, &opendata, + &ekey, &ekey_len, &privkey, &method, &method_len, &iv, &iv_len) == FAILURE) { + return; + } + + pkey = php_openssl_evp_from_zval(privkey, 0, "", 0, 0, &keyresource); + if (pkey == NULL) { + php_error_docref(NULL, E_WARNING, "unable to coerce parameter 4 into a private key"); + RETURN_FALSE; + } + + PHP_OPENSSL_CHECK_SIZE_T_TO_INT(ekey_len, ekey); + PHP_OPENSSL_CHECK_SIZE_T_TO_INT(data_len, data); + + if (method) { + cipher = EVP_get_cipherbyname(method); + if (!cipher) { + php_error_docref(NULL, E_WARNING, "Unknown signature algorithm."); + RETURN_FALSE; + } + } else { + cipher = EVP_rc4(); + } + + cipher_iv_len = EVP_CIPHER_iv_length(cipher); + if (cipher_iv_len > 0) { + if (!iv) { + php_error_docref(NULL, E_WARNING, + "Cipher algorithm requires an IV to be supplied as a sixth parameter"); + RETURN_FALSE; + } + if ((size_t)cipher_iv_len != iv_len) { + php_error_docref(NULL, E_WARNING, "IV length is invalid"); + RETURN_FALSE; + } + iv_buf = (unsigned char *)iv; + } else { + iv_buf = NULL; + } + + buf = emalloc(data_len + 1); + + ctx = EVP_CIPHER_CTX_new(); + if (ctx != NULL && EVP_OpenInit(ctx, cipher, (unsigned char *)ekey, (int)ekey_len, iv_buf, pkey) && + EVP_OpenUpdate(ctx, buf, &len1, (unsigned char *)data, (int)data_len) && + EVP_OpenFinal(ctx, buf + len1, &len2) && (len1 + len2 > 0)) { + zval_ptr_dtor(opendata); + buf[len1 + len2] = '\0'; + ZVAL_NEW_STR(opendata, zend_string_init((char*)buf, len1 + len2, 0)); + RETVAL_TRUE; + } else { + php_openssl_store_errors(); + RETVAL_FALSE; + } + + efree(buf); + if (keyresource == NULL) { + EVP_PKEY_free(pkey); + } + EVP_CIPHER_CTX_free(ctx); +} +/* }}} */ + +static void php_openssl_add_method_or_alias(const OBJ_NAME *name, void *arg) /* {{{ */ +{ + add_next_index_string((zval*)arg, (char*)name->name); +} +/* }}} */ + +static void php_openssl_add_method(const OBJ_NAME *name, void *arg) /* {{{ */ +{ + if (name->alias == 0) { + add_next_index_string((zval*)arg, (char*)name->name); + } +} +/* }}} */ + +/* {{{ proto array openssl_get_md_methods([bool aliases = false]) + Return array of available digest methods */ +PHP_FUNCTION(openssl_get_md_methods) +{ + zend_bool aliases = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &aliases) == FAILURE) { + return; + } + array_init(return_value); + OBJ_NAME_do_all_sorted(OBJ_NAME_TYPE_MD_METH, + aliases ? php_openssl_add_method_or_alias: php_openssl_add_method, + return_value); +} +/* }}} */ + +/* {{{ proto array openssl_get_cipher_methods([bool aliases = false]) + Return array of available cipher methods */ +PHP_FUNCTION(openssl_get_cipher_methods) +{ + zend_bool aliases = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &aliases) == FAILURE) { + return; + } + array_init(return_value); + OBJ_NAME_do_all_sorted(OBJ_NAME_TYPE_CIPHER_METH, + aliases ? php_openssl_add_method_or_alias: php_openssl_add_method, + return_value); +} +/* }}} */ + +/* {{{ proto array openssl_get_curve_names() + Return array of available elliptic curves */ +#ifdef HAVE_EVP_PKEY_EC +PHP_FUNCTION(openssl_get_curve_names) +{ + EC_builtin_curve *curves = NULL; + const char *sname; + size_t i; + size_t len = EC_get_builtin_curves(NULL, 0); + + curves = emalloc(sizeof(EC_builtin_curve) * len); + if (!EC_get_builtin_curves(curves, len)) { + RETURN_FALSE; + } + + array_init(return_value); + for (i = 0; i < len; i++) { + sname = OBJ_nid2sn(curves[i].nid); + if (sname != NULL) { + add_next_index_string(return_value, sname); + } + } + efree(curves); +} +#endif +/* }}} */ + +/* {{{ proto string openssl_digest(string data, string method [, bool raw_output=false]) + Computes digest hash value for given data using given method, returns raw or binhex encoded string */ +PHP_FUNCTION(openssl_digest) +{ + zend_bool raw_output = 0; + char *data, *method; + size_t data_len, method_len; + const EVP_MD *mdtype; + EVP_MD_CTX *md_ctx; + unsigned int siglen; + zend_string *sigbuf; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss|b", &data, &data_len, &method, &method_len, &raw_output) == FAILURE) { + return; + } + mdtype = EVP_get_digestbyname(method); + if (!mdtype) { + php_error_docref(NULL, E_WARNING, "Unknown signature algorithm"); + RETURN_FALSE; + } + + siglen = EVP_MD_size(mdtype); + sigbuf = zend_string_alloc(siglen, 0); + + md_ctx = EVP_MD_CTX_create(); + if (EVP_DigestInit(md_ctx, mdtype) && + EVP_DigestUpdate(md_ctx, (unsigned char *)data, data_len) && + EVP_DigestFinal (md_ctx, (unsigned char *)ZSTR_VAL(sigbuf), &siglen)) { + if (raw_output) { + ZSTR_VAL(sigbuf)[siglen] = '\0'; + ZSTR_LEN(sigbuf) = siglen; + RETVAL_STR(sigbuf); + } else { + int digest_str_len = siglen * 2; + zend_string *digest_str = zend_string_alloc(digest_str_len, 0); + + make_digest_ex(ZSTR_VAL(digest_str), (unsigned char*)ZSTR_VAL(sigbuf), siglen); + ZSTR_VAL(digest_str)[digest_str_len] = '\0'; + zend_string_release_ex(sigbuf, 0); + RETVAL_NEW_STR(digest_str); + } + } else { + php_openssl_store_errors(); + zend_string_release_ex(sigbuf, 0); + RETVAL_FALSE; + } + + EVP_MD_CTX_destroy(md_ctx); +} +/* }}} */ + +/* Cipher mode info */ +struct php_openssl_cipher_mode { + zend_bool is_aead; + zend_bool is_single_run_aead; + int aead_get_tag_flag; + int aead_set_tag_flag; + int aead_ivlen_flag; +}; + +static void php_openssl_load_cipher_mode(struct php_openssl_cipher_mode *mode, const EVP_CIPHER *cipher_type) /* {{{ */ +{ + switch (EVP_CIPHER_mode(cipher_type)) { +#ifdef EVP_CIPH_GCM_MODE + case EVP_CIPH_GCM_MODE: + mode->is_aead = 1; + mode->is_single_run_aead = 0; + mode->aead_get_tag_flag = EVP_CTRL_GCM_GET_TAG; + mode->aead_set_tag_flag = EVP_CTRL_GCM_SET_TAG; + mode->aead_ivlen_flag = EVP_CTRL_GCM_SET_IVLEN; + break; +#endif +#ifdef EVP_CIPH_CCM_MODE + case EVP_CIPH_CCM_MODE: + mode->is_aead = 1; + mode->is_single_run_aead = 1; + mode->aead_get_tag_flag = EVP_CTRL_CCM_GET_TAG; + mode->aead_set_tag_flag = EVP_CTRL_CCM_SET_TAG; + mode->aead_ivlen_flag = EVP_CTRL_CCM_SET_IVLEN; + break; +#endif + default: + memset(mode, 0, sizeof(struct php_openssl_cipher_mode)); + } +} +/* }}} */ + +static int php_openssl_validate_iv(char **piv, size_t *piv_len, size_t iv_required_len, + zend_bool *free_iv, EVP_CIPHER_CTX *cipher_ctx, struct php_openssl_cipher_mode *mode) /* {{{ */ +{ + char *iv_new; + + /* Best case scenario, user behaved */ + if (*piv_len == iv_required_len) { + return SUCCESS; + } + + if (mode->is_aead) { + if (EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_ivlen_flag, *piv_len, NULL) != 1) { + php_error_docref(NULL, E_WARNING, "Setting of IV length for AEAD mode failed"); + return FAILURE; + } + return SUCCESS; + } + + iv_new = ecalloc(1, iv_required_len + 1); + + if (*piv_len == 0) { + /* BC behavior */ + *piv_len = iv_required_len; + *piv = iv_new; + *free_iv = 1; + return SUCCESS; + + } + + if (*piv_len < iv_required_len) { + php_error_docref(NULL, E_WARNING, + "IV passed is only %zd bytes long, cipher expects an IV of precisely %zd bytes, padding with \\0", + *piv_len, iv_required_len); + memcpy(iv_new, *piv, *piv_len); + *piv_len = iv_required_len; + *piv = iv_new; + *free_iv = 1; + return SUCCESS; + } + + php_error_docref(NULL, E_WARNING, + "IV passed is %zd bytes long which is longer than the %zd expected by selected cipher, truncating", + *piv_len, iv_required_len); + memcpy(iv_new, *piv, iv_required_len); + *piv_len = iv_required_len; + *piv = iv_new; + *free_iv = 1; + return SUCCESS; + +} +/* }}} */ + +static int php_openssl_cipher_init(const EVP_CIPHER *cipher_type, + EVP_CIPHER_CTX *cipher_ctx, struct php_openssl_cipher_mode *mode, + char **ppassword, size_t *ppassword_len, zend_bool *free_password, + char **piv, size_t *piv_len, zend_bool *free_iv, + char *tag, int tag_len, zend_long options, int enc) /* {{{ */ +{ + unsigned char *key; + int key_len, password_len; + size_t max_iv_len; + + *free_password = 0; + + max_iv_len = EVP_CIPHER_iv_length(cipher_type); + if (enc && *piv_len == 0 && max_iv_len > 0 && !mode->is_aead) { + php_error_docref(NULL, E_WARNING, + "Using an empty Initialization Vector (iv) is potentially insecure and not recommended"); + } + + if (!EVP_CipherInit_ex(cipher_ctx, cipher_type, NULL, NULL, NULL, enc)) { + php_openssl_store_errors(); + return FAILURE; + } + if (php_openssl_validate_iv(piv, piv_len, max_iv_len, free_iv, cipher_ctx, mode) == FAILURE) { + return FAILURE; + } + if (mode->is_single_run_aead && enc) { + if (!EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_set_tag_flag, tag_len, NULL)) { + php_error_docref(NULL, E_WARNING, "Setting tag length for AEAD cipher failed"); + return FAILURE; + } + } else if (!enc && tag && tag_len > 0) { + if (!mode->is_aead) { + php_error_docref(NULL, E_WARNING, "The tag cannot be used because the cipher method does not support AEAD"); + } else if (!EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_set_tag_flag, tag_len, (unsigned char *) tag)) { + php_error_docref(NULL, E_WARNING, "Setting tag for AEAD cipher decryption failed"); + return FAILURE; + } + } + /* check and set key */ + password_len = (int) *ppassword_len; + key_len = EVP_CIPHER_key_length(cipher_type); + if (key_len > password_len) { + if ((OPENSSL_DONT_ZERO_PAD_KEY & options) && !EVP_CIPHER_CTX_set_key_length(cipher_ctx, password_len)) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "Key length cannot be set for the cipher method"); + return FAILURE; + } + key = emalloc(key_len); + memset(key, 0, key_len); + memcpy(key, *ppassword, password_len); + *ppassword = (char *) key; + *ppassword_len = key_len; + *free_password = 1; + } else { + if (password_len > key_len && !EVP_CIPHER_CTX_set_key_length(cipher_ctx, password_len)) { + php_openssl_store_errors(); + } + key = (unsigned char*)*ppassword; + } + + if (!EVP_CipherInit_ex(cipher_ctx, NULL, NULL, key, (unsigned char *)*piv, enc)) { + php_openssl_store_errors(); + return FAILURE; + } + if (options & OPENSSL_ZERO_PADDING) { + EVP_CIPHER_CTX_set_padding(cipher_ctx, 0); + } + + return SUCCESS; +} +/* }}} */ + +static int php_openssl_cipher_update(const EVP_CIPHER *cipher_type, + EVP_CIPHER_CTX *cipher_ctx, struct php_openssl_cipher_mode *mode, + zend_string **poutbuf, int *poutlen, char *data, size_t data_len, + char *aad, size_t aad_len, int enc) /* {{{ */ +{ + int i = 0; + + if (mode->is_single_run_aead && !EVP_CipherUpdate(cipher_ctx, NULL, &i, NULL, (int)data_len)) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "Setting of data length failed"); + return FAILURE; + } + + if (mode->is_aead && !EVP_CipherUpdate(cipher_ctx, NULL, &i, (unsigned char *)aad, (int)aad_len)) { + php_openssl_store_errors(); + php_error_docref(NULL, E_WARNING, "Setting of additional application data failed"); + return FAILURE; + } + + *poutbuf = zend_string_alloc((int)data_len + EVP_CIPHER_block_size(cipher_type), 0); + + if (!EVP_CipherUpdate(cipher_ctx, (unsigned char*)ZSTR_VAL(*poutbuf), + &i, (unsigned char *)data, (int)data_len)) { + /* we don't show warning when we fail but if we ever do, then it should look like this: + if (mode->is_single_run_aead && !enc) { + php_error_docref(NULL, E_WARNING, "Tag verifycation failed"); + } else { + php_error_docref(NULL, E_WARNING, enc ? "Encryption failed" : "Decryption failed"); + } + */ + php_openssl_store_errors(); + zend_string_release_ex(*poutbuf, 0); + return FAILURE; + } + + *poutlen = i; + + return SUCCESS; +} +/* }}} */ + +/* {{{ proto string openssl_encrypt(string data, string method, string password [, int options=0 [, string $iv=''[, string &$tag = ''[, string $aad = ''[, int $tag_length = 16]]]]]) + Encrypts given data with given method and key, returns raw or base64 encoded string */ +PHP_FUNCTION(openssl_encrypt) +{ + zend_long options = 0, tag_len = 16; + char *data, *method, *password, *iv = "", *aad = ""; + size_t data_len, method_len, password_len, iv_len = 0, aad_len = 0; + zval *tag = NULL; + const EVP_CIPHER *cipher_type; + EVP_CIPHER_CTX *cipher_ctx; + struct php_openssl_cipher_mode mode; + int i=0, outlen; + zend_string *outbuf; + zend_bool free_iv = 0, free_password = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sss|lsz/sl", &data, &data_len, &method, &method_len, + &password, &password_len, &options, &iv, &iv_len, &tag, &aad, &aad_len, &tag_len) == FAILURE) { + return; + } + + PHP_OPENSSL_CHECK_SIZE_T_TO_INT(data_len, data); + PHP_OPENSSL_CHECK_SIZE_T_TO_INT(password_len, password); + PHP_OPENSSL_CHECK_SIZE_T_TO_INT(aad_len, aad); + PHP_OPENSSL_CHECK_LONG_TO_INT(tag_len, tag_len); + + cipher_type = EVP_get_cipherbyname(method); + if (!cipher_type) { + php_error_docref(NULL, E_WARNING, "Unknown cipher algorithm"); + RETURN_FALSE; + } + + cipher_ctx = EVP_CIPHER_CTX_new(); + if (!cipher_ctx) { + php_error_docref(NULL, E_WARNING, "Failed to create cipher context"); + RETURN_FALSE; + } + php_openssl_load_cipher_mode(&mode, cipher_type); + + if (php_openssl_cipher_init(cipher_type, cipher_ctx, &mode, + &password, &password_len, &free_password, + &iv, &iv_len, &free_iv, NULL, tag_len, options, 1) == FAILURE || + php_openssl_cipher_update(cipher_type, cipher_ctx, &mode, &outbuf, &outlen, + data, data_len, aad, aad_len, 1) == FAILURE) { + RETVAL_FALSE; + } else if (EVP_EncryptFinal(cipher_ctx, (unsigned char *)ZSTR_VAL(outbuf) + outlen, &i)) { + outlen += i; + if (options & OPENSSL_RAW_DATA) { + ZSTR_VAL(outbuf)[outlen] = '\0'; + ZSTR_LEN(outbuf) = outlen; + RETVAL_STR(outbuf); + } else { + zend_string *base64_str; + + base64_str = php_base64_encode((unsigned char*)ZSTR_VAL(outbuf), outlen); + zend_string_release_ex(outbuf, 0); + outbuf = base64_str; + RETVAL_STR(base64_str); + } + if (mode.is_aead && tag) { + zend_string *tag_str = zend_string_alloc(tag_len, 0); + + if (EVP_CIPHER_CTX_ctrl(cipher_ctx, mode.aead_get_tag_flag, tag_len, ZSTR_VAL(tag_str)) == 1) { + zval_ptr_dtor(tag); + ZSTR_VAL(tag_str)[tag_len] = '\0'; + ZSTR_LEN(tag_str) = tag_len; + ZVAL_NEW_STR(tag, tag_str); + } else { + php_error_docref(NULL, E_WARNING, "Retrieving verification tag failed"); + zend_string_release_ex(tag_str, 0); + zend_string_release_ex(outbuf, 0); + RETVAL_FALSE; + } + } else if (tag) { + zval_ptr_dtor(tag); + ZVAL_NULL(tag); + php_error_docref(NULL, E_WARNING, + "The authenticated tag cannot be provided for cipher that doesn not support AEAD"); + } else if (mode.is_aead) { + php_error_docref(NULL, E_WARNING, "A tag should be provided when using AEAD mode"); + zend_string_release_ex(outbuf, 0); + RETVAL_FALSE; + } + } else { + php_openssl_store_errors(); + zend_string_release_ex(outbuf, 0); + RETVAL_FALSE; + } + + if (free_password) { + efree(password); + } + if (free_iv) { + efree(iv); + } + EVP_CIPHER_CTX_cleanup(cipher_ctx); + EVP_CIPHER_CTX_free(cipher_ctx); +} +/* }}} */ + +/* {{{ proto string openssl_decrypt(string data, string method, string password [, int options=0 [, string $iv = ''[, string $tag = ''[, string $aad = '']]]]) + Takes raw or base64 encoded string and decrypts it using given method and key */ +PHP_FUNCTION(openssl_decrypt) +{ + zend_long options = 0; + char *data, *method, *password, *iv = "", *tag = NULL, *aad = ""; + size_t data_len, method_len, password_len, iv_len = 0, tag_len = 0, aad_len = 0; + const EVP_CIPHER *cipher_type; + EVP_CIPHER_CTX *cipher_ctx; + struct php_openssl_cipher_mode mode; + int i = 0, outlen; + zend_string *outbuf; + zend_string *base64_str = NULL; + zend_bool free_iv = 0, free_password = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sss|lsss", &data, &data_len, &method, &method_len, + &password, &password_len, &options, &iv, &iv_len, &tag, &tag_len, &aad, &aad_len) == FAILURE) { + return; + } + + if (!method_len) { + php_error_docref(NULL, E_WARNING, "Unknown cipher algorithm"); + RETURN_FALSE; + } + + PHP_OPENSSL_CHECK_SIZE_T_TO_INT(data_len, data); + PHP_OPENSSL_CHECK_SIZE_T_TO_INT(password_len, password); + PHP_OPENSSL_CHECK_SIZE_T_TO_INT(aad_len, aad); + PHP_OPENSSL_CHECK_SIZE_T_TO_INT(tag_len, tag); + + cipher_type = EVP_get_cipherbyname(method); + if (!cipher_type) { + php_error_docref(NULL, E_WARNING, "Unknown cipher algorithm"); + RETURN_FALSE; + } + + cipher_ctx = EVP_CIPHER_CTX_new(); + if (!cipher_ctx) { + php_error_docref(NULL, E_WARNING, "Failed to create cipher context"); + RETURN_FALSE; + } + + php_openssl_load_cipher_mode(&mode, cipher_type); + + if (!(options & OPENSSL_RAW_DATA)) { + base64_str = php_base64_decode((unsigned char*)data, data_len); + if (!base64_str) { + php_error_docref(NULL, E_WARNING, "Failed to base64 decode the input"); + EVP_CIPHER_CTX_free(cipher_ctx); + RETURN_FALSE; + } + data_len = ZSTR_LEN(base64_str); + data = ZSTR_VAL(base64_str); + } + + if (php_openssl_cipher_init(cipher_type, cipher_ctx, &mode, + &password, &password_len, &free_password, + &iv, &iv_len, &free_iv, tag, tag_len, options, 0) == FAILURE || + php_openssl_cipher_update(cipher_type, cipher_ctx, &mode, &outbuf, &outlen, + data, data_len, aad, aad_len, 0) == FAILURE) { + RETVAL_FALSE; + } else if (mode.is_single_run_aead || + EVP_DecryptFinal(cipher_ctx, (unsigned char *)ZSTR_VAL(outbuf) + outlen, &i)) { + outlen += i; + ZSTR_VAL(outbuf)[outlen] = '\0'; + ZSTR_LEN(outbuf) = outlen; + RETVAL_STR(outbuf); + } else { + php_openssl_store_errors(); + zend_string_release_ex(outbuf, 0); + RETVAL_FALSE; + } + + if (free_password) { + efree(password); + } + if (free_iv) { + efree(iv); + } + if (base64_str) { + zend_string_release_ex(base64_str, 0); + } + EVP_CIPHER_CTX_cleanup(cipher_ctx); + EVP_CIPHER_CTX_free(cipher_ctx); +} +/* }}} */ + +/* {{{ proto int openssl_cipher_iv_length(string $method) */ +PHP_FUNCTION(openssl_cipher_iv_length) +{ + char *method; + size_t method_len; + const EVP_CIPHER *cipher_type; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &method, &method_len) == FAILURE) { + return; + } + + if (!method_len) { + php_error_docref(NULL, E_WARNING, "Unknown cipher algorithm"); + RETURN_FALSE; + } + + cipher_type = EVP_get_cipherbyname(method); + if (!cipher_type) { + php_error_docref(NULL, E_WARNING, "Unknown cipher algorithm"); + RETURN_FALSE; + } + + RETURN_LONG(EVP_CIPHER_iv_length(cipher_type)); +} +/* }}} */ + + +/* {{{ proto string openssl_random_pseudo_bytes(int length [, &bool returned_strong_result]) + Returns a string of the length specified filled with random pseudo bytes */ +PHP_FUNCTION(openssl_random_pseudo_bytes) +{ + zend_long buffer_length; + zend_string *buffer = NULL; + zval *zstrong_result_returned = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l|z/", &buffer_length, &zstrong_result_returned) == FAILURE) { + return; + } + + if (zstrong_result_returned) { + zval_ptr_dtor(zstrong_result_returned); + ZVAL_FALSE(zstrong_result_returned); + } + + if (buffer_length <= 0 +#ifndef PHP_WIN32 + || ZEND_LONG_INT_OVFL(buffer_length) +#endif + ) { + RETURN_FALSE; + } + buffer = zend_string_alloc(buffer_length, 0); + +#ifdef PHP_WIN32 + /* random/urandom equivalent on Windows */ + if (php_win32_get_random_bytes((unsigned char*)buffer->val, (size_t) buffer_length) == FAILURE){ + zend_string_release_ex(buffer, 0); + if (zstrong_result_returned) { + ZVAL_FALSE(zstrong_result_returned); + } + RETURN_FALSE; + } +#else + + PHP_OPENSSL_CHECK_LONG_TO_INT(buffer_length, length); + PHP_OPENSSL_RAND_ADD_TIME(); + /* FIXME loop if requested size > INT_MAX */ + if (RAND_bytes((unsigned char*)ZSTR_VAL(buffer), (int)buffer_length) <= 0) { + zend_string_release_ex(buffer, 0); + if (zstrong_result_returned) { + ZVAL_FALSE(zstrong_result_returned); + } + RETURN_FALSE; + } else { + php_openssl_store_errors(); + } +#endif + + ZSTR_VAL(buffer)[buffer_length] = 0; + RETVAL_NEW_STR(buffer); + + if (zstrong_result_returned) { + ZVAL_BOOL(zstrong_result_returned, 1); + } +} +/* }}} */ + +/* + * Local variables: + * tab-width: 8 + * c-basic-offset: 8 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/ext/openssl/tests/cipher_tests.inc b/ext/openssl/tests/cipher_tests.inc index b1e46b411e54e..779bfa8515cbd 100644 --- a/ext/openssl/tests/cipher_tests.inc +++ b/ext/openssl/tests/cipher_tests.inc @@ -1,5 +1,26 @@ <?php $php_openssl_cipher_tests = array( + 'aes-128-ccm' => array( + array( + 'key' => '404142434445464748494a4b4c4d4e4f', + 'iv' => '1011121314151617', + 'aad' => '000102030405060708090a0b0c0d0e0f', + 'tag' => '1fc64fbfaccd', + 'pt' => '202122232425262728292a2b2c2d2e2f', + 'ct' => 'd2a1f0e051ea5f62081a7792073d593d', + ), + array( + 'key' => '404142434445464748494a4b4c4d4e4f', + 'iv' => '101112131415161718191a1b', + 'aad' => '000102030405060708090a0b0c0d0e0f' . + '10111213', + 'tag' => '484392fbc1b09951', + 'pt' => '202122232425262728292a2b2c2d2e2f' . + '3031323334353637', + 'ct' => 'e3b201a9f5b71a7a9b1ceaeccd97e70b' . + '6176aad9a4428aa5', + ), + ), 'aes-256-ccm' => array( array( 'key' => '1bde3251d41a8b5ea013c195ae128b21' . diff --git a/ext/openssl/tests/openssl_decrypt_ccm.phpt b/ext/openssl/tests/openssl_decrypt_ccm.phpt index a5f01b87cea8b..08ef5bb7b7c43 100644 --- a/ext/openssl/tests/openssl_decrypt_ccm.phpt +++ b/ext/openssl/tests/openssl_decrypt_ccm.phpt @@ -10,14 +10,16 @@ if (!in_array('aes-256-ccm', openssl_get_cipher_methods())) --FILE-- <?php require_once __DIR__ . "/cipher_tests.inc"; -$method = 'aes-256-ccm'; -$tests = openssl_get_cipher_tests($method); +$methods = ['aes-128-ccm', 'aes-256-ccm']; -foreach ($tests as $idx => $test) { - echo "TEST $idx\n"; - $pt = openssl_decrypt($test['ct'], $method, $test['key'], OPENSSL_RAW_DATA, - $test['iv'], $test['tag'], $test['aad']); - var_dump($test['pt'] === $pt); +foreach ($methods as $method) { + $tests = openssl_get_cipher_tests($method); + foreach ($tests as $idx => $test) { + echo "$method - TEST $idx\n"; + $pt = openssl_decrypt($test['ct'], $method, $test['key'], OPENSSL_RAW_DATA, + $test['iv'], $test['tag'], $test['aad']); + var_dump($test['pt'] === $pt); + } } // no IV @@ -32,7 +34,11 @@ var_dump(openssl_decrypt($test['ct'], $method, $test['key'], OPENSSL_RAW_DATA, ?> --EXPECTF-- -TEST 0 +aes-128-ccm - TEST 0 +bool(true) +aes-128-ccm - TEST 1 +bool(true) +aes-256-ccm - TEST 0 bool(true) Warning: openssl_decrypt(): Setting of IV length for AEAD mode failed in %s on line %d diff --git a/ext/openssl/tests/openssl_encrypt_ccm.phpt b/ext/openssl/tests/openssl_encrypt_ccm.phpt index fb5dbbc849d06..8c4c41f81870c 100644 --- a/ext/openssl/tests/openssl_encrypt_ccm.phpt +++ b/ext/openssl/tests/openssl_encrypt_ccm.phpt @@ -10,15 +10,17 @@ if (!in_array('aes-256-ccm', openssl_get_cipher_methods())) --FILE-- <?php require_once __DIR__ . "/cipher_tests.inc"; -$method = 'aes-256-ccm'; -$tests = openssl_get_cipher_tests($method); +$methods = ['aes-128-ccm', 'aes-256-ccm']; -foreach ($tests as $idx => $test) { - echo "TEST $idx\n"; - $ct = openssl_encrypt($test['pt'], $method, $test['key'], OPENSSL_RAW_DATA, - $test['iv'], $tag, $test['aad'], strlen($test['tag'])); - var_dump($test['ct'] === $ct); - var_dump($test['tag'] === $tag); +foreach ($methods as $method) { + $tests = openssl_get_cipher_tests($method); + foreach ($tests as $idx => $test) { + echo "$method - TEST $idx\n"; + $ct = openssl_encrypt($test['pt'], $method, $test['key'], OPENSSL_RAW_DATA, + $test['iv'], $tag, $test['aad'], strlen($test['tag'])); + var_dump($test['ct'] === $ct); + var_dump($test['tag'] === $tag); + } } // Empty IV error @@ -32,7 +34,13 @@ var_dump(strlen($tag)); var_dump(openssl_encrypt('data', $method, 'password', 0, str_repeat('x', 16), $tag, '', 1024)); ?> --EXPECTF-- -TEST 0 +aes-128-ccm - TEST 0 +bool(true) +bool(true) +aes-128-ccm - TEST 1 +bool(true) +bool(true) +aes-256-ccm - TEST 0 bool(true) bool(true) diff --git a/ext/phar/phar_object.c b/ext/phar/phar_object.c index ed5c546adc5f4..fcd77b7899114 100644 --- a/ext/phar/phar_object.c +++ b/ext/phar/phar_object.c @@ -1404,6 +1404,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); @@ -1671,6 +1672,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..ed5c546adc5f4 --- /dev/null +++ b/ext/phar/phar_object.c.orig @@ -0,0 +1,5448 @@ +/* + +----------------------------------------------------------------------+ + | phar php single-file executable PHP extension | + +----------------------------------------------------------------------+ + | Copyright (c) 2005-2018 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 <cellog@php.net> | + | Marcus Boerger <helly@php.net> | + +----------------------------------------------------------------------+ +*/ + +#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, size_t entry_len, char *basename, size_t 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 > (size_t)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, size_t entry_len, char *arch, char *basename, char *ru, size_t 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) = (cwd - (entry + 1)); + PHAR_G(cwd) = estrndup(entry + 1, PHAR_G(cwd_len)); + } else { + PHAR_G(cwd_len) = (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, size_t 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("<html>\n <head>\n <title>Access Denied\n \n \n

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

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

\n \n", sizeof("Access Denied\n \n") - 1); +} +/* }}} */ + +static void phar_do_404(phar_archive_data *phar, char *fname, size_t fname_len, char *f404, size_t 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("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, size_t fname_len, char **entry, size_t *entry_len, char **ru, size_t *ru_len) /* {{{ */ +{ + char *e = *entry + 1, *u = NULL, *u1 = NULL, *saveu = NULL; + size_t 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 = strlen(u + 1); + e_len -= u_len + 1; + } 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; + size_t 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 = 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; + size_t 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; + } + + fname = (char*)zend_get_executed_filename(); + fname_len = 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, actual_len, path, 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 (HT_FLAGS(&PHAR_G(phar_fname_map)) && 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, 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; + size_t ru_len = 0; + char *fname, *path_info, *mime_type = NULL, *entry, *pt; + const char *basename; + size_t fname_len, index_php_len = 0; + size_t entry_len; + int 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 (phar_open_executed_filename(alias, 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 = 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 = 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 = 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); + entry = estrndup(Z_STRVAL_P(fci.retval), Z_STRLEN_P(fci.retval)); + entry_len = 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, 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 = 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, fname_len, NULL, 0, NULL) || + (info = phar_get_entry_info(phar, entry, entry_len, NULL, 0)) == NULL) { + phar_do_404(phar, fname, fname_len, f404, 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, fname_len, NULL, 0, NULL) || + (info = phar_get_entry_info(phar, entry, entry_len, NULL, 0)) == NULL) { + phar_do_404(phar, fname, fname_len, f404, 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 = 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; + } + + phar_request_initialize(); + + RETVAL_BOOL(phar_open_executed_filename(alias, 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; + } + + phar_request_initialize(); + + RETVAL_BOOL(phar_open_from_filename(fname, fname_len, alias, 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; + size_t ext_len; + int is_executable; + zend_bool executable = 1; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "p|b", &fname, &fname_len, &executable) == FAILURE) { + return; + } + + is_executable = executable; + RETVAL_BOOL(phar_detect_phar_fname_ext(fname, 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 const spl_other_handler phar_spl_foreign_handler = { + phar_spl_foreign_dtor, + phar_spl_foreign_clone +}; + +/* {{{ proto Phar::__construct(string fname [, int flags [, string alias]]) + * Construct a Phar archive object + * + * proto 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; + size_t arch_len, entry_len; + zend_bool 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 (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, 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, fname_len, alias, 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; + size_t 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 (!fname_len) { + zend_throw_exception_ex(phar_ce_PharException, 0, "Unknown phar archive \"\""); + return; + } + + if (FAILURE == phar_open_from_filename(fname, 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 = 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 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; + uint32_t 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; + size_t str_key_len, base_len = p_obj->l; + phar_entry_data *data; + php_stream *fp; + size_t 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_ptr_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; + } + + str_key_len = Z_STRLEN(key); + str_key = estrndup(Z_STRVAL(key), str_key_len); + + save = str_key; + zval_ptr_dtor_str(&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 = 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 = 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 = 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 = 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 = 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_ptr_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; + } + + str_key_len = Z_STRLEN(key); + str_key = estrndup(Z_STRVAL(key), str_key_len); + + save = str_key; + zval_ptr_dtor_str(&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_ex(opened, 0); + } + + 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_ex(opened, 0); + } + + 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_ptr_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 = (uint32_t)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 = (uint32_t)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) /* {{{ */ +{ + 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 = NULL; + const char *pcr_error; + size_t ext_len = ext ? strlen(ext) : 0; + size_t new_len, oldname_len, phar_ext_len; + phar_archive_data *pphar = NULL; + php_stream_statbuf ssb; + + int phar_ext_list_len, i = 0; + char *ext_pos = NULL; + /* Array of PHAR extensions, Must be in order, starting with longest + * ending with the shortest. */ + char *phar_ext_list[] = { + ".phar.tar.bz2", + ".phar.tar.gz", + ".phar.php", + ".phar.bz2", + ".phar.zip", + ".phar.tar", + ".phar.gz", + ".tar.bz2", + ".tar.gz", + ".phar", + ".tar", + ".zip" + }; + + 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; + } + + + 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); + /* Copy the old name to create base for the new name */ + basename = estrndup(oldname, oldname_len); + + phar_ext_list_len = sizeof(phar_ext_list)/sizeof(phar_ext_list[0]); + /* Remove possible PHAR extensions */ + /* phar_ext_list must be in order of longest extension to shortest */ + for (i=0; i < phar_ext_list_len; i++) { + phar_ext_len = strlen(phar_ext_list[i]); + if (phar_ext_len && oldname_len > phar_ext_len) { + /* Check if the basename strings ends with the extension */ + if (memcmp(phar_ext_list[i], basename + (oldname_len - phar_ext_len), phar_ext_len) == 0) { + ext_pos = basename + (oldname_len - phar_ext_len); + ext_pos[0] = '\0'; + break; + } + } + ext_pos = NULL; + } + + /* If no default PHAR extension found remove the last extension */ + if (!ext_pos) { + ext_pos = strrchr(basename, '.'); + if (ext_pos) { + ext_pos[0] = '\0'; + } + } + ext_pos = NULL; + + if (ext[0] == '.') { + ++ext; + } + /* Append extension to the basename */ + spprintf(&newname, 0, "%s.%s", basename, ext); + efree(basename); + + basepath = estrndup(oldpath, (strlen(oldpath) - oldname_len)); + new_len = spprintf(&newpath, 0, "%s%s", basepath, newname); + phar->fname_len = 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), &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; + } + phar->ext_len = ext_len; + + 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 = 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), &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->ext_len = ext_len; + + 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_hash_str_del(&(PHAR_G(phar_fname_map)), newpath, phar->fname_len); + *sphar = NULL; + 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_ptr_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))) { + 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, 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, (uint32_t) fname_len)) { + if (NULL != (entry = zend_hash_str_find_ptr(&phar_obj->archive->manifest, fname, (uint32_t) 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 (alias_len == 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, 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, 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 = 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 = 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; + } + + 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) = 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; + size_t tmp_len = 0; + + PHAR_ARCHIVE_OBJECT(); + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "pp", &oldfile, &oldfile_len, &newfile, &newfile_len) == FAILURE) { + return; + } + 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, (uint32_t) oldfile_len) || NULL == (oldentry = zend_hash_str_find_ptr(&phar_obj->archive->manifest, oldfile, (uint32_t) 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, (uint32_t) newfile_len)) { + if (NULL != (temp = zend_hash_str_find_ptr(&phar_obj->archive->manifest, newfile, (uint32_t) 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 = 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, (uint32_t) 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 = 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_hash_str_exists(&phar_obj->archive->manifest, fname, (uint32_t) fname_len)) { + if (NULL != (entry = zend_hash_str_find_ptr(&phar_obj->archive->manifest, fname, (uint32_t) 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, (uint32_t) 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; + } + + /* 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, 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, size_t filename_len, char *cont_str, size_t cont_len, zval *zresource) +{ + size_t start_pos=0; + char *error; + size_t contents_len; + phar_entry_data *data; + php_stream *contents_file = NULL; + php_stream_statbuf ssb; + + if (filename_len >= sizeof(".phar")-1) { + start_pos = ('/' == filename[0] ? 1 : 0); /* account for any leading slash: multiple-leads handled elsewhere */ + if (!memcmp(&filename[start_pos], ".phar", sizeof(".phar")-1) && (filename[start_pos+5] == '/' || filename[start_pos+5] == '\\' || filename[start_pos+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; + } + + if (contents_file != NULL && php_stream_stat(contents_file, &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 + } + + /* 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, size_t 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 (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, 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_hash_str_exists(&phar_obj->archive->manifest, fname, (uint32_t) fname_len)) { + if (NULL != (entry = zend_hash_str_find_ptr(&phar_obj->archive->manifest, fname, (uint32_t) 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, (uint32_t) 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 (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, 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 (!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, 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; + } + + phar_add_file(&(phar_obj->archive), localname, 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_ex(buf, 0); + 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, size_t 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 */ + { + size_t cnt = 0; + + do { + if ('\\' == filename[cnt]) { + filename[cnt] = '/'; + } + } while (cnt++ <= filename_len); + } +#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; +} +/* }}} */ + +static int extract_helper(phar_archive_data *archive, zend_string *search, char *pathto, size_t pathto_len, zend_bool overwrite, char **error) { /* {{{ */ + int extracted = 0; + phar_entry_info *entry; + + if (!search) { + /* nothing to match -- extract all files */ + ZEND_HASH_FOREACH_PTR(&archive->manifest, entry) { + if (FAILURE == phar_extract_file(overwrite, entry, pathto, pathto_len, error)) return -1; + extracted++; + } ZEND_HASH_FOREACH_END(); + } else if ('/' == ZSTR_VAL(search)[ZSTR_LEN(search) - 1]) { + /* ends in "/" -- extract all entries having that prefix */ + ZEND_HASH_FOREACH_PTR(&archive->manifest, entry) { + if (0 != strncmp(ZSTR_VAL(search), entry->filename, ZSTR_LEN(search))) continue; + if (FAILURE == phar_extract_file(overwrite, entry, pathto, pathto_len, error)) return -1; + extracted++; + } ZEND_HASH_FOREACH_END(); + } else { + /* otherwise, looking for an exact match */ + entry = zend_hash_find_ptr(&archive->manifest, search); + if (NULL == entry) return 0; + if (FAILURE == phar_extract_file(overwrite, entry, pathto, pathto_len, error)) return -1; + return 1; + } + + return extracted; +} +/* }}} */ + +/* {{{ 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) +{ + php_stream *fp; + php_stream_statbuf ssb; + char *pathto; + zend_string *filename; + size_t pathto_len; + int ret; + zval *zval_file; + zval *zval_files = NULL; + zend_bool overwrite = 0; + char *error = NULL; + + 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: + filename = NULL; + break; + case IS_STRING: + filename = Z_STR_P(zval_files); + break; + case IS_ARRAY: + if (zend_hash_num_elements(Z_ARRVAL_P(zval_files)) == 0) { + RETURN_FALSE; + } + + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(zval_files), zval_file) { + ZVAL_DEREF(zval_file); + if (IS_STRING != Z_TYPE_P(zval_file)) { + zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0, + "Invalid argument, array of filenames to extract contains non-string value"); + return; + } + switch (extract_helper(phar_obj->archive, Z_STR_P(zval_file), pathto, pathto_len, overwrite, &error)) { + case -1: + zend_throw_exception_ex(phar_ce_PharException, 0, "Extraction from phar \"%s\" failed: %s", + phar_obj->archive->fname, error); + efree(error); + return; + case 0: + zend_throw_exception_ex(phar_ce_PharException, 0, + "Phar Error: attempted to extract non-existent file or directory \"%s\" from phar \"%s\"", + ZSTR_VAL(Z_STR_P(zval_file)), phar_obj->archive->fname); + return; + } + } ZEND_HASH_FOREACH_END(); + RETURN_TRUE; + default: + zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0, + "Invalid argument, expected a filename (string) or array of filenames"); + return; + } + } else { + filename = NULL; + } + + ret = extract_helper(phar_obj->archive, filename, pathto, pathto_len, overwrite, &error); + if (-1 == ret) { + zend_throw_exception_ex(phar_ce_PharException, 0, "Extraction from phar \"%s\" failed: %s", + phar_obj->archive->fname, error); + efree(error); + } else if (0 == ret && NULL != filename) { + zend_throw_exception_ex(phar_ce_PharException, 0, + "Phar Error: attempted to extract non-existent file or directory \"%s\" from phar \"%s\"", + ZSTR_VAL(filename), phar_obj->archive->fname); + } else { + RETURN_TRUE; + } +} +/* }}} */ + + +/* {{{ proto PharFileInfo::__construct(string entry) + * Construct a Phar entry object + */ +PHP_METHOD(PharFileInfo, __construct) +{ + char *fname, *arch, *entry, *error; + size_t fname_len; + size_t 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, 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 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() + + +static const 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() + +static const 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() + +static const 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 +}; + +static const 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, zend_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, zend_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/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/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/session/session.c b/ext/session/session.c index 1364d16c8add1..92a50524d8aa2 100644 --- a/ext/session/session.c +++ b/ext/session/session.c @@ -3307,10 +3307,12 @@ static int php_session_rfc1867_callback(unsigned int event, void *event_data, vo if (PS(rfc1867_cleanup)) { php_session_rfc1867_cleanup(progress); } else { - SEPARATE_ARRAY(&progress->data); - add_assoc_bool_ex(&progress->data, "done", sizeof("done") - 1, 1); - Z_LVAL_P(progress->post_bytes_processed) = data->post_bytes_processed; - php_session_rfc1867_update(progress, 1); + if (!Z_ISUNDEF(progress->data)) { + SEPARATE_ARRAY(&progress->data); + add_assoc_bool_ex(&progress->data, "done", sizeof("done") - 1, 1); + Z_LVAL_P(progress->post_bytes_processed) = data->post_bytes_processed; + php_session_rfc1867_update(progress, 1); + } } php_rshutdown_session_globals(); } diff --git a/ext/session/session.c.orig b/ext/session/session.c.orig new file mode 100644 index 0000000000000..1364d16c8add1 --- /dev/null +++ b/ext/session/session.c.orig @@ -0,0 +1,3368 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2018 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 | + | Andrei Zmievski | + +----------------------------------------------------------------------+ + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" + +#ifdef PHP_WIN32 +# include "win32/winutil.h" +# include "win32/time.h" +#else +# include +#endif + +#include +#include + +#include "php_ini.h" +#include "SAPI.h" +#include "rfc1867.h" +#include "php_variables.h" +#include "php_session.h" +#include "ext/standard/php_random.h" +#include "ext/standard/php_var.h" +#include "ext/date/php_date.h" +#include "ext/standard/php_lcg.h" +#include "ext/standard/url_scanner_ex.h" +#include "ext/standard/info.h" +#include "zend_smart_str.h" +#include "ext/standard/url.h" +#include "ext/standard/basic_functions.h" +#include "ext/standard/head.h" + +#include "mod_files.h" +#include "mod_user.h" + +#ifdef HAVE_LIBMM +#include "mod_mm.h" +#endif + +PHPAPI ZEND_DECLARE_MODULE_GLOBALS(ps) + +static int php_session_rfc1867_callback(unsigned int event, void *event_data, void **extra); +static int (*php_session_rfc1867_orig_callback)(unsigned int event, void *event_data, void **extra); +static void php_session_track_init(void); + +/* SessionHandler class */ +zend_class_entry *php_session_class_entry; + +/* SessionHandlerInterface */ +zend_class_entry *php_session_iface_entry; + +/* SessionIdInterface */ +zend_class_entry *php_session_id_iface_entry; + +/* SessionUpdateTimestampHandler class */ +zend_class_entry *php_session_update_timestamp_class_entry; + +/* SessionUpdateTimestampInterface */ +zend_class_entry *php_session_update_timestamp_iface_entry; + +#define PS_MAX_SID_LENGTH 256 + +/* *********** + * Helpers * + *********** */ + +#define IF_SESSION_VARS() \ + if (Z_ISREF_P(&PS(http_session_vars)) && Z_TYPE_P(Z_REFVAL(PS(http_session_vars))) == IS_ARRAY) + +#define SESSION_CHECK_ACTIVE_STATE \ + if (PS(session_status) == php_session_active) { \ + php_error_docref(NULL, E_WARNING, "A session is active. You cannot change the session module's ini settings at this time"); \ + return FAILURE; \ + } + +#define SESSION_CHECK_OUTPUT_STATE \ + if (SG(headers_sent) && stage != ZEND_INI_STAGE_DEACTIVATE) { \ + php_error_docref(NULL, E_WARNING, "Headers already sent. You cannot change the session module's ini settings at this time"); \ + return FAILURE; \ + } + +#define APPLY_TRANS_SID (PS(use_trans_sid) && !PS(use_only_cookies)) + +static int php_session_send_cookie(void); +static int php_session_abort(void); + +/* Initialized in MINIT, readonly otherwise. */ +static int my_module_number = 0; + +/* Dispatched by RINIT and by php_session_destroy */ +static inline void php_rinit_session_globals(void) /* {{{ */ +{ + /* Do NOT init PS(mod_user_names) here! */ + /* TODO: These could be moved to MINIT and removed. These should be initialized by php_rshutdown_session_globals() always when execution is finished. */ + PS(id) = NULL; + PS(session_status) = php_session_none; + PS(in_save_handler) = 0; + PS(set_handler) = 0; + PS(mod_data) = NULL; + PS(mod_user_is_open) = 0; + PS(define_sid) = 1; + PS(session_vars) = NULL; + PS(module_number) = my_module_number; + ZVAL_UNDEF(&PS(http_session_vars)); +} +/* }}} */ + +/* Dispatched by RSHUTDOWN and by php_session_destroy */ +static inline void php_rshutdown_session_globals(void) /* {{{ */ +{ + /* Do NOT destroy PS(mod_user_names) here! */ + if (!Z_ISUNDEF(PS(http_session_vars))) { + zval_ptr_dtor(&PS(http_session_vars)); + ZVAL_UNDEF(&PS(http_session_vars)); + } + if (PS(mod_data) || PS(mod_user_implemented)) { + zend_try { + PS(mod)->s_close(&PS(mod_data)); + } zend_end_try(); + } + if (PS(id)) { + zend_string_release_ex(PS(id), 0); + PS(id) = NULL; + } + + if (PS(session_vars)) { + zend_string_release_ex(PS(session_vars), 0); + PS(session_vars) = NULL; + } + + /* User save handlers may end up directly here by misuse, bugs in user script, etc. */ + /* Set session status to prevent error while restoring save handler INI value. */ + PS(session_status) = php_session_none; +} +/* }}} */ + +PHPAPI int php_session_destroy(void) /* {{{ */ +{ + int retval = SUCCESS; + + if (PS(session_status) != php_session_active) { + php_error_docref(NULL, E_WARNING, "Trying to destroy uninitialized session"); + return FAILURE; + } + + if (PS(id) && PS(mod)->s_destroy(&PS(mod_data), PS(id)) == FAILURE) { + retval = FAILURE; + php_error_docref(NULL, E_WARNING, "Session object destruction failed"); + } + + php_rshutdown_session_globals(); + php_rinit_session_globals(); + + return retval; +} +/* }}} */ + +PHPAPI void php_add_session_var(zend_string *name) /* {{{ */ +{ + IF_SESSION_VARS() { + zval *sess_var = Z_REFVAL(PS(http_session_vars)); + SEPARATE_ARRAY(sess_var); + if (!zend_hash_exists(Z_ARRVAL_P(sess_var), name)) { + zval empty_var; + ZVAL_NULL(&empty_var); + zend_hash_update(Z_ARRVAL_P(sess_var), name, &empty_var); + } + } +} +/* }}} */ + +PHPAPI zval* php_set_session_var(zend_string *name, zval *state_val, php_unserialize_data_t *var_hash) /* {{{ */ +{ + IF_SESSION_VARS() { + zval *sess_var = Z_REFVAL(PS(http_session_vars)); + SEPARATE_ARRAY(sess_var); + return zend_hash_update(Z_ARRVAL_P(sess_var), name, state_val); + } + return NULL; +} +/* }}} */ + +PHPAPI zval* php_get_session_var(zend_string *name) /* {{{ */ +{ + IF_SESSION_VARS() { + return zend_hash_find(Z_ARRVAL_P(Z_REFVAL(PS(http_session_vars))), name); + } + return NULL; +} +/* }}} */ + +static void php_session_track_init(void) /* {{{ */ +{ + zval session_vars; + zend_string *var_name = zend_string_init("_SESSION", sizeof("_SESSION") - 1, 0); + /* Unconditionally destroy existing array -- possible dirty data */ + zend_delete_global_variable(var_name); + + if (!Z_ISUNDEF(PS(http_session_vars))) { + zval_ptr_dtor(&PS(http_session_vars)); + } + + array_init(&session_vars); + ZVAL_NEW_REF(&PS(http_session_vars), &session_vars); + Z_ADDREF_P(&PS(http_session_vars)); + zend_hash_update_ind(&EG(symbol_table), var_name, &PS(http_session_vars)); + zend_string_release_ex(var_name, 0); +} +/* }}} */ + +static zend_string *php_session_encode(void) /* {{{ */ +{ + IF_SESSION_VARS() { + if (!PS(serializer)) { + php_error_docref(NULL, E_WARNING, "Unknown session.serialize_handler. Failed to encode session object"); + return NULL; + } + return PS(serializer)->encode(); + } else { + php_error_docref(NULL, E_WARNING, "Cannot encode non-existent session"); + } + return NULL; +} +/* }}} */ + +static int php_session_decode(zend_string *data) /* {{{ */ +{ + if (!PS(serializer)) { + php_error_docref(NULL, E_WARNING, "Unknown session.serialize_handler. Failed to decode session object"); + return FAILURE; + } + if (PS(serializer)->decode(ZSTR_VAL(data), ZSTR_LEN(data)) == FAILURE) { + php_session_destroy(); + php_session_track_init(); + php_error_docref(NULL, E_WARNING, "Failed to decode session object. Session has been destroyed"); + return FAILURE; + } + return SUCCESS; +} +/* }}} */ + +/* + * Note that we cannot use the BASE64 alphabet here, because + * it contains "/" and "+": both are unacceptable for simple inclusion + * into URLs. + */ + +static char hexconvtab[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,-"; + +static void bin_to_readable(unsigned char *in, size_t inlen, char *out, size_t outlen, char nbits) /* {{{ */ +{ + unsigned char *p, *q; + unsigned short w; + int mask; + int have; + + p = (unsigned char *)in; + q = (unsigned char *)in + inlen; + + w = 0; + have = 0; + mask = (1 << nbits) - 1; + + while (outlen--) { + if (have < nbits) { + if (p < q) { + w |= *p++ << have; + have += 8; + } else { + /* Should never happen. Input must be large enough. */ + ZEND_ASSERT(0); + break; + } + } + + /* consume nbits */ + *out++ = hexconvtab[w & mask]; + w >>= nbits; + have -= nbits; + } + + *out = '\0'; +} +/* }}} */ + +#define PS_EXTRA_RAND_BYTES 60 + +PHPAPI zend_string *php_session_create_id(PS_CREATE_SID_ARGS) /* {{{ */ +{ + unsigned char rbuf[PS_MAX_SID_LENGTH + PS_EXTRA_RAND_BYTES]; + zend_string *outid; + + /* It would be enough to read ceil(sid_length * sid_bits_per_character / 8) bytes here. + * We read sid_length bytes instead for simplicity. */ + /* Read additional PS_EXTRA_RAND_BYTES just in case CSPRNG is not safe enough */ + if (php_random_bytes_throw(rbuf, PS(sid_length) + PS_EXTRA_RAND_BYTES) == FAILURE) { + return NULL; + } + + outid = zend_string_alloc(PS(sid_length), 0); + bin_to_readable( + rbuf, PS(sid_length), + ZSTR_VAL(outid), ZSTR_LEN(outid), + (char)PS(sid_bits_per_character)); + + return outid; +} +/* }}} */ + +/* Default session id char validation function allowed by ps_modules. + * If you change the logic here, please also update the error message in + * ps_modules appropriately */ +PHPAPI int php_session_valid_key(const char *key) /* {{{ */ +{ + size_t len; + const char *p; + char c; + int ret = SUCCESS; + + for (p = key; (c = *p); p++) { + /* valid characters are a..z,A..Z,0..9 */ + if (!((c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z') + || (c >= '0' && c <= '9') + || c == ',' + || c == '-')) { + ret = FAILURE; + break; + } + } + + len = p - key; + + /* Somewhat arbitrary length limit here, but should be way more than + anyone needs and avoids file-level warnings later on if we exceed MAX_PATH */ + if (len == 0 || len > PS_MAX_SID_LENGTH) { + ret = FAILURE; + } + + return ret; +} +/* }}} */ + + +static zend_long php_session_gc(zend_bool immediate) /* {{{ */ +{ + int nrand; + zend_long num = -1; + + /* GC must be done before reading session data. */ + if ((PS(mod_data) || PS(mod_user_implemented))) { + if (immediate) { + PS(mod)->s_gc(&PS(mod_data), PS(gc_maxlifetime), &num); + return num; + } + nrand = (zend_long) ((float) PS(gc_divisor) * php_combined_lcg()); + if (PS(gc_probability) > 0 && nrand < PS(gc_probability)) { + PS(mod)->s_gc(&PS(mod_data), PS(gc_maxlifetime), &num); + } + } + return num; +} /* }}} */ + +static int php_session_initialize(void) /* {{{ */ +{ + zend_string *val = NULL; + + PS(session_status) = php_session_active; + + if (!PS(mod)) { + PS(session_status) = php_session_disabled; + php_error_docref(NULL, E_WARNING, "No storage module chosen - failed to initialize session"); + return FAILURE; + } + + /* Open session handler first */ + if (PS(mod)->s_open(&PS(mod_data), PS(save_path), PS(session_name)) == FAILURE + /* || PS(mod_data) == NULL */ /* FIXME: open must set valid PS(mod_data) with success */ + ) { + php_session_abort(); + php_error_docref(NULL, E_WARNING, "Failed to initialize storage module: %s (path: %s)", PS(mod)->s_name, PS(save_path)); + return FAILURE; + } + + /* If there is no ID, use session module to create one */ + if (!PS(id) || !ZSTR_VAL(PS(id))[0]) { + if (PS(id)) { + zend_string_release_ex(PS(id), 0); + } + PS(id) = PS(mod)->s_create_sid(&PS(mod_data)); + if (!PS(id)) { + php_session_abort(); + zend_throw_error(NULL, "Failed to create session ID: %s (path: %s)", PS(mod)->s_name, PS(save_path)); + return FAILURE; + } + if (PS(use_cookies)) { + PS(send_cookie) = 1; + } + } else if (PS(use_strict_mode) && PS(mod)->s_validate_sid && + PS(mod)->s_validate_sid(&PS(mod_data), PS(id)) == FAILURE) { + if (PS(id)) { + zend_string_release_ex(PS(id), 0); + } + PS(id) = PS(mod)->s_create_sid(&PS(mod_data)); + if (!PS(id)) { + PS(id) = php_session_create_id(NULL); + } + if (PS(use_cookies)) { + PS(send_cookie) = 1; + } + } + + if (php_session_reset_id() == FAILURE) { + php_session_abort(); + return FAILURE; + } + + /* Read data */ + php_session_track_init(); + if (PS(mod)->s_read(&PS(mod_data), PS(id), &val, PS(gc_maxlifetime)) == FAILURE) { + php_session_abort(); + /* FYI: Some broken save handlers return FAILURE for non-existent session ID, this is incorrect */ + php_error_docref(NULL, E_WARNING, "Failed to read session data: %s (path: %s)", PS(mod)->s_name, PS(save_path)); + return FAILURE; + } + + /* GC must be done after read */ + php_session_gc(0); + + if (PS(session_vars)) { + zend_string_release_ex(PS(session_vars), 0); + PS(session_vars) = NULL; + } + if (val) { + if (PS(lazy_write)) { + PS(session_vars) = zend_string_copy(val); + } + php_session_decode(val); + zend_string_release_ex(val, 0); + } + return SUCCESS; +} +/* }}} */ + +static void php_session_save_current_state(int write) /* {{{ */ +{ + int ret = FAILURE; + + if (write) { + IF_SESSION_VARS() { + if (PS(mod_data) || PS(mod_user_implemented)) { + zend_string *val; + + val = php_session_encode(); + if (val) { + if (PS(lazy_write) && PS(session_vars) + && PS(mod)->s_update_timestamp + && PS(mod)->s_update_timestamp != php_session_update_timestamp + && ZSTR_LEN(val) == ZSTR_LEN(PS(session_vars)) + && !memcmp(ZSTR_VAL(val), ZSTR_VAL(PS(session_vars)), ZSTR_LEN(val)) + ) { + ret = PS(mod)->s_update_timestamp(&PS(mod_data), PS(id), val, PS(gc_maxlifetime)); + } else { + ret = PS(mod)->s_write(&PS(mod_data), PS(id), val, PS(gc_maxlifetime)); + } + zend_string_release_ex(val, 0); + } else { + ret = PS(mod)->s_write(&PS(mod_data), PS(id), ZSTR_EMPTY_ALLOC(), PS(gc_maxlifetime)); + } + } + + if ((ret == FAILURE) && !EG(exception)) { + if (!PS(mod_user_implemented)) { + php_error_docref(NULL, E_WARNING, "Failed to write session data (%s). Please " + "verify that the current setting of session.save_path " + "is correct (%s)", + PS(mod)->s_name, + PS(save_path)); + } else { + php_error_docref(NULL, E_WARNING, "Failed to write session data using user " + "defined save handler. (session.save_path: %s)", PS(save_path)); + } + } + } + } + + if (PS(mod_data) || PS(mod_user_implemented)) { + PS(mod)->s_close(&PS(mod_data)); + } +} +/* }}} */ + +static void php_session_normalize_vars() /* {{{ */ +{ + PS_ENCODE_VARS; + + IF_SESSION_VARS() { + PS_ENCODE_LOOP( + if (Z_TYPE_P(struc) == IS_PTR) { + zval *zv = (zval *)Z_PTR_P(struc); + ZVAL_COPY_VALUE(struc, zv); + ZVAL_UNDEF(zv); + } + ); + } +} +/* }}} */ + +/* ************************* + * INI Settings/Handlers * + ************************* */ + +static PHP_INI_MH(OnUpdateSaveHandler) /* {{{ */ +{ + const ps_module *tmp; + + SESSION_CHECK_ACTIVE_STATE; + SESSION_CHECK_OUTPUT_STATE; + + tmp = _php_find_ps_module(ZSTR_VAL(new_value)); + + if (PG(modules_activated) && !tmp) { + int err_type; + + if (stage == ZEND_INI_STAGE_RUNTIME) { + err_type = E_WARNING; + } else { + err_type = E_ERROR; + } + + /* Do not output error when restoring ini options. */ + if (stage != ZEND_INI_STAGE_DEACTIVATE) { + php_error_docref(NULL, err_type, "Cannot find save handler '%s'", ZSTR_VAL(new_value)); + } + + return FAILURE; + } + + /* "user" save handler should not be set by user */ + if (!PS(set_handler) && tmp == ps_user_ptr) { + php_error_docref(NULL, E_RECOVERABLE_ERROR, "Cannot set 'user' save handler by ini_set() or session_module_name()"); + return FAILURE; + } + + PS(default_mod) = PS(mod); + PS(mod) = tmp; + + return SUCCESS; +} +/* }}} */ + +static PHP_INI_MH(OnUpdateSerializer) /* {{{ */ +{ + const ps_serializer *tmp; + + SESSION_CHECK_ACTIVE_STATE; + SESSION_CHECK_OUTPUT_STATE; + + tmp = _php_find_ps_serializer(ZSTR_VAL(new_value)); + + if (PG(modules_activated) && !tmp) { + int err_type; + + if (stage == ZEND_INI_STAGE_RUNTIME) { + err_type = E_WARNING; + } else { + err_type = E_ERROR; + } + + /* Do not output error when restoring ini options. */ + if (stage != ZEND_INI_STAGE_DEACTIVATE) { + php_error_docref(NULL, err_type, "Cannot find serialization handler '%s'", ZSTR_VAL(new_value)); + } + return FAILURE; + } + PS(serializer) = tmp; + + return SUCCESS; +} +/* }}} */ + +static PHP_INI_MH(OnUpdateTransSid) /* {{{ */ +{ + SESSION_CHECK_ACTIVE_STATE; + SESSION_CHECK_OUTPUT_STATE; + + if (!strncasecmp(ZSTR_VAL(new_value), "on", sizeof("on"))) { + PS(use_trans_sid) = (zend_bool) 1; + } else { + PS(use_trans_sid) = (zend_bool) atoi(ZSTR_VAL(new_value)); + } + + return SUCCESS; +} +/* }}} */ + + +static PHP_INI_MH(OnUpdateSaveDir) /* {{{ */ +{ + SESSION_CHECK_ACTIVE_STATE; + SESSION_CHECK_OUTPUT_STATE; + + /* Only do the safemode/open_basedir check at runtime */ + if (stage == PHP_INI_STAGE_RUNTIME || stage == PHP_INI_STAGE_HTACCESS) { + char *p; + + if (memchr(ZSTR_VAL(new_value), '\0', ZSTR_LEN(new_value)) != NULL) { + return FAILURE; + } + + /* we do not use zend_memrchr() since path can contain ; itself */ + if ((p = strchr(ZSTR_VAL(new_value), ';'))) { + char *p2; + p++; + if ((p2 = strchr(p, ';'))) { + p = p2 + 1; + } + } else { + p = ZSTR_VAL(new_value); + } + + if (PG(open_basedir) && *p && php_check_open_basedir(p)) { + return FAILURE; + } + } + + return OnUpdateString(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage); +} +/* }}} */ + + +static PHP_INI_MH(OnUpdateName) /* {{{ */ +{ + SESSION_CHECK_ACTIVE_STATE; + SESSION_CHECK_OUTPUT_STATE; + + /* Numeric session.name won't work at all */ + if ((!ZSTR_LEN(new_value) || is_numeric_string(ZSTR_VAL(new_value), ZSTR_LEN(new_value), NULL, NULL, 0))) { + int err_type; + + if (stage == ZEND_INI_STAGE_RUNTIME || stage == ZEND_INI_STAGE_ACTIVATE || stage == ZEND_INI_STAGE_STARTUP) { + err_type = E_WARNING; + } else { + err_type = E_ERROR; + } + + /* Do not output error when restoring ini options. */ + if (stage != ZEND_INI_STAGE_DEACTIVATE) { + php_error_docref(NULL, err_type, "session.name cannot be a numeric or empty '%s'", ZSTR_VAL(new_value)); + } + return FAILURE; + } + + return OnUpdateStringUnempty(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage); +} +/* }}} */ + + +static PHP_INI_MH(OnUpdateCookieLifetime) /* {{{ */ +{ + SESSION_CHECK_ACTIVE_STATE; + SESSION_CHECK_OUTPUT_STATE; + if (atol(ZSTR_VAL(new_value)) < 0) { + php_error_docref(NULL, E_WARNING, "CookieLifetime cannot be negative"); + return FAILURE; + } + return OnUpdateLongGEZero(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage); +} +/* }}} */ + + +static PHP_INI_MH(OnUpdateSessionLong) /* {{{ */ +{ + SESSION_CHECK_ACTIVE_STATE; + SESSION_CHECK_OUTPUT_STATE; + return OnUpdateLong(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage); +} +/* }}} */ + + +static PHP_INI_MH(OnUpdateSessionString) /* {{{ */ +{ + SESSION_CHECK_ACTIVE_STATE; + SESSION_CHECK_OUTPUT_STATE; + return OnUpdateString(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage); +} +/* }}} */ + + +static PHP_INI_MH(OnUpdateSessionBool) /* {{{ */ +{ + SESSION_CHECK_OUTPUT_STATE; + SESSION_CHECK_ACTIVE_STATE; + return OnUpdateBool(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage); +} +/* }}} */ + + +static PHP_INI_MH(OnUpdateSidLength) /* {{{ */ +{ + zend_long val; + char *endptr = NULL; + + SESSION_CHECK_OUTPUT_STATE; + SESSION_CHECK_ACTIVE_STATE; + val = ZEND_STRTOL(ZSTR_VAL(new_value), &endptr, 10); + if (endptr && (*endptr == '\0') + && val >= 22 && val <= PS_MAX_SID_LENGTH) { + /* Numeric value */ + PS(sid_length) = val; + return SUCCESS; + } + + php_error_docref(NULL, E_WARNING, "session.configuration 'session.sid_length' must be between 22 and 256."); + return FAILURE; +} +/* }}} */ + +static PHP_INI_MH(OnUpdateSidBits) /* {{{ */ +{ + zend_long val; + char *endptr = NULL; + + SESSION_CHECK_OUTPUT_STATE; + SESSION_CHECK_ACTIVE_STATE; + val = ZEND_STRTOL(ZSTR_VAL(new_value), &endptr, 10); + if (endptr && (*endptr == '\0') + && val >= 4 && val <=6) { + /* Numeric value */ + PS(sid_bits_per_character) = val; + return SUCCESS; + } + + php_error_docref(NULL, E_WARNING, "session.configuration 'session.sid_bits_per_character' must be between 4 and 6."); + return FAILURE; +} +/* }}} */ + + +static PHP_INI_MH(OnUpdateLazyWrite) /* {{{ */ +{ + SESSION_CHECK_ACTIVE_STATE; + SESSION_CHECK_OUTPUT_STATE; + return OnUpdateBool(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage); +} +/* }}} */ + + + +static PHP_INI_MH(OnUpdateRfc1867Freq) /* {{{ */ +{ + int tmp; + tmp = zend_atoi(ZSTR_VAL(new_value), ZSTR_LEN(new_value)); + if(tmp < 0) { + php_error_docref(NULL, E_WARNING, "session.upload_progress.freq must be greater than or equal to zero"); + return FAILURE; + } + if(ZSTR_LEN(new_value) > 0 && ZSTR_VAL(new_value)[ZSTR_LEN(new_value)-1] == '%') { + if(tmp > 100) { + php_error_docref(NULL, E_WARNING, "session.upload_progress.freq cannot be over 100%%"); + return FAILURE; + } + PS(rfc1867_freq) = -tmp; + } else { + PS(rfc1867_freq) = tmp; + } + return SUCCESS; +} /* }}} */ + +/* {{{ PHP_INI + */ +PHP_INI_BEGIN() + STD_PHP_INI_ENTRY("session.save_path", "", PHP_INI_ALL, OnUpdateSaveDir, save_path, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.name", "PHPSESSID", PHP_INI_ALL, OnUpdateName, session_name, php_ps_globals, ps_globals) + PHP_INI_ENTRY("session.save_handler", "files", PHP_INI_ALL, OnUpdateSaveHandler) + STD_PHP_INI_BOOLEAN("session.auto_start", "0", PHP_INI_PERDIR, OnUpdateBool, auto_start, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.gc_probability", "1", PHP_INI_ALL, OnUpdateSessionLong, gc_probability, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.gc_divisor", "100", PHP_INI_ALL, OnUpdateSessionLong, gc_divisor, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.gc_maxlifetime", "1440", PHP_INI_ALL, OnUpdateSessionLong, gc_maxlifetime, php_ps_globals, ps_globals) + PHP_INI_ENTRY("session.serialize_handler", "php", PHP_INI_ALL, OnUpdateSerializer) + STD_PHP_INI_ENTRY("session.cookie_lifetime", "0", PHP_INI_ALL, OnUpdateCookieLifetime,cookie_lifetime, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.cookie_path", "/", PHP_INI_ALL, OnUpdateSessionString, cookie_path, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.cookie_domain", "", PHP_INI_ALL, OnUpdateSessionString, cookie_domain, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.cookie_secure", "0", PHP_INI_ALL, OnUpdateSessionBool, cookie_secure, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.cookie_httponly", "0", PHP_INI_ALL, OnUpdateSessionBool, cookie_httponly, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.cookie_samesite", "", PHP_INI_ALL, OnUpdateString, cookie_samesite, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.use_cookies", "1", PHP_INI_ALL, OnUpdateSessionBool, use_cookies, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.use_only_cookies", "1", PHP_INI_ALL, OnUpdateSessionBool, use_only_cookies, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.use_strict_mode", "0", PHP_INI_ALL, OnUpdateSessionBool, use_strict_mode, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.referer_check", "", PHP_INI_ALL, OnUpdateSessionString, extern_referer_chk, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.cache_limiter", "nocache", PHP_INI_ALL, OnUpdateSessionString, cache_limiter, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.cache_expire", "180", PHP_INI_ALL, OnUpdateSessionLong, cache_expire, php_ps_globals, ps_globals) + PHP_INI_ENTRY("session.use_trans_sid", "0", PHP_INI_ALL, OnUpdateTransSid) + PHP_INI_ENTRY("session.sid_length", "32", PHP_INI_ALL, OnUpdateSidLength) + PHP_INI_ENTRY("session.sid_bits_per_character", "4", PHP_INI_ALL, OnUpdateSidBits) + STD_PHP_INI_BOOLEAN("session.lazy_write", "1", PHP_INI_ALL, OnUpdateLazyWrite, lazy_write, php_ps_globals, ps_globals) + + /* Upload progress */ + STD_PHP_INI_BOOLEAN("session.upload_progress.enabled", + "1", ZEND_INI_PERDIR, OnUpdateBool, rfc1867_enabled, php_ps_globals, ps_globals) + STD_PHP_INI_BOOLEAN("session.upload_progress.cleanup", + "1", ZEND_INI_PERDIR, OnUpdateBool, rfc1867_cleanup, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.upload_progress.prefix", + "upload_progress_", ZEND_INI_PERDIR, OnUpdateString, rfc1867_prefix, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.upload_progress.name", + "PHP_SESSION_UPLOAD_PROGRESS", ZEND_INI_PERDIR, OnUpdateString, rfc1867_name, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.upload_progress.freq", "1%", ZEND_INI_PERDIR, OnUpdateRfc1867Freq, rfc1867_freq, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.upload_progress.min_freq", + "1", ZEND_INI_PERDIR, OnUpdateReal, rfc1867_min_freq,php_ps_globals, ps_globals) + + /* Commented out until future discussion */ + /* PHP_INI_ENTRY("session.encode_sources", "globals,track", PHP_INI_ALL, NULL) */ +PHP_INI_END() +/* }}} */ + +/* *************** + * Serializers * + *************** */ +PS_SERIALIZER_ENCODE_FUNC(php_serialize) /* {{{ */ +{ + smart_str buf = {0}; + php_serialize_data_t var_hash; + + IF_SESSION_VARS() { + PHP_VAR_SERIALIZE_INIT(var_hash); + php_var_serialize(&buf, Z_REFVAL(PS(http_session_vars)), &var_hash); + PHP_VAR_SERIALIZE_DESTROY(var_hash); + } + return buf.s; +} +/* }}} */ + +PS_SERIALIZER_DECODE_FUNC(php_serialize) /* {{{ */ +{ + const char *endptr = val + vallen; + zval session_vars; + php_unserialize_data_t var_hash; + int result; + zend_string *var_name = zend_string_init("_SESSION", sizeof("_SESSION") - 1, 0); + + ZVAL_NULL(&session_vars); + PHP_VAR_UNSERIALIZE_INIT(var_hash); + result = php_var_unserialize( + &session_vars, (const unsigned char **)&val, (const unsigned char *)endptr, &var_hash); + PHP_VAR_UNSERIALIZE_DESTROY(var_hash); + if (!result) { + zval_ptr_dtor(&session_vars); + ZVAL_NULL(&session_vars); + } + + if (!Z_ISUNDEF(PS(http_session_vars))) { + zval_ptr_dtor(&PS(http_session_vars)); + } + if (Z_TYPE(session_vars) == IS_NULL) { + array_init(&session_vars); + } + ZVAL_NEW_REF(&PS(http_session_vars), &session_vars); + Z_ADDREF_P(&PS(http_session_vars)); + zend_hash_update_ind(&EG(symbol_table), var_name, &PS(http_session_vars)); + zend_string_release_ex(var_name, 0); + return SUCCESS; +} +/* }}} */ + +#define PS_BIN_NR_OF_BITS 8 +#define PS_BIN_UNDEF (1<<(PS_BIN_NR_OF_BITS-1)) +#define PS_BIN_MAX (PS_BIN_UNDEF-1) + +PS_SERIALIZER_ENCODE_FUNC(php_binary) /* {{{ */ +{ + smart_str buf = {0}; + php_serialize_data_t var_hash; + PS_ENCODE_VARS; + + PHP_VAR_SERIALIZE_INIT(var_hash); + + PS_ENCODE_LOOP( + if (ZSTR_LEN(key) > PS_BIN_MAX) continue; + smart_str_appendc(&buf, (unsigned char)ZSTR_LEN(key)); + smart_str_appendl(&buf, ZSTR_VAL(key), ZSTR_LEN(key)); + php_var_serialize(&buf, struc, &var_hash); + ); + + smart_str_0(&buf); + PHP_VAR_SERIALIZE_DESTROY(var_hash); + + return buf.s; +} +/* }}} */ + +PS_SERIALIZER_DECODE_FUNC(php_binary) /* {{{ */ +{ + const char *p; + const char *endptr = val + vallen; + int namelen; + zend_string *name; + php_unserialize_data_t var_hash; + zval *current, rv; + + PHP_VAR_UNSERIALIZE_INIT(var_hash); + + for (p = val; p < endptr; ) { + namelen = ((unsigned char)(*p)) & (~PS_BIN_UNDEF); + + if (namelen < 0 || namelen > PS_BIN_MAX || (p + namelen) >= endptr) { + PHP_VAR_UNSERIALIZE_DESTROY(var_hash); + return FAILURE; + } + + name = zend_string_init(p + 1, namelen, 0); + p += namelen + 1; + current = var_tmp_var(&var_hash); + + if (php_var_unserialize(current, (const unsigned char **) &p, (const unsigned char *) endptr, &var_hash)) { + ZVAL_PTR(&rv, current); + php_set_session_var(name, &rv, &var_hash); + } else { + zend_string_release_ex(name, 0); + php_session_normalize_vars(); + PHP_VAR_UNSERIALIZE_DESTROY(var_hash); + return FAILURE; + } + zend_string_release_ex(name, 0); + } + + php_session_normalize_vars(); + PHP_VAR_UNSERIALIZE_DESTROY(var_hash); + + return SUCCESS; +} +/* }}} */ + +#define PS_DELIMITER '|' + +PS_SERIALIZER_ENCODE_FUNC(php) /* {{{ */ +{ + smart_str buf = {0}; + php_serialize_data_t var_hash; + PS_ENCODE_VARS; + + PHP_VAR_SERIALIZE_INIT(var_hash); + + PS_ENCODE_LOOP( + smart_str_appendl(&buf, ZSTR_VAL(key), ZSTR_LEN(key)); + if (memchr(ZSTR_VAL(key), PS_DELIMITER, ZSTR_LEN(key))) { + PHP_VAR_SERIALIZE_DESTROY(var_hash); + smart_str_free(&buf); + return NULL; + } + smart_str_appendc(&buf, PS_DELIMITER); + php_var_serialize(&buf, struc, &var_hash); + ); + + smart_str_0(&buf); + + PHP_VAR_SERIALIZE_DESTROY(var_hash); + return buf.s; +} +/* }}} */ + +PS_SERIALIZER_DECODE_FUNC(php) /* {{{ */ +{ + const char *p, *q; + const char *endptr = val + vallen; + ptrdiff_t namelen; + zend_string *name; + int retval = SUCCESS; + php_unserialize_data_t var_hash; + zval *current, rv; + + PHP_VAR_UNSERIALIZE_INIT(var_hash); + + p = val; + + while (p < endptr) { + q = p; + while (*q != PS_DELIMITER) { + if (++q >= endptr) goto break_outer_loop; + } + + namelen = q - p; + name = zend_string_init(p, namelen, 0); + q++; + + current = var_tmp_var(&var_hash); + if (php_var_unserialize(current, (const unsigned char **)&q, (const unsigned char *)endptr, &var_hash)) { + ZVAL_PTR(&rv, current); + php_set_session_var(name, &rv, &var_hash); + } else { + zend_string_release_ex(name, 0); + retval = FAILURE; + goto break_outer_loop; + } + zend_string_release_ex(name, 0); + p = q; + } + +break_outer_loop: + php_session_normalize_vars(); + + PHP_VAR_UNSERIALIZE_DESTROY(var_hash); + + return retval; +} +/* }}} */ + +#define MAX_SERIALIZERS 32 +#define PREDEFINED_SERIALIZERS 3 + +static ps_serializer ps_serializers[MAX_SERIALIZERS + 1] = { + PS_SERIALIZER_ENTRY(php_serialize), + PS_SERIALIZER_ENTRY(php), + PS_SERIALIZER_ENTRY(php_binary) +}; + +PHPAPI int php_session_register_serializer(const char *name, zend_string *(*encode)(PS_SERIALIZER_ENCODE_ARGS), int (*decode)(PS_SERIALIZER_DECODE_ARGS)) /* {{{ */ +{ + int ret = FAILURE; + int i; + + for (i = 0; i < MAX_SERIALIZERS; i++) { + if (ps_serializers[i].name == NULL) { + ps_serializers[i].name = name; + ps_serializers[i].encode = encode; + ps_serializers[i].decode = decode; + ps_serializers[i + 1].name = NULL; + ret = SUCCESS; + break; + } + } + return ret; +} +/* }}} */ + +/* ******************* + * Storage Modules * + ******************* */ + +#define MAX_MODULES 32 +#define PREDEFINED_MODULES 2 + +static const ps_module *ps_modules[MAX_MODULES + 1] = { + ps_files_ptr, + ps_user_ptr +}; + +PHPAPI int php_session_register_module(const ps_module *ptr) /* {{{ */ +{ + int ret = FAILURE; + int i; + + for (i = 0; i < MAX_MODULES; i++) { + if (!ps_modules[i]) { + ps_modules[i] = ptr; + ret = SUCCESS; + break; + } + } + return ret; +} +/* }}} */ + +/* Dummy PS module function */ +PHPAPI int php_session_validate_sid(PS_VALIDATE_SID_ARGS) { + return SUCCESS; +} + +/* Dummy PS module function */ +PHPAPI int php_session_update_timestamp(PS_UPDATE_TIMESTAMP_ARGS) { + return SUCCESS; +} + + +/* ****************** + * Cache Limiters * + ****************** */ + +typedef struct { + char *name; + void (*func)(void); +} php_session_cache_limiter_t; + +#define CACHE_LIMITER(name) _php_cache_limiter_##name +#define CACHE_LIMITER_FUNC(name) static void CACHE_LIMITER(name)(void) +#define CACHE_LIMITER_ENTRY(name) { #name, CACHE_LIMITER(name) }, +#define ADD_HEADER(a) sapi_add_header(a, strlen(a), 1); +#define MAX_STR 512 + +static char *month_names[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +static char *week_days[] = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" +}; + +static inline void strcpy_gmt(char *ubuf, time_t *when) /* {{{ */ +{ + char buf[MAX_STR]; + struct tm tm, *res; + int n; + + res = php_gmtime_r(when, &tm); + + if (!res) { + ubuf[0] = '\0'; + return; + } + + n = slprintf(buf, sizeof(buf), "%s, %02d %s %d %02d:%02d:%02d GMT", /* SAFE */ + week_days[tm.tm_wday], tm.tm_mday, + month_names[tm.tm_mon], tm.tm_year + 1900, + tm.tm_hour, tm.tm_min, + tm.tm_sec); + memcpy(ubuf, buf, n); + ubuf[n] = '\0'; +} +/* }}} */ + +static inline void last_modified(void) /* {{{ */ +{ + const char *path; + zend_stat_t sb; + char buf[MAX_STR + 1]; + + path = SG(request_info).path_translated; + if (path) { + if (VCWD_STAT(path, &sb) == -1) { + return; + } + +#define LAST_MODIFIED "Last-Modified: " + memcpy(buf, LAST_MODIFIED, sizeof(LAST_MODIFIED) - 1); + strcpy_gmt(buf + sizeof(LAST_MODIFIED) - 1, &sb.st_mtime); + ADD_HEADER(buf); + } +} +/* }}} */ + +#define EXPIRES "Expires: " +CACHE_LIMITER_FUNC(public) /* {{{ */ +{ + char buf[MAX_STR + 1]; + struct timeval tv; + time_t now; + + gettimeofday(&tv, NULL); + now = tv.tv_sec + PS(cache_expire) * 60; + memcpy(buf, EXPIRES, sizeof(EXPIRES) - 1); + strcpy_gmt(buf + sizeof(EXPIRES) - 1, &now); + ADD_HEADER(buf); + + snprintf(buf, sizeof(buf) , "Cache-Control: public, max-age=" ZEND_LONG_FMT, PS(cache_expire) * 60); /* SAFE */ + ADD_HEADER(buf); + + last_modified(); +} +/* }}} */ + +CACHE_LIMITER_FUNC(private_no_expire) /* {{{ */ +{ + char buf[MAX_STR + 1]; + + snprintf(buf, sizeof(buf), "Cache-Control: private, max-age=" ZEND_LONG_FMT, PS(cache_expire) * 60); /* SAFE */ + ADD_HEADER(buf); + + last_modified(); +} +/* }}} */ + +CACHE_LIMITER_FUNC(private) /* {{{ */ +{ + ADD_HEADER("Expires: Thu, 19 Nov 1981 08:52:00 GMT"); + CACHE_LIMITER(private_no_expire)(); +} +/* }}} */ + +CACHE_LIMITER_FUNC(nocache) /* {{{ */ +{ + ADD_HEADER("Expires: Thu, 19 Nov 1981 08:52:00 GMT"); + + /* For HTTP/1.1 conforming clients */ + ADD_HEADER("Cache-Control: no-store, no-cache, must-revalidate"); + + /* For HTTP/1.0 conforming clients */ + ADD_HEADER("Pragma: no-cache"); +} +/* }}} */ + +static const php_session_cache_limiter_t php_session_cache_limiters[] = { + CACHE_LIMITER_ENTRY(public) + CACHE_LIMITER_ENTRY(private) + CACHE_LIMITER_ENTRY(private_no_expire) + CACHE_LIMITER_ENTRY(nocache) + {0} +}; + +static int php_session_cache_limiter(void) /* {{{ */ +{ + const php_session_cache_limiter_t *lim; + + if (PS(cache_limiter)[0] == '\0') return 0; + if (PS(session_status) != php_session_active) return -1; + + if (SG(headers_sent)) { + const char *output_start_filename = php_output_get_start_filename(); + int output_start_lineno = php_output_get_start_lineno(); + + php_session_abort(); + if (output_start_filename) { + php_error_docref(NULL, E_WARNING, "Cannot send session cache limiter - headers already sent (output started at %s:%d)", output_start_filename, output_start_lineno); + } else { + php_error_docref(NULL, E_WARNING, "Cannot send session cache limiter - headers already sent"); + } + return -2; + } + + for (lim = php_session_cache_limiters; lim->name; lim++) { + if (!strcasecmp(lim->name, PS(cache_limiter))) { + lim->func(); + return 0; + } + } + + return -1; +} +/* }}} */ + +/* ********************* + * Cookie Management * + ********************* */ + +/* + * Remove already sent session ID cookie. + * It must be directly removed from SG(sapi_header) because sapi_add_header_ex() + * removes all of matching cookie. i.e. It deletes all of Set-Cookie headers. + */ +static void php_session_remove_cookie(void) { + sapi_header_struct *header; + zend_llist *l = &SG(sapi_headers).headers; + zend_llist_element *next; + zend_llist_element *current; + char *session_cookie; + zend_string *e_session_name; + size_t session_cookie_len; + size_t len = sizeof("Set-Cookie")-1; + + e_session_name = php_url_encode(PS(session_name), strlen(PS(session_name))); + spprintf(&session_cookie, 0, "Set-Cookie: %s=", ZSTR_VAL(e_session_name)); + zend_string_free(e_session_name); + + session_cookie_len = strlen(session_cookie); + current = l->head; + while (current) { + header = (sapi_header_struct *)(current->data); + next = current->next; + if (header->header_len > len && header->header[len] == ':' + && !strncmp(header->header, session_cookie, session_cookie_len)) { + if (current->prev) { + current->prev->next = next; + } else { + l->head = next; + } + if (next) { + next->prev = current->prev; + } else { + l->tail = current->prev; + } + sapi_free_header(header); + efree(current); + --l->count; + } + current = next; + } + efree(session_cookie); +} + +static int php_session_send_cookie(void) /* {{{ */ +{ + smart_str ncookie = {0}; + zend_string *date_fmt = NULL; + zend_string *e_session_name, *e_id; + + if (SG(headers_sent)) { + const char *output_start_filename = php_output_get_start_filename(); + int output_start_lineno = php_output_get_start_lineno(); + + if (output_start_filename) { + php_error_docref(NULL, E_WARNING, "Cannot send session cookie - headers already sent by (output started at %s:%d)", output_start_filename, output_start_lineno); + } else { + php_error_docref(NULL, E_WARNING, "Cannot send session cookie - headers already sent"); + } + return FAILURE; + } + + /* URL encode session_name and id because they might be user supplied */ + e_session_name = php_url_encode(PS(session_name), strlen(PS(session_name))); + e_id = php_url_encode(ZSTR_VAL(PS(id)), ZSTR_LEN(PS(id))); + + smart_str_appendl(&ncookie, "Set-Cookie: ", sizeof("Set-Cookie: ")-1); + smart_str_appendl(&ncookie, ZSTR_VAL(e_session_name), ZSTR_LEN(e_session_name)); + smart_str_appendc(&ncookie, '='); + smart_str_appendl(&ncookie, ZSTR_VAL(e_id), ZSTR_LEN(e_id)); + + zend_string_release_ex(e_session_name, 0); + zend_string_release_ex(e_id, 0); + + if (PS(cookie_lifetime) > 0) { + struct timeval tv; + time_t t; + + gettimeofday(&tv, NULL); + t = tv.tv_sec + PS(cookie_lifetime); + + if (t > 0) { + date_fmt = php_format_date("D, d-M-Y H:i:s T", sizeof("D, d-M-Y H:i:s T")-1, t, 0); + smart_str_appends(&ncookie, COOKIE_EXPIRES); + smart_str_appendl(&ncookie, ZSTR_VAL(date_fmt), ZSTR_LEN(date_fmt)); + zend_string_release_ex(date_fmt, 0); + + smart_str_appends(&ncookie, COOKIE_MAX_AGE); + smart_str_append_long(&ncookie, PS(cookie_lifetime)); + } + } + + if (PS(cookie_path)[0]) { + smart_str_appends(&ncookie, COOKIE_PATH); + smart_str_appends(&ncookie, PS(cookie_path)); + } + + if (PS(cookie_domain)[0]) { + smart_str_appends(&ncookie, COOKIE_DOMAIN); + smart_str_appends(&ncookie, PS(cookie_domain)); + } + + if (PS(cookie_secure)) { + smart_str_appends(&ncookie, COOKIE_SECURE); + } + + if (PS(cookie_httponly)) { + smart_str_appends(&ncookie, COOKIE_HTTPONLY); + } + + if (PS(cookie_samesite)[0]) { + smart_str_appends(&ncookie, COOKIE_SAMESITE); + smart_str_appends(&ncookie, PS(cookie_samesite)); + } + + smart_str_0(&ncookie); + + php_session_remove_cookie(); /* remove already sent session ID cookie */ + /* 'replace' must be 0 here, else a previous Set-Cookie + header, probably sent with setcookie() will be replaced! */ + sapi_add_header_ex(estrndup(ZSTR_VAL(ncookie.s), ZSTR_LEN(ncookie.s)), ZSTR_LEN(ncookie.s), 0, 0); + smart_str_free(&ncookie); + + return SUCCESS; +} +/* }}} */ + +PHPAPI const ps_module *_php_find_ps_module(char *name) /* {{{ */ +{ + const ps_module *ret = NULL; + const ps_module **mod; + int i; + + for (i = 0, mod = ps_modules; i < MAX_MODULES; i++, mod++) { + if (*mod && !strcasecmp(name, (*mod)->s_name)) { + ret = *mod; + break; + } + } + return ret; +} +/* }}} */ + +PHPAPI const ps_serializer *_php_find_ps_serializer(char *name) /* {{{ */ +{ + const ps_serializer *ret = NULL; + const ps_serializer *mod; + + for (mod = ps_serializers; mod->name; mod++) { + if (!strcasecmp(name, mod->name)) { + ret = mod; + break; + } + } + return ret; +} +/* }}} */ + +static void ppid2sid(zval *ppid) { + ZVAL_DEREF(ppid); + if (Z_TYPE_P(ppid) == IS_STRING) { + PS(id) = zend_string_init(Z_STRVAL_P(ppid), Z_STRLEN_P(ppid), 0); + PS(send_cookie) = 0; + } else { + PS(id) = NULL; + PS(send_cookie) = 1; + } +} + + +PHPAPI int php_session_reset_id(void) /* {{{ */ +{ + int module_number = PS(module_number); + zval *sid, *data, *ppid; + zend_bool apply_trans_sid; + + if (!PS(id)) { + php_error_docref(NULL, E_WARNING, "Cannot set session ID - session ID is not initialized"); + return FAILURE; + } + + if (PS(use_cookies) && PS(send_cookie)) { + php_session_send_cookie(); + PS(send_cookie) = 0; + } + + /* If the SID constant exists, destroy it. */ + /* We must not delete any items in EG(zend_constants) */ + /* zend_hash_str_del(EG(zend_constants), "sid", sizeof("sid") - 1); */ + sid = zend_get_constant_str("SID", sizeof("SID") - 1); + + if (PS(define_sid)) { + smart_str var = {0}; + + smart_str_appends(&var, PS(session_name)); + smart_str_appendc(&var, '='); + smart_str_appends(&var, ZSTR_VAL(PS(id))); + smart_str_0(&var); + if (sid) { + zval_ptr_dtor_str(sid); + ZVAL_NEW_STR(sid, var.s); + } else { + REGISTER_STRINGL_CONSTANT("SID", ZSTR_VAL(var.s), ZSTR_LEN(var.s), 0); + smart_str_free(&var); + } + } else { + if (sid) { + zval_ptr_dtor_str(sid); + ZVAL_EMPTY_STRING(sid); + } else { + REGISTER_STRINGL_CONSTANT("SID", "", 0, 0); + } + } + + /* Apply trans sid if sid cookie is not set */ + apply_trans_sid = 0; + if (APPLY_TRANS_SID) { + apply_trans_sid = 1; + if (PS(use_cookies) && + (data = zend_hash_str_find(&EG(symbol_table), "_COOKIE", sizeof("_COOKIE") - 1))) { + ZVAL_DEREF(data); + if (Z_TYPE_P(data) == IS_ARRAY && + (ppid = zend_hash_str_find(Z_ARRVAL_P(data), PS(session_name), strlen(PS(session_name))))) { + ZVAL_DEREF(ppid); + apply_trans_sid = 0; + } + } + } + if (apply_trans_sid) { + zend_string *sname; + sname = zend_string_init(PS(session_name), strlen(PS(session_name)), 0); + php_url_scanner_reset_session_var(sname, 1); /* This may fail when session name has changed */ + zend_string_release_ex(sname, 0); + php_url_scanner_add_session_var(PS(session_name), strlen(PS(session_name)), ZSTR_VAL(PS(id)), ZSTR_LEN(PS(id)), 1); + } + return SUCCESS; +} +/* }}} */ + + +PHPAPI int php_session_start(void) /* {{{ */ +{ + zval *ppid; + zval *data; + char *p, *value; + size_t lensess; + + switch (PS(session_status)) { + case php_session_active: + php_error(E_NOTICE, "A session had already been started - ignoring session_start()"); + return FAILURE; + break; + + case php_session_disabled: + value = zend_ini_string("session.save_handler", sizeof("session.save_handler") - 1, 0); + if (!PS(mod) && value) { + PS(mod) = _php_find_ps_module(value); + if (!PS(mod)) { + php_error_docref(NULL, E_WARNING, "Cannot find save handler '%s' - session startup failed", value); + return FAILURE; + } + } + value = zend_ini_string("session.serialize_handler", sizeof("session.serialize_handler") - 1, 0); + if (!PS(serializer) && value) { + PS(serializer) = _php_find_ps_serializer(value); + if (!PS(serializer)) { + php_error_docref(NULL, E_WARNING, "Cannot find serialization handler '%s' - session startup failed", value); + return FAILURE; + } + } + PS(session_status) = php_session_none; + /* Fall through */ + + case php_session_none: + default: + /* Setup internal flags */ + PS(define_sid) = !PS(use_only_cookies); /* SID constant is defined when non-cookie ID is used */ + PS(send_cookie) = PS(use_cookies) || PS(use_only_cookies); + } + + lensess = strlen(PS(session_name)); + + /* + * Cookies are preferred, because initially cookie and get + * variables will be available. + * URL/POST session ID may be used when use_only_cookies=Off. + * session.use_strice_mode=On prevents session adoption. + * Session based file upload progress uses non-cookie ID. + */ + + if (!PS(id)) { + if (PS(use_cookies) && (data = zend_hash_str_find(&EG(symbol_table), "_COOKIE", sizeof("_COOKIE") - 1))) { + ZVAL_DEREF(data); + if (Z_TYPE_P(data) == IS_ARRAY && (ppid = zend_hash_str_find(Z_ARRVAL_P(data), PS(session_name), lensess))) { + ppid2sid(ppid); + PS(send_cookie) = 0; + PS(define_sid) = 0; + } + } + /* Initialize session ID from non cookie values */ + if (!PS(use_only_cookies)) { + if (!PS(id) && (data = zend_hash_str_find(&EG(symbol_table), "_GET", sizeof("_GET") - 1))) { + ZVAL_DEREF(data); + if (Z_TYPE_P(data) == IS_ARRAY && (ppid = zend_hash_str_find(Z_ARRVAL_P(data), PS(session_name), lensess))) { + ppid2sid(ppid); + } + } + if (!PS(id) && (data = zend_hash_str_find(&EG(symbol_table), "_POST", sizeof("_POST") - 1))) { + ZVAL_DEREF(data); + if (Z_TYPE_P(data) == IS_ARRAY && (ppid = zend_hash_str_find(Z_ARRVAL_P(data), PS(session_name), lensess))) { + ppid2sid(ppid); + } + } + /* Check the REQUEST_URI symbol for a string of the form + * '=' to allow URLs of the form + * http://yoursite/=/script.php */ + if (!PS(id) && zend_is_auto_global_str("_SERVER", sizeof("_SERVER") - 1) == SUCCESS && + (data = zend_hash_str_find(Z_ARRVAL(PG(http_globals)[TRACK_VARS_SERVER]), "REQUEST_URI", sizeof("REQUEST_URI") - 1)) && + Z_TYPE_P(data) == IS_STRING && + (p = strstr(Z_STRVAL_P(data), PS(session_name))) && + p[lensess] == '=' + ) { + char *q; + p += lensess + 1; + if ((q = strpbrk(p, "/?\\"))) { + PS(id) = zend_string_init(p, q - p, 0); + } + } + /* Check whether the current request was referred to by + * an external site which invalidates the previously found id. */ + if (PS(id) && PS(extern_referer_chk)[0] != '\0' && + !Z_ISUNDEF(PG(http_globals)[TRACK_VARS_SERVER]) && + (data = zend_hash_str_find(Z_ARRVAL(PG(http_globals)[TRACK_VARS_SERVER]), "HTTP_REFERER", sizeof("HTTP_REFERER") - 1)) && + Z_TYPE_P(data) == IS_STRING && + Z_STRLEN_P(data) != 0 && + strstr(Z_STRVAL_P(data), PS(extern_referer_chk)) == NULL + ) { + zend_string_release_ex(PS(id), 0); + PS(id) = NULL; + } + } + } + + /* Finally check session id for dangerous characters + * Security note: session id may be embedded in HTML pages.*/ + if (PS(id) && strpbrk(ZSTR_VAL(PS(id)), "\r\n\t <>'\"\\")) { + zend_string_release_ex(PS(id), 0); + PS(id) = NULL; + } + + if (php_session_initialize() == FAILURE + || php_session_cache_limiter() == -2) { + PS(session_status) = php_session_none; + if (PS(id)) { + zend_string_release_ex(PS(id), 0); + PS(id) = NULL; + } + return FAILURE; + } + return SUCCESS; +} +/* }}} */ + +PHPAPI int php_session_flush(int write) /* {{{ */ +{ + if (PS(session_status) == php_session_active) { + php_session_save_current_state(write); + PS(session_status) = php_session_none; + return SUCCESS; + } + return FAILURE; +} +/* }}} */ + +static int php_session_abort(void) /* {{{ */ +{ + if (PS(session_status) == php_session_active) { + if (PS(mod_data) || PS(mod_user_implemented)) { + PS(mod)->s_close(&PS(mod_data)); + } + PS(session_status) = php_session_none; + return SUCCESS; + } + return FAILURE; +} +/* }}} */ + +static int php_session_reset(void) /* {{{ */ +{ + if (PS(session_status) == php_session_active + && php_session_initialize() == SUCCESS) { + return SUCCESS; + } + return FAILURE; +} +/* }}} */ + + +/* This API is not used by any PHP modules including session currently. + session_adapt_url() may be used to set Session ID to target url without + starting "URL-Rewriter" output handler. */ +PHPAPI void session_adapt_url(const char *url, size_t urllen, char **new, size_t *newlen) /* {{{ */ +{ + if (APPLY_TRANS_SID && (PS(session_status) == php_session_active)) { + *new = php_url_scanner_adapt_single_url(url, urllen, PS(session_name), ZSTR_VAL(PS(id)), newlen, 1); + } +} +/* }}} */ + +/* ******************************** + * Userspace exported functions * + ******************************** */ + +/* {{{ proto bool session_set_cookie_params(int lifetime [, string path [, string domain [, bool secure[, bool httponly]]]]) + session_set_cookie_params(array options) + Set session cookie parameters */ +static PHP_FUNCTION(session_set_cookie_params) +{ + zval *lifetime_or_options = NULL; + zend_string *lifetime = NULL, *path = NULL, *domain = NULL, *samesite = NULL; + zend_bool secure = 0, secure_null = 1; + zend_bool httponly = 0, httponly_null = 1; + zend_string *ini_name; + int result; + int found = 0; + + if (!PS(use_cookies)) { + return; + } + + ZEND_PARSE_PARAMETERS_START(1, 5) + Z_PARAM_ZVAL(lifetime_or_options) + Z_PARAM_OPTIONAL + Z_PARAM_STR(path) + Z_PARAM_STR(domain) + Z_PARAM_BOOL_EX(secure, secure_null, 1, 0) + Z_PARAM_BOOL_EX(httponly, httponly_null, 1, 0) + ZEND_PARSE_PARAMETERS_END(); + + if (PS(session_status) == php_session_active) { + php_error_docref(NULL, E_WARNING, "Cannot change session cookie parameters when session is active"); + RETURN_FALSE; + } + + if (SG(headers_sent)) { + php_error_docref(NULL, E_WARNING, "Cannot change session cookie parameters when headers already sent"); + RETURN_FALSE; + } + + if (Z_TYPE_P(lifetime_or_options) == IS_ARRAY) { + zend_string *key; + zval *value; + + if (path) { + path = NULL; + domain = NULL; + secure_null = 1; + httponly_null = 1; + php_error_docref(NULL, E_WARNING, "Cannot pass arguments after the options array"); + RETURN_FALSE; + } + + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(lifetime_or_options), key, value) { + if (key) { + ZVAL_DEREF(value); + if(!strcasecmp("lifetime", ZSTR_VAL(key))) { + lifetime = zval_get_string(value); + found++; + } else if(!strcasecmp("path", ZSTR_VAL(key))) { + path = zval_get_string(value); + found++; + } else if(!strcasecmp("domain", ZSTR_VAL(key))) { + domain = zval_get_string(value); + found++; + } else if(!strcasecmp("secure", ZSTR_VAL(key))) { + secure = zval_is_true(value); + secure_null = 0; + found++; + } else if(!strcasecmp("httponly", ZSTR_VAL(key))) { + httponly = zval_is_true(value); + httponly_null = 0; + found++; + } else if(!strcasecmp("samesite", ZSTR_VAL(key))) { + samesite = zval_get_string(value); + found++; + } else { + php_error_docref(NULL, E_WARNING, "Unrecognized key '%s' found in the options array", ZSTR_VAL(key)); + } + } else { + php_error_docref(NULL, E_WARNING, "Numeric key found in the options array"); + } + } ZEND_HASH_FOREACH_END(); + + if (found == 0) { + php_error_docref(NULL, E_WARNING, "No valid keys were found in the options array"); + RETURN_FALSE; + } + } else { + lifetime = zval_get_string(lifetime_or_options); + } + + if (lifetime) { + ini_name = zend_string_init("session.cookie_lifetime", sizeof("session.cookie_lifetime") - 1, 0); + result = zend_alter_ini_entry(ini_name, lifetime, PHP_INI_USER, PHP_INI_STAGE_RUNTIME); + zend_string_release(lifetime); + zend_string_release_ex(ini_name, 0); + if (result == FAILURE) { + RETURN_FALSE; + } + } + if (path) { + ini_name = zend_string_init("session.cookie_path", sizeof("session.cookie_path") - 1, 0); + result = zend_alter_ini_entry(ini_name, path, PHP_INI_USER, PHP_INI_STAGE_RUNTIME); + if (found > 0) { + zend_string_release(path); + } + zend_string_release_ex(ini_name, 0); + if (result == FAILURE) { + RETURN_FALSE; + } + } + if (domain) { + ini_name = zend_string_init("session.cookie_domain", sizeof("session.cookie_domain") - 1, 0); + result = zend_alter_ini_entry(ini_name, domain, PHP_INI_USER, PHP_INI_STAGE_RUNTIME); + if (found > 0) { + zend_string_release(domain); + } + zend_string_release_ex(ini_name, 0); + if (result == FAILURE) { + RETURN_FALSE; + } + } + if (!secure_null) { + ini_name = zend_string_init("session.cookie_secure", sizeof("session.cookie_secure") - 1, 0); + result = zend_alter_ini_entry_chars(ini_name, secure ? "1" : "0", 1, PHP_INI_USER, PHP_INI_STAGE_RUNTIME); + zend_string_release_ex(ini_name, 0); + if (result == FAILURE) { + RETURN_FALSE; + } + } + if (!httponly_null) { + ini_name = zend_string_init("session.cookie_httponly", sizeof("session.cookie_httponly") - 1, 0); + result = zend_alter_ini_entry_chars(ini_name, httponly ? "1" : "0", 1, PHP_INI_USER, PHP_INI_STAGE_RUNTIME); + zend_string_release_ex(ini_name, 0); + if (result == FAILURE) { + RETURN_FALSE; + } + } + if (samesite) { + ini_name = zend_string_init("session.cookie_samesite", sizeof("session.cookie_samesite") - 1, 0); + result = zend_alter_ini_entry(ini_name, samesite, PHP_INI_USER, PHP_INI_STAGE_RUNTIME); + if (found > 0) { + zend_string_release(samesite); + } + zend_string_release_ex(ini_name, 0); + if (result == FAILURE) { + RETURN_FALSE; + } + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto array session_get_cookie_params(void) + Return the session cookie parameters */ +static PHP_FUNCTION(session_get_cookie_params) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + array_init(return_value); + + add_assoc_long(return_value, "lifetime", PS(cookie_lifetime)); + add_assoc_string(return_value, "path", PS(cookie_path)); + add_assoc_string(return_value, "domain", PS(cookie_domain)); + add_assoc_bool(return_value, "secure", PS(cookie_secure)); + add_assoc_bool(return_value, "httponly", PS(cookie_httponly)); + add_assoc_string(return_value, "samesite", PS(cookie_samesite)); +} +/* }}} */ + +/* {{{ proto string session_name([string newname]) + Return the current session name. If newname is given, the session name is replaced with newname */ +static PHP_FUNCTION(session_name) +{ + zend_string *name = NULL; + zend_string *ini_name; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|S", &name) == FAILURE) { + return; + } + + if (name && PS(session_status) == php_session_active) { + php_error_docref(NULL, E_WARNING, "Cannot change session name when session is active"); + RETURN_FALSE; + } + + if (name && SG(headers_sent)) { + php_error_docref(NULL, E_WARNING, "Cannot change session name when headers already sent"); + RETURN_FALSE; + } + + RETVAL_STRING(PS(session_name)); + + if (name) { + ini_name = zend_string_init("session.name", sizeof("session.name") - 1, 0); + zend_alter_ini_entry(ini_name, name, PHP_INI_USER, PHP_INI_STAGE_RUNTIME); + zend_string_release_ex(ini_name, 0); + } +} +/* }}} */ + +/* {{{ proto string session_module_name([string newname]) + Return the current module name used for accessing session data. If newname is given, the module name is replaced with newname */ +static PHP_FUNCTION(session_module_name) +{ + zend_string *name = NULL; + zend_string *ini_name; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|S", &name) == FAILURE) { + return; + } + + if (name && PS(session_status) == php_session_active) { + php_error_docref(NULL, E_WARNING, "Cannot change save handler module when session is active"); + RETURN_FALSE; + } + + if (name && SG(headers_sent)) { + php_error_docref(NULL, E_WARNING, "Cannot change save handler module when headers already sent"); + RETURN_FALSE; + } + + /* Set return_value to current module name */ + if (PS(mod) && PS(mod)->s_name) { + RETVAL_STRING(PS(mod)->s_name); + } else { + RETVAL_EMPTY_STRING(); + } + + if (name) { + if (!_php_find_ps_module(ZSTR_VAL(name))) { + php_error_docref(NULL, E_WARNING, "Cannot find named PHP session module (%s)", ZSTR_VAL(name)); + + zval_ptr_dtor_str(return_value); + RETURN_FALSE; + } + if (PS(mod_data) || PS(mod_user_implemented)) { + PS(mod)->s_close(&PS(mod_data)); + } + PS(mod_data) = NULL; + + ini_name = zend_string_init("session.save_handler", sizeof("session.save_handler") - 1, 0); + zend_alter_ini_entry(ini_name, name, PHP_INI_USER, PHP_INI_STAGE_RUNTIME); + zend_string_release_ex(ini_name, 0); + } +} +/* }}} */ + +/* {{{ proto bool session_set_save_handler(string open, string close, string read, string write, string destroy, string gc, string create_sid) + Sets user-level functions */ +static PHP_FUNCTION(session_set_save_handler) +{ + zval *args = NULL; + int i, num_args, argc = ZEND_NUM_ARGS(); + zend_string *ini_name, *ini_val; + + if (PS(session_status) == php_session_active) { + php_error_docref(NULL, E_WARNING, "Cannot change save handler when session is active"); + RETURN_FALSE; + } + + if (SG(headers_sent)) { + php_error_docref(NULL, E_WARNING, "Cannot change save handler when headers already sent"); + RETURN_FALSE; + } + + if (argc > 0 && argc <= 2) { + zval *obj = NULL; + zend_string *func_name; + zend_function *current_mptr; + zend_bool register_shutdown = 1; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "O|b", &obj, php_session_iface_entry, ®ister_shutdown) == FAILURE) { + RETURN_FALSE; + } + + /* For compatibility reason, implemeted interface is not checked */ + /* Find implemented methods - SessionHandlerInterface */ + i = 0; + ZEND_HASH_FOREACH_STR_KEY(&php_session_iface_entry->function_table, func_name) { + if ((current_mptr = zend_hash_find_ptr(&Z_OBJCE_P(obj)->function_table, func_name))) { + if (!Z_ISUNDEF(PS(mod_user_names).names[i])) { + zval_ptr_dtor(&PS(mod_user_names).names[i]); + } + + array_init_size(&PS(mod_user_names).names[i], 2); + Z_ADDREF_P(obj); + add_next_index_zval(&PS(mod_user_names).names[i], obj); + add_next_index_str(&PS(mod_user_names).names[i], zend_string_copy(func_name)); + } else { + php_error_docref(NULL, E_ERROR, "Session handler's function table is corrupt"); + RETURN_FALSE; + } + + ++i; + } ZEND_HASH_FOREACH_END(); + + /* Find implemented methods - SessionIdInterface (optional) */ + ZEND_HASH_FOREACH_STR_KEY(&php_session_id_iface_entry->function_table, func_name) { + if ((current_mptr = zend_hash_find_ptr(&Z_OBJCE_P(obj)->function_table, func_name))) { + if (!Z_ISUNDEF(PS(mod_user_names).names[i])) { + zval_ptr_dtor(&PS(mod_user_names).names[i]); + } + array_init_size(&PS(mod_user_names).names[i], 2); + Z_ADDREF_P(obj); + add_next_index_zval(&PS(mod_user_names).names[i], obj); + add_next_index_str(&PS(mod_user_names).names[i], zend_string_copy(func_name)); + } else { + if (!Z_ISUNDEF(PS(mod_user_names).names[i])) { + zval_ptr_dtor(&PS(mod_user_names).names[i]); + ZVAL_UNDEF(&PS(mod_user_names).names[i]); + } + } + + ++i; + } ZEND_HASH_FOREACH_END(); + + /* Find implemented methods - SessionUpdateTimestampInterface (optional) */ + ZEND_HASH_FOREACH_STR_KEY(&php_session_update_timestamp_iface_entry->function_table, func_name) { + if ((current_mptr = zend_hash_find_ptr(&Z_OBJCE_P(obj)->function_table, func_name))) { + if (!Z_ISUNDEF(PS(mod_user_names).names[i])) { + zval_ptr_dtor(&PS(mod_user_names).names[i]); + } + array_init_size(&PS(mod_user_names).names[i], 2); + Z_ADDREF_P(obj); + add_next_index_zval(&PS(mod_user_names).names[i], obj); + add_next_index_str(&PS(mod_user_names).names[i], zend_string_copy(func_name)); + } else { + if (!Z_ISUNDEF(PS(mod_user_names).names[i])) { + zval_ptr_dtor(&PS(mod_user_names).names[i]); + ZVAL_UNDEF(&PS(mod_user_names).names[i]); + } + } + ++i; + } ZEND_HASH_FOREACH_END(); + + if (register_shutdown) { + /* create shutdown function */ + php_shutdown_function_entry shutdown_function_entry; + shutdown_function_entry.arg_count = 1; + shutdown_function_entry.arguments = (zval *) safe_emalloc(sizeof(zval), 1, 0); + + ZVAL_STRING(&shutdown_function_entry.arguments[0], "session_register_shutdown"); + + /* add shutdown function, removing the old one if it exists */ + if (!register_user_shutdown_function("session_shutdown", sizeof("session_shutdown") - 1, &shutdown_function_entry)) { + zval_ptr_dtor(&shutdown_function_entry.arguments[0]); + efree(shutdown_function_entry.arguments); + php_error_docref(NULL, E_WARNING, "Unable to register session shutdown function"); + RETURN_FALSE; + } + } else { + /* remove shutdown function */ + remove_user_shutdown_function("session_shutdown", sizeof("session_shutdown") - 1); + } + + if (PS(mod) && PS(session_status) != php_session_active && PS(mod) != &ps_mod_user) { + ini_name = zend_string_init("session.save_handler", sizeof("session.save_handler") - 1, 0); + ini_val = zend_string_init("user", sizeof("user") - 1, 0); + PS(set_handler) = 1; + zend_alter_ini_entry(ini_name, ini_val, PHP_INI_USER, PHP_INI_STAGE_RUNTIME); + PS(set_handler) = 0; + zend_string_release_ex(ini_val, 0); + zend_string_release_ex(ini_name, 0); + } + + RETURN_TRUE; + } + + /* Set procedural save handler functions */ + if (argc < 6 || PS_NUM_APIS < argc) { + WRONG_PARAM_COUNT; + } + + if (zend_parse_parameters(argc, "+", &args, &num_args) == FAILURE) { + return; + } + + /* remove shutdown function */ + remove_user_shutdown_function("session_shutdown", sizeof("session_shutdown") - 1); + + /* At this point argc can only be between 6 and PS_NUM_APIS */ + for (i = 0; i < argc; i++) { + if (!zend_is_callable(&args[i], 0, NULL)) { + zend_string *name = zend_get_callable_name(&args[i]); + php_error_docref(NULL, E_WARNING, "Argument %d is not a valid callback", i+1); + zend_string_release_ex(name, 0); + RETURN_FALSE; + } + } + + if (PS(mod) && PS(mod) != &ps_mod_user) { + ini_name = zend_string_init("session.save_handler", sizeof("session.save_handler") - 1, 0); + ini_val = zend_string_init("user", sizeof("user") - 1, 0); + PS(set_handler) = 1; + zend_alter_ini_entry(ini_name, ini_val, PHP_INI_USER, PHP_INI_STAGE_RUNTIME); + PS(set_handler) = 0; + zend_string_release_ex(ini_val, 0); + zend_string_release_ex(ini_name, 0); + } + + for (i = 0; i < argc; i++) { + if (!Z_ISUNDEF(PS(mod_user_names).names[i])) { + zval_ptr_dtor(&PS(mod_user_names).names[i]); + } + ZVAL_COPY(&PS(mod_user_names).names[i], &args[i]); + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto string session_save_path([string newname]) + Return the current save path passed to module_name. If newname is given, the save path is replaced with newname */ +static PHP_FUNCTION(session_save_path) +{ + zend_string *name = NULL; + zend_string *ini_name; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|S", &name) == FAILURE) { + return; + } + + if (name && PS(session_status) == php_session_active) { + php_error_docref(NULL, E_WARNING, "Cannot change save path when session is active"); + RETURN_FALSE; + } + + if (name && SG(headers_sent)) { + php_error_docref(NULL, E_WARNING, "Cannot change save path when headers already sent"); + RETURN_FALSE; + } + + RETVAL_STRING(PS(save_path)); + + if (name) { + if (memchr(ZSTR_VAL(name), '\0', ZSTR_LEN(name)) != NULL) { + php_error_docref(NULL, E_WARNING, "The save_path cannot contain NULL characters"); + zval_ptr_dtor_str(return_value); + RETURN_FALSE; + } + ini_name = zend_string_init("session.save_path", sizeof("session.save_path") - 1, 0); + zend_alter_ini_entry(ini_name, name, PHP_INI_USER, PHP_INI_STAGE_RUNTIME); + zend_string_release_ex(ini_name, 0); + } +} +/* }}} */ + +/* {{{ proto string session_id([string newid]) + Return the current session id. If newid is given, the session id is replaced with newid */ +static PHP_FUNCTION(session_id) +{ + zend_string *name = NULL; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "|S", &name) == FAILURE) { + return; + } + + if (name && PS(use_cookies) && SG(headers_sent)) { + php_error_docref(NULL, E_WARNING, "Cannot change session id when headers already sent"); + RETURN_FALSE; + } + + if (name && PS(session_status) == php_session_active) { + php_error_docref(NULL, E_WARNING, "Cannot change session id when session is active"); + RETURN_FALSE; + } + + if (PS(id)) { + /* keep compatibility for "\0" characters ??? + * see: ext/session/tests/session_id_error3.phpt */ + size_t len = strlen(ZSTR_VAL(PS(id))); + if (UNEXPECTED(len != ZSTR_LEN(PS(id)))) { + RETVAL_NEW_STR(zend_string_init(ZSTR_VAL(PS(id)), len, 0)); + } else { + RETVAL_STR_COPY(PS(id)); + } + } else { + RETVAL_EMPTY_STRING(); + } + + if (name) { + if (PS(id)) { + zend_string_release_ex(PS(id), 0); + } + PS(id) = zend_string_copy(name); + } +} +/* }}} */ + +/* {{{ proto bool session_regenerate_id([bool delete_old_session]) + Update the current session id with a newly generated one. If delete_old_session is set to true, remove the old session. */ +static PHP_FUNCTION(session_regenerate_id) +{ + zend_bool del_ses = 0; + zend_string *data; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &del_ses) == FAILURE) { + return; + } + + if (PS(session_status) != php_session_active) { + php_error_docref(NULL, E_WARNING, "Cannot regenerate session id - session is not active"); + RETURN_FALSE; + } + + if (SG(headers_sent)) { + php_error_docref(NULL, E_WARNING, "Cannot regenerate session id - headers already sent"); + RETURN_FALSE; + } + + /* Process old session data */ + if (del_ses) { + if (PS(mod)->s_destroy(&PS(mod_data), PS(id)) == FAILURE) { + PS(mod)->s_close(&PS(mod_data)); + PS(session_status) = php_session_none; + php_error_docref(NULL, E_WARNING, "Session object destruction failed. ID: %s (path: %s)", PS(mod)->s_name, PS(save_path)); + RETURN_FALSE; + } + } else { + int ret; + data = php_session_encode(); + if (data) { + ret = PS(mod)->s_write(&PS(mod_data), PS(id), data, PS(gc_maxlifetime)); + zend_string_release_ex(data, 0); + } else { + ret = PS(mod)->s_write(&PS(mod_data), PS(id), ZSTR_EMPTY_ALLOC(), PS(gc_maxlifetime)); + } + if (ret == FAILURE) { + PS(mod)->s_close(&PS(mod_data)); + PS(session_status) = php_session_none; + php_error_docref(NULL, E_WARNING, "Session write failed. ID: %s (path: %s)", PS(mod)->s_name, PS(save_path)); + RETURN_FALSE; + } + } + PS(mod)->s_close(&PS(mod_data)); + + /* New session data */ + if (PS(session_vars)) { + zend_string_release_ex(PS(session_vars), 0); + PS(session_vars) = NULL; + } + zend_string_release_ex(PS(id), 0); + PS(id) = NULL; + + if (PS(mod)->s_open(&PS(mod_data), PS(save_path), PS(session_name)) == FAILURE) { + PS(session_status) = php_session_none; + zend_throw_error(NULL, "Failed to open session: %s (path: %s)", PS(mod)->s_name, PS(save_path)); + RETURN_FALSE; + } + + PS(id) = PS(mod)->s_create_sid(&PS(mod_data)); + if (!PS(id)) { + PS(session_status) = php_session_none; + zend_throw_error(NULL, "Failed to create new session ID: %s (path: %s)", PS(mod)->s_name, PS(save_path)); + RETURN_FALSE; + } + if (PS(use_strict_mode) && PS(mod)->s_validate_sid && + PS(mod)->s_validate_sid(&PS(mod_data), PS(id)) == FAILURE) { + zend_string_release_ex(PS(id), 0); + PS(id) = PS(mod)->s_create_sid(&PS(mod_data)); + if (!PS(id)) { + PS(mod)->s_close(&PS(mod_data)); + PS(session_status) = php_session_none; + zend_throw_error(NULL, "Failed to create session ID by collision: %s (path: %s)", PS(mod)->s_name, PS(save_path)); + RETURN_FALSE; + } + } + /* Read is required to make new session data at this point. */ + if (PS(mod)->s_read(&PS(mod_data), PS(id), &data, PS(gc_maxlifetime)) == FAILURE) { + PS(mod)->s_close(&PS(mod_data)); + PS(session_status) = php_session_none; + zend_throw_error(NULL, "Failed to create(read) session ID: %s (path: %s)", PS(mod)->s_name, PS(save_path)); + RETURN_FALSE; + } + if (data) { + zend_string_release_ex(data, 0); + } + + if (PS(use_cookies)) { + PS(send_cookie) = 1; + } + if (php_session_reset_id() == FAILURE) { + RETURN_FALSE; + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto string session_create_id([string prefix]) + Generate new session ID. Intended for user save handlers. */ +/* This is not used yet */ +static PHP_FUNCTION(session_create_id) +{ + zend_string *prefix = NULL, *new_id; + smart_str id = {0}; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|S", &prefix) == FAILURE) { + return; + } + + if (prefix && ZSTR_LEN(prefix)) { + if (php_session_valid_key(ZSTR_VAL(prefix)) == FAILURE) { + /* E_ERROR raised for security reason. */ + php_error_docref(NULL, E_WARNING, "Prefix cannot contain special characters. Only aphanumeric, ',', '-' are allowed"); + RETURN_FALSE; + } else { + smart_str_append(&id, prefix); + } + } + + if (!PS(in_save_handler) && PS(session_status) == php_session_active) { + int limit = 3; + while (limit--) { + new_id = PS(mod)->s_create_sid(&PS(mod_data)); + if (!PS(mod)->s_validate_sid) { + break; + } else { + /* Detect collision and retry */ + if (PS(mod)->s_validate_sid(&PS(mod_data), new_id) == FAILURE) { + zend_string_release_ex(new_id, 0); + continue; + } + break; + } + } + } else { + new_id = php_session_create_id(NULL); + } + + if (new_id) { + smart_str_append(&id, new_id); + zend_string_release_ex(new_id, 0); + } else { + smart_str_free(&id); + php_error_docref(NULL, E_WARNING, "Failed to create new ID"); + RETURN_FALSE; + } + smart_str_0(&id); + RETVAL_NEW_STR(id.s); +} +/* }}} */ + +/* {{{ proto string session_cache_limiter([string new_cache_limiter]) + Return the current cache limiter. If new_cache_limited is given, the current cache_limiter is replaced with new_cache_limiter */ +static PHP_FUNCTION(session_cache_limiter) +{ + zend_string *limiter = NULL; + zend_string *ini_name; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|S", &limiter) == FAILURE) { + return; + } + + if (limiter && PS(session_status) == php_session_active) { + php_error_docref(NULL, E_WARNING, "Cannot change cache limiter when session is active"); + RETURN_FALSE; + } + + if (limiter && SG(headers_sent)) { + php_error_docref(NULL, E_WARNING, "Cannot change cache limiter when headers already sent"); + RETURN_FALSE; + } + + RETVAL_STRING(PS(cache_limiter)); + + if (limiter) { + ini_name = zend_string_init("session.cache_limiter", sizeof("session.cache_limiter") - 1, 0); + zend_alter_ini_entry(ini_name, limiter, PHP_INI_USER, PHP_INI_STAGE_RUNTIME); + zend_string_release_ex(ini_name, 0); + } +} +/* }}} */ + +/* {{{ proto int session_cache_expire([int new_cache_expire]) + Return the current cache expire. If new_cache_expire is given, the current cache_expire is replaced with new_cache_expire */ +static PHP_FUNCTION(session_cache_expire) +{ + zval *expires = NULL; + zend_string *ini_name; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|z", &expires) == FAILURE) { + return; + } + + if (expires && PS(session_status) == php_session_active) { + php_error_docref(NULL, E_WARNING, "Cannot change cache expire when session is active"); + RETURN_LONG(PS(cache_expire)); + } + + if (expires && SG(headers_sent)) { + php_error_docref(NULL, E_WARNING, "Cannot change cache expire when headers already sent"); + RETURN_FALSE; + } + + RETVAL_LONG(PS(cache_expire)); + + if (expires) { + convert_to_string_ex(expires); + ini_name = zend_string_init("session.cache_expire", sizeof("session.cache_expire") - 1, 0); + zend_alter_ini_entry(ini_name, Z_STR_P(expires), ZEND_INI_USER, ZEND_INI_STAGE_RUNTIME); + zend_string_release_ex(ini_name, 0); + } +} +/* }}} */ + +/* {{{ proto string session_encode(void) + Serializes the current setup and returns the serialized representation */ +static PHP_FUNCTION(session_encode) +{ + zend_string *enc; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + enc = php_session_encode(); + if (enc == NULL) { + RETURN_FALSE; + } + + RETURN_STR(enc); +} +/* }}} */ + +/* {{{ proto bool session_decode(string data) + Deserializes data and reinitializes the variables */ +static PHP_FUNCTION(session_decode) +{ + zend_string *str = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &str) == FAILURE) { + return; + } + + if (PS(session_status) != php_session_active) { + php_error_docref(NULL, E_WARNING, "Session is not active. You cannot decode session data"); + RETURN_FALSE; + } + + if (php_session_decode(str) == FAILURE) { + RETURN_FALSE; + } + RETURN_TRUE; +} +/* }}} */ + +static int php_session_start_set_ini(zend_string *varname, zend_string *new_value) { + int ret; + smart_str buf ={0}; + smart_str_appends(&buf, "session"); + smart_str_appendc(&buf, '.'); + smart_str_append(&buf, varname); + smart_str_0(&buf); + ret = zend_alter_ini_entry_ex(buf.s, new_value, PHP_INI_USER, PHP_INI_STAGE_RUNTIME, 0); + smart_str_free(&buf); + return ret; +} + +/* {{{ proto bool session_start([array options]) ++ Begin session */ +static PHP_FUNCTION(session_start) +{ + zval *options = NULL; + zval *value; + zend_ulong num_idx; + zend_string *str_idx; + zend_long read_and_close = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|a", &options) == FAILURE) { + RETURN_FALSE; + } + + if (PS(session_status) == php_session_active) { + php_error_docref(NULL, E_NOTICE, "A session had already been started - ignoring"); + RETURN_TRUE; + } + + /* + * TODO: To prevent unusable session with trans sid, actual output started status is + * required. i.e. There shouldn't be any outputs in output buffer, otherwise session + * module is unable to rewrite output. + */ + if (PS(use_cookies) && SG(headers_sent)) { + php_error_docref(NULL, E_WARNING, "Cannot start session when headers already sent"); + RETURN_FALSE; + } + + /* set options */ + if (options) { + ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(options), num_idx, str_idx, value) { + if (str_idx) { + switch(Z_TYPE_P(value)) { + case IS_STRING: + case IS_TRUE: + case IS_FALSE: + case IS_LONG: + if (zend_string_equals_literal(str_idx, "read_and_close")) { + read_and_close = zval_get_long(value); + } else { + zend_string *tmp_val; + zend_string *val = zval_get_tmp_string(value, &tmp_val); + if (php_session_start_set_ini(str_idx, val) == FAILURE) { + php_error_docref(NULL, E_WARNING, "Setting option '%s' failed", ZSTR_VAL(str_idx)); + } + zend_tmp_string_release(tmp_val); + } + break; + default: + php_error_docref(NULL, E_WARNING, "Option(%s) value must be string, boolean or long", ZSTR_VAL(str_idx)); + break; + } + } + (void) num_idx; + } ZEND_HASH_FOREACH_END(); + } + + php_session_start(); + + if (PS(session_status) != php_session_active) { + IF_SESSION_VARS() { + zval *sess_var = Z_REFVAL(PS(http_session_vars)); + SEPARATE_ARRAY(sess_var); + /* Clean $_SESSION. */ + zend_hash_clean(Z_ARRVAL_P(sess_var)); + } + RETURN_FALSE; + } + + if (read_and_close) { + php_session_flush(0); + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool session_destroy(void) + Destroy the current session and all data associated with it */ +static PHP_FUNCTION(session_destroy) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + RETURN_BOOL(php_session_destroy() == SUCCESS); +} +/* }}} */ + +/* {{{ proto bool session_unset(void) + Unset all registered variables */ +static PHP_FUNCTION(session_unset) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (PS(session_status) != php_session_active) { + RETURN_FALSE; + } + + IF_SESSION_VARS() { + zval *sess_var = Z_REFVAL(PS(http_session_vars)); + SEPARATE_ARRAY(sess_var); + + /* Clean $_SESSION. */ + zend_hash_clean(Z_ARRVAL_P(sess_var)); + } + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto int session_gc(void) + Perform GC and return number of deleted sessions */ +static PHP_FUNCTION(session_gc) +{ + zend_long num; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (PS(session_status) != php_session_active) { + php_error_docref(NULL, E_WARNING, "Session is not active"); + RETURN_FALSE; + } + + num = php_session_gc(1); + if (num < 0) { + RETURN_FALSE; + } + + RETURN_LONG(num); +} +/* }}} */ + + +/* {{{ proto bool session_write_close(void) + Write session data and end session */ +static PHP_FUNCTION(session_write_close) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (PS(session_status) != php_session_active) { + RETURN_FALSE; + } + php_session_flush(1); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool session_abort(void) + Abort session and end session. Session data will not be written */ +static PHP_FUNCTION(session_abort) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (PS(session_status) != php_session_active) { + RETURN_FALSE; + } + php_session_abort(); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool session_reset(void) + Reset session data from saved session data */ +static PHP_FUNCTION(session_reset) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (PS(session_status) != php_session_active) { + RETURN_FALSE; + } + php_session_reset(); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto int session_status(void) + Returns the current session status */ +static PHP_FUNCTION(session_status) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + RETURN_LONG(PS(session_status)); +} +/* }}} */ + +/* {{{ proto void session_register_shutdown(void) + Registers session_write_close() as a shutdown function */ +static PHP_FUNCTION(session_register_shutdown) +{ + php_shutdown_function_entry shutdown_function_entry; + + /* This function is registered itself as a shutdown function by + * session_set_save_handler($obj). The reason we now register another + * shutdown function is in case the user registered their own shutdown + * function after calling session_set_save_handler(), which expects + * the session still to be available. + */ + + shutdown_function_entry.arg_count = 1; + shutdown_function_entry.arguments = (zval *) safe_emalloc(sizeof(zval), 1, 0); + + ZVAL_STRING(&shutdown_function_entry.arguments[0], "session_write_close"); + + if (!append_user_shutdown_function(shutdown_function_entry)) { + zval_ptr_dtor(&shutdown_function_entry.arguments[0]); + efree(shutdown_function_entry.arguments); + + /* Unable to register shutdown function, presumably because of lack + * of memory, so flush the session now. It would be done in rshutdown + * anyway but the handler will have had it's dtor called by then. + * If the user does have a later shutdown function which needs the + * session then tough luck. + */ + php_session_flush(1); + php_error_docref(NULL, E_WARNING, "Unable to register session flush function"); + } +} +/* }}} */ + +/* {{{ arginfo */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_session_name, 0, 0, 0) + ZEND_ARG_INFO(0, name) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_session_module_name, 0, 0, 0) + ZEND_ARG_INFO(0, module) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_session_save_path, 0, 0, 0) + ZEND_ARG_INFO(0, path) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_session_id, 0, 0, 0) + ZEND_ARG_INFO(0, id) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_session_create_id, 0, 0, 0) + ZEND_ARG_INFO(0, prefix) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_session_regenerate_id, 0, 0, 0) + ZEND_ARG_INFO(0, delete_old_session) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_session_decode, 0, 0, 1) + ZEND_ARG_INFO(0, data) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_session_void, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_session_set_save_handler, 0, 0, 1) + ZEND_ARG_INFO(0, open) + ZEND_ARG_INFO(0, close) + ZEND_ARG_INFO(0, read) + ZEND_ARG_INFO(0, write) + ZEND_ARG_INFO(0, destroy) + ZEND_ARG_INFO(0, gc) + ZEND_ARG_INFO(0, create_sid) + ZEND_ARG_INFO(0, validate_sid) + ZEND_ARG_INFO(0, update_timestamp) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_session_cache_limiter, 0, 0, 0) + ZEND_ARG_INFO(0, cache_limiter) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_session_cache_expire, 0, 0, 0) + ZEND_ARG_INFO(0, new_cache_expire) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_session_set_cookie_params, 0, 0, 1) + ZEND_ARG_INFO(0, lifetime_or_options) + ZEND_ARG_INFO(0, path) + ZEND_ARG_INFO(0, domain) + ZEND_ARG_INFO(0, secure) + ZEND_ARG_INFO(0, httponly) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_session_class_open, 0) + ZEND_ARG_INFO(0, save_path) + ZEND_ARG_INFO(0, session_name) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_session_class_close, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_session_class_read, 0) + ZEND_ARG_INFO(0, key) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_session_class_write, 0) + ZEND_ARG_INFO(0, key) + ZEND_ARG_INFO(0, val) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_session_class_destroy, 0) + ZEND_ARG_INFO(0, key) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_session_class_gc, 0) + ZEND_ARG_INFO(0, maxlifetime) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_session_class_create_sid, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_session_class_validateId, 0) + ZEND_ARG_INFO(0, key) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_session_class_updateTimestamp, 0) + ZEND_ARG_INFO(0, key) + ZEND_ARG_INFO(0, val) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_session_start, 0, 0, 0) + ZEND_ARG_INFO(0, options) /* array */ +ZEND_END_ARG_INFO() +/* }}} */ + +/* {{{ session_functions[] + */ +static const zend_function_entry session_functions[] = { + PHP_FE(session_name, arginfo_session_name) + PHP_FE(session_module_name, arginfo_session_module_name) + PHP_FE(session_save_path, arginfo_session_save_path) + PHP_FE(session_id, arginfo_session_id) + PHP_FE(session_create_id, arginfo_session_create_id) + PHP_FE(session_regenerate_id, arginfo_session_regenerate_id) + PHP_FE(session_decode, arginfo_session_decode) + PHP_FE(session_encode, arginfo_session_void) + PHP_FE(session_start, arginfo_session_start) + PHP_FE(session_destroy, arginfo_session_void) + PHP_FE(session_unset, arginfo_session_void) + PHP_FE(session_gc, arginfo_session_void) + PHP_FE(session_set_save_handler, arginfo_session_set_save_handler) + PHP_FE(session_cache_limiter, arginfo_session_cache_limiter) + PHP_FE(session_cache_expire, arginfo_session_cache_expire) + PHP_FE(session_set_cookie_params, arginfo_session_set_cookie_params) + PHP_FE(session_get_cookie_params, arginfo_session_void) + PHP_FE(session_write_close, arginfo_session_void) + PHP_FE(session_abort, arginfo_session_void) + PHP_FE(session_reset, arginfo_session_void) + PHP_FE(session_status, arginfo_session_void) + PHP_FE(session_register_shutdown, arginfo_session_void) + PHP_FALIAS(session_commit, session_write_close, arginfo_session_void) + PHP_FE_END +}; +/* }}} */ + +/* {{{ SessionHandlerInterface functions[] +*/ +static const zend_function_entry php_session_iface_functions[] = { + PHP_ABSTRACT_ME(SessionHandlerInterface, open, arginfo_session_class_open) + PHP_ABSTRACT_ME(SessionHandlerInterface, close, arginfo_session_class_close) + PHP_ABSTRACT_ME(SessionHandlerInterface, read, arginfo_session_class_read) + PHP_ABSTRACT_ME(SessionHandlerInterface, write, arginfo_session_class_write) + PHP_ABSTRACT_ME(SessionHandlerInterface, destroy, arginfo_session_class_destroy) + PHP_ABSTRACT_ME(SessionHandlerInterface, gc, arginfo_session_class_gc) + PHP_FE_END +}; +/* }}} */ + +/* {{{ SessionIdInterface functions[] +*/ +static const zend_function_entry php_session_id_iface_functions[] = { + PHP_ABSTRACT_ME(SessionIdInterface, create_sid, arginfo_session_class_create_sid) + PHP_FE_END +}; +/* }}} */ + +/* {{{ SessionUpdateTimestampHandler functions[] + */ +static const zend_function_entry php_session_update_timestamp_iface_functions[] = { + PHP_ABSTRACT_ME(SessionUpdateTimestampHandlerInterface, validateId, arginfo_session_class_validateId) + PHP_ABSTRACT_ME(SessionUpdateTimestampHandlerInterface, updateTimestamp, arginfo_session_class_updateTimestamp) + PHP_FE_END +}; +/* }}} */ + +/* {{{ SessionHandler functions[] + */ +static const zend_function_entry php_session_class_functions[] = { + PHP_ME(SessionHandler, open, arginfo_session_class_open, ZEND_ACC_PUBLIC) + PHP_ME(SessionHandler, close, arginfo_session_class_close, ZEND_ACC_PUBLIC) + PHP_ME(SessionHandler, read, arginfo_session_class_read, ZEND_ACC_PUBLIC) + PHP_ME(SessionHandler, write, arginfo_session_class_write, ZEND_ACC_PUBLIC) + PHP_ME(SessionHandler, destroy, arginfo_session_class_destroy, ZEND_ACC_PUBLIC) + PHP_ME(SessionHandler, gc, arginfo_session_class_gc, ZEND_ACC_PUBLIC) + PHP_ME(SessionHandler, create_sid, arginfo_session_class_create_sid, ZEND_ACC_PUBLIC) + PHP_FE_END +}; +/* }}} */ + +/* ******************************** + * Module Setup and Destruction * + ******************************** */ + +static int php_rinit_session(zend_bool auto_start) /* {{{ */ +{ + php_rinit_session_globals(); + + if (PS(mod) == NULL) { + char *value; + + value = zend_ini_string("session.save_handler", sizeof("session.save_handler") - 1, 0); + if (value) { + PS(mod) = _php_find_ps_module(value); + } + } + + if (PS(serializer) == NULL) { + char *value; + + value = zend_ini_string("session.serialize_handler", sizeof("session.serialize_handler") - 1, 0); + if (value) { + PS(serializer) = _php_find_ps_serializer(value); + } + } + + if (PS(mod) == NULL || PS(serializer) == NULL) { + /* current status is unusable */ + PS(session_status) = php_session_disabled; + return SUCCESS; + } + + if (auto_start) { + php_session_start(); + } + + return SUCCESS; +} /* }}} */ + +static PHP_RINIT_FUNCTION(session) /* {{{ */ +{ + return php_rinit_session(PS(auto_start)); +} +/* }}} */ + +static PHP_RSHUTDOWN_FUNCTION(session) /* {{{ */ +{ + int i; + + if (PS(session_status) == php_session_active) { + zend_try { + php_session_flush(1); + } zend_end_try(); + } + php_rshutdown_session_globals(); + + /* this should NOT be done in php_rshutdown_session_globals() */ + for (i = 0; i < PS_NUM_APIS; i++) { + if (!Z_ISUNDEF(PS(mod_user_names).names[i])) { + zval_ptr_dtor(&PS(mod_user_names).names[i]); + ZVAL_UNDEF(&PS(mod_user_names).names[i]); + } + } + + return SUCCESS; +} +/* }}} */ + +static PHP_GINIT_FUNCTION(ps) /* {{{ */ +{ + int i; + +#if defined(COMPILE_DL_SESSION) && defined(ZTS) + ZEND_TSRMLS_CACHE_UPDATE(); +#endif + + ps_globals->save_path = NULL; + ps_globals->session_name = NULL; + ps_globals->id = NULL; + ps_globals->mod = NULL; + ps_globals->serializer = NULL; + ps_globals->mod_data = NULL; + ps_globals->session_status = php_session_none; + ps_globals->default_mod = NULL; + ps_globals->mod_user_implemented = 0; + ps_globals->mod_user_is_open = 0; + ps_globals->session_vars = NULL; + ps_globals->set_handler = 0; + for (i = 0; i < PS_NUM_APIS; i++) { + ZVAL_UNDEF(&ps_globals->mod_user_names.names[i]); + } + ZVAL_UNDEF(&ps_globals->http_session_vars); +} +/* }}} */ + +static PHP_MINIT_FUNCTION(session) /* {{{ */ +{ + zend_class_entry ce; + + zend_register_auto_global(zend_string_init_interned("_SESSION", sizeof("_SESSION") - 1, 1), 0, NULL); + + my_module_number = module_number; + PS(module_number) = module_number; + + PS(session_status) = php_session_none; + REGISTER_INI_ENTRIES(); + +#ifdef HAVE_LIBMM + PHP_MINIT(ps_mm) (INIT_FUNC_ARGS_PASSTHRU); +#endif + php_session_rfc1867_orig_callback = php_rfc1867_callback; + php_rfc1867_callback = php_session_rfc1867_callback; + + /* Register interfaces */ + INIT_CLASS_ENTRY(ce, PS_IFACE_NAME, php_session_iface_functions); + php_session_iface_entry = zend_register_internal_class(&ce); + php_session_iface_entry->ce_flags |= ZEND_ACC_INTERFACE; + + INIT_CLASS_ENTRY(ce, PS_SID_IFACE_NAME, php_session_id_iface_functions); + php_session_id_iface_entry = zend_register_internal_class(&ce); + php_session_id_iface_entry->ce_flags |= ZEND_ACC_INTERFACE; + + INIT_CLASS_ENTRY(ce, PS_UPDATE_TIMESTAMP_IFACE_NAME, php_session_update_timestamp_iface_functions); + php_session_update_timestamp_iface_entry = zend_register_internal_class(&ce); + php_session_update_timestamp_iface_entry->ce_flags |= ZEND_ACC_INTERFACE; + + /* Register base class */ + INIT_CLASS_ENTRY(ce, PS_CLASS_NAME, php_session_class_functions); + php_session_class_entry = zend_register_internal_class(&ce); + zend_class_implements(php_session_class_entry, 1, php_session_iface_entry); + zend_class_implements(php_session_class_entry, 1, php_session_id_iface_entry); + + REGISTER_LONG_CONSTANT("PHP_SESSION_DISABLED", php_session_disabled, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PHP_SESSION_NONE", php_session_none, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PHP_SESSION_ACTIVE", php_session_active, CONST_CS | CONST_PERSISTENT); + + return SUCCESS; +} +/* }}} */ + +static PHP_MSHUTDOWN_FUNCTION(session) /* {{{ */ +{ + UNREGISTER_INI_ENTRIES(); + +#ifdef HAVE_LIBMM + PHP_MSHUTDOWN(ps_mm) (SHUTDOWN_FUNC_ARGS_PASSTHRU); +#endif + + /* reset rfc1867 callbacks */ + php_session_rfc1867_orig_callback = NULL; + if (php_rfc1867_callback == php_session_rfc1867_callback) { + php_rfc1867_callback = NULL; + } + + ps_serializers[PREDEFINED_SERIALIZERS].name = NULL; + memset(&ps_modules[PREDEFINED_MODULES], 0, (MAX_MODULES-PREDEFINED_MODULES)*sizeof(ps_module *)); + + return SUCCESS; +} +/* }}} */ + +static PHP_MINFO_FUNCTION(session) /* {{{ */ +{ + const ps_module **mod; + ps_serializer *ser; + smart_str save_handlers = {0}; + smart_str ser_handlers = {0}; + int i; + + /* Get save handlers */ + for (i = 0, mod = ps_modules; i < MAX_MODULES; i++, mod++) { + if (*mod && (*mod)->s_name) { + smart_str_appends(&save_handlers, (*mod)->s_name); + smart_str_appendc(&save_handlers, ' '); + } + } + + /* Get serializer handlers */ + for (i = 0, ser = ps_serializers; i < MAX_SERIALIZERS; i++, ser++) { + if (ser && ser->name) { + smart_str_appends(&ser_handlers, ser->name); + smart_str_appendc(&ser_handlers, ' '); + } + } + + php_info_print_table_start(); + php_info_print_table_row(2, "Session Support", "enabled" ); + + if (save_handlers.s) { + smart_str_0(&save_handlers); + php_info_print_table_row(2, "Registered save handlers", ZSTR_VAL(save_handlers.s)); + smart_str_free(&save_handlers); + } else { + php_info_print_table_row(2, "Registered save handlers", "none"); + } + + if (ser_handlers.s) { + smart_str_0(&ser_handlers); + php_info_print_table_row(2, "Registered serializer handlers", ZSTR_VAL(ser_handlers.s)); + smart_str_free(&ser_handlers); + } else { + php_info_print_table_row(2, "Registered serializer handlers", "none"); + } + + php_info_print_table_end(); + + DISPLAY_INI_ENTRIES(); +} +/* }}} */ + +static const zend_module_dep session_deps[] = { /* {{{ */ + ZEND_MOD_OPTIONAL("hash") + ZEND_MOD_REQUIRED("spl") + ZEND_MOD_END +}; +/* }}} */ + +/* ************************ + * Upload hook handling * + ************************ */ + +static zend_bool early_find_sid_in(zval *dest, int where, php_session_rfc1867_progress *progress) /* {{{ */ +{ + zval *ppid; + + if (Z_ISUNDEF(PG(http_globals)[where])) { + return 0; + } + + if ((ppid = zend_hash_str_find(Z_ARRVAL(PG(http_globals)[where]), PS(session_name), progress->sname_len)) + && Z_TYPE_P(ppid) == IS_STRING) { + zval_ptr_dtor(dest); + ZVAL_COPY_DEREF(dest, ppid); + return 1; + } + + return 0; +} /* }}} */ + +static void php_session_rfc1867_early_find_sid(php_session_rfc1867_progress *progress) /* {{{ */ +{ + + if (PS(use_cookies)) { + sapi_module.treat_data(PARSE_COOKIE, NULL, NULL); + if (early_find_sid_in(&progress->sid, TRACK_VARS_COOKIE, progress)) { + progress->apply_trans_sid = 0; + return; + } + } + if (PS(use_only_cookies)) { + return; + } + sapi_module.treat_data(PARSE_GET, NULL, NULL); + early_find_sid_in(&progress->sid, TRACK_VARS_GET, progress); +} /* }}} */ + +static zend_bool php_check_cancel_upload(php_session_rfc1867_progress *progress) /* {{{ */ +{ + zval *progress_ary, *cancel_upload; + + if ((progress_ary = zend_symtable_find(Z_ARRVAL_P(Z_REFVAL(PS(http_session_vars))), progress->key.s)) == NULL) { + return 0; + } + if (Z_TYPE_P(progress_ary) != IS_ARRAY) { + return 0; + } + if ((cancel_upload = zend_hash_str_find(Z_ARRVAL_P(progress_ary), "cancel_upload", sizeof("cancel_upload") - 1)) == NULL) { + return 0; + } + return Z_TYPE_P(cancel_upload) == IS_TRUE; +} /* }}} */ + +static void php_session_rfc1867_update(php_session_rfc1867_progress *progress, int force_update) /* {{{ */ +{ + if (!force_update) { + if (Z_LVAL_P(progress->post_bytes_processed) < progress->next_update) { + return; + } +#ifdef HAVE_GETTIMEOFDAY + if (PS(rfc1867_min_freq) > 0.0) { + struct timeval tv = {0}; + double dtv; + gettimeofday(&tv, NULL); + dtv = (double) tv.tv_sec + tv.tv_usec / 1000000.0; + if (dtv < progress->next_update_time) { + return; + } + progress->next_update_time = dtv + PS(rfc1867_min_freq); + } +#endif + progress->next_update = Z_LVAL_P(progress->post_bytes_processed) + progress->update_step; + } + + php_session_initialize(); + PS(session_status) = php_session_active; + IF_SESSION_VARS() { + zval *sess_var = Z_REFVAL(PS(http_session_vars)); + SEPARATE_ARRAY(sess_var); + + progress->cancel_upload |= php_check_cancel_upload(progress); + Z_TRY_ADDREF(progress->data); + zend_hash_update(Z_ARRVAL_P(sess_var), progress->key.s, &progress->data); + } + php_session_flush(1); +} /* }}} */ + +static void php_session_rfc1867_cleanup(php_session_rfc1867_progress *progress) /* {{{ */ +{ + php_session_initialize(); + PS(session_status) = php_session_active; + IF_SESSION_VARS() { + zval *sess_var = Z_REFVAL(PS(http_session_vars)); + SEPARATE_ARRAY(sess_var); + zend_hash_del(Z_ARRVAL_P(sess_var), progress->key.s); + } + php_session_flush(1); +} /* }}} */ + +static int php_session_rfc1867_callback(unsigned int event, void *event_data, void **extra) /* {{{ */ +{ + php_session_rfc1867_progress *progress; + int retval = SUCCESS; + + if (php_session_rfc1867_orig_callback) { + retval = php_session_rfc1867_orig_callback(event, event_data, extra); + } + if (!PS(rfc1867_enabled)) { + return retval; + } + + progress = PS(rfc1867_progress); + + switch(event) { + case MULTIPART_EVENT_START: { + multipart_event_start *data = (multipart_event_start *) event_data; + progress = ecalloc(1, sizeof(php_session_rfc1867_progress)); + progress->content_length = data->content_length; + progress->sname_len = strlen(PS(session_name)); + PS(rfc1867_progress) = progress; + } + break; + case MULTIPART_EVENT_FORMDATA: { + multipart_event_formdata *data = (multipart_event_formdata *) event_data; + size_t value_len; + + if (Z_TYPE(progress->sid) && progress->key.s) { + break; + } + + /* orig callback may have modified *data->newlength */ + if (data->newlength) { + value_len = *data->newlength; + } else { + value_len = data->length; + } + + if (data->name && data->value && value_len) { + size_t name_len = strlen(data->name); + + if (name_len == progress->sname_len && memcmp(data->name, PS(session_name), name_len) == 0) { + zval_ptr_dtor(&progress->sid); + ZVAL_STRINGL(&progress->sid, (*data->value), value_len); + } else if (name_len == strlen(PS(rfc1867_name)) && memcmp(data->name, PS(rfc1867_name), name_len + 1) == 0) { + smart_str_free(&progress->key); + smart_str_appends(&progress->key, PS(rfc1867_prefix)); + smart_str_appendl(&progress->key, *data->value, value_len); + smart_str_0(&progress->key); + + progress->apply_trans_sid = APPLY_TRANS_SID; + php_session_rfc1867_early_find_sid(progress); + } + } + } + break; + case MULTIPART_EVENT_FILE_START: { + multipart_event_file_start *data = (multipart_event_file_start *) event_data; + + /* Do nothing when $_POST["PHP_SESSION_UPLOAD_PROGRESS"] is not set + * or when we have no session id */ + if (!Z_TYPE(progress->sid) || !progress->key.s) { + break; + } + + /* First FILE_START event, initializing data */ + if (Z_ISUNDEF(progress->data)) { + + if (PS(rfc1867_freq) >= 0) { + progress->update_step = PS(rfc1867_freq); + } else if (PS(rfc1867_freq) < 0) { /* % of total size */ + progress->update_step = progress->content_length * -PS(rfc1867_freq) / 100; + } + progress->next_update = 0; + progress->next_update_time = 0.0; + + array_init(&progress->data); + array_init(&progress->files); + + add_assoc_long_ex(&progress->data, "start_time", sizeof("start_time") - 1, (zend_long)sapi_get_request_time()); + add_assoc_long_ex(&progress->data, "content_length", sizeof("content_length") - 1, progress->content_length); + add_assoc_long_ex(&progress->data, "bytes_processed", sizeof("bytes_processed") - 1, data->post_bytes_processed); + add_assoc_bool_ex(&progress->data, "done", sizeof("done") - 1, 0); + add_assoc_zval_ex(&progress->data, "files", sizeof("files") - 1, &progress->files); + + progress->post_bytes_processed = zend_hash_str_find(Z_ARRVAL(progress->data), "bytes_processed", sizeof("bytes_processed") - 1); + + php_rinit_session(0); + PS(id) = zend_string_init(Z_STRVAL(progress->sid), Z_STRLEN(progress->sid), 0); + if (progress->apply_trans_sid) { + /* Enable trans sid by modifying flags */ + PS(use_trans_sid) = 1; + PS(use_only_cookies) = 0; + } + PS(send_cookie) = 0; + } + + array_init(&progress->current_file); + + /* Each uploaded file has its own array. Trying to make it close to $_FILES entries. */ + add_assoc_string_ex(&progress->current_file, "field_name", sizeof("field_name") - 1, data->name); + add_assoc_string_ex(&progress->current_file, "name", sizeof("name") - 1, *data->filename); + add_assoc_null_ex(&progress->current_file, "tmp_name", sizeof("tmp_name") - 1); + add_assoc_long_ex(&progress->current_file, "error", sizeof("error") - 1, 0); + + add_assoc_bool_ex(&progress->current_file, "done", sizeof("done") - 1, 0); + add_assoc_long_ex(&progress->current_file, "start_time", sizeof("start_time") - 1, (zend_long)time(NULL)); + add_assoc_long_ex(&progress->current_file, "bytes_processed", sizeof("bytes_processed") - 1, 0); + + add_next_index_zval(&progress->files, &progress->current_file); + + progress->current_file_bytes_processed = zend_hash_str_find(Z_ARRVAL(progress->current_file), "bytes_processed", sizeof("bytes_processed") - 1); + + Z_LVAL_P(progress->current_file_bytes_processed) = data->post_bytes_processed; + php_session_rfc1867_update(progress, 0); + } + break; + case MULTIPART_EVENT_FILE_DATA: { + multipart_event_file_data *data = (multipart_event_file_data *) event_data; + + if (!Z_TYPE(progress->sid) || !progress->key.s) { + break; + } + + Z_LVAL_P(progress->current_file_bytes_processed) = data->offset + data->length; + Z_LVAL_P(progress->post_bytes_processed) = data->post_bytes_processed; + + php_session_rfc1867_update(progress, 0); + } + break; + case MULTIPART_EVENT_FILE_END: { + multipart_event_file_end *data = (multipart_event_file_end *) event_data; + + if (!Z_TYPE(progress->sid) || !progress->key.s) { + break; + } + + if (data->temp_filename) { + add_assoc_string_ex(&progress->current_file, "tmp_name", sizeof("tmp_name") - 1, data->temp_filename); + } + + add_assoc_long_ex(&progress->current_file, "error", sizeof("error") - 1, data->cancel_upload); + add_assoc_bool_ex(&progress->current_file, "done", sizeof("done") - 1, 1); + + Z_LVAL_P(progress->post_bytes_processed) = data->post_bytes_processed; + + php_session_rfc1867_update(progress, 0); + } + break; + case MULTIPART_EVENT_END: { + multipart_event_end *data = (multipart_event_end *) event_data; + + if (Z_TYPE(progress->sid) && progress->key.s) { + if (PS(rfc1867_cleanup)) { + php_session_rfc1867_cleanup(progress); + } else { + SEPARATE_ARRAY(&progress->data); + add_assoc_bool_ex(&progress->data, "done", sizeof("done") - 1, 1); + Z_LVAL_P(progress->post_bytes_processed) = data->post_bytes_processed; + php_session_rfc1867_update(progress, 1); + } + php_rshutdown_session_globals(); + } + + if (!Z_ISUNDEF(progress->data)) { + zval_ptr_dtor(&progress->data); + } + zval_ptr_dtor(&progress->sid); + smart_str_free(&progress->key); + efree(progress); + progress = NULL; + PS(rfc1867_progress) = NULL; + } + break; + } + + if (progress && progress->cancel_upload) { + return FAILURE; + } + return retval; + +} /* }}} */ + +zend_module_entry session_module_entry = { + STANDARD_MODULE_HEADER_EX, + NULL, + session_deps, + "session", + session_functions, + PHP_MINIT(session), PHP_MSHUTDOWN(session), + PHP_RINIT(session), PHP_RSHUTDOWN(session), + PHP_MINFO(session), + PHP_SESSION_VERSION, + PHP_MODULE_GLOBALS(ps), + PHP_GINIT(ps), + NULL, + NULL, + STANDARD_MODULE_PROPERTIES_EX +}; + +#ifdef COMPILE_DL_SESSION +#ifdef ZTS +ZEND_TSRMLS_CACHE_DEFINE() +#endif +ZEND_GET_MODULE(session) +#endif + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/ext/session/tests/bug79221.phpt b/ext/session/tests/bug79221.phpt new file mode 100644 index 0000000000000..b0972c4697054 --- /dev/null +++ b/ext/session/tests/bug79221.phpt @@ -0,0 +1,45 @@ +--TEST-- +Null Pointer Dereference in PHP Session Upload Progress +--INI-- +error_reporting=0 +file_uploads=1 +upload_max_filesize=1024 +session.save_path= +session.name=PHPSESSID +session.serialize_handler=php +session.use_strict_mode=0 +session.use_cookies=1 +session.use_only_cookies=0 +session.upload_progress.enabled=1 +session.upload_progress.cleanup=0 +session.upload_progress.prefix=upload_progress_ +session.upload_progress.name=PHP_SESSION_UPLOAD_PROGRESS +session.upload_progress.freq=1% +session.upload_progress.min_freq=0.000000001 +--COOKIE-- +PHPSESSID=session-upload +--POST_RAW-- +Content-Type: multipart/form-data; boundary=---------------------------20896060251896012921717172737 +-----------------------------20896060251896012921717172737 +Content-Disposition: form-data; name="PHPSESSID" + +session-upload +-----------------------------20896060251896012921717172737 +Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS" + +ryat +-----------------------------20896060251896012921717172737 +Content-Disposition: form-data; file="file"; ryat="filename" + +1 +-----------------------------20896060251896012921717172737-- +--FILE-- + +--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/soap/php_sdl.c b/ext/soap/php_sdl.c index 1875bec41dc5b..67ab4097c2d7a 100644 --- a/ext/soap/php_sdl.c +++ b/ext/soap/php_sdl.c @@ -313,6 +313,8 @@ void sdl_restore_uri_credentials(sdlCtx *ctx) ctx->context = NULL; } +#define SAFE_STR(a) ((a)?a:"") + static void load_wsdl_ex(zval *this_ptr, char *struri, sdlCtx *ctx, int include) { sdlPtr tmpsdl = ctx->sdl; @@ -374,7 +376,7 @@ static void load_wsdl_ex(zval *this_ptr, char *struri, sdlCtx *ctx, int include) if (node_is_equal_ex(trav2, "schema", XSD_NAMESPACE)) { load_schema(ctx, trav2); } else if (is_wsdl_element(trav2) && !node_is_equal(trav2,"documentation")) { - soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav2->name); + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav2->name)); } trav2 = trav2->next; } @@ -435,7 +437,7 @@ static void load_wsdl_ex(zval *this_ptr, char *struri, sdlCtx *ctx, int include) soap_error0(E_ERROR, "Parsing WSDL: has no name attribute"); } } else if (!node_is_equal(trav,"documentation")) { - soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav->name); + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav->name)); } trav = trav->next; } @@ -545,7 +547,7 @@ static sdlSoapBindingFunctionHeaderPtr wsdl_soap_binding_header(sdlCtx* ctx, xml } smart_str_free(&key); } else if (is_wsdl_element(trav) && !node_is_equal(trav,"documentation")) { - soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav->name); + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav->name)); } trav = trav->next; } @@ -647,7 +649,7 @@ static void wsdl_soap_binding_body(sdlCtx* ctx, xmlNodePtr node, char* wsdl_soap } smart_str_free(&key); } else if (is_wsdl_element(trav) && !node_is_equal(trav,"documentation")) { - soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav->name); + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav->name)); } trav = trav->next; } @@ -679,14 +681,14 @@ static HashTable* wsdl_message(sdlCtx *ctx, xmlChar* message_name) sdlParamPtr param; if (trav->ns != NULL && strcmp((char*)trav->ns->href, WSDL_NAMESPACE) != 0) { - soap_error1(E_ERROR, "Parsing WSDL: Unexpected extensibility element <%s>", trav->name); + soap_error1(E_ERROR, "Parsing WSDL: Unexpected extensibility element <%s>", SAFE_STR(trav->name)); } if (node_is_equal(trav,"documentation")) { trav = trav->next; continue; } if (!node_is_equal(trav,"part")) { - soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav->name); + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav->name)); } part = trav; param = emalloc(sizeof(sdlParam)); @@ -695,7 +697,7 @@ static HashTable* wsdl_message(sdlCtx *ctx, xmlChar* message_name) name = get_attribute(part->properties, "name"); if (name == NULL) { - soap_error1(E_ERROR, "Parsing WSDL: No name associated with '%s'", message->name); + soap_error1(E_ERROR, "Parsing WSDL: No name associated with '%s'", SAFE_STR(message->name)); } param->paramName = estrdup((char*)name->children->content); @@ -764,7 +766,7 @@ static sdlPtr load_wsdl(zval *this_ptr, char *struri) continue; } if (!node_is_equal(trav,"port")) { - soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav->name); + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav->name)); } port = trav; @@ -803,7 +805,7 @@ static sdlPtr load_wsdl(zval *this_ptr, char *struri) } } if (trav2 != address && is_wsdl_element(trav2) && !node_is_equal(trav2,"documentation")) { - soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav2->name); + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav2->name)); } trav2 = trav2->next; } @@ -905,7 +907,7 @@ static sdlPtr load_wsdl(zval *this_ptr, char *struri) continue; } if (!node_is_equal(trav2,"operation")) { - soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav2->name); + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav2->name)); } operation = trav2; @@ -924,7 +926,7 @@ static sdlPtr load_wsdl(zval *this_ptr, char *struri) !node_is_equal(trav3,"output") && !node_is_equal(trav3,"fault") && !node_is_equal(trav3,"documentation")) { - soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav3->name); + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav3->name)); } trav3 = trav3->next; } @@ -1102,7 +1104,7 @@ static sdlPtr load_wsdl(zval *this_ptr, char *struri) } } } else if (is_wsdl_element(trav) && !node_is_equal(trav,"documentation")) { - soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", trav->name); + soap_error1(E_ERROR, "Parsing WSDL: Unexpected WSDL element <%s>", SAFE_STR(trav->name)); } trav = trav->next; } diff --git a/ext/soap/php_xml.c b/ext/soap/php_xml.c index 8d07bb0d83234..a1ab525de3505 100644 --- a/ext/soap/php_xml.c +++ b/ext/soap/php_xml.c @@ -203,7 +203,7 @@ xmlNsPtr node_find_ns(xmlNodePtr node) int attr_is_equal_ex(xmlAttrPtr node, char *name, char *ns) { - if (name == NULL || strcmp((char*)node->name, name) == 0) { + if (name == NULL || ((node->name) && strcmp((char*)node->name, name) == 0)) { if (ns) { xmlNsPtr nsPtr = attr_find_ns(node); if (nsPtr) { @@ -219,7 +219,7 @@ int attr_is_equal_ex(xmlAttrPtr node, char *name, char *ns) int node_is_equal_ex(xmlNodePtr node, char *name, char *ns) { - if (name == NULL || strcmp((char*)node->name, name) == 0) { + if (name == NULL || ((node->name) && strcmp((char*)node->name, name) == 0)) { if (ns) { xmlNsPtr nsPtr = node_find_ns(node); if (nsPtr) { diff --git a/ext/soap/tests/bug80672.phpt b/ext/soap/tests/bug80672.phpt new file mode 100644 index 0000000000000..71e2b1d84105e --- /dev/null +++ b/ext/soap/tests/bug80672.phpt @@ -0,0 +1,15 @@ +--TEST-- +Bug #80672 Null Dereference in SoapClient +--SKIPIF-- + +--FILE-- +query(array('sXML' => 'something')); +} catch(SoapFault $e) { + print $e->getMessage(); +} +?> +--EXPECTF-- +SOAP-ERROR: Parsing WSDL: Unexpected WSDL element <> \ No newline at end of file diff --git a/ext/soap/tests/bug80672.xml b/ext/soap/tests/bug80672.xml new file mode 100644 index 0000000000000..0fa185bf1ebba --- /dev/null +++ b/ext/soap/tests/bug80672.xml @@ -0,0 +1,6 @@ + + + + diff --git a/ext/spl/spl_directory.c b/ext/spl/spl_directory.c index 7a91054fc4331..b9e6bf91e8b43 100644 --- a/ext/spl/spl_directory.c +++ b/ext/spl/spl_directory.c @@ -708,10 +708,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/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 fec04ec584cd6..9787b92f57173 100644 --- a/ext/standard/crypt.c +++ b/ext/standard/crypt.c @@ -154,6 +154,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..fec04ec584cd6 --- /dev/null +++ b/ext/standard/crypt.c.orig @@ -0,0 +1,294 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2018 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 | + +----------------------------------------------------------------------+ +*/ + +#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) { + ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN); + efree(output); + return NULL; + } else { + result = zend_string_init(output, strlen(output), 0); + ZEND_SECURE_ZERO(output, 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) { + ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN); + efree(output); + return NULL; + } else { + result = zend_string_init(output, strlen(output), 0); + ZEND_SECURE_ZERO(output, 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; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STRING(str, str_len) + Z_PARAM_OPTIONAL + Z_PARAM_STRING(salt_in, salt_in_len) + ZEND_PARSE_PARAMETERS_END(); + + 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 c1f945f29edd4..aa7e1bc2e6894 100644 --- a/ext/standard/crypt_blowfish.c +++ b/ext/standard/crypt_blowfish.c @@ -376,7 +376,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; \ @@ -404,13 +403,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..c1f945f29edd4 --- /dev/null +++ b/ext/standard/crypt_blowfish.c.orig @@ -0,0 +1,917 @@ +/* + * 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/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/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/tests/streams/parseip-001.phpt b/ext/standard/tests/streams/parseip-001.phpt new file mode 100644 index 0000000000000..594756db6b7cd --- /dev/null +++ b/ext/standard/tests/streams/parseip-001.phpt @@ -0,0 +1,37 @@ +--TEST-- +Use of double-port in fsockopen() +--FILE-- + | + +----------------------------------------------------------------------+ + */ + +#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) + zend_string_release_ex(theurl->scheme, 0); + if (theurl->user) + zend_string_release_ex(theurl->user, 0); + if (theurl->pass) + zend_string_release_ex(theurl->pass, 0); + if (theurl->host) + zend_string_release_ex(theurl->host, 0); + if (theurl->path) + zend_string_release_ex(theurl->path, 0); + if (theurl->query) + zend_string_release_ex(theurl->query, 0); + if (theurl->fragment) + zend_string_release_ex(theurl->fragment, 0); + 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 if (s + 1 < ue && *s == '/' && *(s + 1) == '/') { /* relative-scheme URL */ + s += 2; + e = 0; + goto parse_host; + } else { + goto just_path; + } + } + p++; + } + + if (e + 1 == ue) { /* only scheme is available */ + ret->scheme = zend_string_init(s, (e - s), 0); + php_replace_controlchars_ex(ZSTR_VAL(ret->scheme), ZSTR_LEN(ret->scheme)); + 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 = zend_string_init(s, (e-s), 0); + php_replace_controlchars_ex(ZSTR_VAL(ret->scheme), ZSTR_LEN(ret->scheme)); + + s = e + 1; + goto just_path; + } else { + ret->scheme = zend_string_init(s, (e-s), 0); + php_replace_controlchars_ex(ZSTR_VAL(ret->scheme), ZSTR_LEN(ret->scheme)); + + if (e + 2 < ue && *(e + 2) == '/') { + s = e + 3; + if (zend_string_equals_literal_ci(ret->scheme, "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 { + php_url_free(ret); + return NULL; + } + } else if (p == pp && pp == ue) { + php_url_free(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; + } + + parse_host: + /* 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 = zend_string_init(s, (pp-s), 0); + php_replace_controlchars_ex(ZSTR_VAL(ret->user), ZSTR_LEN(ret->user)); + + pp++; + ret->pass = zend_string_init(pp, (p-pp), 0); + php_replace_controlchars_ex(ZSTR_VAL(ret->pass), ZSTR_LEN(ret->pass)); + } else { + ret->user = zend_string_init(s, (p-s), 0); + php_replace_controlchars_ex(ZSTR_VAL(ret->user), ZSTR_LEN(ret->user)); + } + + 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 */ + php_url_free(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 { + php_url_free(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) { + php_url_free(ret); + return NULL; + } + + ret->host = zend_string_init(s, (p-s), 0); + php_replace_controlchars_ex(ZSTR_VAL(ret->host), ZSTR_LEN(ret->host)); + + if (e == ue) { + return ret; + } + + s = e; + + just_path: + + e = ue; + p = memchr(s, '#', (e - s)); + if (p) { + p++; + if (p < e) { + ret->fragment = zend_string_init(p, (e - p), 0); + php_replace_controlchars_ex(ZSTR_VAL(ret->fragment), ZSTR_LEN(ret->fragment)); + } + e = p-1; + } + + p = memchr(s, '?', (e - s)); + if (p) { + p++; + if (p < e) { + ret->query = zend_string_init(p, (e - p), 0); + php_replace_controlchars_ex(ZSTR_VAL(ret->query), ZSTR_LEN(ret->query)); + } + e = p-1; + } + + if (s < e || s == ue) { + ret->path = zend_string_init(s, (e - s), 0); + php_replace_controlchars_ex(ZSTR_VAL(ret->path), ZSTR_LEN(ret->path)); + } + + 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; + zval tmp; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STRING(str, str_len) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(key) + ZEND_PARSE_PARAMETERS_END(); + + 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_STR_COPY(resource->scheme); + break; + case PHP_URL_HOST: + if (resource->host != NULL) RETVAL_STR_COPY(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_STR_COPY(resource->user); + break; + case PHP_URL_PASS: + if (resource->pass != NULL) RETVAL_STR_COPY(resource->pass); + break; + case PHP_URL_PATH: + if (resource->path != NULL) RETVAL_STR_COPY(resource->path); + break; + case PHP_URL_QUERY: + if (resource->query != NULL) RETVAL_STR_COPY(resource->query); + break; + case PHP_URL_FRAGMENT: + if (resource->fragment != NULL) RETVAL_STR_COPY(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) { + ZVAL_STR_COPY(&tmp, resource->scheme); + zend_hash_add_new(Z_ARRVAL_P(return_value), ZSTR_KNOWN(ZEND_STR_SCHEME), &tmp); + } + if (resource->host != NULL) { + ZVAL_STR_COPY(&tmp, resource->host); + zend_hash_add_new(Z_ARRVAL_P(return_value), ZSTR_KNOWN(ZEND_STR_HOST), &tmp); + } + if (resource->port != 0) { + ZVAL_LONG(&tmp, resource->port); + zend_hash_add_new(Z_ARRVAL_P(return_value), ZSTR_KNOWN(ZEND_STR_PORT), &tmp); + } + if (resource->user != NULL) { + ZVAL_STR_COPY(&tmp, resource->user); + zend_hash_add_new(Z_ARRVAL_P(return_value), ZSTR_KNOWN(ZEND_STR_USER), &tmp); + } + if (resource->pass != NULL) { + ZVAL_STR_COPY(&tmp, resource->pass); + zend_hash_add_new(Z_ARRVAL_P(return_value), ZSTR_KNOWN(ZEND_STR_PASS), &tmp); + } + if (resource->path != NULL) { + ZVAL_STR_COPY(&tmp, resource->path); + zend_hash_add_new(Z_ARRVAL_P(return_value), ZSTR_KNOWN(ZEND_STR_PATH), &tmp); + } + if (resource->query != NULL) { + ZVAL_STR_COPY(&tmp, resource->query); + zend_hash_add_new(Z_ARRVAL_P(return_value), ZSTR_KNOWN(ZEND_STR_QUERY), &tmp); + } + if (resource->fragment != NULL) { + ZVAL_STR_COPY(&tmp, resource->fragment); + zend_hash_add_new(Z_ARRVAL_P(return_value), ZSTR_KNOWN(ZEND_STR_FRAGMENT), &tmp); + } +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; + char *ret; + + str = zend_string_safe_alloc(3, len, 0, 0); + ret = ZSTR_VAL(str); + for (x = 0, y = 0; len--; x++, y++) { + char c = s[x]; + + ret[y] = c; +#ifndef CHARSET_EBCDIC + if ((c < '0' && c != '-' && c != '.') || + (c < 'A' && c > '9') || + (c > 'Z' && c < 'a' && c != '_') || + (c > 'z' && c != '~')) { + ret[y++] = '%'; + ret[y++] = hexchars[(unsigned char) c >> 4]; + ret[y] = hexchars[(unsigned char) c & 15]; +#else /*CHARSET_EBCDIC*/ + if (!isalnum(c) && strchr("_-.~", c) != NULL) { + ret[y++] = '%'; + ret[y++] = hexchars[os_toascii[(unsigned char) c] >> 4]; + ret[y] = hexchars[os_toascii[(unsigned char) c] & 15]; +#endif /*CHARSET_EBCDIC*/ + } + } + ret[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; + + ZEND_PARSE_PARAMETERS_START(1, 3) + Z_PARAM_PATH(url, url_len) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(format) + Z_PARAM_RESOURCE_EX(zcontext, 1, 0) + ZEND_PARSE_PARAMETERS_END(); + + 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/main/rfc1867.c b/main/rfc1867.c index 8a8e335ef4343..617a94d0ef751 100644 --- a/main/rfc1867.c +++ b/main/rfc1867.c @@ -690,7 +690,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; @@ -1124,7 +1125,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/rfc1867.c.orig b/main/rfc1867.c.orig new file mode 100644 index 0000000000000..8a8e335ef4343 --- /dev/null +++ b/main/rfc1867.c.orig @@ -0,0 +1,1350 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2018 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 | + | Jani Taskinen | + +----------------------------------------------------------------------+ + */ + +/* + * This product includes software developed by the Apache Group + * for use in the Apache HTTP server project (http://www.apache.org/). + * + */ + +#include +#include "php.h" +#include "php_open_temporary_file.h" +#include "zend_globals.h" +#include "php_globals.h" +#include "php_variables.h" +#include "rfc1867.h" +#include "ext/standard/php_string.h" +#include "zend_smart_string.h" + +#if defined(PHP_WIN32) && !defined(HAVE_ATOLL) +# define atoll(s) _atoi64(s) +# define HAVE_ATOLL 1 +#endif + +#ifndef DEBUG_FILE_UPLOAD +# define DEBUG_FILE_UPLOAD 0 +#endif + +static int dummy_encoding_translation(void) +{ + return 0; +} + +static char *php_ap_getword(const zend_encoding *encoding, char **line, char stop); +static char *php_ap_getword_conf(const zend_encoding *encoding, char *str); + +static php_rfc1867_encoding_translation_t php_rfc1867_encoding_translation = dummy_encoding_translation; +static php_rfc1867_get_detect_order_t php_rfc1867_get_detect_order = NULL; +static php_rfc1867_set_input_encoding_t php_rfc1867_set_input_encoding = NULL; +static php_rfc1867_getword_t php_rfc1867_getword = php_ap_getword; +static php_rfc1867_getword_conf_t php_rfc1867_getword_conf = php_ap_getword_conf; +static php_rfc1867_basename_t php_rfc1867_basename = NULL; + +PHPAPI int (*php_rfc1867_callback)(unsigned int event, void *event_data, void **extra) = NULL; + +static void safe_php_register_variable(char *var, char *strval, size_t val_len, zval *track_vars_array, zend_bool override_protection); + +/* The longest property name we use in an uploaded file array */ +#define MAX_SIZE_OF_INDEX sizeof("[tmp_name]") + +/* The longest anonymous name */ +#define MAX_SIZE_ANONNAME 33 + +/* Errors */ +#define UPLOAD_ERROR_OK 0 /* File upload successful */ +#define UPLOAD_ERROR_A 1 /* Uploaded file exceeded upload_max_filesize */ +#define UPLOAD_ERROR_B 2 /* Uploaded file exceeded MAX_FILE_SIZE */ +#define UPLOAD_ERROR_C 3 /* Partially uploaded */ +#define UPLOAD_ERROR_D 4 /* No file uploaded */ +#define UPLOAD_ERROR_E 6 /* Missing /tmp or similar directory */ +#define UPLOAD_ERROR_F 7 /* Failed to write file to disk */ +#define UPLOAD_ERROR_X 8 /* File upload stopped by extension */ + +void php_rfc1867_register_constants(void) /* {{{ */ +{ + REGISTER_MAIN_LONG_CONSTANT("UPLOAD_ERR_OK", UPLOAD_ERROR_OK, CONST_CS | CONST_PERSISTENT); + REGISTER_MAIN_LONG_CONSTANT("UPLOAD_ERR_INI_SIZE", UPLOAD_ERROR_A, CONST_CS | CONST_PERSISTENT); + REGISTER_MAIN_LONG_CONSTANT("UPLOAD_ERR_FORM_SIZE", UPLOAD_ERROR_B, CONST_CS | CONST_PERSISTENT); + REGISTER_MAIN_LONG_CONSTANT("UPLOAD_ERR_PARTIAL", UPLOAD_ERROR_C, CONST_CS | CONST_PERSISTENT); + REGISTER_MAIN_LONG_CONSTANT("UPLOAD_ERR_NO_FILE", UPLOAD_ERROR_D, CONST_CS | CONST_PERSISTENT); + REGISTER_MAIN_LONG_CONSTANT("UPLOAD_ERR_NO_TMP_DIR", UPLOAD_ERROR_E, CONST_CS | CONST_PERSISTENT); + REGISTER_MAIN_LONG_CONSTANT("UPLOAD_ERR_CANT_WRITE", UPLOAD_ERROR_F, CONST_CS | CONST_PERSISTENT); + REGISTER_MAIN_LONG_CONSTANT("UPLOAD_ERR_EXTENSION", UPLOAD_ERROR_X, CONST_CS | CONST_PERSISTENT); +} +/* }}} */ + +static void normalize_protected_variable(char *varname) /* {{{ */ +{ + char *s = varname, *index = NULL, *indexend = NULL, *p; + + /* overjump leading space */ + while (*s == ' ') { + s++; + } + + /* and remove it */ + if (s != varname) { + memmove(varname, s, strlen(s)+1); + } + + for (p = varname; *p && *p != '['; p++) { + switch(*p) { + case ' ': + case '.': + *p = '_'; + break; + } + } + + /* find index */ + index = strchr(varname, '['); + if (index) { + index++; + s = index; + } else { + return; + } + + /* done? */ + while (index) { + while (*index == ' ' || *index == '\r' || *index == '\n' || *index=='\t') { + index++; + } + indexend = strchr(index, ']'); + indexend = indexend ? indexend + 1 : index + strlen(index); + + if (s != index) { + memmove(s, index, strlen(index)+1); + s += indexend-index; + } else { + s = indexend; + } + + if (*s == '[') { + s++; + index = s; + } else { + index = NULL; + } + } + *s = '\0'; +} +/* }}} */ + +static void add_protected_variable(char *varname) /* {{{ */ +{ + normalize_protected_variable(varname); + zend_hash_str_add_empty_element(&PG(rfc1867_protected_variables), varname, strlen(varname)); +} +/* }}} */ + +static zend_bool is_protected_variable(char *varname) /* {{{ */ +{ + normalize_protected_variable(varname); + return zend_hash_str_exists(&PG(rfc1867_protected_variables), varname, strlen(varname)); +} +/* }}} */ + +static void safe_php_register_variable(char *var, char *strval, size_t val_len, zval *track_vars_array, zend_bool override_protection) /* {{{ */ +{ + if (override_protection || !is_protected_variable(var)) { + php_register_variable_safe(var, strval, val_len, track_vars_array); + } +} +/* }}} */ + +static void safe_php_register_variable_ex(char *var, zval *val, zval *track_vars_array, zend_bool override_protection) /* {{{ */ +{ + if (override_protection || !is_protected_variable(var)) { + php_register_variable_ex(var, val, track_vars_array); + } +} +/* }}} */ + +static void register_http_post_files_variable(char *strvar, char *val, zval *http_post_files, zend_bool override_protection) /* {{{ */ +{ + safe_php_register_variable(strvar, val, strlen(val), http_post_files, override_protection); +} +/* }}} */ + +static void register_http_post_files_variable_ex(char *var, zval *val, zval *http_post_files, zend_bool override_protection) /* {{{ */ +{ + safe_php_register_variable_ex(var, val, http_post_files, override_protection); +} +/* }}} */ + +static int unlink_filename(zval *el) /* {{{ */ +{ + zend_string *filename = Z_STR_P(el); + VCWD_UNLINK(ZSTR_VAL(filename)); + return 0; +} +/* }}} */ + + +static void free_filename(zval *el) { + zend_string *filename = Z_STR_P(el); + zend_string_release_ex(filename, 0); +} + +PHPAPI void destroy_uploaded_files_hash(void) /* {{{ */ +{ + zend_hash_apply(SG(rfc1867_uploaded_files), unlink_filename); + zend_hash_destroy(SG(rfc1867_uploaded_files)); + FREE_HASHTABLE(SG(rfc1867_uploaded_files)); +} +/* }}} */ + +/* {{{ Following code is based on apache_multipart_buffer.c from libapreq-0.33 package. */ + +#define FILLUNIT (1024 * 5) + +typedef struct { + + /* read buffer */ + char *buffer; + char *buf_begin; + int bufsize; + int bytes_in_buffer; + + /* boundary info */ + char *boundary; + char *boundary_next; + int boundary_next_len; + + const zend_encoding *input_encoding; + const zend_encoding **detect_order; + size_t detect_order_size; +} multipart_buffer; + +typedef struct { + char *key; + char *value; +} mime_header_entry; + +/* + * Fill up the buffer with client data. + * Returns number of bytes added to buffer. + */ +static int fill_buffer(multipart_buffer *self) +{ + int bytes_to_read, total_read = 0, actual_read = 0; + + /* shift the existing data if necessary */ + if (self->bytes_in_buffer > 0 && self->buf_begin != self->buffer) { + memmove(self->buffer, self->buf_begin, self->bytes_in_buffer); + } + + self->buf_begin = self->buffer; + + /* calculate the free space in the buffer */ + bytes_to_read = self->bufsize - self->bytes_in_buffer; + + /* read the required number of bytes */ + while (bytes_to_read > 0) { + + char *buf = self->buffer + self->bytes_in_buffer; + + actual_read = (int)sapi_module.read_post(buf, bytes_to_read); + + /* update the buffer length */ + if (actual_read > 0) { + self->bytes_in_buffer += actual_read; + SG(read_post_bytes) += actual_read; + total_read += actual_read; + bytes_to_read -= actual_read; + } else { + break; + } + } + + return total_read; +} + +/* eof if we are out of bytes, or if we hit the final boundary */ +static int multipart_buffer_eof(multipart_buffer *self) +{ + if ( (self->bytes_in_buffer == 0 && fill_buffer(self) < 1) ) { + return 1; + } else { + return 0; + } +} + +/* create new multipart_buffer structure */ +static multipart_buffer *multipart_buffer_new(char *boundary, int boundary_len) +{ + multipart_buffer *self = (multipart_buffer *) ecalloc(1, sizeof(multipart_buffer)); + + int minsize = boundary_len + 6; + if (minsize < FILLUNIT) minsize = FILLUNIT; + + self->buffer = (char *) ecalloc(1, minsize + 1); + self->bufsize = minsize; + + spprintf(&self->boundary, 0, "--%s", boundary); + + self->boundary_next_len = (int)spprintf(&self->boundary_next, 0, "\n--%s", boundary); + + self->buf_begin = self->buffer; + self->bytes_in_buffer = 0; + + if (php_rfc1867_encoding_translation()) { + php_rfc1867_get_detect_order(&self->detect_order, &self->detect_order_size); + } else { + self->detect_order = NULL; + self->detect_order_size = 0; + } + + self->input_encoding = NULL; + + return self; +} + +/* + * Gets the next CRLF terminated line from the input buffer. + * If it doesn't find a CRLF, and the buffer isn't completely full, returns + * NULL; otherwise, returns the beginning of the null-terminated line, + * minus the CRLF. + * + * Note that we really just look for LF terminated lines. This works + * around a bug in internet explorer for the macintosh which sends mime + * boundaries that are only LF terminated when you use an image submit + * button in a multipart/form-data form. + */ +static char *next_line(multipart_buffer *self) +{ + /* look for LF in the data */ + char* line = self->buf_begin; + char* ptr = memchr(self->buf_begin, '\n', self->bytes_in_buffer); + + if (ptr) { /* LF found */ + + /* terminate the string, remove CRLF */ + if ((ptr - line) > 0 && *(ptr-1) == '\r') { + *(ptr-1) = 0; + } else { + *ptr = 0; + } + + /* bump the pointer */ + self->buf_begin = ptr + 1; + self->bytes_in_buffer -= (self->buf_begin - line); + + } else { /* no LF found */ + + /* buffer isn't completely full, fail */ + if (self->bytes_in_buffer < self->bufsize) { + return NULL; + } + /* return entire buffer as a partial line */ + line[self->bufsize] = 0; + self->buf_begin = ptr; + self->bytes_in_buffer = 0; + } + + return line; +} + +/* Returns the next CRLF terminated line from the client */ +static char *get_line(multipart_buffer *self) +{ + char* ptr = next_line(self); + + if (!ptr) { + fill_buffer(self); + ptr = next_line(self); + } + + return ptr; +} + +/* Free header entry */ +static void php_free_hdr_entry(mime_header_entry *h) +{ + if (h->key) { + efree(h->key); + } + if (h->value) { + efree(h->value); + } +} + +/* finds a boundary */ +static int find_boundary(multipart_buffer *self, char *boundary) +{ + char *line; + + /* loop through lines */ + while( (line = get_line(self)) ) + { + /* finished if we found the boundary */ + if (!strcmp(line, boundary)) { + return 1; + } + } + + /* didn't find the boundary */ + return 0; +} + +/* parse headers */ +static int multipart_buffer_headers(multipart_buffer *self, zend_llist *header) +{ + char *line; + mime_header_entry entry = {0}; + smart_string buf_value = {0}; + char *key = NULL; + + /* didn't find boundary, abort */ + if (!find_boundary(self, self->boundary)) { + return 0; + } + + /* get lines of text, or CRLF_CRLF */ + + while ((line = get_line(self)) && line[0] != '\0') { + /* add header to table */ + char *value = NULL; + + if (php_rfc1867_encoding_translation()) { + self->input_encoding = zend_multibyte_encoding_detector((const unsigned char *) line, strlen(line), self->detect_order, self->detect_order_size); + } + + /* space in the beginning means same header */ + if (!isspace(line[0])) { + value = strchr(line, ':'); + } + + if (value) { + if (buf_value.c && key) { + /* new entry, add the old one to the list */ + smart_string_0(&buf_value); + entry.key = key; + entry.value = buf_value.c; + zend_llist_add_element(header, &entry); + buf_value.c = NULL; + key = NULL; + } + + *value = '\0'; + do { value++; } while (isspace(*value)); + + key = estrdup(line); + smart_string_appends(&buf_value, value); + } else if (buf_value.c) { /* If no ':' on the line, add to previous line */ + smart_string_appends(&buf_value, line); + } else { + continue; + } + } + + if (buf_value.c && key) { + /* add the last one to the list */ + smart_string_0(&buf_value); + entry.key = key; + entry.value = buf_value.c; + zend_llist_add_element(header, &entry); + } + + return 1; +} + +static char *php_mime_get_hdr_value(zend_llist header, char *key) +{ + mime_header_entry *entry; + + if (key == NULL) { + return NULL; + } + + entry = zend_llist_get_first(&header); + while (entry) { + if (!strcasecmp(entry->key, key)) { + return entry->value; + } + entry = zend_llist_get_next(&header); + } + + return NULL; +} + +static char *php_ap_getword(const zend_encoding *encoding, char **line, char stop) +{ + char *pos = *line, quote; + char *res; + + while (*pos && *pos != stop) { + if ((quote = *pos) == '"' || quote == '\'') { + ++pos; + while (*pos && *pos != quote) { + if (*pos == '\\' && pos[1] && pos[1] == quote) { + pos += 2; + } else { + ++pos; + } + } + if (*pos) { + ++pos; + } + } else ++pos; + } + if (*pos == '\0') { + res = estrdup(*line); + *line += strlen(*line); + return res; + } + + res = estrndup(*line, pos - *line); + + while (*pos == stop) { + ++pos; + } + + *line = pos; + return res; +} + +static char *substring_conf(char *start, int len, char quote) +{ + char *result = emalloc(len + 1); + char *resp = result; + int i; + + for (i = 0; i < len && start[i] != quote; ++i) { + if (start[i] == '\\' && (start[i + 1] == '\\' || (quote && start[i + 1] == quote))) { + *resp++ = start[++i]; + } else { + *resp++ = start[i]; + } + } + + *resp = '\0'; + return result; +} + +static char *php_ap_getword_conf(const zend_encoding *encoding, char *str) +{ + while (*str && isspace(*str)) { + ++str; + } + + if (!*str) { + return estrdup(""); + } + + if (*str == '"' || *str == '\'') { + char quote = *str; + + str++; + return substring_conf(str, (int)strlen(str), quote); + } else { + char *strend = str; + + while (*strend && !isspace(*strend)) { + ++strend; + } + return substring_conf(str, strend - str, 0); + } +} + +static char *php_ap_basename(const zend_encoding *encoding, char *path) +{ + char *s = strrchr(path, '\\'); + char *s2 = strrchr(path, '/'); + + if (s && s2) { + if (s > s2) { + ++s; + } else { + s = ++s2; + } + return s; + } else if (s) { + return ++s; + } else if (s2) { + return ++s2; + } + return path; +} + +/* + * Search for a string in a fixed-length byte string. + * If partial is true, partial matches are allowed at the end of the buffer. + * Returns NULL if not found, or a pointer to the start of the first match. + */ +static void *php_ap_memstr(char *haystack, int haystacklen, char *needle, int needlen, int partial) +{ + int len = haystacklen; + char *ptr = haystack; + + /* iterate through first character matches */ + while( (ptr = memchr(ptr, needle[0], len)) ) { + + /* calculate length after match */ + len = haystacklen - (ptr - (char *)haystack); + + /* done if matches up to capacity of buffer */ + if (memcmp(needle, ptr, needlen < len ? needlen : len) == 0 && (partial || len >= needlen)) { + break; + } + + /* next character */ + ptr++; len--; + } + + return ptr; +} + +/* read until a boundary condition */ +static int multipart_buffer_read(multipart_buffer *self, char *buf, size_t bytes, int *end) +{ + size_t len, max; + char *bound; + + /* fill buffer if needed */ + if (bytes > (size_t)self->bytes_in_buffer) { + fill_buffer(self); + } + + /* look for a potential boundary match, only read data up to that point */ + if ((bound = php_ap_memstr(self->buf_begin, self->bytes_in_buffer, self->boundary_next, self->boundary_next_len, 1))) { + max = bound - self->buf_begin; + if (end && php_ap_memstr(self->buf_begin, self->bytes_in_buffer, self->boundary_next, self->boundary_next_len, 0)) { + *end = 1; + } + } else { + max = self->bytes_in_buffer; + } + + /* maximum number of bytes we are reading */ + len = max < bytes-1 ? max : bytes-1; + + /* if we read any data... */ + if (len > 0) { + + /* copy the data */ + memcpy(buf, self->buf_begin, len); + buf[len] = 0; + + if (bound && len > 0 && buf[len-1] == '\r') { + buf[--len] = 0; + } + + /* update the buffer */ + self->bytes_in_buffer -= (int)len; + self->buf_begin += len; + } + + return (int)len; +} + +/* + XXX: this is horrible memory-usage-wise, but we only expect + to do this on small pieces of form data. +*/ +static char *multipart_buffer_read_body(multipart_buffer *self, size_t *len) +{ + char buf[FILLUNIT], *out=NULL; + int total_bytes=0, read_bytes=0; + + while((read_bytes = multipart_buffer_read(self, buf, sizeof(buf), NULL))) { + out = erealloc(out, total_bytes + read_bytes + 1); + memcpy(out + total_bytes, buf, read_bytes); + total_bytes += read_bytes; + } + + if (out) { + out[total_bytes] = '\0'; + } + *len = total_bytes; + + return out; +} +/* }}} */ + +/* + * The combined READER/HANDLER + * + */ + +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; + int64_t total_bytes = 0, max_file_size = 0; + int skip_upload = 0, anonindex = 0, is_anonymous; + HashTable *uploaded_files = NULL; + multipart_buffer *mbuff; + zval *array_ptr = (zval *) arg; + int fd = -1; + zend_llist header; + void *event_extra_data = NULL; + unsigned int llen = 0; + int upload_cnt = INI_INT("max_file_uploads"); + const zend_encoding *internal_encoding = zend_multibyte_get_internal_encoding(); + php_rfc1867_getword_t getword; + php_rfc1867_getword_conf_t getword_conf; + php_rfc1867_basename_t _basename; + zend_long count = 0; + + if (php_rfc1867_encoding_translation() && internal_encoding) { + getword = php_rfc1867_getword; + getword_conf = php_rfc1867_getword_conf; + _basename = php_rfc1867_basename; + } else { + getword = php_ap_getword; + getword_conf = php_ap_getword_conf; + _basename = php_ap_basename; + } + + if (SG(post_max_size) > 0 && SG(request_info).content_length > SG(post_max_size)) { + sapi_module.sapi_error(E_WARNING, "POST Content-Length of " ZEND_LONG_FMT " bytes exceeds the limit of " ZEND_LONG_FMT " bytes", SG(request_info).content_length, SG(post_max_size)); + return; + } + + /* Get the boundary */ + boundary = strstr(content_type_dup, "boundary"); + if (!boundary) { + int content_type_len = (int)strlen(content_type_dup); + char *content_type_lcase = estrndup(content_type_dup, content_type_len); + + php_strtolower(content_type_lcase, content_type_len); + boundary = strstr(content_type_lcase, "boundary"); + if (boundary) { + boundary = content_type_dup + (boundary - content_type_lcase); + } + efree(content_type_lcase); + } + + if (!boundary || !(boundary = strchr(boundary, '='))) { + sapi_module.sapi_error(E_WARNING, "Missing boundary in multipart/form-data POST data"); + return; + } + + boundary++; + boundary_len = (int)strlen(boundary); + + if (boundary[0] == '"') { + boundary++; + boundary_end = strchr(boundary, '"'); + if (!boundary_end) { + sapi_module.sapi_error(E_WARNING, "Invalid boundary in multipart/form-data POST data"); + return; + } + } else { + /* search for the end of the boundary */ + boundary_end = strpbrk(boundary, ",;"); + } + if (boundary_end) { + boundary_end[0] = '\0'; + boundary_len = boundary_end-boundary; + } + + /* Initialize the buffer */ + if (!(mbuff = multipart_buffer_new(boundary, boundary_len))) { + sapi_module.sapi_error(E_WARNING, "Unable to initialize the input buffer"); + return; + } + + /* Initialize $_FILES[] */ + zend_hash_init(&PG(rfc1867_protected_variables), 8, NULL, NULL, 0); + + ALLOC_HASHTABLE(uploaded_files); + zend_hash_init(uploaded_files, 8, NULL, free_filename, 0); + SG(rfc1867_uploaded_files) = uploaded_files; + + if (Z_TYPE(PG(http_globals)[TRACK_VARS_FILES]) != IS_ARRAY) { + /* php_auto_globals_create_files() might have already done that */ + array_init(&PG(http_globals)[TRACK_VARS_FILES]); + } + + zend_llist_init(&header, sizeof(mime_header_entry), (llist_dtor_func_t) php_free_hdr_entry, 0); + + if (php_rfc1867_callback != NULL) { + multipart_event_start event_start; + + event_start.content_length = SG(request_info).content_length; + if (php_rfc1867_callback(MULTIPART_EVENT_START, &event_start, &event_extra_data) == FAILURE) { + goto fileupload_done; + } + } + + while (!multipart_buffer_eof(mbuff)) + { + char buff[FILLUNIT]; + char *cd = NULL, *param = NULL, *filename = NULL, *tmp = NULL; + size_t blen = 0, wlen = 0; + zend_off_t offset; + + zend_llist_clean(&header); + + if (!multipart_buffer_headers(mbuff, &header)) { + goto fileupload_done; + } + + if ((cd = php_mime_get_hdr_value(header, "Content-Disposition"))) { + char *pair = NULL; + int end = 0; + + while (isspace(*cd)) { + ++cd; + } + + while (*cd && (pair = getword(mbuff->input_encoding, &cd, ';'))) + { + char *key = NULL, *word = pair; + + while (isspace(*cd)) { + ++cd; + } + + if (strchr(pair, '=')) { + key = getword(mbuff->input_encoding, &pair, '='); + + if (!strcasecmp(key, "name")) { + if (param) { + efree(param); + } + param = getword_conf(mbuff->input_encoding, pair); + if (mbuff->input_encoding && internal_encoding) { + unsigned char *new_param; + size_t new_param_len; + if ((size_t)-1 != zend_multibyte_encoding_converter(&new_param, &new_param_len, (unsigned char *)param, strlen(param), internal_encoding, mbuff->input_encoding)) { + efree(param); + param = (char *)new_param; + } + } + } else if (!strcasecmp(key, "filename")) { + if (filename) { + efree(filename); + } + filename = getword_conf(mbuff->input_encoding, pair); + if (mbuff->input_encoding && internal_encoding) { + unsigned char *new_filename; + size_t new_filename_len; + if ((size_t)-1 != zend_multibyte_encoding_converter(&new_filename, &new_filename_len, (unsigned char *)filename, strlen(filename), internal_encoding, mbuff->input_encoding)) { + efree(filename); + filename = (char *)new_filename; + } + } + } + } + if (key) { + efree(key); + } + efree(word); + } + + /* Normal form variable, safe to read all data into memory */ + if (!filename && param) { + size_t value_len; + char *value = multipart_buffer_read_body(mbuff, &value_len); + size_t new_val_len; /* Dummy variable */ + + if (!value) { + value = estrdup(""); + value_len = 0; + } + + if (mbuff->input_encoding && internal_encoding) { + unsigned char *new_value; + size_t new_value_len; + if ((size_t)-1 != zend_multibyte_encoding_converter(&new_value, &new_value_len, (unsigned char *)value, value_len, internal_encoding, mbuff->input_encoding)) { + efree(value); + value = (char *)new_value; + value_len = new_value_len; + } + } + + if (++count <= PG(max_input_vars) && sapi_module.input_filter(PARSE_POST, param, &value, value_len, &new_val_len)) { + if (php_rfc1867_callback != NULL) { + multipart_event_formdata event_formdata; + size_t newlength = new_val_len; + + event_formdata.post_bytes_processed = SG(read_post_bytes); + event_formdata.name = param; + event_formdata.value = &value; + event_formdata.length = new_val_len; + event_formdata.newlength = &newlength; + if (php_rfc1867_callback(MULTIPART_EVENT_FORMDATA, &event_formdata, &event_extra_data) == FAILURE) { + efree(param); + efree(value); + continue; + } + new_val_len = newlength; + } + safe_php_register_variable(param, value, new_val_len, array_ptr, 0); + } else { + if (count == PG(max_input_vars) + 1) { + php_error_docref(NULL, E_WARNING, "Input variables exceeded " ZEND_LONG_FMT ". To increase the limit change max_input_vars in php.ini.", PG(max_input_vars)); + } + + if (php_rfc1867_callback != NULL) { + multipart_event_formdata event_formdata; + + event_formdata.post_bytes_processed = SG(read_post_bytes); + event_formdata.name = param; + event_formdata.value = &value; + event_formdata.length = value_len; + event_formdata.newlength = NULL; + php_rfc1867_callback(MULTIPART_EVENT_FORMDATA, &event_formdata, &event_extra_data); + } + } + + if (!strcasecmp(param, "MAX_FILE_SIZE")) { +#ifdef HAVE_ATOLL + max_file_size = atoll(value); +#else + max_file_size = strtoll(value, NULL, 10); +#endif + } + + efree(param); + efree(value); + continue; + } + + /* If file_uploads=off, skip the file part */ + if (!PG(file_uploads)) { + skip_upload = 1; + } else if (upload_cnt <= 0) { + skip_upload = 1; + sapi_module.sapi_error(E_WARNING, "Maximum number of allowable file uploads has been exceeded"); + } + + /* Return with an error if the posted data is garbled */ + if (!param && !filename) { + sapi_module.sapi_error(E_WARNING, "File Upload Mime headers garbled"); + goto fileupload_done; + } + + if (!param) { + is_anonymous = 1; + param = emalloc(MAX_SIZE_ANONNAME); + snprintf(param, MAX_SIZE_ANONNAME, "%u", anonindex++); + } else { + is_anonymous = 0; + } + + /* New Rule: never repair potential malicious user input */ + if (!skip_upload) { + long c = 0; + tmp = param; + + while (*tmp) { + if (*tmp == '[') { + c++; + } else if (*tmp == ']') { + c--; + if (tmp[1] && tmp[1] != '[') { + skip_upload = 1; + break; + } + } + if (c < 0) { + skip_upload = 1; + break; + } + tmp++; + } + /* Brackets should always be closed */ + if(c != 0) { + skip_upload = 1; + } + } + + total_bytes = cancel_upload = 0; + temp_filename = NULL; + fd = -1; + + if (!skip_upload && php_rfc1867_callback != NULL) { + multipart_event_file_start event_file_start; + + event_file_start.post_bytes_processed = SG(read_post_bytes); + event_file_start.name = param; + event_file_start.filename = &filename; + if (php_rfc1867_callback(MULTIPART_EVENT_FILE_START, &event_file_start, &event_extra_data) == FAILURE) { + temp_filename = NULL; + efree(param); + efree(filename); + continue; + } + } + + if (skip_upload) { + efree(param); + efree(filename); + continue; + } + + if (filename[0] == '\0') { +#if DEBUG_FILE_UPLOAD + sapi_module.sapi_error(E_NOTICE, "No file uploaded"); +#endif + cancel_upload = UPLOAD_ERROR_D; + } + + offset = 0; + end = 0; + + if (!cancel_upload) { + /* only bother to open temp file if we have data */ + blen = multipart_buffer_read(mbuff, buff, sizeof(buff), &end); +#if DEBUG_FILE_UPLOAD + if (blen > 0) { +#else + /* in non-debug mode we have no problem with 0-length files */ + { +#endif + fd = php_open_temporary_fd_ex(PG(upload_tmp_dir), "php", &temp_filename, 1); + upload_cnt--; + if (fd == -1) { + sapi_module.sapi_error(E_WARNING, "File upload error - unable to create a temporary file"); + cancel_upload = UPLOAD_ERROR_E; + } + } + } + + while (!cancel_upload && (blen > 0)) + { + if (php_rfc1867_callback != NULL) { + multipart_event_file_data event_file_data; + + event_file_data.post_bytes_processed = SG(read_post_bytes); + event_file_data.offset = offset; + event_file_data.data = buff; + event_file_data.length = blen; + event_file_data.newlength = &blen; + if (php_rfc1867_callback(MULTIPART_EVENT_FILE_DATA, &event_file_data, &event_extra_data) == FAILURE) { + cancel_upload = UPLOAD_ERROR_X; + continue; + } + } + + if (PG(upload_max_filesize) > 0 && (zend_long)(total_bytes+blen) > PG(upload_max_filesize)) { +#if DEBUG_FILE_UPLOAD + sapi_module.sapi_error(E_NOTICE, "upload_max_filesize of " ZEND_LONG_FMT " bytes exceeded - file [%s=%s] not saved", PG(upload_max_filesize), param, filename); +#endif + cancel_upload = UPLOAD_ERROR_A; + } else if (max_file_size && ((zend_long)(total_bytes+blen) > max_file_size)) { +#if DEBUG_FILE_UPLOAD + sapi_module.sapi_error(E_NOTICE, "MAX_FILE_SIZE of %" PRId64 " bytes exceeded - file [%s=%s] not saved", max_file_size, param, filename); +#endif + cancel_upload = UPLOAD_ERROR_B; + } else if (blen > 0) { +#ifdef PHP_WIN32 + wlen = write(fd, buff, (unsigned int)blen); +#else + wlen = write(fd, buff, blen); +#endif + + if (wlen == (size_t)-1) { + /* write failed */ +#if DEBUG_FILE_UPLOAD + sapi_module.sapi_error(E_NOTICE, "write() failed - %s", strerror(errno)); +#endif + cancel_upload = UPLOAD_ERROR_F; + } else if (wlen < blen) { +#if DEBUG_FILE_UPLOAD + sapi_module.sapi_error(E_NOTICE, "Only %zd bytes were written, expected to write %zd", wlen, blen); +#endif + cancel_upload = UPLOAD_ERROR_F; + } else { + total_bytes += wlen; + } + offset += wlen; + } + + /* read data for next iteration */ + blen = multipart_buffer_read(mbuff, buff, sizeof(buff), &end); + } + + if (fd != -1) { /* may not be initialized if file could not be created */ + close(fd); + } + + if (!cancel_upload && !end) { +#if DEBUG_FILE_UPLOAD + sapi_module.sapi_error(E_NOTICE, "Missing mime boundary at the end of the data for file %s", filename[0] != '\0' ? filename : ""); +#endif + cancel_upload = UPLOAD_ERROR_C; + } +#if DEBUG_FILE_UPLOAD + if (filename[0] != '\0' && total_bytes == 0 && !cancel_upload) { + sapi_module.sapi_error(E_WARNING, "Uploaded file size 0 - file [%s=%s] not saved", param, filename); + cancel_upload = 5; + } +#endif + if (php_rfc1867_callback != NULL) { + multipart_event_file_end event_file_end; + + event_file_end.post_bytes_processed = SG(read_post_bytes); + event_file_end.temp_filename = temp_filename ? ZSTR_VAL(temp_filename) : NULL; + event_file_end.cancel_upload = cancel_upload; + if (php_rfc1867_callback(MULTIPART_EVENT_FILE_END, &event_file_end, &event_extra_data) == FAILURE) { + cancel_upload = UPLOAD_ERROR_X; + } + } + + if (cancel_upload) { + if (temp_filename) { + if (cancel_upload != UPLOAD_ERROR_E) { /* file creation failed */ + unlink(ZSTR_VAL(temp_filename)); + } + zend_string_release_ex(temp_filename, 0); + } + temp_filename = NULL; + } else { + zend_hash_add_ptr(SG(rfc1867_uploaded_files), temp_filename, temp_filename); + } + + /* is_arr_upload is true when name of file upload field + * ends in [.*] + * start_arr is set to point to 1st [ */ + is_arr_upload = (start_arr = strchr(param,'[')) && (param[strlen(param)-1] == ']'); + + if (is_arr_upload) { + array_len = (int)strlen(start_arr); + if (array_index) { + efree(array_index); + } + array_index = estrndup(start_arr + 1, array_len - 2); + } + + /* Add $foo_name */ + if (llen < strlen(param) + MAX_SIZE_OF_INDEX + 1) { + llen = (int)strlen(param); + lbuf = (char *) safe_erealloc(lbuf, llen, 1, MAX_SIZE_OF_INDEX + 1); + llen += MAX_SIZE_OF_INDEX + 1; + } + + if (is_arr_upload) { + if (abuf) efree(abuf); + abuf = estrndup(param, strlen(param)-array_len); + snprintf(lbuf, llen, "%s_name[%s]", abuf, array_index); + } else { + snprintf(lbuf, llen, "%s_name", param); + } + + /* The \ check should technically be needed for win32 systems only where + * it is a valid path separator. However, IE in all it's wisdom always sends + * the full path of the file on the user's filesystem, which means that unless + * the user does basename() they get a bogus file name. Until IE's user base drops + * to nill or problem is fixed this code must remain enabled for all systems. */ + s = _basename(internal_encoding, filename); + if (!s) { + s = filename; + } + + if (!is_anonymous) { + safe_php_register_variable(lbuf, s, strlen(s), NULL, 0); + } + + /* Add $foo[name] */ + if (is_arr_upload) { + snprintf(lbuf, llen, "%s[name][%s]", abuf, array_index); + } else { + snprintf(lbuf, llen, "%s[name]", param); + } + register_http_post_files_variable(lbuf, s, &PG(http_globals)[TRACK_VARS_FILES], 0); + efree(filename); + s = NULL; + + /* Possible Content-Type: */ + if (cancel_upload || !(cd = php_mime_get_hdr_value(header, "Content-Type"))) { + cd = ""; + } else { + /* fix for Opera 6.01 */ + s = strchr(cd, ';'); + if (s != NULL) { + *s = '\0'; + } + } + + /* Add $foo_type */ + if (is_arr_upload) { + snprintf(lbuf, llen, "%s_type[%s]", abuf, array_index); + } else { + snprintf(lbuf, llen, "%s_type", param); + } + if (!is_anonymous) { + safe_php_register_variable(lbuf, cd, strlen(cd), NULL, 0); + } + + /* Add $foo[type] */ + if (is_arr_upload) { + snprintf(lbuf, llen, "%s[type][%s]", abuf, array_index); + } else { + snprintf(lbuf, llen, "%s[type]", param); + } + register_http_post_files_variable(lbuf, cd, &PG(http_globals)[TRACK_VARS_FILES], 0); + + /* Restore Content-Type Header */ + if (s != NULL) { + *s = ';'; + } + s = ""; + + { + /* store temp_filename as-is (in case upload_tmp_dir + * contains escapeable characters. escape only the variable name.) */ + zval zfilename; + + /* Initialize variables */ + add_protected_variable(param); + + /* if param is of form xxx[.*] this will cut it to xxx */ + if (!is_anonymous) { + if (temp_filename) { + ZVAL_STR_COPY(&zfilename, temp_filename); + } else { + ZVAL_EMPTY_STRING(&zfilename); + } + safe_php_register_variable_ex(param, &zfilename, NULL, 1); + } + + /* Add $foo[tmp_name] */ + if (is_arr_upload) { + snprintf(lbuf, llen, "%s[tmp_name][%s]", abuf, array_index); + } else { + snprintf(lbuf, llen, "%s[tmp_name]", param); + } + add_protected_variable(lbuf); + if (temp_filename) { + ZVAL_STR_COPY(&zfilename, temp_filename); + } else { + ZVAL_EMPTY_STRING(&zfilename); + } + register_http_post_files_variable_ex(lbuf, &zfilename, &PG(http_globals)[TRACK_VARS_FILES], 1); + } + + { + zval file_size, error_type; + int size_overflow = 0; + char file_size_buf[65]; + + ZVAL_LONG(&error_type, cancel_upload); + + /* Add $foo[error] */ + if (cancel_upload) { + ZVAL_LONG(&file_size, 0); + } else { + if (total_bytes > ZEND_LONG_MAX) { +#ifdef PHP_WIN32 + if (_i64toa_s(total_bytes, file_size_buf, 65, 10)) { + file_size_buf[0] = '0'; + file_size_buf[1] = '\0'; + } +#else + { + int __len = snprintf(file_size_buf, 65, "%" PRId64, total_bytes); + file_size_buf[__len] = '\0'; + } +#endif + size_overflow = 1; + + } else { + ZVAL_LONG(&file_size, total_bytes); + } + } + + if (is_arr_upload) { + snprintf(lbuf, llen, "%s[error][%s]", abuf, array_index); + } else { + snprintf(lbuf, llen, "%s[error]", param); + } + register_http_post_files_variable_ex(lbuf, &error_type, &PG(http_globals)[TRACK_VARS_FILES], 0); + + /* Add $foo_size */ + if (is_arr_upload) { + snprintf(lbuf, llen, "%s_size[%s]", abuf, array_index); + } else { + snprintf(lbuf, llen, "%s_size", param); + } + if (!is_anonymous) { + if (size_overflow) { + ZVAL_STRING(&file_size, file_size_buf); + } + safe_php_register_variable_ex(lbuf, &file_size, NULL, size_overflow); + } + + /* Add $foo[size] */ + if (is_arr_upload) { + snprintf(lbuf, llen, "%s[size][%s]", abuf, array_index); + } else { + snprintf(lbuf, llen, "%s[size]", param); + } + if (size_overflow) { + ZVAL_STRING(&file_size, file_size_buf); + } + register_http_post_files_variable_ex(lbuf, &file_size, &PG(http_globals)[TRACK_VARS_FILES], size_overflow); + } + efree(param); + } + } + +fileupload_done: + if (php_rfc1867_callback != NULL) { + multipart_event_end event_end; + + event_end.post_bytes_processed = SG(read_post_bytes); + php_rfc1867_callback(MULTIPART_EVENT_END, &event_end, &event_extra_data); + } + + if (lbuf) efree(lbuf); + if (abuf) efree(abuf); + if (array_index) efree(array_index); + zend_hash_destroy(&PG(rfc1867_protected_variables)); + zend_llist_destroy(&header); + if (mbuff->boundary_next) efree(mbuff->boundary_next); + if (mbuff->boundary) efree(mbuff->boundary); + if (mbuff->buffer) efree(mbuff->buffer); + if (mbuff) efree(mbuff); +} +/* }}} */ + +SAPI_API void php_rfc1867_set_multibyte_callbacks( + php_rfc1867_encoding_translation_t encoding_translation, + php_rfc1867_get_detect_order_t get_detect_order, + php_rfc1867_set_input_encoding_t set_input_encoding, + php_rfc1867_getword_t getword, + php_rfc1867_getword_conf_t getword_conf, + php_rfc1867_basename_t basename) /* {{{ */ +{ + php_rfc1867_encoding_translation = encoding_translation; + php_rfc1867_get_detect_order = get_detect_order; + php_rfc1867_set_input_encoding = set_input_encoding; + php_rfc1867_getword = getword; + php_rfc1867_getword_conf = getword_conf; + php_rfc1867_basename = basename; +} +/* }}} */ + +/* + * 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/main/streams/xp_socket.c b/main/streams/xp_socket.c index acdb1f8876ca7..c1065cfb09a53 100644 --- a/main/streams/xp_socket.c +++ b/main/streams/xp_socket.c @@ -569,37 +569,44 @@ static inline char *parse_ip_address_ex(const char *str, size_t str_len, int *po char *host = NULL; #ifdef HAVE_IPV6 - char *p; - if (*(str) == '[' && str_len > 1) { /* IPV6 notation to specify raw address with port (i.e. [fe80::1]:80) */ - p = memchr(str + 1, ']', str_len - 2); + char *p = memchr(str + 1, ']', str_len - 2), *e = NULL; if (!p || *(p + 1) != ':') { if (get_err) { *err = strpprintf(0, "Failed to parse IPv6 address \"%s\"", str); } return NULL; } - *portno = atoi(p + 2); + *portno = strtol(p + 2, &e, 10); + if (e && *e) { + if (get_err) { + *err = strpprintf(0, "Failed to parse address \"%s\"", str); + } + return NULL; + } return estrndup(str + 1, p - str - 1); } #endif + if (str_len) { colon = memchr(str, ':', str_len - 1); } else { colon = NULL; } + if (colon) { - *portno = atoi(colon + 1); - host = estrndup(str, colon - str); - } else { - if (get_err) { - *err = strpprintf(0, "Failed to parse address \"%s\"", str); + char *e = NULL; + *portno = strtol(colon + 1, &e, 10); + if (!e || !*e) { + return estrndup(str, colon - str); } - return NULL; } - return host; + if (get_err) { + *err = strpprintf(0, "Failed to parse address \"%s\"", str); + } + return NULL; } static inline char *parse_ip_address(php_stream_xport_param *xparam, int *portno) diff --git a/main/streams/xp_socket.c.orig b/main/streams/xp_socket.c.orig new file mode 100644 index 0000000000000..acdb1f8876ca7 --- /dev/null +++ b/main/streams/xp_socket.c.orig @@ -0,0 +1,937 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2018 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: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#include "php.h" +#include "ext/standard/file.h" +#include "streams/php_streams_int.h" +#include "php_network.h" + +#if defined(PHP_WIN32) || defined(__riscos__) +# undef AF_UNIX +#endif + +#if defined(AF_UNIX) +#include +#endif + +#ifndef MSG_DONTWAIT +# define MSG_DONTWAIT 0 +#endif + +#ifndef MSG_PEEK +# define MSG_PEEK 0 +#endif + +#ifdef PHP_WIN32 +/* send/recv family on windows expects int */ +# define XP_SOCK_BUF_SIZE(sz) (((sz) > INT_MAX) ? INT_MAX : (int)(sz)) +#else +# define XP_SOCK_BUF_SIZE(sz) (sz) +#endif + +const php_stream_ops php_stream_generic_socket_ops; +PHPAPI const php_stream_ops php_stream_socket_ops; +const php_stream_ops php_stream_udp_socket_ops; +#ifdef AF_UNIX +const php_stream_ops php_stream_unix_socket_ops; +const php_stream_ops php_stream_unixdg_socket_ops; +#endif + + +static int php_tcp_sockop_set_option(php_stream *stream, int option, int value, void *ptrparam); + +/* {{{ Generic socket stream operations */ +static size_t php_sockop_write(php_stream *stream, const char *buf, size_t count) +{ + php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; + int didwrite; + struct timeval *ptimeout; + + if (!sock || sock->socket == -1) { + return 0; + } + + if (sock->timeout.tv_sec == -1) + ptimeout = NULL; + else + ptimeout = &sock->timeout; + +retry: + didwrite = send(sock->socket, buf, XP_SOCK_BUF_SIZE(count), (sock->is_blocked && ptimeout) ? MSG_DONTWAIT : 0); + + if (didwrite <= 0) { + int err = php_socket_errno(); + char *estr; + + if (sock->is_blocked && (err == EWOULDBLOCK || err == EAGAIN)) { + int retval; + + sock->timeout_event = 0; + + do { + retval = php_pollfd_for(sock->socket, POLLOUT, ptimeout); + + if (retval == 0) { + sock->timeout_event = 1; + break; + } + + if (retval > 0) { + /* writable now; retry */ + goto retry; + } + + err = php_socket_errno(); + } while (err == EINTR); + } + estr = php_socket_strerror(err, NULL, 0); + php_error_docref(NULL, E_NOTICE, "send of " ZEND_LONG_FMT " bytes failed with errno=%d %s", + (zend_long)count, err, estr); + efree(estr); + } + + if (didwrite > 0) { + php_stream_notify_progress_increment(PHP_STREAM_CONTEXT(stream), didwrite, 0); + } + + if (didwrite < 0) { + didwrite = 0; + } + + return didwrite; +} + +static void php_sock_stream_wait_for_data(php_stream *stream, php_netstream_data_t *sock) +{ + int retval; + struct timeval *ptimeout; + + if (!sock || sock->socket == -1) { + return; + } + + sock->timeout_event = 0; + + if (sock->timeout.tv_sec == -1) + ptimeout = NULL; + else + ptimeout = &sock->timeout; + + while(1) { + retval = php_pollfd_for(sock->socket, PHP_POLLREADABLE, ptimeout); + + if (retval == 0) + sock->timeout_event = 1; + + if (retval >= 0) + break; + + if (php_socket_errno() != EINTR) + break; + } +} + +static size_t php_sockop_read(php_stream *stream, char *buf, size_t count) +{ + php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; + ssize_t nr_bytes = 0; + int err; + + if (!sock || sock->socket == -1) { + return 0; + } + + if (sock->is_blocked) { + php_sock_stream_wait_for_data(stream, sock); + if (sock->timeout_event) + return 0; + } + + nr_bytes = recv(sock->socket, buf, XP_SOCK_BUF_SIZE(count), (sock->is_blocked && sock->timeout.tv_sec != -1) ? MSG_DONTWAIT : 0); + err = php_socket_errno(); + + stream->eof = (nr_bytes == 0 || (nr_bytes == -1 && err != EWOULDBLOCK && err != EAGAIN)); + + if (nr_bytes > 0) { + php_stream_notify_progress_increment(PHP_STREAM_CONTEXT(stream), nr_bytes, 0); + } + + if (nr_bytes < 0) { + nr_bytes = 0; + } + + return nr_bytes; +} + + +static int php_sockop_close(php_stream *stream, int close_handle) +{ + php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; +#ifdef PHP_WIN32 + int n; +#endif + + if (!sock) { + return 0; + } + + if (close_handle) { + +#ifdef PHP_WIN32 + if (sock->socket == -1) + sock->socket = SOCK_ERR; +#endif + if (sock->socket != SOCK_ERR) { +#ifdef PHP_WIN32 + /* prevent more data from coming in */ + shutdown(sock->socket, SHUT_RD); + + /* try to make sure that the OS sends all data before we close the connection. + * Essentially, we are waiting for the socket to become writeable, which means + * that all pending data has been sent. + * We use a small timeout which should encourage the OS to send the data, + * but at the same time avoid hanging indefinitely. + * */ + do { + n = php_pollfd_for_ms(sock->socket, POLLOUT, 500); + } while (n == -1 && php_socket_errno() == EINTR); +#endif + closesocket(sock->socket); + sock->socket = SOCK_ERR; + } + + } + + pefree(sock, php_stream_is_persistent(stream)); + + return 0; +} + +static int php_sockop_flush(php_stream *stream) +{ +#if 0 + php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; + return fsync(sock->socket); +#endif + return 0; +} + +static int php_sockop_stat(php_stream *stream, php_stream_statbuf *ssb) +{ +#if ZEND_WIN32 + return 0; +#else + php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; + + return zend_fstat(sock->socket, &ssb->sb); +#endif +} + +static inline int sock_sendto(php_netstream_data_t *sock, const char *buf, size_t buflen, int flags, + struct sockaddr *addr, socklen_t addrlen + ) +{ + int ret; + if (addr) { + ret = sendto(sock->socket, buf, XP_SOCK_BUF_SIZE(buflen), flags, addr, XP_SOCK_BUF_SIZE(addrlen)); + + return (ret == SOCK_CONN_ERR) ? -1 : ret; + } +#ifdef PHP_WIN32 + return ((ret = send(sock->socket, buf, buflen > INT_MAX ? INT_MAX : (int)buflen, flags)) == SOCK_CONN_ERR) ? -1 : ret; +#else + return ((ret = send(sock->socket, buf, buflen, flags)) == SOCK_CONN_ERR) ? -1 : ret; +#endif +} + +static inline int sock_recvfrom(php_netstream_data_t *sock, char *buf, size_t buflen, int flags, + zend_string **textaddr, + struct sockaddr **addr, socklen_t *addrlen + ) +{ + int ret; + int want_addr = textaddr || addr; + + if (want_addr) { + php_sockaddr_storage sa; + socklen_t sl = sizeof(sa); + ret = recvfrom(sock->socket, buf, XP_SOCK_BUF_SIZE(buflen), flags, (struct sockaddr*)&sa, &sl); + ret = (ret == SOCK_CONN_ERR) ? -1 : ret; + if (sl) { + php_network_populate_name_from_sockaddr((struct sockaddr*)&sa, sl, + textaddr, addr, addrlen); + } else { + if (textaddr) { + *textaddr = ZSTR_EMPTY_ALLOC(); + } + if (addr) { + *addr = NULL; + *addrlen = 0; + } + } + } else { + ret = recv(sock->socket, buf, XP_SOCK_BUF_SIZE(buflen), flags); + ret = (ret == SOCK_CONN_ERR) ? -1 : ret; + } + + return ret; +} + +static int php_sockop_set_option(php_stream *stream, int option, int value, void *ptrparam) +{ + int oldmode, flags; + php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; + php_stream_xport_param *xparam; + + if (!sock) { + return PHP_STREAM_OPTION_RETURN_NOTIMPL; + } + + switch(option) { + case PHP_STREAM_OPTION_CHECK_LIVENESS: + { + struct timeval tv; + char buf; + int alive = 1; + + if (value == -1) { + if (sock->timeout.tv_sec == -1) { + tv.tv_sec = FG(default_socket_timeout); + tv.tv_usec = 0; + } else { + tv = sock->timeout; + } + } else { + tv.tv_sec = value; + tv.tv_usec = 0; + } + + if (sock->socket == -1) { + alive = 0; + } else if (php_pollfd_for(sock->socket, PHP_POLLREADABLE|POLLPRI, &tv) > 0) { +#ifdef PHP_WIN32 + int ret; +#else + ssize_t ret; +#endif + int err; + + ret = recv(sock->socket, &buf, sizeof(buf), MSG_PEEK); + err = php_socket_errno(); + if (0 == ret || /* the counterpart did properly shutdown*/ + (0 > ret && err != EWOULDBLOCK && err != EAGAIN && err != EMSGSIZE)) { /* there was an unrecoverable error */ + alive = 0; + } + } + return alive ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR; + } + + case PHP_STREAM_OPTION_BLOCKING: + oldmode = sock->is_blocked; + if (SUCCESS == php_set_sock_blocking(sock->socket, value)) { + sock->is_blocked = value; + return oldmode; + } + return PHP_STREAM_OPTION_RETURN_ERR; + + case PHP_STREAM_OPTION_READ_TIMEOUT: + sock->timeout = *(struct timeval*)ptrparam; + sock->timeout_event = 0; + return PHP_STREAM_OPTION_RETURN_OK; + + case PHP_STREAM_OPTION_META_DATA_API: + add_assoc_bool((zval *)ptrparam, "timed_out", sock->timeout_event); + add_assoc_bool((zval *)ptrparam, "blocked", sock->is_blocked); + add_assoc_bool((zval *)ptrparam, "eof", stream->eof); + return PHP_STREAM_OPTION_RETURN_OK; + + case PHP_STREAM_OPTION_XPORT_API: + xparam = (php_stream_xport_param *)ptrparam; + + switch (xparam->op) { + case STREAM_XPORT_OP_LISTEN: + xparam->outputs.returncode = (listen(sock->socket, xparam->inputs.backlog) == 0) ? 0: -1; + return PHP_STREAM_OPTION_RETURN_OK; + + case STREAM_XPORT_OP_GET_NAME: + xparam->outputs.returncode = php_network_get_sock_name(sock->socket, + xparam->want_textaddr ? &xparam->outputs.textaddr : NULL, + xparam->want_addr ? &xparam->outputs.addr : NULL, + xparam->want_addr ? &xparam->outputs.addrlen : NULL + ); + return PHP_STREAM_OPTION_RETURN_OK; + + case STREAM_XPORT_OP_GET_PEER_NAME: + xparam->outputs.returncode = php_network_get_peer_name(sock->socket, + xparam->want_textaddr ? &xparam->outputs.textaddr : NULL, + xparam->want_addr ? &xparam->outputs.addr : NULL, + xparam->want_addr ? &xparam->outputs.addrlen : NULL + ); + return PHP_STREAM_OPTION_RETURN_OK; + + case STREAM_XPORT_OP_SEND: + flags = 0; + if ((xparam->inputs.flags & STREAM_OOB) == STREAM_OOB) { + flags |= MSG_OOB; + } + xparam->outputs.returncode = sock_sendto(sock, + xparam->inputs.buf, xparam->inputs.buflen, + flags, + xparam->inputs.addr, + xparam->inputs.addrlen); + if (xparam->outputs.returncode == -1) { + char *err = php_socket_strerror(php_socket_errno(), NULL, 0); + php_error_docref(NULL, E_WARNING, + "%s\n", err); + efree(err); + } + return PHP_STREAM_OPTION_RETURN_OK; + + case STREAM_XPORT_OP_RECV: + flags = 0; + if ((xparam->inputs.flags & STREAM_OOB) == STREAM_OOB) { + flags |= MSG_OOB; + } + if ((xparam->inputs.flags & STREAM_PEEK) == STREAM_PEEK) { + flags |= MSG_PEEK; + } + xparam->outputs.returncode = sock_recvfrom(sock, + xparam->inputs.buf, xparam->inputs.buflen, + flags, + xparam->want_textaddr ? &xparam->outputs.textaddr : NULL, + xparam->want_addr ? &xparam->outputs.addr : NULL, + xparam->want_addr ? &xparam->outputs.addrlen : NULL + ); + return PHP_STREAM_OPTION_RETURN_OK; + + +#ifdef HAVE_SHUTDOWN +# ifndef SHUT_RD +# define SHUT_RD 0 +# endif +# ifndef SHUT_WR +# define SHUT_WR 1 +# endif +# ifndef SHUT_RDWR +# define SHUT_RDWR 2 +# endif + case STREAM_XPORT_OP_SHUTDOWN: { + static const int shutdown_how[] = {SHUT_RD, SHUT_WR, SHUT_RDWR}; + + xparam->outputs.returncode = shutdown(sock->socket, shutdown_how[xparam->how]); + return PHP_STREAM_OPTION_RETURN_OK; + } +#endif + + default: + return PHP_STREAM_OPTION_RETURN_NOTIMPL; + } + + default: + return PHP_STREAM_OPTION_RETURN_NOTIMPL; + } +} + +static int php_sockop_cast(php_stream *stream, int castas, void **ret) +{ + php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; + + if (!sock) { + return FAILURE; + } + + switch(castas) { + case PHP_STREAM_AS_STDIO: + if (ret) { + *(FILE**)ret = fdopen(sock->socket, stream->mode); + if (*ret) + return SUCCESS; + return FAILURE; + } + return SUCCESS; + case PHP_STREAM_AS_FD_FOR_SELECT: + case PHP_STREAM_AS_FD: + case PHP_STREAM_AS_SOCKETD: + if (ret) + *(php_socket_t *)ret = sock->socket; + return SUCCESS; + default: + return FAILURE; + } +} +/* }}} */ + +/* These may look identical, but we need them this way so that + * we can determine which type of socket we are dealing with + * by inspecting stream->ops. + * A "useful" side-effect is that the user's scripts can then + * make similar decisions using stream_get_meta_data. + * */ +const php_stream_ops php_stream_generic_socket_ops = { + php_sockop_write, php_sockop_read, + php_sockop_close, php_sockop_flush, + "generic_socket", + NULL, /* seek */ + php_sockop_cast, + php_sockop_stat, + php_sockop_set_option, +}; + + +const php_stream_ops php_stream_socket_ops = { + php_sockop_write, php_sockop_read, + php_sockop_close, php_sockop_flush, + "tcp_socket", + NULL, /* seek */ + php_sockop_cast, + php_sockop_stat, + php_tcp_sockop_set_option, +}; + +const php_stream_ops php_stream_udp_socket_ops = { + php_sockop_write, php_sockop_read, + php_sockop_close, php_sockop_flush, + "udp_socket", + NULL, /* seek */ + php_sockop_cast, + php_sockop_stat, + php_tcp_sockop_set_option, +}; + +#ifdef AF_UNIX +const php_stream_ops php_stream_unix_socket_ops = { + php_sockop_write, php_sockop_read, + php_sockop_close, php_sockop_flush, + "unix_socket", + NULL, /* seek */ + php_sockop_cast, + php_sockop_stat, + php_tcp_sockop_set_option, +}; +const php_stream_ops php_stream_unixdg_socket_ops = { + php_sockop_write, php_sockop_read, + php_sockop_close, php_sockop_flush, + "udg_socket", + NULL, /* seek */ + php_sockop_cast, + php_sockop_stat, + php_tcp_sockop_set_option, +}; +#endif + + +/* network socket operations */ + +#ifdef AF_UNIX +static inline int parse_unix_address(php_stream_xport_param *xparam, struct sockaddr_un *unix_addr) +{ + memset(unix_addr, 0, sizeof(*unix_addr)); + unix_addr->sun_family = AF_UNIX; + + /* we need to be binary safe on systems that support an abstract + * namespace */ + if (xparam->inputs.namelen >= sizeof(unix_addr->sun_path)) { + /* On linux, when the path begins with a NUL byte we are + * referring to an abstract namespace. In theory we should + * allow an extra byte below, since we don't need the NULL. + * BUT, to get into this branch of code, the name is too long, + * so we don't care. */ + xparam->inputs.namelen = sizeof(unix_addr->sun_path) - 1; + php_error_docref(NULL, E_NOTICE, + "socket path exceeded the maximum allowed length of %lu bytes " + "and was truncated", (unsigned long)sizeof(unix_addr->sun_path)); + } + + memcpy(unix_addr->sun_path, xparam->inputs.name, xparam->inputs.namelen); + + return 1; +} +#endif + +static inline char *parse_ip_address_ex(const char *str, size_t str_len, int *portno, int get_err, zend_string **err) +{ + char *colon; + char *host = NULL; + +#ifdef HAVE_IPV6 + char *p; + + if (*(str) == '[' && str_len > 1) { + /* IPV6 notation to specify raw address with port (i.e. [fe80::1]:80) */ + p = memchr(str + 1, ']', str_len - 2); + if (!p || *(p + 1) != ':') { + if (get_err) { + *err = strpprintf(0, "Failed to parse IPv6 address \"%s\"", str); + } + return NULL; + } + *portno = atoi(p + 2); + return estrndup(str + 1, p - str - 1); + } +#endif + if (str_len) { + colon = memchr(str, ':', str_len - 1); + } else { + colon = NULL; + } + if (colon) { + *portno = atoi(colon + 1); + host = estrndup(str, colon - str); + } else { + if (get_err) { + *err = strpprintf(0, "Failed to parse address \"%s\"", str); + } + return NULL; + } + + return host; +} + +static inline char *parse_ip_address(php_stream_xport_param *xparam, int *portno) +{ + return parse_ip_address_ex(xparam->inputs.name, xparam->inputs.namelen, portno, xparam->want_errortext, &xparam->outputs.error_text); +} + +static inline int php_tcp_sockop_bind(php_stream *stream, php_netstream_data_t *sock, + php_stream_xport_param *xparam) +{ + char *host = NULL; + int portno, err; + long sockopts = STREAM_SOCKOP_NONE; + zval *tmpzval = NULL; + +#ifdef AF_UNIX + if (stream->ops == &php_stream_unix_socket_ops || stream->ops == &php_stream_unixdg_socket_ops) { + struct sockaddr_un unix_addr; + + sock->socket = socket(PF_UNIX, stream->ops == &php_stream_unix_socket_ops ? SOCK_STREAM : SOCK_DGRAM, 0); + + if (sock->socket == SOCK_ERR) { + if (xparam->want_errortext) { + xparam->outputs.error_text = strpprintf(0, "Failed to create unix%s socket %s", + stream->ops == &php_stream_unix_socket_ops ? "" : "datagram", + strerror(errno)); + } + return -1; + } + + parse_unix_address(xparam, &unix_addr); + + return bind(sock->socket, (const struct sockaddr *)&unix_addr, + (socklen_t) XtOffsetOf(struct sockaddr_un, sun_path) + xparam->inputs.namelen); + } +#endif + + host = parse_ip_address(xparam, &portno); + + if (host == NULL) { + return -1; + } + +#ifdef IPV6_V6ONLY + if (PHP_STREAM_CONTEXT(stream) + && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "ipv6_v6only")) != NULL + && Z_TYPE_P(tmpzval) != IS_NULL + ) { + sockopts |= STREAM_SOCKOP_IPV6_V6ONLY; + sockopts |= STREAM_SOCKOP_IPV6_V6ONLY_ENABLED * zend_is_true(tmpzval); + } +#endif + +#ifdef SO_REUSEPORT + if (PHP_STREAM_CONTEXT(stream) + && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_reuseport")) != NULL + && zend_is_true(tmpzval) + ) { + sockopts |= STREAM_SOCKOP_SO_REUSEPORT; + } +#endif + +#ifdef SO_BROADCAST + if (stream->ops == &php_stream_udp_socket_ops /* SO_BROADCAST is only applicable for UDP */ + && PHP_STREAM_CONTEXT(stream) + && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_broadcast")) != NULL + && zend_is_true(tmpzval) + ) { + sockopts |= STREAM_SOCKOP_SO_BROADCAST; + } +#endif + + sock->socket = php_network_bind_socket_to_local_addr(host, portno, + stream->ops == &php_stream_udp_socket_ops ? SOCK_DGRAM : SOCK_STREAM, + sockopts, + xparam->want_errortext ? &xparam->outputs.error_text : NULL, + &err + ); + + if (host) { + efree(host); + } + + return sock->socket == -1 ? -1 : 0; +} + +static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_t *sock, + php_stream_xport_param *xparam) +{ + char *host = NULL, *bindto = NULL; + int portno, bindport = 0; + int err = 0; + int ret; + zval *tmpzval = NULL; + long sockopts = STREAM_SOCKOP_NONE; + +#ifdef AF_UNIX + if (stream->ops == &php_stream_unix_socket_ops || stream->ops == &php_stream_unixdg_socket_ops) { + struct sockaddr_un unix_addr; + + sock->socket = socket(PF_UNIX, stream->ops == &php_stream_unix_socket_ops ? SOCK_STREAM : SOCK_DGRAM, 0); + + if (sock->socket == SOCK_ERR) { + if (xparam->want_errortext) { + xparam->outputs.error_text = strpprintf(0, "Failed to create unix socket"); + } + return -1; + } + + parse_unix_address(xparam, &unix_addr); + + ret = php_network_connect_socket(sock->socket, + (const struct sockaddr *)&unix_addr, (socklen_t) XtOffsetOf(struct sockaddr_un, sun_path) + xparam->inputs.namelen, + xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC, xparam->inputs.timeout, + xparam->want_errortext ? &xparam->outputs.error_text : NULL, + &err); + + xparam->outputs.error_code = err; + + goto out; + } +#endif + + host = parse_ip_address(xparam, &portno); + + if (host == NULL) { + return -1; + } + + if (PHP_STREAM_CONTEXT(stream) && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "bindto")) != NULL) { + if (Z_TYPE_P(tmpzval) != IS_STRING) { + if (xparam->want_errortext) { + xparam->outputs.error_text = strpprintf(0, "local_addr context option is not a string."); + } + efree(host); + return -1; + } + bindto = parse_ip_address_ex(Z_STRVAL_P(tmpzval), Z_STRLEN_P(tmpzval), &bindport, xparam->want_errortext, &xparam->outputs.error_text); + } + +#ifdef SO_BROADCAST + if (stream->ops == &php_stream_udp_socket_ops /* SO_BROADCAST is only applicable for UDP */ + && PHP_STREAM_CONTEXT(stream) + && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "so_broadcast")) != NULL + && zend_is_true(tmpzval) + ) { + sockopts |= STREAM_SOCKOP_SO_BROADCAST; + } +#endif + + if (stream->ops != &php_stream_udp_socket_ops /* TCP_NODELAY is only applicable for TCP */ +#ifdef AF_UNIX + && stream->ops != &php_stream_unix_socket_ops + && stream->ops != &php_stream_unixdg_socket_ops +#endif + && PHP_STREAM_CONTEXT(stream) + && (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_nodelay")) != NULL + && zend_is_true(tmpzval) + ) { + sockopts |= STREAM_SOCKOP_TCP_NODELAY; + } + + /* Note: the test here for php_stream_udp_socket_ops is important, because we + * want the default to be TCP sockets so that the openssl extension can + * re-use this code. */ + + sock->socket = php_network_connect_socket_to_host(host, portno, + stream->ops == &php_stream_udp_socket_ops ? SOCK_DGRAM : SOCK_STREAM, + xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC, + xparam->inputs.timeout, + xparam->want_errortext ? &xparam->outputs.error_text : NULL, + &err, + bindto, + bindport, + sockopts + ); + + ret = sock->socket == -1 ? -1 : 0; + xparam->outputs.error_code = err; + + if (host) { + efree(host); + } + if (bindto) { + efree(bindto); + } + +#ifdef AF_UNIX +out: +#endif + + if (ret >= 0 && xparam->op == STREAM_XPORT_OP_CONNECT_ASYNC && err == EINPROGRESS) { + /* indicates pending connection */ + return 1; + } + + return ret; +} + +static inline int php_tcp_sockop_accept(php_stream *stream, php_netstream_data_t *sock, + php_stream_xport_param *xparam STREAMS_DC) +{ + int clisock; + zend_bool nodelay = 0; + zval *tmpzval = NULL; + + xparam->outputs.client = NULL; + + if ((NULL != PHP_STREAM_CONTEXT(stream)) && + (tmpzval = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "tcp_nodelay")) != NULL && + zend_is_true(tmpzval)) { + nodelay = 1; + } + + clisock = php_network_accept_incoming(sock->socket, + xparam->want_textaddr ? &xparam->outputs.textaddr : NULL, + xparam->want_addr ? &xparam->outputs.addr : NULL, + xparam->want_addr ? &xparam->outputs.addrlen : NULL, + xparam->inputs.timeout, + xparam->want_errortext ? &xparam->outputs.error_text : NULL, + &xparam->outputs.error_code, + nodelay); + + if (clisock >= 0) { + php_netstream_data_t *clisockdata = (php_netstream_data_t*) emalloc(sizeof(*clisockdata)); + + memcpy(clisockdata, sock, sizeof(*clisockdata)); + clisockdata->socket = clisock; + + xparam->outputs.client = php_stream_alloc_rel(stream->ops, clisockdata, NULL, "r+"); + if (xparam->outputs.client) { + xparam->outputs.client->ctx = stream->ctx; + if (stream->ctx) { + GC_ADDREF(stream->ctx); + } + } + } + + return xparam->outputs.client == NULL ? -1 : 0; +} + +static int php_tcp_sockop_set_option(php_stream *stream, int option, int value, void *ptrparam) +{ + php_netstream_data_t *sock = (php_netstream_data_t*)stream->abstract; + php_stream_xport_param *xparam; + + switch(option) { + case PHP_STREAM_OPTION_XPORT_API: + xparam = (php_stream_xport_param *)ptrparam; + + switch(xparam->op) { + case STREAM_XPORT_OP_CONNECT: + case STREAM_XPORT_OP_CONNECT_ASYNC: + xparam->outputs.returncode = php_tcp_sockop_connect(stream, sock, xparam); + return PHP_STREAM_OPTION_RETURN_OK; + + case STREAM_XPORT_OP_BIND: + xparam->outputs.returncode = php_tcp_sockop_bind(stream, sock, xparam); + return PHP_STREAM_OPTION_RETURN_OK; + + + case STREAM_XPORT_OP_ACCEPT: + xparam->outputs.returncode = php_tcp_sockop_accept(stream, sock, xparam STREAMS_CC); + return PHP_STREAM_OPTION_RETURN_OK; + default: + /* fall through */ + ; + } + } + return php_sockop_set_option(stream, option, value, ptrparam); +} + + +PHPAPI php_stream *php_stream_generic_socket_factory(const char *proto, size_t protolen, + const char *resourcename, size_t resourcenamelen, + const char *persistent_id, int options, int flags, + struct timeval *timeout, + php_stream_context *context STREAMS_DC) +{ + php_stream *stream = NULL; + php_netstream_data_t *sock; + const php_stream_ops *ops; + + /* which type of socket ? */ + if (strncmp(proto, "tcp", protolen) == 0) { + ops = &php_stream_socket_ops; + } else if (strncmp(proto, "udp", protolen) == 0) { + ops = &php_stream_udp_socket_ops; + } +#ifdef AF_UNIX + else if (strncmp(proto, "unix", protolen) == 0) { + ops = &php_stream_unix_socket_ops; + } else if (strncmp(proto, "udg", protolen) == 0) { + ops = &php_stream_unixdg_socket_ops; + } +#endif + else { + /* should never happen */ + return NULL; + } + + sock = pemalloc(sizeof(php_netstream_data_t), persistent_id ? 1 : 0); + memset(sock, 0, sizeof(php_netstream_data_t)); + + sock->is_blocked = 1; + sock->timeout.tv_sec = FG(default_socket_timeout); + sock->timeout.tv_usec = 0; + + /* we don't know the socket until we have determined if we are binding or + * connecting */ + sock->socket = -1; + + stream = php_stream_alloc_rel(ops, sock, persistent_id, "r+"); + + if (stream == NULL) { + pefree(sock, persistent_id ? 1 : 0); + return NULL; + } + + if (flags == 0) { + return stream; + } + + return stream; +} + + +/* + * 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 + */