/home/lnzliplg/public_html/vendor.zip
PKyc�\QuQ�����composer/installed.jsonnu�[���{
    "packages": [
        {
            "name": "bacon/bacon-qr-code",
            "version": "2.0.8",
            "version_normalized": "2.0.8.0",
            "source": {
                "type": "git",
                "url": "https://github.com/Bacon/BaconQrCode.git",
                "reference": "8674e51bb65af933a5ffaf1c308a660387c35c22"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/8674e51bb65af933a5ffaf1c308a660387c35c22",
                "reference": "8674e51bb65af933a5ffaf1c308a660387c35c22",
                "shasum": ""
            },
            "require": {
                "dasprid/enum": "^1.0.3",
                "ext-iconv": "*",
                "php": "^7.1 || ^8.0"
            },
            "require-dev": {
                "phly/keep-a-changelog": "^2.1",
                "phpunit/phpunit": "^7 | ^8 | ^9",
                "spatie/phpunit-snapshot-assertions": "^4.2.9",
                "squizlabs/php_codesniffer": "^3.4"
            },
            "suggest": {
                "ext-imagick": "to generate QR code images"
            },
            "time": "2022-12-07T17:46:57+00:00",
            "type": "library",
            "installation-source": "dist",
            "autoload": {
                "psr-4": {
                    "BaconQrCode\\": "src/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-2-Clause"
            ],
            "authors": [
                {
                    "name": "Ben Scholzen 'DASPRiD'",
                    "email": "mail@dasprids.de",
                    "homepage": "https://dasprids.de/",
                    "role": "Developer"
                }
            ],
            "description": "BaconQrCode is a QR code generator for PHP.",
            "homepage": "https://github.com/Bacon/BaconQrCode",
            "support": {
                "issues": "https://github.com/Bacon/BaconQrCode/issues",
                "source": "https://github.com/Bacon/BaconQrCode/tree/2.0.8"
            },
            "install-path": "../bacon/bacon-qr-code"
        },
        {
            "name": "dasprid/enum",
            "version": "1.0.5",
            "version_normalized": "1.0.5.0",
            "source": {
                "type": "git",
                "url": "https://github.com/DASPRiD/Enum.git",
                "reference": "6faf451159fb8ba4126b925ed2d78acfce0dc016"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/6faf451159fb8ba4126b925ed2d78acfce0dc016",
                "reference": "6faf451159fb8ba4126b925ed2d78acfce0dc016",
                "shasum": ""
            },
            "require": {
                "php": ">=7.1 <9.0"
            },
            "require-dev": {
                "phpunit/phpunit": "^7 | ^8 | ^9",
                "squizlabs/php_codesniffer": "*"
            },
            "time": "2023-08-25T16:18:39+00:00",
            "type": "library",
            "installation-source": "dist",
            "autoload": {
                "psr-4": {
                    "DASPRiD\\Enum\\": "src/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-2-Clause"
            ],
            "authors": [
                {
                    "name": "Ben Scholzen 'DASPRiD'",
                    "email": "mail@dasprids.de",
                    "homepage": "https://dasprids.de/",
                    "role": "Developer"
                }
            ],
            "description": "PHP 7.1 enum implementation",
            "keywords": [
                "enum",
                "map"
            ],
            "support": {
                "issues": "https://github.com/DASPRiD/Enum/issues",
                "source": "https://github.com/DASPRiD/Enum/tree/1.0.5"
            },
            "install-path": "../dasprid/enum"
        },
        {
            "name": "guzzlehttp/guzzle",
            "version": "7.8.1",
            "version_normalized": "7.8.1.0",
            "source": {
                "type": "git",
                "url": "https://github.com/guzzle/guzzle.git",
                "reference": "41042bc7ab002487b876a0683fc8dce04ddce104"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104",
                "reference": "41042bc7ab002487b876a0683fc8dce04ddce104",
                "shasum": ""
            },
            "require": {
                "ext-json": "*",
                "guzzlehttp/promises": "^1.5.3 || ^2.0.1",
                "guzzlehttp/psr7": "^1.9.1 || ^2.5.1",
                "php": "^7.2.5 || ^8.0",
                "psr/http-client": "^1.0",
                "symfony/deprecation-contracts": "^2.2 || ^3.0"
            },
            "provide": {
                "psr/http-client-implementation": "1.0"
            },
            "require-dev": {
                "bamarni/composer-bin-plugin": "^1.8.2",
                "ext-curl": "*",
                "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999",
                "php-http/message-factory": "^1.1",
                "phpunit/phpunit": "^8.5.36 || ^9.6.15",
                "psr/log": "^1.1 || ^2.0 || ^3.0"
            },
            "suggest": {
                "ext-curl": "Required for CURL handler support",
                "ext-intl": "Required for Internationalized Domain Name (IDN) support",
                "psr/log": "Required for using the Log middleware"
            },
            "time": "2023-12-03T20:35:24+00:00",
            "type": "library",
            "extra": {
                "bamarni-bin": {
                    "bin-links": true,
                    "forward-command": false
                }
            },
            "installation-source": "dist",
            "autoload": {
                "files": [
                    "src/functions_include.php"
                ],
                "psr-4": {
                    "GuzzleHttp\\": "src/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Graham Campbell",
                    "email": "hello@gjcampbell.co.uk",
                    "homepage": "https://github.com/GrahamCampbell"
                },
                {
                    "name": "Michael Dowling",
                    "email": "mtdowling@gmail.com",
                    "homepage": "https://github.com/mtdowling"
                },
                {
                    "name": "Jeremy Lindblom",
                    "email": "jeremeamia@gmail.com",
                    "homepage": "https://github.com/jeremeamia"
                },
                {
                    "name": "George Mponos",
                    "email": "gmponos@gmail.com",
                    "homepage": "https://github.com/gmponos"
                },
                {
                    "name": "Tobias Nyholm",
                    "email": "tobias.nyholm@gmail.com",
                    "homepage": "https://github.com/Nyholm"
                },
                {
                    "name": "Márk Sági-Kazár",
                    "email": "mark.sagikazar@gmail.com",
                    "homepage": "https://github.com/sagikazarmark"
                },
                {
                    "name": "Tobias Schultze",
                    "email": "webmaster@tubo-world.de",
                    "homepage": "https://github.com/Tobion"
                }
            ],
            "description": "Guzzle is a PHP HTTP client library",
            "keywords": [
                "client",
                "curl",
                "framework",
                "http",
                "http client",
                "psr-18",
                "psr-7",
                "rest",
                "web service"
            ],
            "support": {
                "issues": "https://github.com/guzzle/guzzle/issues",
                "source": "https://github.com/guzzle/guzzle/tree/7.8.1"
            },
            "funding": [
                {
                    "url": "https://github.com/GrahamCampbell",
                    "type": "github"
                },
                {
                    "url": "https://github.com/Nyholm",
                    "type": "github"
                },
                {
                    "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle",
                    "type": "tidelift"
                }
            ],
            "install-path": "../guzzlehttp/guzzle"
        },
        {
            "name": "guzzlehttp/promises",
            "version": "2.0.2",
            "version_normalized": "2.0.2.0",
            "source": {
                "type": "git",
                "url": "https://github.com/guzzle/promises.git",
                "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/guzzle/promises/zipball/bbff78d96034045e58e13dedd6ad91b5d1253223",
                "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223",
                "shasum": ""
            },
            "require": {
                "php": "^7.2.5 || ^8.0"
            },
            "require-dev": {
                "bamarni/composer-bin-plugin": "^1.8.2",
                "phpunit/phpunit": "^8.5.36 || ^9.6.15"
            },
            "time": "2023-12-03T20:19:20+00:00",
            "type": "library",
            "extra": {
                "bamarni-bin": {
                    "bin-links": true,
                    "forward-command": false
                }
            },
            "installation-source": "dist",
            "autoload": {
                "psr-4": {
                    "GuzzleHttp\\Promise\\": "src/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Graham Campbell",
                    "email": "hello@gjcampbell.co.uk",
                    "homepage": "https://github.com/GrahamCampbell"
                },
                {
                    "name": "Michael Dowling",
                    "email": "mtdowling@gmail.com",
                    "homepage": "https://github.com/mtdowling"
                },
                {
                    "name": "Tobias Nyholm",
                    "email": "tobias.nyholm@gmail.com",
                    "homepage": "https://github.com/Nyholm"
                },
                {
                    "name": "Tobias Schultze",
                    "email": "webmaster@tubo-world.de",
                    "homepage": "https://github.com/Tobion"
                }
            ],
            "description": "Guzzle promises library",
            "keywords": [
                "promise"
            ],
            "support": {
                "issues": "https://github.com/guzzle/promises/issues",
                "source": "https://github.com/guzzle/promises/tree/2.0.2"
            },
            "funding": [
                {
                    "url": "https://github.com/GrahamCampbell",
                    "type": "github"
                },
                {
                    "url": "https://github.com/Nyholm",
                    "type": "github"
                },
                {
                    "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises",
                    "type": "tidelift"
                }
            ],
            "install-path": "../guzzlehttp/promises"
        },
        {
            "name": "guzzlehttp/psr7",
            "version": "2.6.2",
            "version_normalized": "2.6.2.0",
            "source": {
                "type": "git",
                "url": "https://github.com/guzzle/psr7.git",
                "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/guzzle/psr7/zipball/45b30f99ac27b5ca93cb4831afe16285f57b8221",
                "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221",
                "shasum": ""
            },
            "require": {
                "php": "^7.2.5 || ^8.0",
                "psr/http-factory": "^1.0",
                "psr/http-message": "^1.1 || ^2.0",
                "ralouphie/getallheaders": "^3.0"
            },
            "provide": {
                "psr/http-factory-implementation": "1.0",
                "psr/http-message-implementation": "1.0"
            },
            "require-dev": {
                "bamarni/composer-bin-plugin": "^1.8.2",
                "http-interop/http-factory-tests": "^0.9",
                "phpunit/phpunit": "^8.5.36 || ^9.6.15"
            },
            "suggest": {
                "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
            },
            "time": "2023-12-03T20:05:35+00:00",
            "type": "library",
            "extra": {
                "bamarni-bin": {
                    "bin-links": true,
                    "forward-command": false
                }
            },
            "installation-source": "dist",
            "autoload": {
                "psr-4": {
                    "GuzzleHttp\\Psr7\\": "src/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Graham Campbell",
                    "email": "hello@gjcampbell.co.uk",
                    "homepage": "https://github.com/GrahamCampbell"
                },
                {
                    "name": "Michael Dowling",
                    "email": "mtdowling@gmail.com",
                    "homepage": "https://github.com/mtdowling"
                },
                {
                    "name": "George Mponos",
                    "email": "gmponos@gmail.com",
                    "homepage": "https://github.com/gmponos"
                },
                {
                    "name": "Tobias Nyholm",
                    "email": "tobias.nyholm@gmail.com",
                    "homepage": "https://github.com/Nyholm"
                },
                {
                    "name": "Márk Sági-Kazár",
                    "email": "mark.sagikazar@gmail.com",
                    "homepage": "https://github.com/sagikazarmark"
                },
                {
                    "name": "Tobias Schultze",
                    "email": "webmaster@tubo-world.de",
                    "homepage": "https://github.com/Tobion"
                },
                {
                    "name": "Márk Sági-Kazár",
                    "email": "mark.sagikazar@gmail.com",
                    "homepage": "https://sagikazarmark.hu"
                }
            ],
            "description": "PSR-7 message implementation that also provides common utility methods",
            "keywords": [
                "http",
                "message",
                "psr-7",
                "request",
                "response",
                "stream",
                "uri",
                "url"
            ],
            "support": {
                "issues": "https://github.com/guzzle/psr7/issues",
                "source": "https://github.com/guzzle/psr7/tree/2.6.2"
            },
            "funding": [
                {
                    "url": "https://github.com/GrahamCampbell",
                    "type": "github"
                },
                {
                    "url": "https://github.com/Nyholm",
                    "type": "github"
                },
                {
                    "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7",
                    "type": "tidelift"
                }
            ],
            "install-path": "../guzzlehttp/psr7"
        },
        {
            "name": "kolab/net_ldap3",
            "version": "v1.1.5",
            "version_normalized": "1.1.5.0",
            "source": {
                "type": "git",
                "url": "https://git.kolab.org/diffusion/PNL/php-net_ldap3.git",
                "reference": "5a319cf437d75aad564ce7fd076cc5423722868b"
            },
            "require": {
                "pear/net_ldap2": ">=2.0.12",
                "php": ">=5.3.3"
            },
            "time": "2023-06-07T08:32:15+00:00",
            "type": "library",
            "installation-source": "source",
            "autoload": {
                "classmap": [
                    "lib/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "GPL-3.0+"
            ],
            "authors": [
                {
                    "name": "Jeroen van Meeuwen",
                    "email": "vanmeeuwen@kolabsys.com",
                    "role": "Lead"
                },
                {
                    "name": "Aleksander Machniak",
                    "email": "machniak@kolabsys.com",
                    "role": "Developer"
                },
                {
                    "name": "Thomas Bruederli",
                    "email": "roundcube@gmail.com",
                    "role": "Developer"
                }
            ],
            "description": "A successor of the PEAR:Net_LDAP2 module providing advanced functionality for accessing LDAP directories",
            "homepage": "http://git.kolab.org/pear/Net_LDAP3/",
            "keywords": [
                "PEAR",
                "ldap",
                "vlv"
            ],
            "install-path": "../kolab/net_ldap3"
        },
        {
            "name": "masterminds/html5",
            "version": "2.7.6",
            "version_normalized": "2.7.6.0",
            "source": {
                "type": "git",
                "url": "https://github.com/Masterminds/html5-php.git",
                "reference": "897eb517a343a2281f11bc5556d6548db7d93947"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/897eb517a343a2281f11bc5556d6548db7d93947",
                "reference": "897eb517a343a2281f11bc5556d6548db7d93947",
                "shasum": ""
            },
            "require": {
                "ext-ctype": "*",
                "ext-dom": "*",
                "ext-libxml": "*",
                "php": ">=5.3.0"
            },
            "require-dev": {
                "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7"
            },
            "time": "2022-08-18T16:18:26+00:00",
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.7-dev"
                }
            },
            "installation-source": "dist",
            "autoload": {
                "psr-4": {
                    "Masterminds\\": "src"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Matt Butcher",
                    "email": "technosophos@gmail.com"
                },
                {
                    "name": "Matt Farina",
                    "email": "matt@mattfarina.com"
                },
                {
                    "name": "Asmir Mustafic",
                    "email": "goetas@gmail.com"
                }
            ],
            "description": "An HTML5 parser and serializer.",
            "homepage": "http://masterminds.github.io/html5-php",
            "keywords": [
                "HTML5",
                "dom",
                "html",
                "parser",
                "querypath",
                "serializer",
                "xml"
            ],
            "support": {
                "issues": "https://github.com/Masterminds/html5-php/issues",
                "source": "https://github.com/Masterminds/html5-php/tree/2.7.6"
            },
            "install-path": "../masterminds/html5"
        },
        {
            "name": "pear/auth_sasl",
            "version": "v1.1.0",
            "version_normalized": "1.1.0.0",
            "source": {
                "type": "git",
                "url": "https://github.com/pear/Auth_SASL.git",
                "reference": "db1ead3dc0bf986d2bab0dbc04d114800cf91dee"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/pear/Auth_SASL/zipball/db1ead3dc0bf986d2bab0dbc04d114800cf91dee",
                "reference": "db1ead3dc0bf986d2bab0dbc04d114800cf91dee",
                "shasum": ""
            },
            "require": {
                "pear/pear_exception": "@stable"
            },
            "require-dev": {
                "phpunit/phpunit": "@stable"
            },
            "time": "2017-03-07T14:37:05+00:00",
            "type": "library",
            "installation-source": "dist",
            "autoload": {
                "psr-0": {
                    "Auth": "./"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "include-path": [
                "./"
            ],
            "license": [
                "BSD"
            ],
            "authors": [
                {
                    "name": "Anish Mistry",
                    "email": "amistry@am-productions.biz",
                    "role": "Lead"
                },
                {
                    "name": "Richard Heyes",
                    "email": "richard@php.net",
                    "role": "Lead"
                },
                {
                    "name": "Michael Bretterklieber",
                    "email": "michael@bretterklieber.com",
                    "role": "Lead"
                }
            ],
            "description": "Abstraction of various SASL mechanism responses",
            "support": {
                "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Auth_SASL",
                "source": "https://github.com/pear/Auth_SASL"
            },
            "install-path": "../pear/auth_sasl"
        },
        {
            "name": "pear/console_commandline",
            "version": "v1.2.6",
            "version_normalized": "1.2.6.0",
            "source": {
                "type": "git",
                "url": "https://github.com/pear/Console_CommandLine.git",
                "reference": "611c5bff2e47ec5a184748cb5fedc2869098ff28"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/pear/Console_CommandLine/zipball/611c5bff2e47ec5a184748cb5fedc2869098ff28",
                "reference": "611c5bff2e47ec5a184748cb5fedc2869098ff28",
                "shasum": ""
            },
            "require": {
                "ext-dom": "*",
                "ext-xml": "*",
                "pear/pear_exception": "^1.0.0",
                "php": ">=5.3.0"
            },
            "require-dev": {
                "phpunit/phpunit": "*"
            },
            "time": "2023-04-02T18:49:53+00:00",
            "type": "library",
            "installation-source": "dist",
            "autoload": {
                "psr-0": {
                    "Console": "./"
                },
                "exclude-from-classmap": [
                    "tests/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "include-path": [
                ""
            ],
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Richard Quadling",
                    "email": "rquadling@gmail.com"
                },
                {
                    "name": "David Jean Louis",
                    "email": "izimobil@gmail.com"
                }
            ],
            "description": "A full featured command line options and arguments parser.",
            "homepage": "https://github.com/pear/Console_CommandLine",
            "keywords": [
                "console"
            ],
            "support": {
                "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Console_CommandLine",
                "source": "https://github.com/pear/Console_CommandLine"
            },
            "install-path": "../pear/console_commandline"
        },
        {
            "name": "pear/console_getopt",
            "version": "v1.4.3",
            "version_normalized": "1.4.3.0",
            "source": {
                "type": "git",
                "url": "https://github.com/pear/Console_Getopt.git",
                "reference": "a41f8d3e668987609178c7c4a9fe48fecac53fa0"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/pear/Console_Getopt/zipball/a41f8d3e668987609178c7c4a9fe48fecac53fa0",
                "reference": "a41f8d3e668987609178c7c4a9fe48fecac53fa0",
                "shasum": ""
            },
            "time": "2019-11-20T18:27:48+00:00",
            "type": "library",
            "installation-source": "dist",
            "autoload": {
                "psr-0": {
                    "Console": "./"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "include-path": [
                "./"
            ],
            "license": [
                "BSD-2-Clause"
            ],
            "authors": [
                {
                    "name": "Andrei Zmievski",
                    "email": "andrei@php.net",
                    "role": "Lead"
                },
                {
                    "name": "Stig Bakken",
                    "email": "stig@php.net",
                    "role": "Developer"
                },
                {
                    "name": "Greg Beaver",
                    "email": "cellog@php.net",
                    "role": "Helper"
                }
            ],
            "description": "More info available on: http://pear.php.net/package/Console_Getopt",
            "support": {
                "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Console_Getopt",
                "source": "https://github.com/pear/Console_Getopt"
            },
            "install-path": "../pear/console_getopt"
        },
        {
            "name": "pear/crypt_gpg",
            "version": "v1.6.9",
            "version_normalized": "1.6.9.0",
            "source": {
                "type": "git",
                "url": "https://github.com/pear/Crypt_GPG.git",
                "reference": "1572e126f8c8e083af6db1ebe43fcd9c7954537c"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/pear/Crypt_GPG/zipball/1572e126f8c8e083af6db1ebe43fcd9c7954537c",
                "reference": "1572e126f8c8e083af6db1ebe43fcd9c7954537c",
                "shasum": ""
            },
            "require": {
                "ext-mbstring": "*",
                "pear/console_commandline": "*",
                "pear/pear_exception": "*",
                "php": ">=5.4.8"
            },
            "require-dev": {
                "phpstan/phpstan": "^1.2",
                "phpunit/phpunit": "^9"
            },
            "suggest": {
                "ext-posix": "May require the posix PHP extension"
            },
            "time": "2024-03-23T10:43:40+00:00",
            "bin": [
                "scripts/crypt-gpg-pinentry"
            ],
            "type": "library",
            "installation-source": "dist",
            "autoload": {
                "classmap": [
                    "Crypt/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "include-path": [
                "./"
            ],
            "license": [
                "LGPL-2.1"
            ],
            "authors": [
                {
                    "name": "Michael Gauthier",
                    "email": "mike@silverorange.com"
                },
                {
                    "name": "Nathan Fredrickson",
                    "email": "nathan@silverorange.com"
                },
                {
                    "name": "Aleksander Machniak",
                    "email": "alec@alec.pl"
                }
            ],
            "description": "Provides an object oriented interface to the GNU Privacy Guard (GnuPG). It requires the GnuPG executable to be on the system.",
            "homepage": "https://github.com/pear/Crypt_GPG",
            "keywords": [
                "PGP",
                "encryption",
                "gnupg",
                "gpg"
            ],
            "support": {
                "issues": "https://pear.php.net/bugs/search.php?cmd=display&package_name[]=Crypt_GPG",
                "source": "https://github.com/pear/Crypt_GPG"
            },
            "install-path": "../pear/crypt_gpg"
        },
        {
            "name": "pear/mail_mime",
            "version": "1.10.12",
            "version_normalized": "1.10.12.0",
            "source": {
                "type": "git",
                "url": "https://github.com/pear/Mail_Mime.git",
                "reference": "d032c7c9335e96d5954ac6e93d33955f3b7246e2"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/pear/Mail_Mime/zipball/d032c7c9335e96d5954ac6e93d33955f3b7246e2",
                "reference": "d032c7c9335e96d5954ac6e93d33955f3b7246e2",
                "shasum": ""
            },
            "require": {
                "pear/pear-core-minimal": "*",
                "php": ">=5.2.0"
            },
            "time": "2024-03-10T20:20:27+00:00",
            "type": "library",
            "installation-source": "dist",
            "autoload": {
                "psr-0": {
                    "Mail": "./"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "include-path": [
                "./"
            ],
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Cipriano Groenendal",
                    "email": "cipri@php.net",
                    "role": "Lead"
                },
                {
                    "name": "Aleksander Machniak",
                    "email": "alec@php.net",
                    "role": "Lead"
                }
            ],
            "description": "Mail_Mime provides classes to create MIME messages",
            "homepage": "http://pear.php.net/package/Mail_Mime",
            "support": {
                "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Mail_Mime",
                "source": "https://github.com/pear/Mail_Mime"
            },
            "install-path": "../pear/mail_mime"
        },
        {
            "name": "pear/net_ldap2",
            "version": "v2.3.0",
            "version_normalized": "2.3.0.0",
            "source": {
                "type": "git",
                "url": "https://github.com/pear/Net_LDAP2.git",
                "reference": "6eae6d20533469ffe1c01684923cd09f0fae3b77"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/pear/Net_LDAP2/zipball/6eae6d20533469ffe1c01684923cd09f0fae3b77",
                "reference": "6eae6d20533469ffe1c01684923cd09f0fae3b77",
                "shasum": ""
            },
            "require": {
                "ext-ldap": "*",
                "pear/pear-core-minimal": "^1.10.1"
            },
            "require-dev": {
                "phpunit/phpunit": "^9 || ^10.3"
            },
            "time": "2023-10-12T20:00:01+00:00",
            "type": "library",
            "installation-source": "dist",
            "autoload": {
                "classmap": [
                    "Net/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "include-path": [
                "."
            ],
            "license": [
                "LGPL-3.0"
            ],
            "description": "Object oriented interface for searching and manipulating LDAP-entries",
            "homepage": "http://pear.php.net/package/Net_LDAP2",
            "keywords": [
                "PEAR",
                "ldap"
            ],
            "support": {
                "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Net_LDAP2",
                "source": "https://github.com/pear/Net_LDAP2"
            },
            "install-path": "../pear/net_ldap2"
        },
        {
            "name": "pear/net_sieve",
            "version": "1.4.7",
            "version_normalized": "1.4.7.0",
            "source": {
                "type": "git",
                "url": "https://github.com/pear/Net_Sieve.git",
                "reference": "31b3ef38d75e681d5589c5ed2267314b79c1dfe8"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/pear/Net_Sieve/zipball/31b3ef38d75e681d5589c5ed2267314b79c1dfe8",
                "reference": "31b3ef38d75e681d5589c5ed2267314b79c1dfe8",
                "shasum": ""
            },
            "require": {
                "pear/net_socket": "~1.2",
                "pear/pear-core-minimal": "~1.10",
                "php": ">=5.6.0"
            },
            "require-dev": {
                "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^9"
            },
            "suggest": {
                "pear/auth_sasl": "Install optionally via your project's composer.json"
            },
            "time": "2024-04-08T07:54:10+00:00",
            "type": "library",
            "installation-source": "dist",
            "autoload": {
                "classmap": [
                    "./"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-2-Clause"
            ],
            "authors": [
                {
                    "name": "Aleksander Machniak",
                    "email": "alec@alec.pl",
                    "role": "Lead"
                },
                {
                    "name": "Jan Schneider",
                    "email": "jan@horde.org",
                    "role": "Lead"
                },
                {
                    "name": "Richard Heyes",
                    "email": "richard@php.net",
                    "role": "Lead"
                },
                {
                    "name": "Damian Fernandez Sosa",
                    "email": "damlists@cnba.uba.ar",
                    "role": "Lead"
                },
                {
                    "name": "Anish Mistry",
                    "email": "amistry@am-productions.biz",
                    "role": "Lead"
                }
            ],
            "description": "More info available on: https://pear.php.net/package/Net_Sieve",
            "support": {
                "issues": "https://pear.php.net/bugs/search.php?cmd=display&package_name[]=Net_Sieve",
                "source": "https://github.com/pear/Net_Sieve"
            },
            "install-path": "../pear/net_sieve"
        },
        {
            "name": "pear/net_smtp",
            "version": "1.10.1",
            "version_normalized": "1.10.1.0",
            "source": {
                "type": "git",
                "url": "https://github.com/pear/Net_SMTP.git",
                "reference": "cfd963dc5cc73b4d64c7769e47dfa0f439dec536"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/pear/Net_SMTP/zipball/cfd963dc5cc73b4d64c7769e47dfa0f439dec536",
                "reference": "cfd963dc5cc73b4d64c7769e47dfa0f439dec536",
                "shasum": ""
            },
            "require": {
                "pear/net_socket": "@stable",
                "pear/pear-core-minimal": "@stable",
                "php": ">=5.4.0"
            },
            "require-dev": {
                "phpunit/phpunit": "*"
            },
            "suggest": {
                "pear/auth_sasl": "Install optionally via your project's composer.json"
            },
            "time": "2022-09-23T21:48:50+00:00",
            "type": "library",
            "installation-source": "dist",
            "autoload": {
                "psr-0": {
                    "Net": "./"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "include-path": [
                "./"
            ],
            "license": [
                "BSD-2-Clause"
            ],
            "authors": [
                {
                    "name": "Jon Parise",
                    "email": "jon@php.net",
                    "homepage": "https://www.indelible.org",
                    "role": "Lead"
                },
                {
                    "name": "Chuck Hagenbuch",
                    "email": "chuck@horde.org",
                    "role": "Lead"
                },
                {
                    "name": "Armin Graefe",
                    "email": "schengawegga@gmail.com",
                    "role": "Lead"
                }
            ],
            "description": "An implementation of the SMTP protocol",
            "homepage": "https://pear.github.io/Net_SMTP/",
            "keywords": [
                "email",
                "mail",
                "smtp"
            ],
            "support": {
                "issues": "https://github.com/pear/Net_SMTP/issues",
                "source": "https://github.com/pear/Net_SMTP"
            },
            "install-path": "../pear/net_smtp"
        },
        {
            "name": "pear/net_socket",
            "version": "v1.2.2",
            "version_normalized": "1.2.2.0",
            "source": {
                "type": "git",
                "url": "https://github.com/pear/Net_Socket.git",
                "reference": "bbe6a12bb4f7059dba161f6ddd43f369c0ec8d09"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/pear/Net_Socket/zipball/bbe6a12bb4f7059dba161f6ddd43f369c0ec8d09",
                "reference": "bbe6a12bb4f7059dba161f6ddd43f369c0ec8d09",
                "shasum": ""
            },
            "require": {
                "pear/pear_exception": "*"
            },
            "require-dev": {
                "phpunit/phpunit": "*"
            },
            "time": "2015-03-22T15:48:19+00:00",
            "type": "library",
            "installation-source": "dist",
            "autoload": {
                "psr-0": {
                    "Net": "./"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "include-path": [
                "./"
            ],
            "license": [
                "PHP License"
            ],
            "authors": [
                {
                    "name": "Chuck Hagenbuch",
                    "email": "chuck@horde.org",
                    "role": "Lead"
                },
                {
                    "name": "Aleksander Machniak",
                    "email": "alec@php.net",
                    "role": "Lead"
                },
                {
                    "name": "Stig Bakken",
                    "email": "stig@php.net",
                    "role": "Lead"
                }
            ],
            "description": "More info available on: http://pear.php.net/package/Net_Socket",
            "support": {
                "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Net_Socket",
                "source": "https://github.com/pear/Net_Socket"
            },
            "install-path": "../pear/net_socket"
        },
        {
            "name": "pear/pear-core-minimal",
            "version": "v1.10.15",
            "version_normalized": "1.10.15.0",
            "source": {
                "type": "git",
                "url": "https://github.com/pear/pear-core-minimal.git",
                "reference": "ce0adade8b97561656ace07cdaac4751c271ea8c"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/pear/pear-core-minimal/zipball/ce0adade8b97561656ace07cdaac4751c271ea8c",
                "reference": "ce0adade8b97561656ace07cdaac4751c271ea8c",
                "shasum": ""
            },
            "require": {
                "pear/console_getopt": "~1.4",
                "pear/pear_exception": "~1.0",
                "php": ">=5.4"
            },
            "replace": {
                "rsky/pear-core-min": "self.version"
            },
            "time": "2024-03-16T18:41:45+00:00",
            "type": "library",
            "installation-source": "dist",
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "include-path": [
                "src/"
            ],
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Christian Weiske",
                    "email": "cweiske@php.net",
                    "role": "Lead"
                }
            ],
            "description": "Minimal set of PEAR core files to be used as composer dependency",
            "support": {
                "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=PEAR",
                "source": "https://github.com/pear/pear-core-minimal"
            },
            "install-path": "../pear/pear-core-minimal"
        },
        {
            "name": "pear/pear_exception",
            "version": "v1.0.2",
            "version_normalized": "1.0.2.0",
            "source": {
                "type": "git",
                "url": "https://github.com/pear/PEAR_Exception.git",
                "reference": "b14fbe2ddb0b9f94f5b24cf08783d599f776fff0"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/pear/PEAR_Exception/zipball/b14fbe2ddb0b9f94f5b24cf08783d599f776fff0",
                "reference": "b14fbe2ddb0b9f94f5b24cf08783d599f776fff0",
                "shasum": ""
            },
            "require": {
                "php": ">=5.2.0"
            },
            "require-dev": {
                "phpunit/phpunit": "<9"
            },
            "time": "2021-03-21T15:43:46+00:00",
            "type": "class",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.0.x-dev"
                }
            },
            "installation-source": "dist",
            "autoload": {
                "classmap": [
                    "PEAR/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "include-path": [
                "."
            ],
            "license": [
                "BSD-2-Clause"
            ],
            "authors": [
                {
                    "name": "Helgi Thormar",
                    "email": "dufuz@php.net"
                },
                {
                    "name": "Greg Beaver",
                    "email": "cellog@php.net"
                }
            ],
            "description": "The PEAR Exception base class.",
            "homepage": "https://github.com/pear/PEAR_Exception",
            "keywords": [
                "exception"
            ],
            "support": {
                "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=PEAR_Exception",
                "source": "https://github.com/pear/PEAR_Exception"
            },
            "install-path": "../pear/pear_exception"
        },
        {
            "name": "psr/http-client",
            "version": "1.0.3",
            "version_normalized": "1.0.3.0",
            "source": {
                "type": "git",
                "url": "https://github.com/php-fig/http-client.git",
                "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90",
                "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90",
                "shasum": ""
            },
            "require": {
                "php": "^7.0 || ^8.0",
                "psr/http-message": "^1.0 || ^2.0"
            },
            "time": "2023-09-23T14:17:50+00:00",
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.0.x-dev"
                }
            },
            "installation-source": "dist",
            "autoload": {
                "psr-4": {
                    "Psr\\Http\\Client\\": "src/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "PHP-FIG",
                    "homepage": "https://www.php-fig.org/"
                }
            ],
            "description": "Common interface for HTTP clients",
            "homepage": "https://github.com/php-fig/http-client",
            "keywords": [
                "http",
                "http-client",
                "psr",
                "psr-18"
            ],
            "support": {
                "source": "https://github.com/php-fig/http-client"
            },
            "install-path": "../psr/http-client"
        },
        {
            "name": "psr/http-factory",
            "version": "1.1.0",
            "version_normalized": "1.1.0.0",
            "source": {
                "type": "git",
                "url": "https://github.com/php-fig/http-factory.git",
                "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
                "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
                "shasum": ""
            },
            "require": {
                "php": ">=7.1",
                "psr/http-message": "^1.0 || ^2.0"
            },
            "time": "2024-04-15T12:06:14+00:00",
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.0.x-dev"
                }
            },
            "installation-source": "dist",
            "autoload": {
                "psr-4": {
                    "Psr\\Http\\Message\\": "src/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "PHP-FIG",
                    "homepage": "https://www.php-fig.org/"
                }
            ],
            "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories",
            "keywords": [
                "factory",
                "http",
                "message",
                "psr",
                "psr-17",
                "psr-7",
                "request",
                "response"
            ],
            "support": {
                "source": "https://github.com/php-fig/http-factory"
            },
            "install-path": "../psr/http-factory"
        },
        {
            "name": "psr/http-message",
            "version": "2.0",
            "version_normalized": "2.0.0.0",
            "source": {
                "type": "git",
                "url": "https://github.com/php-fig/http-message.git",
                "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71",
                "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71",
                "shasum": ""
            },
            "require": {
                "php": "^7.2 || ^8.0"
            },
            "time": "2023-04-04T09:54:51+00:00",
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.0.x-dev"
                }
            },
            "installation-source": "dist",
            "autoload": {
                "psr-4": {
                    "Psr\\Http\\Message\\": "src/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "PHP-FIG",
                    "homepage": "https://www.php-fig.org/"
                }
            ],
            "description": "Common interface for HTTP messages",
            "homepage": "https://github.com/php-fig/http-message",
            "keywords": [
                "http",
                "http-message",
                "psr",
                "psr-7",
                "request",
                "response"
            ],
            "support": {
                "source": "https://github.com/php-fig/http-message/tree/2.0"
            },
            "install-path": "../psr/http-message"
        },
        {
            "name": "ralouphie/getallheaders",
            "version": "3.0.3",
            "version_normalized": "3.0.3.0",
            "source": {
                "type": "git",
                "url": "https://github.com/ralouphie/getallheaders.git",
                "reference": "120b605dfeb996808c31b6477290a714d356e822"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
                "reference": "120b605dfeb996808c31b6477290a714d356e822",
                "shasum": ""
            },
            "require": {
                "php": ">=5.6"
            },
            "require-dev": {
                "php-coveralls/php-coveralls": "^2.1",
                "phpunit/phpunit": "^5 || ^6.5"
            },
            "time": "2019-03-08T08:55:37+00:00",
            "type": "library",
            "installation-source": "dist",
            "autoload": {
                "files": [
                    "src/getallheaders.php"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Ralph Khattar",
                    "email": "ralph.khattar@gmail.com"
                }
            ],
            "description": "A polyfill for getallheaders.",
            "support": {
                "issues": "https://github.com/ralouphie/getallheaders/issues",
                "source": "https://github.com/ralouphie/getallheaders/tree/develop"
            },
            "install-path": "../ralouphie/getallheaders"
        },
        {
            "name": "roundcube/plugin-installer",
            "version": "0.3.7",
            "version_normalized": "0.3.7.0",
            "source": {
                "type": "git",
                "url": "https://github.com/roundcube/plugin-installer.git",
                "reference": "f77c307b7d4e4000cb423cc3fac69fb1a9187aad"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/roundcube/plugin-installer/zipball/f77c307b7d4e4000cb423cc3fac69fb1a9187aad",
                "reference": "f77c307b7d4e4000cb423cc3fac69fb1a9187aad",
                "shasum": ""
            },
            "require": {
                "composer-plugin-api": "^2.1",
                "php": ">=7.3 <8.4",
                "roundcube/roundcubemail": "*"
            },
            "require-dev": {
                "composer/composer": "^2.1",
                "ergebnis/composer-normalize": "^2.13",
                "friendsofphp/php-cs-fixer": "^3.0",
                "phpstan/extension-installer": "^1.1",
                "phpstan/phpstan": "^1.2",
                "phpstan/phpstan-deprecation-rules": "^1.0",
                "phpstan/phpstan-strict-rules": "^1.3"
            },
            "time": "2024-05-16T18:36:45+00:00",
            "type": "composer-plugin",
            "extra": {
                "class": [
                    "Roundcube\\Composer\\RoundcubeInstaller"
                ]
            },
            "installation-source": "dist",
            "autoload": {
                "psr-4": {
                    "Roundcube\\Composer\\": "src/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "GPL-3.0-or-later"
            ],
            "authors": [
                {
                    "name": "Thomas Bruederli",
                    "email": "thomas@roundcube.net"
                },
                {
                    "name": "Till Klampaeckel",
                    "email": "till@php.net"
                },
                {
                    "name": "Philip Weir",
                    "email": "roundcube@tehinterweb.co.uk"
                }
            ],
            "description": "A composer-installer for Roundcube plugins and skins.",
            "support": {
                "issues": "https://github.com/roundcube/plugin-installer/issues",
                "source": "https://github.com/roundcube/plugin-installer/tree/0.3.7"
            },
            "install-path": "../roundcube/plugin-installer"
        },
        {
            "name": "roundcube/rtf-html-php",
            "version": "v2.2",
            "version_normalized": "2.2.0.0",
            "source": {
                "type": "git",
                "url": "https://github.com/roundcube/rtf-html-php.git",
                "reference": "a3432ca249b73bf24fec50114191a63ad8b1478c"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/roundcube/rtf-html-php/zipball/a3432ca249b73bf24fec50114191a63ad8b1478c",
                "reference": "a3432ca249b73bf24fec50114191a63ad8b1478c",
                "shasum": ""
            },
            "require": {
                "ext-iconv": "*",
                "ext-mbstring": "*",
                "php": ">=5.4"
            },
            "require-dev": {
                "phpstan/phpstan": "^1.2",
                "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6 || ^7 | ^9.6"
            },
            "time": "2023-12-31T14:01:02+00:00",
            "type": "library",
            "installation-source": "dist",
            "autoload": {
                "psr-4": {
                    "RtfHtmlPhp\\": "src/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "GPL-2.0"
            ],
            "authors": [
                {
                    "name": "Alexander van Oostenrijk",
                    "email": "alex.vanoostenrijk@gmail.com"
                },
                {
                    "name": "Aleksander Machniak",
                    "email": "alec@alec.pl"
                }
            ],
            "description": "RTF to HTML converter in PHP",
            "keywords": [
                "converter",
                "rtf"
            ],
            "support": {
                "issues": "https://github.com/roundcube/rtf-html-php/issues",
                "source": "https://github.com/roundcube/rtf-html-php/tree/v2.2"
            },
            "install-path": "../roundcube/rtf-html-php"
        },
        {
            "name": "symfony/deprecation-contracts",
            "version": "v2.5.3",
            "version_normalized": "2.5.3.0",
            "source": {
                "type": "git",
                "url": "https://github.com/symfony/deprecation-contracts.git",
                "reference": "80d075412b557d41002320b96a096ca65aa2c98d"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/80d075412b557d41002320b96a096ca65aa2c98d",
                "reference": "80d075412b557d41002320b96a096ca65aa2c98d",
                "shasum": ""
            },
            "require": {
                "php": ">=7.1"
            },
            "time": "2023-01-24T14:02:46+00:00",
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-main": "2.5-dev"
                },
                "thanks": {
                    "name": "symfony/contracts",
                    "url": "https://github.com/symfony/contracts"
                }
            },
            "installation-source": "dist",
            "autoload": {
                "files": [
                    "function.php"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Nicolas Grekas",
                    "email": "p@tchwork.com"
                },
                {
                    "name": "Symfony Community",
                    "homepage": "https://symfony.com/contributors"
                }
            ],
            "description": "A generic function and convention to trigger deprecation notices",
            "homepage": "https://symfony.com",
            "support": {
                "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.3"
            },
            "funding": [
                {
                    "url": "https://symfony.com/sponsor",
                    "type": "custom"
                },
                {
                    "url": "https://github.com/fabpot",
                    "type": "github"
                },
                {
                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
                    "type": "tidelift"
                }
            ],
            "install-path": "../symfony/deprecation-contracts"
        }
    ],
    "dev": false,
    "dev-package-names": []
}
PKyc�\3�MEEcomposer/autoload_static.phpnu�[���<?php

// autoload_static.php @generated by Composer

namespace Composer\Autoload;

class ComposerStaticInitd30534b7ccef70d5be1f4ba9bc89dbe4
{
    public static $files = array (
        '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
        '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
        '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
    );

    public static $prefixLengthsPsr4 = array (
        'R' => 
        array (
            'RtfHtmlPhp\\' => 11,
            'Roundcube\\Composer\\' => 19,
        ),
        'P' => 
        array (
            'Psr\\Http\\Message\\' => 17,
            'Psr\\Http\\Client\\' => 16,
        ),
        'M' => 
        array (
            'Masterminds\\' => 12,
        ),
        'G' => 
        array (
            'GuzzleHttp\\Psr7\\' => 16,
            'GuzzleHttp\\Promise\\' => 19,
            'GuzzleHttp\\' => 11,
        ),
        'D' => 
        array (
            'DASPRiD\\Enum\\' => 13,
        ),
        'B' => 
        array (
            'BaconQrCode\\' => 12,
        ),
    );

    public static $prefixDirsPsr4 = array (
        'RtfHtmlPhp\\' => 
        array (
            0 => __DIR__ . '/..' . '/roundcube/rtf-html-php/src',
        ),
        'Roundcube\\Composer\\' => 
        array (
            0 => __DIR__ . '/..' . '/roundcube/plugin-installer/src',
        ),
        'Psr\\Http\\Message\\' => 
        array (
            0 => __DIR__ . '/..' . '/psr/http-message/src',
            1 => __DIR__ . '/..' . '/psr/http-factory/src',
        ),
        'Psr\\Http\\Client\\' => 
        array (
            0 => __DIR__ . '/..' . '/psr/http-client/src',
        ),
        'Masterminds\\' => 
        array (
            0 => __DIR__ . '/..' . '/masterminds/html5/src',
        ),
        'GuzzleHttp\\Psr7\\' => 
        array (
            0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src',
        ),
        'GuzzleHttp\\Promise\\' => 
        array (
            0 => __DIR__ . '/..' . '/guzzlehttp/promises/src',
        ),
        'GuzzleHttp\\' => 
        array (
            0 => __DIR__ . '/..' . '/guzzlehttp/guzzle/src',
        ),
        'DASPRiD\\Enum\\' => 
        array (
            0 => __DIR__ . '/..' . '/dasprid/enum/src',
        ),
        'BaconQrCode\\' => 
        array (
            0 => __DIR__ . '/..' . '/bacon/bacon-qr-code/src',
        ),
    );

    public static $prefixesPsr0 = array (
        'N' => 
        array (
            'Net' => 
            array (
                0 => __DIR__ . '/..' . '/pear/net_socket',
                1 => __DIR__ . '/..' . '/pear/net_smtp',
            ),
        ),
        'M' => 
        array (
            'Mail' => 
            array (
                0 => __DIR__ . '/..' . '/pear/mail_mime',
            ),
        ),
        'C' => 
        array (
            'Console' => 
            array (
                0 => __DIR__ . '/..' . '/pear/console_getopt',
                1 => __DIR__ . '/..' . '/pear/console_commandline',
            ),
        ),
        'A' => 
        array (
            'Auth' => 
            array (
                0 => __DIR__ . '/..' . '/pear/auth_sasl',
            ),
        ),
    );

    public static $classMap = array (
        'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
        'Crypt_GPG' => __DIR__ . '/..' . '/pear/crypt_gpg/Crypt/GPG.php',
        'Crypt_GPGAbstract' => __DIR__ . '/..' . '/pear/crypt_gpg/Crypt/GPGAbstract.php',
        'Crypt_GPG_BadPassphraseException' => __DIR__ . '/..' . '/pear/crypt_gpg/Crypt/GPG/Exceptions.php',
        'Crypt_GPG_DeletePrivateKeyException' => __DIR__ . '/..' . '/pear/crypt_gpg/Crypt/GPG/Exceptions.php',
        'Crypt_GPG_Engine' => __DIR__ . '/..' . '/pear/crypt_gpg/Crypt/GPG/Engine.php',
        'Crypt_GPG_Exception' => __DIR__ . '/..' . '/pear/crypt_gpg/Crypt/GPG/Exceptions.php',
        'Crypt_GPG_FileException' => __DIR__ . '/..' . '/pear/crypt_gpg/Crypt/GPG/Exceptions.php',
        'Crypt_GPG_InvalidKeyParamsException' => __DIR__ . '/..' . '/pear/crypt_gpg/Crypt/GPG/Exceptions.php',
        'Crypt_GPG_InvalidOperationException' => __DIR__ . '/..' . '/pear/crypt_gpg/Crypt/GPG/Exceptions.php',
        'Crypt_GPG_Key' => __DIR__ . '/..' . '/pear/crypt_gpg/Crypt/GPG/Key.php',
        'Crypt_GPG_KeyGenerator' => __DIR__ . '/..' . '/pear/crypt_gpg/Crypt/GPG/KeyGenerator.php',
        'Crypt_GPG_KeyNotCreatedException' => __DIR__ . '/..' . '/pear/crypt_gpg/Crypt/GPG/Exceptions.php',
        'Crypt_GPG_KeyNotFoundException' => __DIR__ . '/..' . '/pear/crypt_gpg/Crypt/GPG/Exceptions.php',
        'Crypt_GPG_NoDataException' => __DIR__ . '/..' . '/pear/crypt_gpg/Crypt/GPG/Exceptions.php',
        'Crypt_GPG_OpenSubprocessException' => __DIR__ . '/..' . '/pear/crypt_gpg/Crypt/GPG/Exceptions.php',
        'Crypt_GPG_PinEntry' => __DIR__ . '/..' . '/pear/crypt_gpg/Crypt/GPG/PinEntry.php',
        'Crypt_GPG_ProcessControl' => __DIR__ . '/..' . '/pear/crypt_gpg/Crypt/GPG/ProcessControl.php',
        'Crypt_GPG_ProcessHandler' => __DIR__ . '/..' . '/pear/crypt_gpg/Crypt/GPG/ProcessHandler.php',
        'Crypt_GPG_Signature' => __DIR__ . '/..' . '/pear/crypt_gpg/Crypt/GPG/Signature.php',
        'Crypt_GPG_SignatureCreationInfo' => __DIR__ . '/..' . '/pear/crypt_gpg/Crypt/GPG/SignatureCreationInfo.php',
        'Crypt_GPG_SubKey' => __DIR__ . '/..' . '/pear/crypt_gpg/Crypt/GPG/SubKey.php',
        'Crypt_GPG_UserId' => __DIR__ . '/..' . '/pear/crypt_gpg/Crypt/GPG/UserId.php',
        'Net_LDAP2' => __DIR__ . '/..' . '/pear/net_ldap2/Net/LDAP2.php',
        'Net_LDAP2_Entry' => __DIR__ . '/..' . '/pear/net_ldap2/Net/LDAP2/Entry.php',
        'Net_LDAP2_Error' => __DIR__ . '/..' . '/pear/net_ldap2/Net/LDAP2.php',
        'Net_LDAP2_Filter' => __DIR__ . '/..' . '/pear/net_ldap2/Net/LDAP2/Filter.php',
        'Net_LDAP2_LDIF' => __DIR__ . '/..' . '/pear/net_ldap2/Net/LDAP2/LDIF.php',
        'Net_LDAP2_RootDSE' => __DIR__ . '/..' . '/pear/net_ldap2/Net/LDAP2/RootDSE.php',
        'Net_LDAP2_Schema' => __DIR__ . '/..' . '/pear/net_ldap2/Net/LDAP2/Schema.php',
        'Net_LDAP2_SchemaCache' => __DIR__ . '/..' . '/pear/net_ldap2/Net/LDAP2/SchemaCache.interface.php',
        'Net_LDAP2_Search' => __DIR__ . '/..' . '/pear/net_ldap2/Net/LDAP2/Search.php',
        'Net_LDAP2_SimpleFileSchemaCache' => __DIR__ . '/..' . '/pear/net_ldap2/Net/LDAP2/SimpleFileSchemaCache.php',
        'Net_LDAP2_Util' => __DIR__ . '/..' . '/pear/net_ldap2/Net/LDAP2/Util.php',
        'Net_LDAP3' => __DIR__ . '/..' . '/kolab/net_ldap3/lib/Net/LDAP3.php',
        'Net_LDAP3_Result' => __DIR__ . '/..' . '/kolab/net_ldap3/lib/Net/LDAP3/Result.php',
        'Net_Sieve' => __DIR__ . '/..' . '/pear/net_sieve/Sieve.php',
        'OS_Guess' => __DIR__ . '/..' . '/pear/pear-core-minimal/src/OS/Guess.php',
        'PEAR' => __DIR__ . '/..' . '/pear/pear-core-minimal/src/PEAR.php',
        'PEAR_Error' => __DIR__ . '/..' . '/pear/pear-core-minimal/src/PEAR.php',
        'PEAR_ErrorStack' => __DIR__ . '/..' . '/pear/pear-core-minimal/src/PEAR/ErrorStack.php',
        'PEAR_Exception' => __DIR__ . '/..' . '/pear/pear_exception/PEAR/Exception.php',
        'System' => __DIR__ . '/..' . '/pear/pear-core-minimal/src/System.php',
    );

    public static function getInitializer(ClassLoader $loader)
    {
        return \Closure::bind(function () use ($loader) {
            $loader->prefixLengthsPsr4 = ComposerStaticInitd30534b7ccef70d5be1f4ba9bc89dbe4::$prefixLengthsPsr4;
            $loader->prefixDirsPsr4 = ComposerStaticInitd30534b7ccef70d5be1f4ba9bc89dbe4::$prefixDirsPsr4;
            $loader->prefixesPsr0 = ComposerStaticInitd30534b7ccef70d5be1f4ba9bc89dbe4::$prefixesPsr0;
            $loader->classMap = ComposerStaticInitd30534b7ccef70d5be1f4ba9bc89dbe4::$classMap;

        }, null, ClassLoader::class);
    }
}
PKyc�\��%���composer/platform_check.phpnu�[���<?php

// platform_check.php @generated by Composer

$issues = array();

if (!(PHP_VERSION_ID >= 70300)) {
    $issues[] = 'Your Composer dependencies require a PHP version ">= 7.3.0". You are running ' . PHP_VERSION . '.';
}

if ($issues) {
    if (!headers_sent()) {
        header('HTTP/1.1 500 Internal Server Error');
    }
    if (!ini_get('display_errors')) {
        if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
            fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
        } elseif (!headers_sent()) {
            echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
        }
    }
    trigger_error(
        'Composer detected issues in your platform: ' . implode(' ', $issues),
        E_USER_ERROR
    );
}
PKzc�\ 2��??composer/InstalledVersions.phpnu�[���<?php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer;

use Composer\Autoload\ClassLoader;
use Composer\Semver\VersionParser;

/**
 * This class is copied in every Composer installed project and available to all
 *
 * See also https://getcomposer.org/doc/07-runtime.md#installed-versions
 *
 * To require its presence, you can require `composer-runtime-api ^2.0`
 *
 * @final
 */
class InstalledVersions
{
    /**
     * @var mixed[]|null
     * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
     */
    private static $installed;

    /**
     * @var bool|null
     */
    private static $canGetVendors;

    /**
     * @var array[]
     * @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
     */
    private static $installedByVendor = array();

    /**
     * Returns a list of all package names which are present, either by being installed, replaced or provided
     *
     * @return string[]
     * @psalm-return list<string>
     */
    public static function getInstalledPackages()
    {
        $packages = array();
        foreach (self::getInstalled() as $installed) {
            $packages[] = array_keys($installed['versions']);
        }

        if (1 === \count($packages)) {
            return $packages[0];
        }

        return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
    }

    /**
     * Returns a list of all package names with a specific type e.g. 'library'
     *
     * @param  string   $type
     * @return string[]
     * @psalm-return list<string>
     */
    public static function getInstalledPackagesByType($type)
    {
        $packagesByType = array();

        foreach (self::getInstalled() as $installed) {
            foreach ($installed['versions'] as $name => $package) {
                if (isset($package['type']) && $package['type'] === $type) {
                    $packagesByType[] = $name;
                }
            }
        }

        return $packagesByType;
    }

    /**
     * Checks whether the given package is installed
     *
     * This also returns true if the package name is provided or replaced by another package
     *
     * @param  string $packageName
     * @param  bool   $includeDevRequirements
     * @return bool
     */
    public static function isInstalled($packageName, $includeDevRequirements = true)
    {
        foreach (self::getInstalled() as $installed) {
            if (isset($installed['versions'][$packageName])) {
                return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
            }
        }

        return false;
    }

    /**
     * Checks whether the given package satisfies a version constraint
     *
     * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
     *
     *   Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
     *
     * @param  VersionParser $parser      Install composer/semver to have access to this class and functionality
     * @param  string        $packageName
     * @param  string|null   $constraint  A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
     * @return bool
     */
    public static function satisfies(VersionParser $parser, $packageName, $constraint)
    {
        $constraint = $parser->parseConstraints((string) $constraint);
        $provided = $parser->parseConstraints(self::getVersionRanges($packageName));

        return $provided->matches($constraint);
    }

    /**
     * Returns a version constraint representing all the range(s) which are installed for a given package
     *
     * It is easier to use this via isInstalled() with the $constraint argument if you need to check
     * whether a given version of a package is installed, and not just whether it exists
     *
     * @param  string $packageName
     * @return string Version constraint usable with composer/semver
     */
    public static function getVersionRanges($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }

            $ranges = array();
            if (isset($installed['versions'][$packageName]['pretty_version'])) {
                $ranges[] = $installed['versions'][$packageName]['pretty_version'];
            }
            if (array_key_exists('aliases', $installed['versions'][$packageName])) {
                $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
            }
            if (array_key_exists('replaced', $installed['versions'][$packageName])) {
                $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
            }
            if (array_key_exists('provided', $installed['versions'][$packageName])) {
                $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
            }

            return implode(' || ', $ranges);
        }

        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }

    /**
     * @param  string      $packageName
     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
     */
    public static function getVersion($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }

            if (!isset($installed['versions'][$packageName]['version'])) {
                return null;
            }

            return $installed['versions'][$packageName]['version'];
        }

        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }

    /**
     * @param  string      $packageName
     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
     */
    public static function getPrettyVersion($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }

            if (!isset($installed['versions'][$packageName]['pretty_version'])) {
                return null;
            }

            return $installed['versions'][$packageName]['pretty_version'];
        }

        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }

    /**
     * @param  string      $packageName
     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
     */
    public static function getReference($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }

            if (!isset($installed['versions'][$packageName]['reference'])) {
                return null;
            }

            return $installed['versions'][$packageName]['reference'];
        }

        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }

    /**
     * @param  string      $packageName
     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
     */
    public static function getInstallPath($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }

            return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
        }

        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }

    /**
     * @return array
     * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
     */
    public static function getRootPackage()
    {
        $installed = self::getInstalled();

        return $installed[0]['root'];
    }

    /**
     * Returns the raw installed.php data for custom implementations
     *
     * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
     * @return array[]
     * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
     */
    public static function getRawData()
    {
        @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);

        if (null === self::$installed) {
            // only require the installed.php file if this file is loaded from its dumped location,
            // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
            if (substr(__DIR__, -8, 1) !== 'C') {
                self::$installed = include __DIR__ . '/installed.php';
            } else {
                self::$installed = array();
            }
        }

        return self::$installed;
    }

    /**
     * Returns the raw data of all installed.php which are currently loaded for custom implementations
     *
     * @return array[]
     * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
     */
    public static function getAllRawData()
    {
        return self::getInstalled();
    }

    /**
     * Lets you reload the static array from another file
     *
     * This is only useful for complex integrations in which a project needs to use
     * this class but then also needs to execute another project's autoloader in process,
     * and wants to ensure both projects have access to their version of installed.php.
     *
     * A typical case would be PHPUnit, where it would need to make sure it reads all
     * the data it needs from this class, then call reload() with
     * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
     * the project in which it runs can then also use this class safely, without
     * interference between PHPUnit's dependencies and the project's dependencies.
     *
     * @param  array[] $data A vendor/composer/installed.php data set
     * @return void
     *
     * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
     */
    public static function reload($data)
    {
        self::$installed = $data;
        self::$installedByVendor = array();
    }

    /**
     * @return array[]
     * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
     */
    private static function getInstalled()
    {
        if (null === self::$canGetVendors) {
            self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
        }

        $installed = array();

        if (self::$canGetVendors) {
            foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
                if (isset(self::$installedByVendor[$vendorDir])) {
                    $installed[] = self::$installedByVendor[$vendorDir];
                } elseif (is_file($vendorDir.'/composer/installed.php')) {
                    /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
                    $required = require $vendorDir.'/composer/installed.php';
                    $installed[] = self::$installedByVendor[$vendorDir] = $required;
                    if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
                        self::$installed = $installed[count($installed) - 1];
                    }
                }
            }
        }

        if (null === self::$installed) {
            // only require the installed.php file if this file is loaded from its dumped location,
            // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
            if (substr(__DIR__, -8, 1) !== 'C') {
                /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
                $required = require __DIR__ . '/installed.php';
                self::$installed = $required;
            } else {
                self::$installed = array();
            }
        }

        if (self::$installed !== array()) {
            $installed[] = self::$installed;
        }

        return $installed;
    }
}
PKzc�\6�J��composer/autoload_classmap.phpnu�[���<?php

// autoload_classmap.php @generated by Composer

$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);

return array(
    'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
    'Crypt_GPG' => $vendorDir . '/pear/crypt_gpg/Crypt/GPG.php',
    'Crypt_GPGAbstract' => $vendorDir . '/pear/crypt_gpg/Crypt/GPGAbstract.php',
    'Crypt_GPG_BadPassphraseException' => $vendorDir . '/pear/crypt_gpg/Crypt/GPG/Exceptions.php',
    'Crypt_GPG_DeletePrivateKeyException' => $vendorDir . '/pear/crypt_gpg/Crypt/GPG/Exceptions.php',
    'Crypt_GPG_Engine' => $vendorDir . '/pear/crypt_gpg/Crypt/GPG/Engine.php',
    'Crypt_GPG_Exception' => $vendorDir . '/pear/crypt_gpg/Crypt/GPG/Exceptions.php',
    'Crypt_GPG_FileException' => $vendorDir . '/pear/crypt_gpg/Crypt/GPG/Exceptions.php',
    'Crypt_GPG_InvalidKeyParamsException' => $vendorDir . '/pear/crypt_gpg/Crypt/GPG/Exceptions.php',
    'Crypt_GPG_InvalidOperationException' => $vendorDir . '/pear/crypt_gpg/Crypt/GPG/Exceptions.php',
    'Crypt_GPG_Key' => $vendorDir . '/pear/crypt_gpg/Crypt/GPG/Key.php',
    'Crypt_GPG_KeyGenerator' => $vendorDir . '/pear/crypt_gpg/Crypt/GPG/KeyGenerator.php',
    'Crypt_GPG_KeyNotCreatedException' => $vendorDir . '/pear/crypt_gpg/Crypt/GPG/Exceptions.php',
    'Crypt_GPG_KeyNotFoundException' => $vendorDir . '/pear/crypt_gpg/Crypt/GPG/Exceptions.php',
    'Crypt_GPG_NoDataException' => $vendorDir . '/pear/crypt_gpg/Crypt/GPG/Exceptions.php',
    'Crypt_GPG_OpenSubprocessException' => $vendorDir . '/pear/crypt_gpg/Crypt/GPG/Exceptions.php',
    'Crypt_GPG_PinEntry' => $vendorDir . '/pear/crypt_gpg/Crypt/GPG/PinEntry.php',
    'Crypt_GPG_ProcessControl' => $vendorDir . '/pear/crypt_gpg/Crypt/GPG/ProcessControl.php',
    'Crypt_GPG_ProcessHandler' => $vendorDir . '/pear/crypt_gpg/Crypt/GPG/ProcessHandler.php',
    'Crypt_GPG_Signature' => $vendorDir . '/pear/crypt_gpg/Crypt/GPG/Signature.php',
    'Crypt_GPG_SignatureCreationInfo' => $vendorDir . '/pear/crypt_gpg/Crypt/GPG/SignatureCreationInfo.php',
    'Crypt_GPG_SubKey' => $vendorDir . '/pear/crypt_gpg/Crypt/GPG/SubKey.php',
    'Crypt_GPG_UserId' => $vendorDir . '/pear/crypt_gpg/Crypt/GPG/UserId.php',
    'Net_LDAP2' => $vendorDir . '/pear/net_ldap2/Net/LDAP2.php',
    'Net_LDAP2_Entry' => $vendorDir . '/pear/net_ldap2/Net/LDAP2/Entry.php',
    'Net_LDAP2_Error' => $vendorDir . '/pear/net_ldap2/Net/LDAP2.php',
    'Net_LDAP2_Filter' => $vendorDir . '/pear/net_ldap2/Net/LDAP2/Filter.php',
    'Net_LDAP2_LDIF' => $vendorDir . '/pear/net_ldap2/Net/LDAP2/LDIF.php',
    'Net_LDAP2_RootDSE' => $vendorDir . '/pear/net_ldap2/Net/LDAP2/RootDSE.php',
    'Net_LDAP2_Schema' => $vendorDir . '/pear/net_ldap2/Net/LDAP2/Schema.php',
    'Net_LDAP2_SchemaCache' => $vendorDir . '/pear/net_ldap2/Net/LDAP2/SchemaCache.interface.php',
    'Net_LDAP2_Search' => $vendorDir . '/pear/net_ldap2/Net/LDAP2/Search.php',
    'Net_LDAP2_SimpleFileSchemaCache' => $vendorDir . '/pear/net_ldap2/Net/LDAP2/SimpleFileSchemaCache.php',
    'Net_LDAP2_Util' => $vendorDir . '/pear/net_ldap2/Net/LDAP2/Util.php',
    'Net_LDAP3' => $vendorDir . '/kolab/net_ldap3/lib/Net/LDAP3.php',
    'Net_LDAP3_Result' => $vendorDir . '/kolab/net_ldap3/lib/Net/LDAP3/Result.php',
    'Net_Sieve' => $vendorDir . '/pear/net_sieve/Sieve.php',
    'OS_Guess' => $vendorDir . '/pear/pear-core-minimal/src/OS/Guess.php',
    'PEAR' => $vendorDir . '/pear/pear-core-minimal/src/PEAR.php',
    'PEAR_Error' => $vendorDir . '/pear/pear-core-minimal/src/PEAR.php',
    'PEAR_ErrorStack' => $vendorDir . '/pear/pear-core-minimal/src/PEAR/ErrorStack.php',
    'PEAR_Exception' => $vendorDir . '/pear/pear_exception/PEAR/Exception.php',
    'System' => $vendorDir . '/pear/pear-core-minimal/src/System.php',
);
PKzc�\ �..composer/LICENSEnu�[���
Copyright (c) Nils Adermann, Jordi Boggiano

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

PKzc�\2@u�?�?composer/ClassLoader.phpnu�[���<?php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Autoload;

/**
 * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
 *
 *     $loader = new \Composer\Autoload\ClassLoader();
 *
 *     // register classes with namespaces
 *     $loader->add('Symfony\Component', __DIR__.'/component');
 *     $loader->add('Symfony',           __DIR__.'/framework');
 *
 *     // activate the autoloader
 *     $loader->register();
 *
 *     // to enable searching the include path (eg. for PEAR packages)
 *     $loader->setUseIncludePath(true);
 *
 * In this example, if you try to use a class in the Symfony\Component
 * namespace or one of its children (Symfony\Component\Console for instance),
 * the autoloader will first look for the class under the component/
 * directory, and it will then fallback to the framework/ directory if not
 * found before giving up.
 *
 * This class is loosely based on the Symfony UniversalClassLoader.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @see    https://www.php-fig.org/psr/psr-0/
 * @see    https://www.php-fig.org/psr/psr-4/
 */
class ClassLoader
{
    /** @var \Closure(string):void */
    private static $includeFile;

    /** @var string|null */
    private $vendorDir;

    // PSR-4
    /**
     * @var array<string, array<string, int>>
     */
    private $prefixLengthsPsr4 = array();
    /**
     * @var array<string, list<string>>
     */
    private $prefixDirsPsr4 = array();
    /**
     * @var list<string>
     */
    private $fallbackDirsPsr4 = array();

    // PSR-0
    /**
     * List of PSR-0 prefixes
     *
     * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
     *
     * @var array<string, array<string, list<string>>>
     */
    private $prefixesPsr0 = array();
    /**
     * @var list<string>
     */
    private $fallbackDirsPsr0 = array();

    /** @var bool */
    private $useIncludePath = false;

    /**
     * @var array<string, string>
     */
    private $classMap = array();

    /** @var bool */
    private $classMapAuthoritative = false;

    /**
     * @var array<string, bool>
     */
    private $missingClasses = array();

    /** @var string|null */
    private $apcuPrefix;

    /**
     * @var array<string, self>
     */
    private static $registeredLoaders = array();

    /**
     * @param string|null $vendorDir
     */
    public function __construct($vendorDir = null)
    {
        $this->vendorDir = $vendorDir;
        self::initializeIncludeClosure();
    }

    /**
     * @return array<string, list<string>>
     */
    public function getPrefixes()
    {
        if (!empty($this->prefixesPsr0)) {
            return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
        }

        return array();
    }

    /**
     * @return array<string, list<string>>
     */
    public function getPrefixesPsr4()
    {
        return $this->prefixDirsPsr4;
    }

    /**
     * @return list<string>
     */
    public function getFallbackDirs()
    {
        return $this->fallbackDirsPsr0;
    }

    /**
     * @return list<string>
     */
    public function getFallbackDirsPsr4()
    {
        return $this->fallbackDirsPsr4;
    }

    /**
     * @return array<string, string> Array of classname => path
     */
    public function getClassMap()
    {
        return $this->classMap;
    }

    /**
     * @param array<string, string> $classMap Class to filename map
     *
     * @return void
     */
    public function addClassMap(array $classMap)
    {
        if ($this->classMap) {
            $this->classMap = array_merge($this->classMap, $classMap);
        } else {
            $this->classMap = $classMap;
        }
    }

    /**
     * Registers a set of PSR-0 directories for a given prefix, either
     * appending or prepending to the ones previously set for this prefix.
     *
     * @param string              $prefix  The prefix
     * @param list<string>|string $paths   The PSR-0 root directories
     * @param bool                $prepend Whether to prepend the directories
     *
     * @return void
     */
    public function add($prefix, $paths, $prepend = false)
    {
        $paths = (array) $paths;
        if (!$prefix) {
            if ($prepend) {
                $this->fallbackDirsPsr0 = array_merge(
                    $paths,
                    $this->fallbackDirsPsr0
                );
            } else {
                $this->fallbackDirsPsr0 = array_merge(
                    $this->fallbackDirsPsr0,
                    $paths
                );
            }

            return;
        }

        $first = $prefix[0];
        if (!isset($this->prefixesPsr0[$first][$prefix])) {
            $this->prefixesPsr0[$first][$prefix] = $paths;

            return;
        }
        if ($prepend) {
            $this->prefixesPsr0[$first][$prefix] = array_merge(
                $paths,
                $this->prefixesPsr0[$first][$prefix]
            );
        } else {
            $this->prefixesPsr0[$first][$prefix] = array_merge(
                $this->prefixesPsr0[$first][$prefix],
                $paths
            );
        }
    }

    /**
     * Registers a set of PSR-4 directories for a given namespace, either
     * appending or prepending to the ones previously set for this namespace.
     *
     * @param string              $prefix  The prefix/namespace, with trailing '\\'
     * @param list<string>|string $paths   The PSR-4 base directories
     * @param bool                $prepend Whether to prepend the directories
     *
     * @throws \InvalidArgumentException
     *
     * @return void
     */
    public function addPsr4($prefix, $paths, $prepend = false)
    {
        $paths = (array) $paths;
        if (!$prefix) {
            // Register directories for the root namespace.
            if ($prepend) {
                $this->fallbackDirsPsr4 = array_merge(
                    $paths,
                    $this->fallbackDirsPsr4
                );
            } else {
                $this->fallbackDirsPsr4 = array_merge(
                    $this->fallbackDirsPsr4,
                    $paths
                );
            }
        } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
            // Register directories for a new namespace.
            $length = strlen($prefix);
            if ('\\' !== $prefix[$length - 1]) {
                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
            }
            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
            $this->prefixDirsPsr4[$prefix] = $paths;
        } elseif ($prepend) {
            // Prepend directories for an already registered namespace.
            $this->prefixDirsPsr4[$prefix] = array_merge(
                $paths,
                $this->prefixDirsPsr4[$prefix]
            );
        } else {
            // Append directories for an already registered namespace.
            $this->prefixDirsPsr4[$prefix] = array_merge(
                $this->prefixDirsPsr4[$prefix],
                $paths
            );
        }
    }

    /**
     * Registers a set of PSR-0 directories for a given prefix,
     * replacing any others previously set for this prefix.
     *
     * @param string              $prefix The prefix
     * @param list<string>|string $paths  The PSR-0 base directories
     *
     * @return void
     */
    public function set($prefix, $paths)
    {
        if (!$prefix) {
            $this->fallbackDirsPsr0 = (array) $paths;
        } else {
            $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
        }
    }

    /**
     * Registers a set of PSR-4 directories for a given namespace,
     * replacing any others previously set for this namespace.
     *
     * @param string              $prefix The prefix/namespace, with trailing '\\'
     * @param list<string>|string $paths  The PSR-4 base directories
     *
     * @throws \InvalidArgumentException
     *
     * @return void
     */
    public function setPsr4($prefix, $paths)
    {
        if (!$prefix) {
            $this->fallbackDirsPsr4 = (array) $paths;
        } else {
            $length = strlen($prefix);
            if ('\\' !== $prefix[$length - 1]) {
                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
            }
            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
            $this->prefixDirsPsr4[$prefix] = (array) $paths;
        }
    }

    /**
     * Turns on searching the include path for class files.
     *
     * @param bool $useIncludePath
     *
     * @return void
     */
    public function setUseIncludePath($useIncludePath)
    {
        $this->useIncludePath = $useIncludePath;
    }

    /**
     * Can be used to check if the autoloader uses the include path to check
     * for classes.
     *
     * @return bool
     */
    public function getUseIncludePath()
    {
        return $this->useIncludePath;
    }

    /**
     * Turns off searching the prefix and fallback directories for classes
     * that have not been registered with the class map.
     *
     * @param bool $classMapAuthoritative
     *
     * @return void
     */
    public function setClassMapAuthoritative($classMapAuthoritative)
    {
        $this->classMapAuthoritative = $classMapAuthoritative;
    }

    /**
     * Should class lookup fail if not found in the current class map?
     *
     * @return bool
     */
    public function isClassMapAuthoritative()
    {
        return $this->classMapAuthoritative;
    }

    /**
     * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
     *
     * @param string|null $apcuPrefix
     *
     * @return void
     */
    public function setApcuPrefix($apcuPrefix)
    {
        $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
    }

    /**
     * The APCu prefix in use, or null if APCu caching is not enabled.
     *
     * @return string|null
     */
    public function getApcuPrefix()
    {
        return $this->apcuPrefix;
    }

    /**
     * Registers this instance as an autoloader.
     *
     * @param bool $prepend Whether to prepend the autoloader or not
     *
     * @return void
     */
    public function register($prepend = false)
    {
        spl_autoload_register(array($this, 'loadClass'), true, $prepend);

        if (null === $this->vendorDir) {
            return;
        }

        if ($prepend) {
            self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
        } else {
            unset(self::$registeredLoaders[$this->vendorDir]);
            self::$registeredLoaders[$this->vendorDir] = $this;
        }
    }

    /**
     * Unregisters this instance as an autoloader.
     *
     * @return void
     */
    public function unregister()
    {
        spl_autoload_unregister(array($this, 'loadClass'));

        if (null !== $this->vendorDir) {
            unset(self::$registeredLoaders[$this->vendorDir]);
        }
    }

    /**
     * Loads the given class or interface.
     *
     * @param  string    $class The name of the class
     * @return true|null True if loaded, null otherwise
     */
    public function loadClass($class)
    {
        if ($file = $this->findFile($class)) {
            $includeFile = self::$includeFile;
            $includeFile($file);

            return true;
        }

        return null;
    }

    /**
     * Finds the path to the file where the class is defined.
     *
     * @param string $class The name of the class
     *
     * @return string|false The path if found, false otherwise
     */
    public function findFile($class)
    {
        // class map lookup
        if (isset($this->classMap[$class])) {
            return $this->classMap[$class];
        }
        if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
            return false;
        }
        if (null !== $this->apcuPrefix) {
            $file = apcu_fetch($this->apcuPrefix.$class, $hit);
            if ($hit) {
                return $file;
            }
        }

        $file = $this->findFileWithExtension($class, '.php');

        // Search for Hack files if we are running on HHVM
        if (false === $file && defined('HHVM_VERSION')) {
            $file = $this->findFileWithExtension($class, '.hh');
        }

        if (null !== $this->apcuPrefix) {
            apcu_add($this->apcuPrefix.$class, $file);
        }

        if (false === $file) {
            // Remember that this class does not exist.
            $this->missingClasses[$class] = true;
        }

        return $file;
    }

    /**
     * Returns the currently registered loaders keyed by their corresponding vendor directories.
     *
     * @return array<string, self>
     */
    public static function getRegisteredLoaders()
    {
        return self::$registeredLoaders;
    }

    /**
     * @param  string       $class
     * @param  string       $ext
     * @return string|false
     */
    private function findFileWithExtension($class, $ext)
    {
        // PSR-4 lookup
        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;

        $first = $class[0];
        if (isset($this->prefixLengthsPsr4[$first])) {
            $subPath = $class;
            while (false !== $lastPos = strrpos($subPath, '\\')) {
                $subPath = substr($subPath, 0, $lastPos);
                $search = $subPath . '\\';
                if (isset($this->prefixDirsPsr4[$search])) {
                    $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
                    foreach ($this->prefixDirsPsr4[$search] as $dir) {
                        if (file_exists($file = $dir . $pathEnd)) {
                            return $file;
                        }
                    }
                }
            }
        }

        // PSR-4 fallback dirs
        foreach ($this->fallbackDirsPsr4 as $dir) {
            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
                return $file;
            }
        }

        // PSR-0 lookup
        if (false !== $pos = strrpos($class, '\\')) {
            // namespaced class name
            $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
                . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
        } else {
            // PEAR-like class name
            $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
        }

        if (isset($this->prefixesPsr0[$first])) {
            foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
                if (0 === strpos($class, $prefix)) {
                    foreach ($dirs as $dir) {
                        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                            return $file;
                        }
                    }
                }
            }
        }

        // PSR-0 fallback dirs
        foreach ($this->fallbackDirsPsr0 as $dir) {
            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                return $file;
            }
        }

        // PSR-0 include paths.
        if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
            return $file;
        }

        return false;
    }

    /**
     * @return void
     */
    private static function initializeIncludeClosure()
    {
        if (self::$includeFile !== null) {
            return;
        }

        /**
         * Scope isolated include.
         *
         * Prevents access to $this/self from included files.
         *
         * @param  string $file
         * @return void
         */
        self::$includeFile = \Closure::bind(static function($file) {
            include $file;
        }, null, null);
    }
}
PKzc�\�k���composer/autoload_files.phpnu�[���<?php

// autoload_files.php @generated by Composer

$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);

return array(
    '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
    '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
    '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
);
PKzc�\�)�)composer/installed.phpnu�[���<?php return array(
    'root' => array(
        'name' => 'roundcube/roundcubemail',
        'pretty_version' => '1.0.0+no-version-set',
        'version' => '1.0.0.0',
        'reference' => null,
        'type' => 'library',
        'install_path' => __DIR__ . '/../../',
        'aliases' => array(),
        'dev' => false,
    ),
    'versions' => array(
        'bacon/bacon-qr-code' => array(
            'pretty_version' => '2.0.8',
            'version' => '2.0.8.0',
            'reference' => '8674e51bb65af933a5ffaf1c308a660387c35c22',
            'type' => 'library',
            'install_path' => __DIR__ . '/../bacon/bacon-qr-code',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'dasprid/enum' => array(
            'pretty_version' => '1.0.5',
            'version' => '1.0.5.0',
            'reference' => '6faf451159fb8ba4126b925ed2d78acfce0dc016',
            'type' => 'library',
            'install_path' => __DIR__ . '/../dasprid/enum',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'guzzlehttp/guzzle' => array(
            'pretty_version' => '7.8.1',
            'version' => '7.8.1.0',
            'reference' => '41042bc7ab002487b876a0683fc8dce04ddce104',
            'type' => 'library',
            'install_path' => __DIR__ . '/../guzzlehttp/guzzle',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'guzzlehttp/promises' => array(
            'pretty_version' => '2.0.2',
            'version' => '2.0.2.0',
            'reference' => 'bbff78d96034045e58e13dedd6ad91b5d1253223',
            'type' => 'library',
            'install_path' => __DIR__ . '/../guzzlehttp/promises',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'guzzlehttp/psr7' => array(
            'pretty_version' => '2.6.2',
            'version' => '2.6.2.0',
            'reference' => '45b30f99ac27b5ca93cb4831afe16285f57b8221',
            'type' => 'library',
            'install_path' => __DIR__ . '/../guzzlehttp/psr7',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'kolab/net_ldap3' => array(
            'pretty_version' => 'v1.1.5',
            'version' => '1.1.5.0',
            'reference' => '5a319cf437d75aad564ce7fd076cc5423722868b',
            'type' => 'library',
            'install_path' => __DIR__ . '/../kolab/net_ldap3',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'masterminds/html5' => array(
            'pretty_version' => '2.7.6',
            'version' => '2.7.6.0',
            'reference' => '897eb517a343a2281f11bc5556d6548db7d93947',
            'type' => 'library',
            'install_path' => __DIR__ . '/../masterminds/html5',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'pear/auth_sasl' => array(
            'pretty_version' => 'v1.1.0',
            'version' => '1.1.0.0',
            'reference' => 'db1ead3dc0bf986d2bab0dbc04d114800cf91dee',
            'type' => 'library',
            'install_path' => __DIR__ . '/../pear/auth_sasl',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'pear/console_commandline' => array(
            'pretty_version' => 'v1.2.6',
            'version' => '1.2.6.0',
            'reference' => '611c5bff2e47ec5a184748cb5fedc2869098ff28',
            'type' => 'library',
            'install_path' => __DIR__ . '/../pear/console_commandline',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'pear/console_getopt' => array(
            'pretty_version' => 'v1.4.3',
            'version' => '1.4.3.0',
            'reference' => 'a41f8d3e668987609178c7c4a9fe48fecac53fa0',
            'type' => 'library',
            'install_path' => __DIR__ . '/../pear/console_getopt',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'pear/crypt_gpg' => array(
            'pretty_version' => 'v1.6.9',
            'version' => '1.6.9.0',
            'reference' => '1572e126f8c8e083af6db1ebe43fcd9c7954537c',
            'type' => 'library',
            'install_path' => __DIR__ . '/../pear/crypt_gpg',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'pear/mail_mime' => array(
            'pretty_version' => '1.10.12',
            'version' => '1.10.12.0',
            'reference' => 'd032c7c9335e96d5954ac6e93d33955f3b7246e2',
            'type' => 'library',
            'install_path' => __DIR__ . '/../pear/mail_mime',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'pear/net_ldap2' => array(
            'pretty_version' => 'v2.3.0',
            'version' => '2.3.0.0',
            'reference' => '6eae6d20533469ffe1c01684923cd09f0fae3b77',
            'type' => 'library',
            'install_path' => __DIR__ . '/../pear/net_ldap2',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'pear/net_sieve' => array(
            'pretty_version' => '1.4.7',
            'version' => '1.4.7.0',
            'reference' => '31b3ef38d75e681d5589c5ed2267314b79c1dfe8',
            'type' => 'library',
            'install_path' => __DIR__ . '/../pear/net_sieve',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'pear/net_smtp' => array(
            'pretty_version' => '1.10.1',
            'version' => '1.10.1.0',
            'reference' => 'cfd963dc5cc73b4d64c7769e47dfa0f439dec536',
            'type' => 'library',
            'install_path' => __DIR__ . '/../pear/net_smtp',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'pear/net_socket' => array(
            'pretty_version' => 'v1.2.2',
            'version' => '1.2.2.0',
            'reference' => 'bbe6a12bb4f7059dba161f6ddd43f369c0ec8d09',
            'type' => 'library',
            'install_path' => __DIR__ . '/../pear/net_socket',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'pear/pear-core-minimal' => array(
            'pretty_version' => 'v1.10.15',
            'version' => '1.10.15.0',
            'reference' => 'ce0adade8b97561656ace07cdaac4751c271ea8c',
            'type' => 'library',
            'install_path' => __DIR__ . '/../pear/pear-core-minimal',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'pear/pear_exception' => array(
            'pretty_version' => 'v1.0.2',
            'version' => '1.0.2.0',
            'reference' => 'b14fbe2ddb0b9f94f5b24cf08783d599f776fff0',
            'type' => 'class',
            'install_path' => __DIR__ . '/../pear/pear_exception',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'psr/http-client' => array(
            'pretty_version' => '1.0.3',
            'version' => '1.0.3.0',
            'reference' => 'bb5906edc1c324c9a05aa0873d40117941e5fa90',
            'type' => 'library',
            'install_path' => __DIR__ . '/../psr/http-client',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'psr/http-client-implementation' => array(
            'dev_requirement' => false,
            'provided' => array(
                0 => '1.0',
            ),
        ),
        'psr/http-factory' => array(
            'pretty_version' => '1.1.0',
            'version' => '1.1.0.0',
            'reference' => '2b4765fddfe3b508ac62f829e852b1501d3f6e8a',
            'type' => 'library',
            'install_path' => __DIR__ . '/../psr/http-factory',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'psr/http-factory-implementation' => array(
            'dev_requirement' => false,
            'provided' => array(
                0 => '1.0',
            ),
        ),
        'psr/http-message' => array(
            'pretty_version' => '2.0',
            'version' => '2.0.0.0',
            'reference' => '402d35bcb92c70c026d1a6a9883f06b2ead23d71',
            'type' => 'library',
            'install_path' => __DIR__ . '/../psr/http-message',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'psr/http-message-implementation' => array(
            'dev_requirement' => false,
            'provided' => array(
                0 => '1.0',
            ),
        ),
        'ralouphie/getallheaders' => array(
            'pretty_version' => '3.0.3',
            'version' => '3.0.3.0',
            'reference' => '120b605dfeb996808c31b6477290a714d356e822',
            'type' => 'library',
            'install_path' => __DIR__ . '/../ralouphie/getallheaders',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'roundcube/plugin-installer' => array(
            'pretty_version' => '0.3.7',
            'version' => '0.3.7.0',
            'reference' => 'f77c307b7d4e4000cb423cc3fac69fb1a9187aad',
            'type' => 'composer-plugin',
            'install_path' => __DIR__ . '/../roundcube/plugin-installer',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'roundcube/roundcubemail' => array(
            'pretty_version' => '1.0.0+no-version-set',
            'version' => '1.0.0.0',
            'reference' => null,
            'type' => 'library',
            'install_path' => __DIR__ . '/../../',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'roundcube/rtf-html-php' => array(
            'pretty_version' => 'v2.2',
            'version' => '2.2.0.0',
            'reference' => 'a3432ca249b73bf24fec50114191a63ad8b1478c',
            'type' => 'library',
            'install_path' => __DIR__ . '/../roundcube/rtf-html-php',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'rsky/pear-core-min' => array(
            'dev_requirement' => false,
            'replaced' => array(
                0 => 'v1.10.15',
            ),
        ),
        'symfony/deprecation-contracts' => array(
            'pretty_version' => 'v2.5.3',
            'version' => '2.5.3.0',
            'reference' => '80d075412b557d41002320b96a096ca65aa2c98d',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/deprecation-contracts',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
    ),
);
PKzc�\�]p�

composer/include_paths.phpnu�[���<?php

// include_paths.php @generated by Composer

$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);

return array(
    $vendorDir . '/pear/pear_exception',
    $vendorDir . '/pear/console_getopt',
    $vendorDir . '/pear/pear-core-minimal/src',
    $vendorDir . '/pear/net_ldap2',
    $vendorDir . '/pear/auth_sasl',
    $vendorDir . '/pear/console_commandline',
    $vendorDir . '/pear/crypt_gpg',
    $vendorDir . '/pear/mail_mime',
    $vendorDir . '/pear/net_socket',
    $vendorDir . '/pear/net_smtp',
);
PKzc�\���99composer/autoload_real.phpnu�[���<?php

// autoload_real.php @generated by Composer

class ComposerAutoloaderInitd30534b7ccef70d5be1f4ba9bc89dbe4
{
    private static $loader;

    public static function loadClassLoader($class)
    {
        if ('Composer\Autoload\ClassLoader' === $class) {
            require __DIR__ . '/ClassLoader.php';
        }
    }

    /**
     * @return \Composer\Autoload\ClassLoader
     */
    public static function getLoader()
    {
        if (null !== self::$loader) {
            return self::$loader;
        }

        require __DIR__ . '/platform_check.php';

        spl_autoload_register(array('ComposerAutoloaderInitd30534b7ccef70d5be1f4ba9bc89dbe4', 'loadClassLoader'), true, true);
        self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
        spl_autoload_unregister(array('ComposerAutoloaderInitd30534b7ccef70d5be1f4ba9bc89dbe4', 'loadClassLoader'));

        $includePaths = require __DIR__ . '/include_paths.php';
        $includePaths[] = get_include_path();
        set_include_path(implode(PATH_SEPARATOR, $includePaths));

        require __DIR__ . '/autoload_static.php';
        call_user_func(\Composer\Autoload\ComposerStaticInitd30534b7ccef70d5be1f4ba9bc89dbe4::getInitializer($loader));

        $loader->register(true);

        $filesToLoad = \Composer\Autoload\ComposerStaticInitd30534b7ccef70d5be1f4ba9bc89dbe4::$files;
        $requireFile = \Closure::bind(static function ($fileIdentifier, $file) {
            if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
                $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;

                require $file;
            }
        }, null, null);
        foreach ($filesToLoad as $fileIdentifier => $file) {
            $requireFile($fileIdentifier, $file);
        }

        return $loader;
    }
}
PK{c�\�3�2��composer/autoload_psr4.phpnu�[���<?php

// autoload_psr4.php @generated by Composer

$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);

return array(
    'RtfHtmlPhp\\' => array($vendorDir . '/roundcube/rtf-html-php/src'),
    'Roundcube\\Composer\\' => array($vendorDir . '/roundcube/plugin-installer/src'),
    'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src', $vendorDir . '/psr/http-factory/src'),
    'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'),
    'Masterminds\\' => array($vendorDir . '/masterminds/html5/src'),
    'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'),
    'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'),
    'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'),
    'DASPRiD\\Enum\\' => array($vendorDir . '/dasprid/enum/src'),
    'BaconQrCode\\' => array($vendorDir . '/bacon/bacon-qr-code/src'),
);
PK{c�\*���� composer/autoload_namespaces.phpnu�[���<?php

// autoload_namespaces.php @generated by Composer

$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);

return array(
    'Net' => array($vendorDir . '/pear/net_socket', $vendorDir . '/pear/net_smtp'),
    'Mail' => array($vendorDir . '/pear/mail_mime'),
    'Console' => array($vendorDir . '/pear/console_getopt', $vendorDir . '/pear/console_commandline'),
    'Auth' => array($vendorDir . '/pear/auth_sasl'),
);
PK{c�\U�@@"bacon/bacon-qr-code/src/Writer.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode;

use BaconQrCode\Common\ErrorCorrectionLevel;
use BaconQrCode\Common\Version;
use BaconQrCode\Encoder\Encoder;
use BaconQrCode\Exception\InvalidArgumentException;
use BaconQrCode\Renderer\RendererInterface;

/**
 * QR code writer.
 */
final class Writer
{
    /**
     * Renderer instance.
     *
     * @var RendererInterface
     */
    private $renderer;

    /**
     * Creates a new writer with a specific renderer.
     */
    public function __construct(RendererInterface $renderer)
    {
        $this->renderer = $renderer;
    }

    /**
     * Writes QR code and returns it as string.
     *
     * Content is a string which *should* be encoded in UTF-8, in case there are
     * non ASCII-characters present.
     *
     * @throws InvalidArgumentException if the content is empty
     */
    public function writeString(
        string $content,
        string $encoding = Encoder::DEFAULT_BYTE_MODE_ECODING,
        ?ErrorCorrectionLevel $ecLevel = null,
        ?Version $forcedVersion = null
    ) : string {
        if (strlen($content) === 0) {
            throw new InvalidArgumentException('Found empty contents');
        }

        if (null === $ecLevel) {
            $ecLevel = ErrorCorrectionLevel::L();
        }

        return $this->renderer->render(Encoder::encode($content, $ecLevel, $encoding, $forcedVersion));
    }

    /**
     * Writes QR code to a file.
     *
     * @see Writer::writeString()
     */
    public function writeFile(
        string $content,
        string $filename,
        string $encoding = Encoder::DEFAULT_BYTE_MODE_ECODING,
        ?ErrorCorrectionLevel $ecLevel = null,
        ?Version $forcedVersion = null
    ) : void {
        file_put_contents($filename, $this->writeString($content, $encoding, $ecLevel, $forcedVersion));
    }
}
PK{c�\�����>bacon/bacon-qr-code/src/Exception/UnexpectedValueException.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Exception;

final class UnexpectedValueException extends \UnexpectedValueException implements ExceptionInterface
{
}
PK{c�\y&��:bacon/bacon-qr-code/src/Exception/OutOfBoundsException.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Exception;

final class OutOfBoundsException extends \OutOfBoundsException implements ExceptionInterface
{
}
PK{c�\6�����>bacon/bacon-qr-code/src/Exception/InvalidArgumentException.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Exception;

final class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
PK{c�\"�s���8bacon/bacon-qr-code/src/Exception/ExceptionInterface.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Exception;

use Throwable;

interface ExceptionInterface extends Throwable
{
}
PK{c�\3/f!��6bacon/bacon-qr-code/src/Exception/RuntimeException.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Exception;

final class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
PK{c�\��-��5bacon/bacon-qr-code/src/Exception/WriterException.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Exception;

final class WriterException extends \RuntimeException implements ExceptionInterface
{
}
PK{c�\JJ�o88,bacon/bacon-qr-code/src/Common/BitMatrix.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Common;

use BaconQrCode\Exception\InvalidArgumentException;
use SplFixedArray;

/**
 * Bit matrix.
 *
 * Represents a 2D matrix of bits. In function arguments below, and throughout
 * the common module, x is the column position, and y is the row position. The
 * ordering is always x, y. The origin is at the top-left.
 */
class BitMatrix
{
    /**
     * Width of the bit matrix.
     *
     * @var int
     */
    private $width;

    /**
     * Height of the bit matrix.
     *
     * @var int
     */
    private $height;

    /**
     * Size in bits of each individual row.
     *
     * @var int
     */
    private $rowSize;

    /**
     * Bits representation.
     *
     * @var SplFixedArray<int>
     */
    private $bits;

    /**
     * @throws InvalidArgumentException if a dimension is smaller than zero
     */
    public function __construct(int $width, int $height = null)
    {
        if (null === $height) {
            $height = $width;
        }

        if ($width < 1 || $height < 1) {
            throw new InvalidArgumentException('Both dimensions must be greater than zero');
        }

        $this->width = $width;
        $this->height = $height;
        $this->rowSize = ($width + 31) >> 5;
        $this->bits = SplFixedArray::fromArray(array_fill(0, $this->rowSize * $height, 0));
    }

    /**
     * Gets the requested bit, where true means black.
     */
    public function get(int $x, int $y) : bool
    {
        $offset = $y * $this->rowSize + ($x >> 5);
        return 0 !== (BitUtils::unsignedRightShift($this->bits[$offset], ($x & 0x1f)) & 1);
    }

    /**
     * Sets the given bit to true.
     */
    public function set(int $x, int $y) : void
    {
        $offset = $y * $this->rowSize + ($x >> 5);
        $this->bits[$offset] = $this->bits[$offset] | (1 << ($x & 0x1f));
    }

    /**
     * Flips the given bit.
     */
    public function flip(int $x, int $y) : void
    {
        $offset = $y * $this->rowSize + ($x >> 5);
        $this->bits[$offset] = $this->bits[$offset] ^ (1 << ($x & 0x1f));
    }

    /**
     * Clears all bits (set to false).
     */
    public function clear() : void
    {
        $max = count($this->bits);

        for ($i = 0; $i < $max; ++$i) {
            $this->bits[$i] = 0;
        }
    }

    /**
     * Sets a square region of the bit matrix to true.
     *
     * @throws InvalidArgumentException if left or top are negative
     * @throws InvalidArgumentException if width or height are smaller than 1
     * @throws InvalidArgumentException if region does not fit into the matix
     */
    public function setRegion(int $left, int $top, int $width, int $height) : void
    {
        if ($top < 0 || $left < 0) {
            throw new InvalidArgumentException('Left and top must be non-negative');
        }

        if ($height < 1 || $width < 1) {
            throw new InvalidArgumentException('Width and height must be at least 1');
        }

        $right = $left + $width;
        $bottom = $top + $height;

        if ($bottom > $this->height || $right > $this->width) {
            throw new InvalidArgumentException('The region must fit inside the matrix');
        }

        for ($y = $top; $y < $bottom; ++$y) {
            $offset = $y * $this->rowSize;

            for ($x = $left; $x < $right; ++$x) {
                $index = $offset + ($x >> 5);
                $this->bits[$index] = $this->bits[$index] | (1 << ($x & 0x1f));
            }
        }
    }

    /**
     * A fast method to retrieve one row of data from the matrix as a BitArray.
     */
    public function getRow(int $y, BitArray $row = null) : BitArray
    {
        if (null === $row || $row->getSize() < $this->width) {
            $row = new BitArray($this->width);
        }

        $offset = $y * $this->rowSize;

        for ($x = 0; $x < $this->rowSize; ++$x) {
            $row->setBulk($x << 5, $this->bits[$offset + $x]);
        }

        return $row;
    }

    /**
     * Sets a row of data from a BitArray.
     */
    public function setRow(int $y, BitArray $row) : void
    {
        $bits = $row->getBitArray();

        for ($i = 0; $i < $this->rowSize; ++$i) {
            $this->bits[$y * $this->rowSize + $i] = $bits[$i];
        }
    }

    /**
     * This is useful in detecting the enclosing rectangle of a 'pure' barcode.
     *
     * @return int[]|null
     */
    public function getEnclosingRectangle() : ?array
    {
        $left = $this->width;
        $top = $this->height;
        $right = -1;
        $bottom = -1;

        for ($y = 0; $y < $this->height; ++$y) {
            for ($x32 = 0; $x32 < $this->rowSize; ++$x32) {
                $bits = $this->bits[$y * $this->rowSize + $x32];

                if (0 !== $bits) {
                    if ($y < $top) {
                        $top = $y;
                    }

                    if ($y > $bottom) {
                        $bottom = $y;
                    }

                    if ($x32 * 32 < $left) {
                        $bit = 0;

                        while (($bits << (31 - $bit)) === 0) {
                            $bit++;
                        }

                        if (($x32 * 32 + $bit) < $left) {
                            $left = $x32 * 32 + $bit;
                        }
                    }
                }

                if ($x32 * 32 + 31 > $right) {
                    $bit = 31;

                    while (0 === BitUtils::unsignedRightShift($bits, $bit)) {
                        --$bit;
                    }

                    if (($x32 * 32 + $bit) > $right) {
                        $right = $x32 * 32 + $bit;
                    }
                }
            }
        }

        $width = $right - $left;
        $height = $bottom - $top;

        if ($width < 0 || $height < 0) {
            return null;
        }

        return [$left, $top, $width, $height];
    }

    /**
     * Gets the most top left set bit.
     *
     * This is useful in detecting a corner of a 'pure' barcode.
     *
     * @return int[]|null
     */
    public function getTopLeftOnBit() : ?array
    {
        $bitsOffset = 0;

        while ($bitsOffset < count($this->bits) && 0 === $this->bits[$bitsOffset]) {
            ++$bitsOffset;
        }

        if (count($this->bits) === $bitsOffset) {
            return null;
        }

        $x = intdiv($bitsOffset, $this->rowSize);
        $y = ($bitsOffset % $this->rowSize) << 5;

        $bits = $this->bits[$bitsOffset];
        $bit = 0;

        while (0 === ($bits << (31 - $bit))) {
            ++$bit;
        }

        $x += $bit;

        return [$x, $y];
    }

    /**
     * Gets the most bottom right set bit.
     *
     * This is useful in detecting a corner of a 'pure' barcode.
     *
     * @return int[]|null
     */
    public function getBottomRightOnBit() : ?array
    {
        $bitsOffset = count($this->bits) - 1;

        while ($bitsOffset >= 0 && 0 === $this->bits[$bitsOffset]) {
            --$bitsOffset;
        }

        if ($bitsOffset < 0) {
            return null;
        }

        $x = intdiv($bitsOffset, $this->rowSize);
        $y = ($bitsOffset % $this->rowSize) << 5;

        $bits = $this->bits[$bitsOffset];
        $bit  = 0;

        while (0 === BitUtils::unsignedRightShift($bits, $bit)) {
            --$bit;
        }

        $x += $bit;

        return [$x, $y];
    }

    /**
     * Gets the width of the matrix,
     */
    public function getWidth() : int
    {
        return $this->width;
    }

    /**
     * Gets the height of the matrix.
     */
    public function getHeight() : int
    {
        return $this->height;
    }
}
PK{c�\}@v==7bacon/bacon-qr-code/src/Common/ErrorCorrectionLevel.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Common;

use BaconQrCode\Exception\OutOfBoundsException;
use DASPRiD\Enum\AbstractEnum;

/**
 * Enum representing the four error correction levels.
 *
 * @method static self L() ~7% correction
 * @method static self M() ~15% correction
 * @method static self Q() ~25% correction
 * @method static self H() ~30% correction
 */
final class ErrorCorrectionLevel extends AbstractEnum
{
    protected const L = [0x01];
    protected const M = [0x00];
    protected const Q = [0x03];
    protected const H = [0x02];

    /**
     * @var int
     */
    private $bits;

    protected function __construct(int $bits)
    {
        $this->bits = $bits;
    }

    /**
     * @throws OutOfBoundsException if number of bits is invalid
     */
    public static function forBits(int $bits) : self
    {
        switch ($bits) {
            case 0:
                return self::M();

            case 1:
                return self::L();

            case 2:
                return self::H();

            case 3:
                return self::Q();
        }

        throw new OutOfBoundsException('Invalid number of bits');
    }

    /**
     * Returns the two bits used to encode this error correction level.
     */
    public function getBits() : int
    {
        return $this->bits;
    }
}
PK{c�\���.��2bacon/bacon-qr-code/src/Common/CharacterSetEci.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Common;

use BaconQrCode\Exception\InvalidArgumentException;
use DASPRiD\Enum\AbstractEnum;

/**
 * Encapsulates a Character Set ECI, according to "Extended Channel Interpretations" 5.3.1.1 of ISO 18004.
 *
 * @method static self CP437()
 * @method static self ISO8859_1()
 * @method static self ISO8859_2()
 * @method static self ISO8859_3()
 * @method static self ISO8859_4()
 * @method static self ISO8859_5()
 * @method static self ISO8859_6()
 * @method static self ISO8859_7()
 * @method static self ISO8859_8()
 * @method static self ISO8859_9()
 * @method static self ISO8859_10()
 * @method static self ISO8859_11()
 * @method static self ISO8859_12()
 * @method static self ISO8859_13()
 * @method static self ISO8859_14()
 * @method static self ISO8859_15()
 * @method static self ISO8859_16()
 * @method static self SJIS()
 * @method static self CP1250()
 * @method static self CP1251()
 * @method static self CP1252()
 * @method static self CP1256()
 * @method static self UNICODE_BIG_UNMARKED()
 * @method static self UTF8()
 * @method static self ASCII()
 * @method static self BIG5()
 * @method static self GB18030()
 * @method static self EUC_KR()
 */
final class CharacterSetEci extends AbstractEnum
{
    protected const CP437 = [[0, 2]];
    protected const ISO8859_1 = [[1, 3], 'ISO-8859-1'];
    protected const ISO8859_2 = [[4], 'ISO-8859-2'];
    protected const ISO8859_3 = [[5], 'ISO-8859-3'];
    protected const ISO8859_4 = [[6], 'ISO-8859-4'];
    protected const ISO8859_5 = [[7], 'ISO-8859-5'];
    protected const ISO8859_6 = [[8], 'ISO-8859-6'];
    protected const ISO8859_7 = [[9], 'ISO-8859-7'];
    protected const ISO8859_8 = [[10], 'ISO-8859-8'];
    protected const ISO8859_9 = [[11], 'ISO-8859-9'];
    protected const ISO8859_10 = [[12], 'ISO-8859-10'];
    protected const ISO8859_11 = [[13], 'ISO-8859-11'];
    protected const ISO8859_12 = [[14], 'ISO-8859-12'];
    protected const ISO8859_13 = [[15], 'ISO-8859-13'];
    protected const ISO8859_14 = [[16], 'ISO-8859-14'];
    protected const ISO8859_15 = [[17], 'ISO-8859-15'];
    protected const ISO8859_16 = [[18], 'ISO-8859-16'];
    protected const SJIS = [[20], 'Shift_JIS'];
    protected const CP1250 = [[21], 'windows-1250'];
    protected const CP1251 = [[22], 'windows-1251'];
    protected const CP1252 = [[23], 'windows-1252'];
    protected const CP1256 = [[24], 'windows-1256'];
    protected const UNICODE_BIG_UNMARKED = [[25], 'UTF-16BE', 'UnicodeBig'];
    protected const UTF8 = [[26], 'UTF-8'];
    protected const ASCII = [[27, 170], 'US-ASCII'];
    protected const BIG5 = [[28]];
    protected const GB18030 = [[29], 'GB2312', 'EUC_CN', 'GBK'];
    protected const EUC_KR = [[30], 'EUC-KR'];

    /**
     * @var int[]
     */
    private $values;

    /**
     * @var string[]
     */
    private $otherEncodingNames;

    /**
     * @var array<int, self>|null
     */
    private static $valueToEci;

    /**
     * @var array<string, self>|null
     */
    private static $nameToEci;

    /**
     * @param int[] $values
     */
    public function __construct(array $values, string ...$otherEncodingNames)
    {
        $this->values = $values;
        $this->otherEncodingNames = $otherEncodingNames;
    }

    /**
     * Returns the primary value.
     */
    public function getValue() : int
    {
        return $this->values[0];
    }

    /**
     * Gets character set ECI by value.
     *
     * Returns the representing ECI of a given value, or null if it is legal but unsupported.
     *
     * @throws InvalidArgumentException if value is not between 0 and 900
     */
    public static function getCharacterSetEciByValue(int $value) : ?self
    {
        if ($value < 0 || $value >= 900) {
            throw new InvalidArgumentException('Value must be between 0 and 900');
        }

        $valueToEci = self::valueToEci();

        if (! array_key_exists($value, $valueToEci)) {
            return null;
        }

        return $valueToEci[$value];
    }

    /**
     * Returns character set ECI by name.
     *
     * Returns the representing ECI of a given name, or null if it is legal but unsupported
     */
    public static function getCharacterSetEciByName(string $name) : ?self
    {
        $nameToEci = self::nameToEci();
        $name = strtolower($name);

        if (! array_key_exists($name, $nameToEci)) {
            return null;
        }

        return $nameToEci[$name];
    }

    private static function valueToEci() : array
    {
        if (null !== self::$valueToEci) {
            return self::$valueToEci;
        }

        self::$valueToEci = [];

        foreach (self::values() as $eci) {
            foreach ($eci->values as $value) {
                self::$valueToEci[$value] = $eci;
            }
        }

        return self::$valueToEci;
    }

    private static function nameToEci() : array
    {
        if (null !== self::$nameToEci) {
            return self::$nameToEci;
        }

        self::$nameToEci = [];

        foreach (self::values() as $eci) {
            self::$nameToEci[strtolower($eci->name())] = $eci;

            foreach ($eci->otherEncodingNames as $name) {
                self::$nameToEci[strtolower($name)] = $eci;
            }
        }

        return self::$nameToEci;
    }
}
PK{c�\�}n�pUpU*bacon/bacon-qr-code/src/Common/Version.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Common;

use BaconQrCode\Exception\InvalidArgumentException;
use SplFixedArray;

/**
 * Version representation.
 */
final class Version
{
    private const VERSION_DECODE_INFO = [
        0x07c94,
        0x085bc,
        0x09a99,
        0x0a4d3,
        0x0bbf6,
        0x0c762,
        0x0d847,
        0x0e60d,
        0x0f928,
        0x10b78,
        0x1145d,
        0x12a17,
        0x13532,
        0x149a6,
        0x15683,
        0x168c9,
        0x177ec,
        0x18ec4,
        0x191e1,
        0x1afab,
        0x1b08e,
        0x1cc1a,
        0x1d33f,
        0x1ed75,
        0x1f250,
        0x209d5,
        0x216f0,
        0x228ba,
        0x2379f,
        0x24b0b,
        0x2542e,
        0x26a64,
        0x27541,
        0x28c69,
    ];

    /**
     * Version number of this version.
     *
     * @var int
     */
    private $versionNumber;

    /**
     * Alignment pattern centers.
     *
     * @var SplFixedArray
     */
    private $alignmentPatternCenters;

    /**
     * Error correction blocks.
     *
     * @var EcBlocks[]
     */
    private $ecBlocks;

    /**
     * Total number of codewords.
     *
     * @var int
     */
    private $totalCodewords;

    /**
     * Cached version instances.
     *
     * @var array<int, self>|null
     */
    private static $versions;

    /**
     * @param int[] $alignmentPatternCenters
     */
    private function __construct(
        int $versionNumber,
        array $alignmentPatternCenters,
        EcBlocks ...$ecBlocks
    ) {
        $this->versionNumber = $versionNumber;
        $this->alignmentPatternCenters = $alignmentPatternCenters;
        $this->ecBlocks = $ecBlocks;

        $totalCodewords = 0;
        $ecCodewords = $ecBlocks[0]->getEcCodewordsPerBlock();

        foreach ($ecBlocks[0]->getEcBlocks() as $ecBlock) {
            $totalCodewords += $ecBlock->getCount() * ($ecBlock->getDataCodewords() + $ecCodewords);
        }

        $this->totalCodewords = $totalCodewords;
    }

    /**
     * Returns the version number.
     */
    public function getVersionNumber() : int
    {
        return $this->versionNumber;
    }

    /**
     * Returns the alignment pattern centers.
     *
     * @return int[]
     */
    public function getAlignmentPatternCenters() : array
    {
        return $this->alignmentPatternCenters;
    }

    /**
     * Returns the total number of codewords.
     */
    public function getTotalCodewords() : int
    {
        return $this->totalCodewords;
    }

    /**
     * Calculates the dimension for the current version.
     */
    public function getDimensionForVersion() : int
    {
        return 17 + 4 * $this->versionNumber;
    }

    /**
     * Returns the number of EC blocks for a specific EC level.
     */
    public function getEcBlocksForLevel(ErrorCorrectionLevel $ecLevel) : EcBlocks
    {
        return $this->ecBlocks[$ecLevel->ordinal()];
    }

    /**
     * Gets a provisional version number for a specific dimension.
     *
     * @throws InvalidArgumentException if dimension is not 1 mod 4
     */
    public static function getProvisionalVersionForDimension(int $dimension) : self
    {
        if (1 !== $dimension % 4) {
            throw new InvalidArgumentException('Dimension is not 1 mod 4');
        }

        return self::getVersionForNumber(intdiv($dimension - 17, 4));
    }

    /**
     * Gets a version instance for a specific version number.
     *
     * @throws InvalidArgumentException if version number is out of range
     */
    public static function getVersionForNumber(int $versionNumber) : self
    {
        if ($versionNumber < 1 || $versionNumber > 40) {
            throw new InvalidArgumentException('Version number must be between 1 and 40');
        }

        return self::versions()[$versionNumber - 1];
    }

    /**
     * Decodes version information from an integer and returns the version.
     */
    public static function decodeVersionInformation(int $versionBits) : ?self
    {
        $bestDifference = PHP_INT_MAX;
        $bestVersion = 0;

        foreach (self::VERSION_DECODE_INFO as $i => $targetVersion) {
            if ($targetVersion === $versionBits) {
                return self::getVersionForNumber($i + 7);
            }

            $bitsDifference = FormatInformation::numBitsDiffering($versionBits, $targetVersion);

            if ($bitsDifference < $bestDifference) {
                $bestVersion = $i + 7;
                $bestDifference = $bitsDifference;
            }
        }

        if ($bestDifference <= 3) {
            return self::getVersionForNumber($bestVersion);
        }

        return null;
    }

    /**
     * Builds the function pattern for the current version.
     */
    public function buildFunctionPattern() : BitMatrix
    {
        $dimension = $this->getDimensionForVersion();
        $bitMatrix = new BitMatrix($dimension);

        // Top left finder pattern + separator + format
        $bitMatrix->setRegion(0, 0, 9, 9);
        // Top right finder pattern + separator + format
        $bitMatrix->setRegion($dimension - 8, 0, 8, 9);
        // Bottom left finder pattern + separator + format
        $bitMatrix->setRegion(0, $dimension - 8, 9, 8);

        // Alignment patterns
        $max = count($this->alignmentPatternCenters);

        for ($x = 0; $x < $max; ++$x) {
            $i = $this->alignmentPatternCenters[$x] - 2;

            for ($y = 0; $y < $max; ++$y) {
                if (($x === 0 && ($y === 0 || $y === $max - 1)) || ($x === $max - 1 && $y === 0)) {
                    // No alignment patterns near the three finder paterns
                    continue;
                }

                $bitMatrix->setRegion($this->alignmentPatternCenters[$y] - 2, $i, 5, 5);
            }
        }

        // Vertical timing pattern
        $bitMatrix->setRegion(6, 9, 1, $dimension - 17);
        // Horizontal timing pattern
        $bitMatrix->setRegion(9, 6, $dimension - 17, 1);

        if ($this->versionNumber > 6) {
            // Version info, top right
            $bitMatrix->setRegion($dimension - 11, 0, 3, 6);
            // Version info, bottom left
            $bitMatrix->setRegion(0, $dimension - 11, 6, 3);
        }

        return $bitMatrix;
    }

    /**
     * Returns a string representation for the version.
     */
    public function __toString() : string
    {
        return (string) $this->versionNumber;
    }

    /**
     * Build and cache a specific version.
     *
     * See ISO 18004:2006 6.5.1 Table 9.
     *
     * @return array<int, self>
     */
    private static function versions() : array
    {
        if (null !== self::$versions) {
            return self::$versions;
        }

        return self::$versions = [
            new self(
                1,
                [],
                new EcBlocks(7, new EcBlock(1, 19)),
                new EcBlocks(10, new EcBlock(1, 16)),
                new EcBlocks(13, new EcBlock(1, 13)),
                new EcBlocks(17, new EcBlock(1, 9))
            ),
            new self(
                2,
                [6, 18],
                new EcBlocks(10, new EcBlock(1, 34)),
                new EcBlocks(16, new EcBlock(1, 28)),
                new EcBlocks(22, new EcBlock(1, 22)),
                new EcBlocks(28, new EcBlock(1, 16))
            ),
            new self(
                3,
                [6, 22],
                new EcBlocks(15, new EcBlock(1, 55)),
                new EcBlocks(26, new EcBlock(1, 44)),
                new EcBlocks(18, new EcBlock(2, 17)),
                new EcBlocks(22, new EcBlock(2, 13))
            ),
            new self(
                4,
                [6, 26],
                new EcBlocks(20, new EcBlock(1, 80)),
                new EcBlocks(18, new EcBlock(2, 32)),
                new EcBlocks(26, new EcBlock(3, 24)),
                new EcBlocks(16, new EcBlock(4, 9))
            ),
            new self(
                5,
                [6, 30],
                new EcBlocks(26, new EcBlock(1, 108)),
                new EcBlocks(24, new EcBlock(2, 43)),
                new EcBlocks(18, new EcBlock(2, 15), new EcBlock(2, 16)),
                new EcBlocks(22, new EcBlock(2, 11), new EcBlock(2, 12))
            ),
            new self(
                6,
                [6, 34],
                new EcBlocks(18, new EcBlock(2, 68)),
                new EcBlocks(16, new EcBlock(4, 27)),
                new EcBlocks(24, new EcBlock(4, 19)),
                new EcBlocks(28, new EcBlock(4, 15))
            ),
            new self(
                7,
                [6, 22, 38],
                new EcBlocks(20, new EcBlock(2, 78)),
                new EcBlocks(18, new EcBlock(4, 31)),
                new EcBlocks(18, new EcBlock(2, 14), new EcBlock(4, 15)),
                new EcBlocks(26, new EcBlock(4, 13), new EcBlock(1, 14))
            ),
            new self(
                8,
                [6, 24, 42],
                new EcBlocks(24, new EcBlock(2, 97)),
                new EcBlocks(22, new EcBlock(2, 38), new EcBlock(2, 39)),
                new EcBlocks(22, new EcBlock(4, 18), new EcBlock(2, 19)),
                new EcBlocks(26, new EcBlock(4, 14), new EcBlock(2, 15))
            ),
            new self(
                9,
                [6, 26, 46],
                new EcBlocks(30, new EcBlock(2, 116)),
                new EcBlocks(22, new EcBlock(3, 36), new EcBlock(2, 37)),
                new EcBlocks(20, new EcBlock(4, 16), new EcBlock(4, 17)),
                new EcBlocks(24, new EcBlock(4, 12), new EcBlock(4, 13))
            ),
            new self(
                10,
                [6, 28, 50],
                new EcBlocks(18, new EcBlock(2, 68), new EcBlock(2, 69)),
                new EcBlocks(26, new EcBlock(4, 43), new EcBlock(1, 44)),
                new EcBlocks(24, new EcBlock(6, 19), new EcBlock(2, 20)),
                new EcBlocks(28, new EcBlock(6, 15), new EcBlock(2, 16))
            ),
            new self(
                11,
                [6, 30, 54],
                new EcBlocks(20, new EcBlock(4, 81)),
                new EcBlocks(30, new EcBlock(1, 50), new EcBlock(4, 51)),
                new EcBlocks(28, new EcBlock(4, 22), new EcBlock(4, 23)),
                new EcBlocks(24, new EcBlock(3, 12), new EcBlock(8, 13))
            ),
            new self(
                12,
                [6, 32, 58],
                new EcBlocks(24, new EcBlock(2, 92), new EcBlock(2, 93)),
                new EcBlocks(22, new EcBlock(6, 36), new EcBlock(2, 37)),
                new EcBlocks(26, new EcBlock(4, 20), new EcBlock(6, 21)),
                new EcBlocks(28, new EcBlock(7, 14), new EcBlock(4, 15))
            ),
            new self(
                13,
                [6, 34, 62],
                new EcBlocks(26, new EcBlock(4, 107)),
                new EcBlocks(22, new EcBlock(8, 37), new EcBlock(1, 38)),
                new EcBlocks(24, new EcBlock(8, 20), new EcBlock(4, 21)),
                new EcBlocks(22, new EcBlock(12, 11), new EcBlock(4, 12))
            ),
            new self(
                14,
                [6, 26, 46, 66],
                new EcBlocks(30, new EcBlock(3, 115), new EcBlock(1, 116)),
                new EcBlocks(24, new EcBlock(4, 40), new EcBlock(5, 41)),
                new EcBlocks(20, new EcBlock(11, 16), new EcBlock(5, 17)),
                new EcBlocks(24, new EcBlock(11, 12), new EcBlock(5, 13))
            ),
            new self(
                15,
                [6, 26, 48, 70],
                new EcBlocks(22, new EcBlock(5, 87), new EcBlock(1, 88)),
                new EcBlocks(24, new EcBlock(5, 41), new EcBlock(5, 42)),
                new EcBlocks(30, new EcBlock(5, 24), new EcBlock(7, 25)),
                new EcBlocks(24, new EcBlock(11, 12), new EcBlock(7, 13))
            ),
            new self(
                16,
                [6, 26, 50, 74],
                new EcBlocks(24, new EcBlock(5, 98), new EcBlock(1, 99)),
                new EcBlocks(28, new EcBlock(7, 45), new EcBlock(3, 46)),
                new EcBlocks(24, new EcBlock(15, 19), new EcBlock(2, 20)),
                new EcBlocks(30, new EcBlock(3, 15), new EcBlock(13, 16))
            ),
            new self(
                17,
                [6, 30, 54, 78],
                new EcBlocks(28, new EcBlock(1, 107), new EcBlock(5, 108)),
                new EcBlocks(28, new EcBlock(10, 46), new EcBlock(1, 47)),
                new EcBlocks(28, new EcBlock(1, 22), new EcBlock(15, 23)),
                new EcBlocks(28, new EcBlock(2, 14), new EcBlock(17, 15))
            ),
            new self(
                18,
                [6, 30, 56, 82],
                new EcBlocks(30, new EcBlock(5, 120), new EcBlock(1, 121)),
                new EcBlocks(26, new EcBlock(9, 43), new EcBlock(4, 44)),
                new EcBlocks(28, new EcBlock(17, 22), new EcBlock(1, 23)),
                new EcBlocks(28, new EcBlock(2, 14), new EcBlock(19, 15))
            ),
            new self(
                19,
                [6, 30, 58, 86],
                new EcBlocks(28, new EcBlock(3, 113), new EcBlock(4, 114)),
                new EcBlocks(26, new EcBlock(3, 44), new EcBlock(11, 45)),
                new EcBlocks(26, new EcBlock(17, 21), new EcBlock(4, 22)),
                new EcBlocks(26, new EcBlock(9, 13), new EcBlock(16, 14))
            ),
            new self(
                20,
                [6, 34, 62, 90],
                new EcBlocks(28, new EcBlock(3, 107), new EcBlock(5, 108)),
                new EcBlocks(26, new EcBlock(3, 41), new EcBlock(13, 42)),
                new EcBlocks(30, new EcBlock(15, 24), new EcBlock(5, 25)),
                new EcBlocks(28, new EcBlock(15, 15), new EcBlock(10, 16))
            ),
            new self(
                21,
                [6, 28, 50, 72, 94],
                new EcBlocks(28, new EcBlock(4, 116), new EcBlock(4, 117)),
                new EcBlocks(26, new EcBlock(17, 42)),
                new EcBlocks(28, new EcBlock(17, 22), new EcBlock(6, 23)),
                new EcBlocks(30, new EcBlock(19, 16), new EcBlock(6, 17))
            ),
            new self(
                22,
                [6, 26, 50, 74, 98],
                new EcBlocks(28, new EcBlock(2, 111), new EcBlock(7, 112)),
                new EcBlocks(28, new EcBlock(17, 46)),
                new EcBlocks(30, new EcBlock(7, 24), new EcBlock(16, 25)),
                new EcBlocks(24, new EcBlock(34, 13))
            ),
            new self(
                23,
                [6, 30, 54, 78, 102],
                new EcBlocks(30, new EcBlock(4, 121), new EcBlock(5, 122)),
                new EcBlocks(28, new EcBlock(4, 47), new EcBlock(14, 48)),
                new EcBlocks(30, new EcBlock(11, 24), new EcBlock(14, 25)),
                new EcBlocks(30, new EcBlock(16, 15), new EcBlock(14, 16))
            ),
            new self(
                24,
                [6, 28, 54, 80, 106],
                new EcBlocks(30, new EcBlock(6, 117), new EcBlock(4, 118)),
                new EcBlocks(28, new EcBlock(6, 45), new EcBlock(14, 46)),
                new EcBlocks(30, new EcBlock(11, 24), new EcBlock(16, 25)),
                new EcBlocks(30, new EcBlock(30, 16), new EcBlock(2, 17))
            ),
            new self(
                25,
                [6, 32, 58, 84, 110],
                new EcBlocks(26, new EcBlock(8, 106), new EcBlock(4, 107)),
                new EcBlocks(28, new EcBlock(8, 47), new EcBlock(13, 48)),
                new EcBlocks(30, new EcBlock(7, 24), new EcBlock(22, 25)),
                new EcBlocks(30, new EcBlock(22, 15), new EcBlock(13, 16))
            ),
            new self(
                26,
                [6, 30, 58, 86, 114],
                new EcBlocks(28, new EcBlock(10, 114), new EcBlock(2, 115)),
                new EcBlocks(28, new EcBlock(19, 46), new EcBlock(4, 47)),
                new EcBlocks(28, new EcBlock(28, 22), new EcBlock(6, 23)),
                new EcBlocks(30, new EcBlock(33, 16), new EcBlock(4, 17))
            ),
            new self(
                27,
                [6, 34, 62, 90, 118],
                new EcBlocks(30, new EcBlock(8, 122), new EcBlock(4, 123)),
                new EcBlocks(28, new EcBlock(22, 45), new EcBlock(3, 46)),
                new EcBlocks(30, new EcBlock(8, 23), new EcBlock(26, 24)),
                new EcBlocks(30, new EcBlock(12, 15), new EcBlock(28, 16))
            ),
            new self(
                28,
                [6, 26, 50, 74, 98, 122],
                new EcBlocks(30, new EcBlock(3, 117), new EcBlock(10, 118)),
                new EcBlocks(28, new EcBlock(3, 45), new EcBlock(23, 46)),
                new EcBlocks(30, new EcBlock(4, 24), new EcBlock(31, 25)),
                new EcBlocks(30, new EcBlock(11, 15), new EcBlock(31, 16))
            ),
            new self(
                29,
                [6, 30, 54, 78, 102, 126],
                new EcBlocks(30, new EcBlock(7, 116), new EcBlock(7, 117)),
                new EcBlocks(28, new EcBlock(21, 45), new EcBlock(7, 46)),
                new EcBlocks(30, new EcBlock(1, 23), new EcBlock(37, 24)),
                new EcBlocks(30, new EcBlock(19, 15), new EcBlock(26, 16))
            ),
            new self(
                30,
                [6, 26, 52, 78, 104, 130],
                new EcBlocks(30, new EcBlock(5, 115), new EcBlock(10, 116)),
                new EcBlocks(28, new EcBlock(19, 47), new EcBlock(10, 48)),
                new EcBlocks(30, new EcBlock(15, 24), new EcBlock(25, 25)),
                new EcBlocks(30, new EcBlock(23, 15), new EcBlock(25, 16))
            ),
            new self(
                31,
                [6, 30, 56, 82, 108, 134],
                new EcBlocks(30, new EcBlock(13, 115), new EcBlock(3, 116)),
                new EcBlocks(28, new EcBlock(2, 46), new EcBlock(29, 47)),
                new EcBlocks(30, new EcBlock(42, 24), new EcBlock(1, 25)),
                new EcBlocks(30, new EcBlock(23, 15), new EcBlock(28, 16))
            ),
            new self(
                32,
                [6, 34, 60, 86, 112, 138],
                new EcBlocks(30, new EcBlock(17, 115)),
                new EcBlocks(28, new EcBlock(10, 46), new EcBlock(23, 47)),
                new EcBlocks(30, new EcBlock(10, 24), new EcBlock(35, 25)),
                new EcBlocks(30, new EcBlock(19, 15), new EcBlock(35, 16))
            ),
            new self(
                33,
                [6, 30, 58, 86, 114, 142],
                new EcBlocks(30, new EcBlock(17, 115), new EcBlock(1, 116)),
                new EcBlocks(28, new EcBlock(14, 46), new EcBlock(21, 47)),
                new EcBlocks(30, new EcBlock(29, 24), new EcBlock(19, 25)),
                new EcBlocks(30, new EcBlock(11, 15), new EcBlock(46, 16))
            ),
            new self(
                34,
                [6, 34, 62, 90, 118, 146],
                new EcBlocks(30, new EcBlock(13, 115), new EcBlock(6, 116)),
                new EcBlocks(28, new EcBlock(14, 46), new EcBlock(23, 47)),
                new EcBlocks(30, new EcBlock(44, 24), new EcBlock(7, 25)),
                new EcBlocks(30, new EcBlock(59, 16), new EcBlock(1, 17))
            ),
            new self(
                35,
                [6, 30, 54, 78, 102, 126, 150],
                new EcBlocks(30, new EcBlock(12, 121), new EcBlock(7, 122)),
                new EcBlocks(28, new EcBlock(12, 47), new EcBlock(26, 48)),
                new EcBlocks(30, new EcBlock(39, 24), new EcBlock(14, 25)),
                new EcBlocks(30, new EcBlock(22, 15), new EcBlock(41, 16))
            ),
            new self(
                36,
                [6, 24, 50, 76, 102, 128, 154],
                new EcBlocks(30, new EcBlock(6, 121), new EcBlock(14, 122)),
                new EcBlocks(28, new EcBlock(6, 47), new EcBlock(34, 48)),
                new EcBlocks(30, new EcBlock(46, 24), new EcBlock(10, 25)),
                new EcBlocks(30, new EcBlock(2, 15), new EcBlock(64, 16))
            ),
            new self(
                37,
                [6, 28, 54, 80, 106, 132, 158],
                new EcBlocks(30, new EcBlock(17, 122), new EcBlock(4, 123)),
                new EcBlocks(28, new EcBlock(29, 46), new EcBlock(14, 47)),
                new EcBlocks(30, new EcBlock(49, 24), new EcBlock(10, 25)),
                new EcBlocks(30, new EcBlock(24, 15), new EcBlock(46, 16))
            ),
            new self(
                38,
                [6, 32, 58, 84, 110, 136, 162],
                new EcBlocks(30, new EcBlock(4, 122), new EcBlock(18, 123)),
                new EcBlocks(28, new EcBlock(13, 46), new EcBlock(32, 47)),
                new EcBlocks(30, new EcBlock(48, 24), new EcBlock(14, 25)),
                new EcBlocks(30, new EcBlock(42, 15), new EcBlock(32, 16))
            ),
            new self(
                39,
                [6, 26, 54, 82, 110, 138, 166],
                new EcBlocks(30, new EcBlock(20, 117), new EcBlock(4, 118)),
                new EcBlocks(28, new EcBlock(40, 47), new EcBlock(7, 48)),
                new EcBlocks(30, new EcBlock(43, 24), new EcBlock(22, 25)),
                new EcBlocks(30, new EcBlock(10, 15), new EcBlock(67, 16))
            ),
            new self(
                40,
                [6, 30, 58, 86, 114, 142, 170],
                new EcBlocks(30, new EcBlock(19, 118), new EcBlock(6, 119)),
                new EcBlocks(28, new EcBlock(18, 47), new EcBlock(31, 48)),
                new EcBlocks(30, new EcBlock(34, 24), new EcBlock(34, 25)),
                new EcBlocks(30, new EcBlock(20, 15), new EcBlock(61, 16))
            ),
        ];
    }
}
PK{c�\�"�9:9:3bacon/bacon-qr-code/src/Common/ReedSolomonCodec.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Common;

use BaconQrCode\Exception\InvalidArgumentException;
use BaconQrCode\Exception\RuntimeException;
use SplFixedArray;

/**
 * Reed-Solomon codec for 8-bit characters.
 *
 * Based on libfec by Phil Karn, KA9Q.
 */
final class ReedSolomonCodec
{
    /**
     * Symbol size in bits.
     *
     * @var int
     */
    private $symbolSize;

    /**
     * Block size in symbols.
     *
     * @var int
     */
    private $blockSize;

    /**
     * First root of RS code generator polynomial, index form.
     *
     * @var int
     */
    private $firstRoot;

    /**
     * Primitive element to generate polynomial roots, index form.
     *
     * @var int
     */
    private $primitive;

    /**
     * Prim-th root of 1, index form.
     *
     * @var int
     */
    private $iPrimitive;

    /**
     * RS code generator polynomial degree (number of roots).
     *
     * @var int
     */
    private $numRoots;

    /**
     * Padding bytes at front of shortened block.
     *
     * @var int
     */
    private $padding;

    /**
     * Log lookup table.
     *
     * @var SplFixedArray
     */
    private $alphaTo;

    /**
     * Anti-Log lookup table.
     *
     * @var SplFixedArray
     */
    private $indexOf;

    /**
     * Generator polynomial.
     *
     * @var SplFixedArray
     */
    private $generatorPoly;

    /**
     * @throws InvalidArgumentException if symbol size ist not between 0 and 8
     * @throws InvalidArgumentException if first root is invalid
     * @throws InvalidArgumentException if num roots is invalid
     * @throws InvalidArgumentException if padding is invalid
     * @throws RuntimeException if field generator polynomial is not primitive
     */
    public function __construct(
        int $symbolSize,
        int $gfPoly,
        int $firstRoot,
        int $primitive,
        int $numRoots,
        int $padding
    ) {
        if ($symbolSize < 0 || $symbolSize > 8) {
            throw new InvalidArgumentException('Symbol size must be between 0 and 8');
        }

        if ($firstRoot < 0 || $firstRoot >= (1 << $symbolSize)) {
            throw new InvalidArgumentException('First root must be between 0 and ' . (1 << $symbolSize));
        }

        if ($numRoots < 0 || $numRoots >= (1 << $symbolSize)) {
            throw new InvalidArgumentException('Num roots must be between 0 and ' . (1 << $symbolSize));
        }

        if ($padding < 0 || $padding >= ((1 << $symbolSize) - 1 - $numRoots)) {
            throw new InvalidArgumentException(
                'Padding must be between 0 and ' . ((1 << $symbolSize) - 1 - $numRoots)
            );
        }

        $this->symbolSize = $symbolSize;
        $this->blockSize = (1 << $symbolSize) - 1;
        $this->padding = $padding;
        $this->alphaTo = SplFixedArray::fromArray(array_fill(0, $this->blockSize + 1, 0), false);
        $this->indexOf = SplFixedArray::fromArray(array_fill(0, $this->blockSize + 1, 0), false);

        // Generate galous field lookup table
        $this->indexOf[0] = $this->blockSize;
        $this->alphaTo[$this->blockSize] = 0;

        $sr = 1;

        for ($i = 0; $i < $this->blockSize; ++$i) {
            $this->indexOf[$sr] = $i;
            $this->alphaTo[$i]  = $sr;

            $sr <<= 1;

            if ($sr & (1 << $symbolSize)) {
                $sr ^= $gfPoly;
            }

            $sr &= $this->blockSize;
        }

        if (1 !== $sr) {
            throw new RuntimeException('Field generator polynomial is not primitive');
        }

        // Form RS code generator polynomial from its roots
        $this->generatorPoly = SplFixedArray::fromArray(array_fill(0, $numRoots + 1, 0), false);
        $this->firstRoot = $firstRoot;
        $this->primitive = $primitive;
        $this->numRoots = $numRoots;

        // Find prim-th root of 1, used in decoding
        for ($iPrimitive = 1; ($iPrimitive % $primitive) !== 0; $iPrimitive += $this->blockSize) {
        }

        $this->iPrimitive = intdiv($iPrimitive, $primitive);

        $this->generatorPoly[0] = 1;

        for ($i = 0, $root = $firstRoot * $primitive; $i < $numRoots; ++$i, $root += $primitive) {
            $this->generatorPoly[$i + 1] = 1;

            for ($j = $i; $j > 0; $j--) {
                if ($this->generatorPoly[$j] !== 0) {
                    $this->generatorPoly[$j] = $this->generatorPoly[$j - 1] ^ $this->alphaTo[
                        $this->modNn($this->indexOf[$this->generatorPoly[$j]] + $root)
                    ];
                } else {
                    $this->generatorPoly[$j] = $this->generatorPoly[$j - 1];
                }
            }

            $this->generatorPoly[$j] = $this->alphaTo[$this->modNn($this->indexOf[$this->generatorPoly[0]] + $root)];
        }

        // Convert generator poly to index form for quicker encoding
        for ($i = 0; $i <= $numRoots; ++$i) {
            $this->generatorPoly[$i] = $this->indexOf[$this->generatorPoly[$i]];
        }
    }

    /**
     * Encodes data and writes result back into parity array.
     */
    public function encode(SplFixedArray $data, SplFixedArray $parity) : void
    {
        for ($i = 0; $i < $this->numRoots; ++$i) {
            $parity[$i] = 0;
        }

        $iterations = $this->blockSize - $this->numRoots - $this->padding;

        for ($i = 0; $i < $iterations; ++$i) {
            $feedback = $this->indexOf[$data[$i] ^ $parity[0]];

            if ($feedback !== $this->blockSize) {
                // Feedback term is non-zero
                $feedback = $this->modNn($this->blockSize - $this->generatorPoly[$this->numRoots] + $feedback);

                for ($j = 1; $j < $this->numRoots; ++$j) {
                    $parity[$j] = $parity[$j] ^ $this->alphaTo[
                        $this->modNn($feedback + $this->generatorPoly[$this->numRoots - $j])
                    ];
                }
            }

            for ($j = 0; $j < $this->numRoots - 1; ++$j) {
                $parity[$j] = $parity[$j + 1];
            }

            if ($feedback !== $this->blockSize) {
                $parity[$this->numRoots - 1] = $this->alphaTo[$this->modNn($feedback + $this->generatorPoly[0])];
            } else {
                $parity[$this->numRoots - 1] = 0;
            }
        }
    }

    /**
     * Decodes received data.
     */
    public function decode(SplFixedArray $data, SplFixedArray $erasures = null) : ?int
    {
        // This speeds up the initialization a bit.
        $numRootsPlusOne = SplFixedArray::fromArray(array_fill(0, $this->numRoots + 1, 0), false);
        $numRoots = SplFixedArray::fromArray(array_fill(0, $this->numRoots, 0), false);

        $lambda = clone $numRootsPlusOne;
        $b = clone $numRootsPlusOne;
        $t = clone $numRootsPlusOne;
        $omega = clone $numRootsPlusOne;
        $root = clone $numRoots;
        $loc = clone $numRoots;

        $numErasures = (null !== $erasures ? count($erasures) : 0);

        // Form the Syndromes; i.e., evaluate data(x) at roots of g(x)
        $syndromes = SplFixedArray::fromArray(array_fill(0, $this->numRoots, $data[0]), false);

        for ($i = 1; $i < $this->blockSize - $this->padding; ++$i) {
            for ($j = 0; $j < $this->numRoots; ++$j) {
                if ($syndromes[$j] === 0) {
                    $syndromes[$j] = $data[$i];
                } else {
                    $syndromes[$j] = $data[$i] ^ $this->alphaTo[
                        $this->modNn($this->indexOf[$syndromes[$j]] + ($this->firstRoot + $j) * $this->primitive)
                    ];
                }
            }
        }

        // Convert syndromes to index form, checking for nonzero conditions
        $syndromeError = 0;

        for ($i = 0; $i < $this->numRoots; ++$i) {
            $syndromeError |= $syndromes[$i];
            $syndromes[$i] = $this->indexOf[$syndromes[$i]];
        }

        if (! $syndromeError) {
            // If syndrome is zero, data[] is a codeword and there are no errors to correct, so return data[]
            // unmodified.
            return 0;
        }

        $lambda[0] = 1;

        if ($numErasures > 0) {
            // Init lambda to be the erasure locator polynomial
            $lambda[1] = $this->alphaTo[$this->modNn($this->primitive * ($this->blockSize - 1 - $erasures[0]))];

            for ($i = 1; $i < $numErasures; ++$i) {
                $u = $this->modNn($this->primitive * ($this->blockSize - 1 - $erasures[$i]));

                for ($j = $i + 1; $j > 0; --$j) {
                    $tmp = $this->indexOf[$lambda[$j - 1]];

                    if ($tmp !== $this->blockSize) {
                        $lambda[$j] = $lambda[$j] ^ $this->alphaTo[$this->modNn($u + $tmp)];
                    }
                }
            }
        }

        for ($i = 0; $i <= $this->numRoots; ++$i) {
            $b[$i] = $this->indexOf[$lambda[$i]];
        }

        // Begin Berlekamp-Massey algorithm to determine error+erasure locator polynomial
        $r  = $numErasures;
        $el = $numErasures;

        while (++$r <= $this->numRoots) {
            // Compute discrepancy at the r-th step in poly form
            $discrepancyR = 0;

            for ($i = 0; $i < $r; ++$i) {
                if ($lambda[$i] !== 0 && $syndromes[$r - $i - 1] !== $this->blockSize) {
                    $discrepancyR ^= $this->alphaTo[
                        $this->modNn($this->indexOf[$lambda[$i]] + $syndromes[$r - $i - 1])
                    ];
                }
            }

            $discrepancyR = $this->indexOf[$discrepancyR];

            if ($discrepancyR === $this->blockSize) {
                $tmp = $b->toArray();
                array_unshift($tmp, $this->blockSize);
                array_pop($tmp);
                $b = SplFixedArray::fromArray($tmp, false);
                continue;
            }

            $t[0] = $lambda[0];

            for ($i = 0; $i < $this->numRoots; ++$i) {
                if ($b[$i] !== $this->blockSize) {
                    $t[$i + 1] = $lambda[$i + 1] ^ $this->alphaTo[$this->modNn($discrepancyR + $b[$i])];
                } else {
                    $t[$i + 1] = $lambda[$i + 1];
                }
            }

            if (2 * $el <= $r + $numErasures - 1) {
                $el = $r + $numErasures - $el;

                for ($i = 0; $i <= $this->numRoots; ++$i) {
                    $b[$i] = (
                        $lambda[$i] === 0
                        ? $this->blockSize
                        : $this->modNn($this->indexOf[$lambda[$i]] - $discrepancyR + $this->blockSize)
                    );
                }
            } else {
                $tmp = $b->toArray();
                array_unshift($tmp, $this->blockSize);
                array_pop($tmp);
                $b = SplFixedArray::fromArray($tmp, false);
            }

            $lambda = clone $t;
        }

        // Convert lambda to index form and compute deg(lambda(x))
        $degLambda = 0;

        for ($i = 0; $i <= $this->numRoots; ++$i) {
            $lambda[$i] = $this->indexOf[$lambda[$i]];

            if ($lambda[$i] !== $this->blockSize) {
                $degLambda = $i;
            }
        }

        // Find roots of the error+erasure locator polynomial by Chien search.
        $reg = clone $lambda;
        $reg[0] = 0;
        $count = 0;
        $i = 1;

        for ($k = $this->iPrimitive - 1; $i <= $this->blockSize; ++$i, $k = $this->modNn($k + $this->iPrimitive)) {
            $q = 1;

            for ($j = $degLambda; $j > 0; $j--) {
                if ($reg[$j] !== $this->blockSize) {
                    $reg[$j] = $this->modNn($reg[$j] + $j);
                    $q ^= $this->alphaTo[$reg[$j]];
                }
            }

            if ($q !== 0) {
                // Not a root
                continue;
            }

            // Store root (index-form) and error location number
            $root[$count] = $i;
            $loc[$count] = $k;

            if (++$count === $degLambda) {
                break;
            }
        }

        if ($degLambda !== $count) {
            // deg(lambda) unequal to number of roots: uncorrectable error detected
            return null;
        }

        // Compute err+eras evaluate poly omega(x) = s(x)*lambda(x) (modulo x**numRoots). In index form. Also find
        // deg(omega).
        $degOmega = $degLambda - 1;

        for ($i = 0; $i <= $degOmega; ++$i) {
            $tmp = 0;

            for ($j = $i; $j >= 0; --$j) {
                if ($syndromes[$i - $j] !== $this->blockSize && $lambda[$j] !== $this->blockSize) {
                    $tmp ^= $this->alphaTo[$this->modNn($syndromes[$i - $j] + $lambda[$j])];
                }
            }

            $omega[$i] = $this->indexOf[$tmp];
        }

        // Compute error values in poly-form. num1 = omega(inv(X(l))), num2 = inv(X(l))**(firstRoot-1) and
        // den = lambda_pr(inv(X(l))) all in poly form.
        for ($j = $count - 1; $j >= 0; --$j) {
            $num1 = 0;

            for ($i = $degOmega; $i >= 0; $i--) {
                if ($omega[$i] !== $this->blockSize) {
                    $num1 ^= $this->alphaTo[$this->modNn($omega[$i] + $i * $root[$j])];
                }
            }

            $num2 = $this->alphaTo[$this->modNn($root[$j] * ($this->firstRoot - 1) + $this->blockSize)];
            $den  = 0;

            // lambda[i+1] for i even is the formal derivativelambda_pr of lambda[i]
            for ($i = min($degLambda, $this->numRoots - 1) & ~1; $i >= 0; $i -= 2) {
                if ($lambda[$i + 1] !== $this->blockSize) {
                    $den ^= $this->alphaTo[$this->modNn($lambda[$i + 1] + $i * $root[$j])];
                }
            }

            // Apply error to data
            if ($num1 !== 0 && $loc[$j] >= $this->padding) {
                $data[$loc[$j] - $this->padding] = $data[$loc[$j] - $this->padding] ^ (
                    $this->alphaTo[
                        $this->modNn(
                            $this->indexOf[$num1] + $this->indexOf[$num2] + $this->blockSize - $this->indexOf[$den]
                        )
                    ]
                );
            }
        }

        if (null !== $erasures) {
            if (count($erasures) < $count) {
                $erasures->setSize($count);
            }

            for ($i = 0; $i < $count; $i++) {
                $erasures[$i] = $loc[$i];
            }
        }

        return $count;
    }

    /**
     * Computes $x % GF_SIZE, where GF_SIZE is 2**GF_BITS - 1, without a slow divide.
     */
    private function modNn(int $x) : int
    {
        while ($x >= $this->blockSize) {
            $x -= $this->blockSize;
            $x = ($x >> $this->symbolSize) + ($x & $this->blockSize);
        }

        return $x;
    }
}
PK{c�\׉�ʊ�+bacon/bacon-qr-code/src/Common/BitUtils.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Common;

/**
 * General bit utilities.
 *
 * All utility methods are based on 32-bit integers and also work on 64-bit
 * systems.
 */
final class BitUtils
{
    private function __construct()
    {
    }

    /**
     * Performs an unsigned right shift.
     *
     * This is the same as the unsigned right shift operator ">>>" in other
     * languages.
     */
    public static function unsignedRightShift(int $a, int $b) : int
    {
        return (
            $a >= 0
            ? $a >> $b
            : (($a & 0x7fffffff) >> $b) | (0x40000000 >> ($b - 1))
        );
    }

    /**
     * Gets the number of trailing zeros.
     */
    public static function numberOfTrailingZeros(int $i) : int
    {
        $lastPos = strrpos(str_pad(decbin($i), 32, '0', STR_PAD_LEFT), '1');
        return $lastPos === false ? 32 : 31 - $lastPos;
    }
}
PK{c�\$� L??'bacon/bacon-qr-code/src/Common/Mode.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Common;

use DASPRiD\Enum\AbstractEnum;

/**
 * Enum representing various modes in which data can be encoded to bits.
 *
 * @method static self TERMINATOR()
 * @method static self NUMERIC()
 * @method static self ALPHANUMERIC()
 * @method static self STRUCTURED_APPEND()
 * @method static self BYTE()
 * @method static self ECI()
 * @method static self KANJI()
 * @method static self FNC1_FIRST_POSITION()
 * @method static self FNC1_SECOND_POSITION()
 * @method static self HANZI()
 */
final class Mode extends AbstractEnum
{
    protected const TERMINATOR = [[0, 0, 0], 0x00];
    protected const NUMERIC = [[10, 12, 14], 0x01];
    protected const ALPHANUMERIC = [[9, 11, 13], 0x02];
    protected const STRUCTURED_APPEND = [[0, 0, 0], 0x03];
    protected const BYTE = [[8, 16, 16], 0x04];
    protected const ECI = [[0, 0, 0], 0x07];
    protected const KANJI = [[8, 10, 12], 0x08];
    protected const FNC1_FIRST_POSITION = [[0, 0, 0], 0x05];
    protected const FNC1_SECOND_POSITION = [[0, 0, 0], 0x09];
    protected const HANZI = [[8, 10, 12], 0x0d];

    /**
     * @var int[]
     */
    private $characterCountBitsForVersions;

    /**
     * @var int
     */
    private $bits;

    /**
     * @param int[] $characterCountBitsForVersions
     */
    protected function __construct(array $characterCountBitsForVersions, int $bits)
    {
        $this->characterCountBitsForVersions = $characterCountBitsForVersions;
        $this->bits = $bits;
    }

    /**
     * Returns the number of bits used in a specific QR code version.
     */
    public function getCharacterCountBits(Version $version) : int
    {
        $number = $version->getVersionNumber();

        if ($number <= 9) {
            $offset = 0;
        } elseif ($number <= 26) {
            $offset = 1;
        } else {
            $offset = 2;
        }

        return $this->characterCountBitsForVersions[$offset];
    }

    /**
     * Returns the four bits used to encode this mode.
     */
    public function getBits() : int
    {
        return $this->bits;
    }
}
PK{c�\ζ#բ�4bacon/bacon-qr-code/src/Common/FormatInformation.phpnu�[���<?php
/**
 * BaconQrCode
 *
 * @link      http://github.com/Bacon/BaconQrCode For the canonical source repository
 * @copyright 2013 Ben 'DASPRiD' Scholzen
 * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
 */

namespace BaconQrCode\Common;

/**
 * Encapsulates a QR Code's format information, including the data mask used and error correction level.
 */
class FormatInformation
{
    /**
     * Mask for format information.
     */
    private const FORMAT_INFO_MASK_QR = 0x5412;

    /**
     * Lookup table for decoding format information.
     *
     * See ISO 18004:2006, Annex C, Table C.1
     */
    private const FORMAT_INFO_DECODE_LOOKUP = [
        [0x5412, 0x00],
        [0x5125, 0x01],
        [0x5e7c, 0x02],
        [0x5b4b, 0x03],
        [0x45f9, 0x04],
        [0x40ce, 0x05],
        [0x4f97, 0x06],
        [0x4aa0, 0x07],
        [0x77c4, 0x08],
        [0x72f3, 0x09],
        [0x7daa, 0x0a],
        [0x789d, 0x0b],
        [0x662f, 0x0c],
        [0x6318, 0x0d],
        [0x6c41, 0x0e],
        [0x6976, 0x0f],
        [0x1689, 0x10],
        [0x13be, 0x11],
        [0x1ce7, 0x12],
        [0x19d0, 0x13],
        [0x0762, 0x14],
        [0x0255, 0x15],
        [0x0d0c, 0x16],
        [0x083b, 0x17],
        [0x355f, 0x18],
        [0x3068, 0x19],
        [0x3f31, 0x1a],
        [0x3a06, 0x1b],
        [0x24b4, 0x1c],
        [0x2183, 0x1d],
        [0x2eda, 0x1e],
        [0x2bed, 0x1f],
    ];

    /**
     * Offset i holds the number of 1 bits in the binary representation of i.
     *
     * @var int[]
     */
    private const BITS_SET_IN_HALF_BYTE = [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4];

    /**
     * Error correction level.
     *
     * @var ErrorCorrectionLevel
     */
    private $ecLevel;

    /**
     * Data mask.
     *
     * @var int
     */
    private $dataMask;

    protected function __construct(int $formatInfo)
    {
        $this->ecLevel = ErrorCorrectionLevel::forBits(($formatInfo >> 3) & 0x3);
        $this->dataMask = $formatInfo & 0x7;
    }

    /**
     * Checks how many bits are different between two integers.
     */
    public static function numBitsDiffering(int $a, int $b) : int
    {
        $a ^= $b;

        return (
            self::BITS_SET_IN_HALF_BYTE[$a & 0xf]
            + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 4) & 0xf)]
            + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 8) & 0xf)]
            + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 12) & 0xf)]
            + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 16) & 0xf)]
            + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 20) & 0xf)]
            + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 24) & 0xf)]
            + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 28) & 0xf)]
        );
    }

    /**
     * Decodes format information.
     */
    public static function decodeFormatInformation(int $maskedFormatInfo1, int $maskedFormatInfo2) : ?self
    {
        $formatInfo = self::doDecodeFormatInformation($maskedFormatInfo1, $maskedFormatInfo2);

        if (null !== $formatInfo) {
            return $formatInfo;
        }

        // Should return null, but, some QR codes apparently do not mask this info. Try again by actually masking the
        // pattern first.
        return self::doDecodeFormatInformation(
            $maskedFormatInfo1 ^ self::FORMAT_INFO_MASK_QR,
            $maskedFormatInfo2 ^ self::FORMAT_INFO_MASK_QR
        );
    }

    /**
     * Internal method for decoding format information.
     */
    private static function doDecodeFormatInformation(int $maskedFormatInfo1, int $maskedFormatInfo2) : ?self
    {
        $bestDifference = PHP_INT_MAX;
        $bestFormatInfo = 0;

        foreach (self::FORMAT_INFO_DECODE_LOOKUP as $decodeInfo) {
            $targetInfo = $decodeInfo[0];

            if ($targetInfo === $maskedFormatInfo1 || $targetInfo === $maskedFormatInfo2) {
                // Found an exact match
                return new self($decodeInfo[1]);
            }

            $bitsDifference = self::numBitsDiffering($maskedFormatInfo1, $targetInfo);

            if ($bitsDifference < $bestDifference) {
                $bestFormatInfo = $decodeInfo[1];
                $bestDifference = $bitsDifference;
            }

            if ($maskedFormatInfo1 !== $maskedFormatInfo2) {
                // Also try the other option
                $bitsDifference = self::numBitsDiffering($maskedFormatInfo2, $targetInfo);

                if ($bitsDifference < $bestDifference) {
                    $bestFormatInfo = $decodeInfo[1];
                    $bestDifference = $bitsDifference;
                }
            }
        }

        // Hamming distance of the 32 masked codes is 7, by construction, so <= 3 bits differing means we found a match.
        if ($bestDifference <= 3) {
            return new self($bestFormatInfo);
        }

        return null;
    }

    /**
     * Returns the error correction level.
     */
    public function getErrorCorrectionLevel() : ErrorCorrectionLevel
    {
        return $this->ecLevel;
    }

    /**
     * Returns the data mask.
     */
    public function getDataMask() : int
    {
        return $this->dataMask;
    }

    /**
     * Hashes the code of the EC level.
     */
    public function hashCode() : int
    {
        return ($this->ecLevel->getBits() << 3) | $this->dataMask;
    }

    /**
     * Verifies if this instance equals another one.
     */
    public function equals(self $other) : bool
    {
        return (
            $this->ecLevel === $other->ecLevel
            && $this->dataMask === $other->dataMask
        );
    }
}
PK{c�\E7����+bacon/bacon-qr-code/src/Common/EcBlocks.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Common;

/**
 * Encapsulates a set of error-correction blocks in one symbol version.
 *
 * Most versions will use blocks of differing sizes within one version, so, this encapsulates the parameters for each
 * set of blocks. It also holds the number of error-correction codewords per block since it will be the same across all
 * blocks within one version.
 */
final class EcBlocks
{
    /**
     * Number of EC codewords per block.
     *
     * @var int
     */
    private $ecCodewordsPerBlock;

    /**
     * List of EC blocks.
     *
     * @var EcBlock[]
     */
    private $ecBlocks;

    public function __construct(int $ecCodewordsPerBlock, EcBlock ...$ecBlocks)
    {
        $this->ecCodewordsPerBlock = $ecCodewordsPerBlock;
        $this->ecBlocks = $ecBlocks;
    }

    /**
     * Returns the number of EC codewords per block.
     */
    public function getEcCodewordsPerBlock() : int
    {
        return $this->ecCodewordsPerBlock;
    }

    /**
     * Returns the total number of EC block appearances.
     */
    public function getNumBlocks() : int
    {
        $total = 0;

        foreach ($this->ecBlocks as $ecBlock) {
            $total += $ecBlock->getCount();
        }

        return $total;
    }

    /**
     * Returns the total count of EC codewords.
     */
    public function getTotalEcCodewords() : int
    {
        return $this->ecCodewordsPerBlock * $this->getNumBlocks();
    }

    /**
     * Returns the EC blocks included in this collection.
     *
     * @return EcBlock[]
     */
    public function getEcBlocks() : array
    {
        return $this->ecBlocks;
    }
}
PK{c�\�礹�"�"+bacon/bacon-qr-code/src/Common/BitArray.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Common;

use BaconQrCode\Exception\InvalidArgumentException;
use SplFixedArray;

/**
 * A simple, fast array of bits.
 */
final class BitArray
{
    /**
     * Bits represented as an array of integers.
     *
     * @var SplFixedArray<int>
     */
    private $bits;

    /**
     * Size of the bit array in bits.
     *
     * @var int
     */
    private $size;

    /**
     * Creates a new bit array with a given size.
     */
    public function __construct(int $size = 0)
    {
        $this->size = $size;
        $this->bits = SplFixedArray::fromArray(array_fill(0, ($this->size + 31) >> 3, 0));
    }

    /**
     * Gets the size in bits.
     */
    public function getSize() : int
    {
        return $this->size;
    }

    /**
     * Gets the size in bytes.
     */
    public function getSizeInBytes() : int
    {
        return ($this->size + 7) >> 3;
    }

    /**
     * Ensures that the array has a minimum capacity.
     */
    public function ensureCapacity(int $size) : void
    {
        if ($size > count($this->bits) << 5) {
            $this->bits->setSize(($size + 31) >> 5);
        }
    }

    /**
     * Gets a specific bit.
     */
    public function get(int $i) : bool
    {
        return 0 !== ($this->bits[$i >> 5] & (1 << ($i & 0x1f)));
    }

    /**
     * Sets a specific bit.
     */
    public function set(int $i) : void
    {
        $this->bits[$i >> 5] = $this->bits[$i >> 5] | 1 << ($i & 0x1f);
    }

    /**
     * Flips a specific bit.
     */
    public function flip(int $i) : void
    {
        $this->bits[$i >> 5] ^= 1 << ($i & 0x1f);
    }

    /**
     * Gets the next set bit position from a given position.
     */
    public function getNextSet(int $from) : int
    {
        if ($from >= $this->size) {
            return $this->size;
        }

        $bitsOffset = $from >> 5;
        $currentBits = $this->bits[$bitsOffset];
        $bitsLength = count($this->bits);
        $currentBits &= ~((1 << ($from & 0x1f)) - 1);

        while (0 === $currentBits) {
            if (++$bitsOffset === $bitsLength) {
                return $this->size;
            }

            $currentBits = $this->bits[$bitsOffset];
        }

        $result = ($bitsOffset << 5) + BitUtils::numberOfTrailingZeros($currentBits);
        return $result > $this->size ? $this->size : $result;
    }

    /**
     * Gets the next unset bit position from a given position.
     */
    public function getNextUnset(int $from) : int
    {
        if ($from >= $this->size) {
            return $this->size;
        }

        $bitsOffset = $from >> 5;
        $currentBits = ~$this->bits[$bitsOffset];
        $bitsLength = count($this->bits);
        $currentBits &= ~((1 << ($from & 0x1f)) - 1);

        while (0 === $currentBits) {
            if (++$bitsOffset === $bitsLength) {
                return $this->size;
            }

            $currentBits = ~$this->bits[$bitsOffset];
        }

        $result = ($bitsOffset << 5) + BitUtils::numberOfTrailingZeros($currentBits);
        return $result > $this->size ? $this->size : $result;
    }

    /**
     * Sets a bulk of bits.
     */
    public function setBulk(int $i, int $newBits) : void
    {
        $this->bits[$i >> 5] = $newBits;
    }

    /**
     * Sets a range of bits.
     *
     * @throws InvalidArgumentException if end is smaller than start
     */
    public function setRange(int $start, int $end) : void
    {
        if ($end < $start) {
            throw new InvalidArgumentException('End must be greater or equal to start');
        }

        if ($end === $start) {
            return;
        }

        --$end;

        $firstInt = $start >> 5;
        $lastInt = $end >> 5;

        for ($i = $firstInt; $i <= $lastInt; ++$i) {
            $firstBit = $i > $firstInt ? 0 : $start & 0x1f;
            $lastBit = $i < $lastInt ? 31 : $end & 0x1f;

            if (0 === $firstBit && 31 === $lastBit) {
                $mask = 0x7fffffff;
            } else {
                $mask = 0;

                for ($j = $firstBit; $j < $lastBit; ++$j) {
                    $mask |= 1 << $j;
                }
            }

            $this->bits[$i] = $this->bits[$i] | $mask;
        }
    }

    /**
     * Clears the bit array, unsetting every bit.
     */
    public function clear() : void
    {
        $bitsLength = count($this->bits);

        for ($i = 0; $i < $bitsLength; ++$i) {
            $this->bits[$i] = 0;
        }
    }

    /**
     * Checks if a range of bits is set or not set.

     * @throws InvalidArgumentException if end is smaller than start
     */
    public function isRange(int $start, int $end, bool $value) : bool
    {
        if ($end < $start) {
            throw new InvalidArgumentException('End must be greater or equal to start');
        }

        if ($end === $start) {
            return true;
        }

        --$end;

        $firstInt = $start >> 5;
        $lastInt = $end >> 5;

        for ($i = $firstInt; $i <= $lastInt; ++$i) {
            $firstBit = $i > $firstInt ? 0 : $start & 0x1f;
            $lastBit = $i < $lastInt ? 31 : $end & 0x1f;

            if (0 === $firstBit && 31 === $lastBit) {
                $mask = 0x7fffffff;
            } else {
                $mask = 0;

                for ($j = $firstBit; $j <= $lastBit; ++$j) {
                    $mask |= 1 << $j;
                }
            }

            if (($this->bits[$i] & $mask) !== ($value ? $mask : 0)) {
                return false;
            }
        }

        return true;
    }

    /**
     * Appends a bit to the array.
     */
    public function appendBit(bool $bit) : void
    {
        $this->ensureCapacity($this->size + 1);

        if ($bit) {
            $this->bits[$this->size >> 5] = $this->bits[$this->size >> 5] | (1 << ($this->size & 0x1f));
        }

        ++$this->size;
    }

    /**
     * Appends a number of bits (up to 32) to the array.

     * @throws InvalidArgumentException if num bits is not between 0 and 32
     */
    public function appendBits(int $value, int $numBits) : void
    {
        if ($numBits < 0 || $numBits > 32) {
            throw new InvalidArgumentException('Num bits must be between 0 and 32');
        }

        $this->ensureCapacity($this->size + $numBits);

        for ($numBitsLeft = $numBits; $numBitsLeft > 0; $numBitsLeft--) {
            $this->appendBit((($value >> ($numBitsLeft - 1)) & 0x01) === 1);
        }
    }

    /**
     * Appends another bit array to this array.
     */
    public function appendBitArray(self $other) : void
    {
        $otherSize = $other->getSize();
        $this->ensureCapacity($this->size + $other->getSize());

        for ($i = 0; $i < $otherSize; ++$i) {
            $this->appendBit($other->get($i));
        }
    }

    /**
     * Makes an exclusive-or comparision on the current bit array.
     *
     * @throws InvalidArgumentException if sizes don't match
     */
    public function xorBits(self $other) : void
    {
        $bitsLength = count($this->bits);
        $otherBits  = $other->getBitArray();

        if ($bitsLength !== count($otherBits)) {
            throw new InvalidArgumentException('Sizes don\'t match');
        }

        for ($i = 0; $i < $bitsLength; ++$i) {
            $this->bits[$i] = $this->bits[$i] ^ $otherBits[$i];
        }
    }

    /**
     * Converts the bit array to a byte array.
     *
     * @return SplFixedArray<int>
     */
    public function toBytes(int $bitOffset, int $numBytes) : SplFixedArray
    {
        $bytes = new SplFixedArray($numBytes);

        for ($i = 0; $i < $numBytes; ++$i) {
            $byte = 0;

            for ($j = 0; $j < 8; ++$j) {
                if ($this->get($bitOffset)) {
                    $byte |= 1 << (7 - $j);
                }

                ++$bitOffset;
            }

            $bytes[$i] = $byte;
        }

        return $bytes;
    }

    /**
     * Gets the internal bit array.
     *
     * @return SplFixedArray<int>
     */
    public function getBitArray() : SplFixedArray
    {
        return $this->bits;
    }

    /**
     * Reverses the array.
     */
    public function reverse() : void
    {
        $newBits = new SplFixedArray(count($this->bits));

        for ($i = 0; $i < $this->size; ++$i) {
            if ($this->get($this->size - $i - 1)) {
                $newBits[$i >> 5] = $newBits[$i >> 5] | (1 << ($i & 0x1f));
            }
        }

        $this->bits = $newBits;
    }

    /**
     * Returns a string representation of the bit array.
     */
    public function __toString() : string
    {
        $result = '';

        for ($i = 0; $i < $this->size; ++$i) {
            if (0 === ($i & 0x07)) {
                $result .= ' ';
            }

            $result .= $this->get($i) ? 'X' : '.';
        }

        return $result;
    }
}
PK{c�\20�`��*bacon/bacon-qr-code/src/Common/EcBlock.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Common;

/**
 * Encapsulates the parameters for one error-correction block in one symbol version.
 *
 * This includes the number of data codewords, and the number of times a block with these parameters is used
 * consecutively in the QR code version's format.
 */
final class EcBlock
{
    /**
     * How many times the block is used.
     *
     * @var int
     */
    private $count;

    /**
     * Number of data codewords.
     *
     * @var int
     */
    private $dataCodewords;

    public function __construct(int $count, int $dataCodewords)
    {
        $this->count = $count;
        $this->dataCodewords = $dataCodewords;
    }

    /**
     * Returns how many times the block is used.
     */
    public function getCount() : int
    {
        return $this->count;
    }

    /**
     * Returns the number of data codewords.
     */
    public function getDataCodewords() : int
    {
        return $this->dataCodewords;
    }
}
PK{c�\=@�3�A�A.bacon/bacon-qr-code/src/Encoder/MatrixUtil.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Encoder;

use BaconQrCode\Common\BitArray;
use BaconQrCode\Common\ErrorCorrectionLevel;
use BaconQrCode\Common\Version;
use BaconQrCode\Exception\RuntimeException;
use BaconQrCode\Exception\WriterException;

/**
 * Matrix utility.
 */
final class MatrixUtil
{
    /**
     * Position detection pattern.
     */
    private const POSITION_DETECTION_PATTERN = [
        [1, 1, 1, 1, 1, 1, 1],
        [1, 0, 0, 0, 0, 0, 1],
        [1, 0, 1, 1, 1, 0, 1],
        [1, 0, 1, 1, 1, 0, 1],
        [1, 0, 1, 1, 1, 0, 1],
        [1, 0, 0, 0, 0, 0, 1],
        [1, 1, 1, 1, 1, 1, 1],
    ];

    /**
     * Position adjustment pattern.
     */
    private const POSITION_ADJUSTMENT_PATTERN = [
        [1, 1, 1, 1, 1],
        [1, 0, 0, 0, 1],
        [1, 0, 1, 0, 1],
        [1, 0, 0, 0, 1],
        [1, 1, 1, 1, 1],
    ];

    /**
     * Coordinates for position adjustment patterns for each version.
     */
    private const POSITION_ADJUSTMENT_PATTERN_COORDINATE_TABLE = [
        [null, null, null, null, null, null, null], // Version 1
        [   6,   18, null, null, null, null, null], // Version 2
        [   6,   22, null, null, null, null, null], // Version 3
        [   6,   26, null, null, null, null, null], // Version 4
        [   6,   30, null, null, null, null, null], // Version 5
        [   6,   34, null, null, null, null, null], // Version 6
        [   6,   22,   38, null, null, null, null], // Version 7
        [   6,   24,   42, null, null, null, null], // Version 8
        [   6,   26,   46, null, null, null, null], // Version 9
        [   6,   28,   50, null, null, null, null], // Version 10
        [   6,   30,   54, null, null, null, null], // Version 11
        [   6,   32,   58, null, null, null, null], // Version 12
        [   6,   34,   62, null, null, null, null], // Version 13
        [   6,   26,   46,   66, null, null, null], // Version 14
        [   6,   26,   48,   70, null, null, null], // Version 15
        [   6,   26,   50,   74, null, null, null], // Version 16
        [   6,   30,   54,   78, null, null, null], // Version 17
        [   6,   30,   56,   82, null, null, null], // Version 18
        [   6,   30,   58,   86, null, null, null], // Version 19
        [   6,   34,   62,   90, null, null, null], // Version 20
        [   6,   28,   50,   72,   94, null, null], // Version 21
        [   6,   26,   50,   74,   98, null, null], // Version 22
        [   6,   30,   54,   78,  102, null, null], // Version 23
        [   6,   28,   54,   80,  106, null, null], // Version 24
        [   6,   32,   58,   84,  110, null, null], // Version 25
        [   6,   30,   58,   86,  114, null, null], // Version 26
        [   6,   34,   62,   90,  118, null, null], // Version 27
        [   6,   26,   50,   74,   98,  122, null], // Version 28
        [   6,   30,   54,   78,  102,  126, null], // Version 29
        [   6,   26,   52,   78,  104,  130, null], // Version 30
        [   6,   30,   56,   82,  108,  134, null], // Version 31
        [   6,   34,   60,   86,  112,  138, null], // Version 32
        [   6,   30,   58,   86,  114,  142, null], // Version 33
        [   6,   34,   62,   90,  118,  146, null], // Version 34
        [   6,   30,   54,   78,  102,  126,  150], // Version 35
        [   6,   24,   50,   76,  102,  128,  154], // Version 36
        [   6,   28,   54,   80,  106,  132,  158], // Version 37
        [   6,   32,   58,   84,  110,  136,  162], // Version 38
        [   6,   26,   54,   82,  110,  138,  166], // Version 39
        [   6,   30,   58,   86,  114,  142,  170], // Version 40
    ];

    /**
     * Type information coordinates.
     */
    private const TYPE_INFO_COORDINATES = [
        [8, 0],
        [8, 1],
        [8, 2],
        [8, 3],
        [8, 4],
        [8, 5],
        [8, 7],
        [8, 8],
        [7, 8],
        [5, 8],
        [4, 8],
        [3, 8],
        [2, 8],
        [1, 8],
        [0, 8],
    ];

    /**
     * Version information polynomial.
     */
    private const VERSION_INFO_POLY = 0x1f25;

    /**
     * Type information polynomial.
     */
    private const TYPE_INFO_POLY = 0x537;

    /**
     * Type information mask pattern.
     */
    private const TYPE_INFO_MASK_PATTERN = 0x5412;

    /**
     * Clears a given matrix.
     */
    public static function clearMatrix(ByteMatrix $matrix) : void
    {
        $matrix->clear(-1);
    }

    /**
     * Builds a complete matrix.
     */
    public static function buildMatrix(
        BitArray $dataBits,
        ErrorCorrectionLevel $level,
        Version $version,
        int $maskPattern,
        ByteMatrix $matrix
    ) : void {
        self::clearMatrix($matrix);
        self::embedBasicPatterns($version, $matrix);
        self::embedTypeInfo($level, $maskPattern, $matrix);
        self::maybeEmbedVersionInfo($version, $matrix);
        self::embedDataBits($dataBits, $maskPattern, $matrix);
    }

    /**
     * Removes the position detection patterns from a matrix.
     *
     * This can be useful if you need to render those patterns separately.
     */
    public static function removePositionDetectionPatterns(ByteMatrix $matrix) : void
    {
        $pdpWidth = count(self::POSITION_DETECTION_PATTERN[0]);

        self::removePositionDetectionPattern(0, 0, $matrix);
        self::removePositionDetectionPattern($matrix->getWidth() - $pdpWidth, 0, $matrix);
        self::removePositionDetectionPattern(0, $matrix->getWidth() - $pdpWidth, $matrix);
    }

    /**
     * Embeds type information into a matrix.
     */
    private static function embedTypeInfo(ErrorCorrectionLevel $level, int $maskPattern, ByteMatrix $matrix) : void
    {
        $typeInfoBits = new BitArray();
        self::makeTypeInfoBits($level, $maskPattern, $typeInfoBits);

        $typeInfoBitsSize = $typeInfoBits->getSize();

        for ($i = 0; $i < $typeInfoBitsSize; ++$i) {
            $bit = $typeInfoBits->get($typeInfoBitsSize - 1 - $i);

            $x1 = self::TYPE_INFO_COORDINATES[$i][0];
            $y1 = self::TYPE_INFO_COORDINATES[$i][1];

            $matrix->set($x1, $y1, (int) $bit);

            if ($i < 8) {
                $x2 = $matrix->getWidth() - $i - 1;
                $y2 = 8;
            } else {
                $x2 = 8;
                $y2 = $matrix->getHeight() - 7 + ($i - 8);
            }

            $matrix->set($x2, $y2, (int) $bit);
        }
    }

    /**
     * Generates type information bits and appends them to a bit array.
     *
     * @throws RuntimeException if bit array resulted in invalid size
     */
    private static function makeTypeInfoBits(ErrorCorrectionLevel $level, int $maskPattern, BitArray $bits) : void
    {
        $typeInfo = ($level->getBits() << 3) | $maskPattern;
        $bits->appendBits($typeInfo, 5);

        $bchCode = self::calculateBchCode($typeInfo, self::TYPE_INFO_POLY);
        $bits->appendBits($bchCode, 10);

        $maskBits = new BitArray();
        $maskBits->appendBits(self::TYPE_INFO_MASK_PATTERN, 15);
        $bits->xorBits($maskBits);

        if (15 !== $bits->getSize()) {
            throw new RuntimeException('Bit array resulted in invalid size: ' . $bits->getSize());
        }
    }

    /**
     * Embeds version information if required.
     */
    private static function maybeEmbedVersionInfo(Version $version, ByteMatrix $matrix) : void
    {
        if ($version->getVersionNumber() < 7) {
            return;
        }

        $versionInfoBits = new BitArray();
        self::makeVersionInfoBits($version, $versionInfoBits);

        $bitIndex = 6 * 3 - 1;

        for ($i = 0; $i < 6; ++$i) {
            for ($j = 0; $j < 3; ++$j) {
                $bit = $versionInfoBits->get($bitIndex);
                --$bitIndex;

                $matrix->set($i, $matrix->getHeight() - 11 + $j, (int) $bit);
                $matrix->set($matrix->getHeight() - 11 + $j, $i, (int) $bit);
            }
        }
    }

    /**
     * Generates version information bits and appends them to a bit array.
     *
     * @throws RuntimeException if bit array resulted in invalid size
     */
    private static function makeVersionInfoBits(Version $version, BitArray $bits) : void
    {
        $bits->appendBits($version->getVersionNumber(), 6);

        $bchCode = self::calculateBchCode($version->getVersionNumber(), self::VERSION_INFO_POLY);
        $bits->appendBits($bchCode, 12);

        if (18 !== $bits->getSize()) {
            throw new RuntimeException('Bit array resulted in invalid size: ' . $bits->getSize());
        }
    }

    /**
     * Calculates the BCH code for a value and a polynomial.
     */
    private static function calculateBchCode(int $value, int $poly) : int
    {
        $msbSetInPoly = self::findMsbSet($poly);
        $value <<= $msbSetInPoly - 1;

        while (self::findMsbSet($value) >= $msbSetInPoly) {
            $value ^= $poly << (self::findMsbSet($value) - $msbSetInPoly);
        }

        return $value;
    }

    /**
     * Finds and MSB set.
     */
    private static function findMsbSet(int $value) : int
    {
        $numDigits = 0;

        while (0 !== $value) {
            $value >>= 1;
            ++$numDigits;
        }

        return $numDigits;
    }

    /**
     * Embeds basic patterns into a matrix.
     */
    private static function embedBasicPatterns(Version $version, ByteMatrix $matrix) : void
    {
        self::embedPositionDetectionPatternsAndSeparators($matrix);
        self::embedDarkDotAtLeftBottomCorner($matrix);
        self::maybeEmbedPositionAdjustmentPatterns($version, $matrix);
        self::embedTimingPatterns($matrix);
    }

    /**
     * Embeds position detection patterns and separators into a byte matrix.
     */
    private static function embedPositionDetectionPatternsAndSeparators(ByteMatrix $matrix) : void
    {
        $pdpWidth = count(self::POSITION_DETECTION_PATTERN[0]);

        self::embedPositionDetectionPattern(0, 0, $matrix);
        self::embedPositionDetectionPattern($matrix->getWidth() - $pdpWidth, 0, $matrix);
        self::embedPositionDetectionPattern(0, $matrix->getWidth() - $pdpWidth, $matrix);

        $hspWidth = 8;

        self::embedHorizontalSeparationPattern(0, $hspWidth - 1, $matrix);
        self::embedHorizontalSeparationPattern($matrix->getWidth() - $hspWidth, $hspWidth - 1, $matrix);
        self::embedHorizontalSeparationPattern(0, $matrix->getWidth() - $hspWidth, $matrix);

        $vspSize = 7;

        self::embedVerticalSeparationPattern($vspSize, 0, $matrix);
        self::embedVerticalSeparationPattern($matrix->getHeight() - $vspSize - 1, 0, $matrix);
        self::embedVerticalSeparationPattern($vspSize, $matrix->getHeight() - $vspSize, $matrix);
    }

    /**
     * Embeds a single position detection pattern into a byte matrix.
     */
    private static function embedPositionDetectionPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void
    {
        for ($y = 0; $y < 7; ++$y) {
            for ($x = 0; $x < 7; ++$x) {
                $matrix->set($xStart + $x, $yStart + $y, self::POSITION_DETECTION_PATTERN[$y][$x]);
            }
        }
    }

    private static function removePositionDetectionPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void
    {
        for ($y = 0; $y < 7; ++$y) {
            for ($x = 0; $x < 7; ++$x) {
                $matrix->set($xStart + $x, $yStart + $y, 0);
            }
        }
    }

    /**
     * Embeds a single horizontal separation pattern.
     *
     * @throws RuntimeException if a byte was already set
     */
    private static function embedHorizontalSeparationPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void
    {
        for ($x = 0; $x < 8; $x++) {
            if (-1 !== $matrix->get($xStart + $x, $yStart)) {
                throw new RuntimeException('Byte already set');
            }

            $matrix->set($xStart + $x, $yStart, 0);
        }
    }

    /**
     * Embeds a single vertical separation pattern.
     *
     * @throws RuntimeException if a byte was already set
     */
    private static function embedVerticalSeparationPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void
    {
        for ($y = 0; $y < 7; $y++) {
            if (-1 !== $matrix->get($xStart, $yStart + $y)) {
                throw new RuntimeException('Byte already set');
            }

            $matrix->set($xStart, $yStart + $y, 0);
        }
    }

    /**
     * Embeds a dot at the left bottom corner.
     *
     * @throws RuntimeException if a byte was already set to 0
     */
    private static function embedDarkDotAtLeftBottomCorner(ByteMatrix $matrix) : void
    {
        if (0 === $matrix->get(8, $matrix->getHeight() - 8)) {
            throw new RuntimeException('Byte already set to 0');
        }

        $matrix->set(8, $matrix->getHeight() - 8, 1);
    }

    /**
     * Embeds position adjustment patterns if required.
     */
    private static function maybeEmbedPositionAdjustmentPatterns(Version $version, ByteMatrix $matrix) : void
    {
        if ($version->getVersionNumber() < 2) {
            return;
        }

        $index = $version->getVersionNumber() - 1;

        $coordinates = self::POSITION_ADJUSTMENT_PATTERN_COORDINATE_TABLE[$index];
        $numCoordinates = count($coordinates);

        for ($i = 0; $i < $numCoordinates; ++$i) {
            for ($j = 0; $j < $numCoordinates; ++$j) {
                $y = $coordinates[$i];
                $x = $coordinates[$j];

                if (null === $x || null === $y) {
                    continue;
                }

                if (-1 === $matrix->get($x, $y)) {
                    self::embedPositionAdjustmentPattern($x - 2, $y - 2, $matrix);
                }
            }
        }
    }

    /**
     * Embeds a single position adjustment pattern.
     */
    private static function embedPositionAdjustmentPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void
    {
        for ($y = 0; $y < 5; $y++) {
            for ($x = 0; $x < 5; $x++) {
                $matrix->set($xStart + $x, $yStart + $y, self::POSITION_ADJUSTMENT_PATTERN[$y][$x]);
            }
        }
    }

    /**
     * Embeds timing patterns into a matrix.
     */
    private static function embedTimingPatterns(ByteMatrix $matrix) : void
    {
        $matrixWidth = $matrix->getWidth();

        for ($i = 8; $i < $matrixWidth - 8; ++$i) {
            $bit = ($i + 1) % 2;

            if (-1 === $matrix->get($i, 6)) {
                $matrix->set($i, 6, $bit);
            }

            if (-1 === $matrix->get(6, $i)) {
                $matrix->set(6, $i, $bit);
            }
        }
    }

    /**
     * Embeds "dataBits" using "getMaskPattern".
     *
     * For debugging purposes, it skips masking process if "getMaskPattern" is -1. See 8.7 of JISX0510:2004 (p.38) for
     * how to embed data bits.
     *
     * @throws WriterException if not all bits could be consumed
     */
    private static function embedDataBits(BitArray $dataBits, int $maskPattern, ByteMatrix $matrix) : void
    {
        $bitIndex = 0;
        $direction = -1;

        // Start from the right bottom cell.
        $x = $matrix->getWidth() - 1;
        $y = $matrix->getHeight() - 1;

        while ($x > 0) {
            // Skip vertical timing pattern.
            if (6 === $x) {
                --$x;
            }

            while ($y >= 0 && $y < $matrix->getHeight()) {
                for ($i = 0; $i < 2; $i++) {
                    $xx = $x - $i;

                    // Skip the cell if it's not empty.
                    if (-1 !== $matrix->get($xx, $y)) {
                        continue;
                    }

                    if ($bitIndex < $dataBits->getSize()) {
                        $bit = $dataBits->get($bitIndex);
                        ++$bitIndex;
                    } else {
                        // Padding bit. If there is no bit left, we'll fill the
                        // left cells with 0, as described in 8.4.9 of
                        // JISX0510:2004 (p. 24).
                        $bit = false;
                    }

                    // Skip masking if maskPattern is -1.
                    if (-1 !== $maskPattern && MaskUtil::getDataMaskBit($maskPattern, $xx, $y)) {
                        $bit = ! $bit;
                    }

                    $matrix->set($xx, $y, (int) $bit);
                }

                $y += $direction;
            }

            $direction  = -$direction;
            $y += $direction;
            $x -= 2;
        }

        // All bits should be consumed
        if ($dataBits->getSize() !== $bitIndex) {
            throw new WriterException('Not all bits consumed (' . $bitIndex . ' out of ' . $dataBits->getSize() .')');
        }
    }
}
PK{c�\4�q��U�U+bacon/bacon-qr-code/src/Encoder/Encoder.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Encoder;

use BaconQrCode\Common\BitArray;
use BaconQrCode\Common\CharacterSetEci;
use BaconQrCode\Common\ErrorCorrectionLevel;
use BaconQrCode\Common\Mode;
use BaconQrCode\Common\ReedSolomonCodec;
use BaconQrCode\Common\Version;
use BaconQrCode\Exception\WriterException;
use SplFixedArray;

/**
 * Encoder.
 */
final class Encoder
{
    /**
     * Default byte encoding.
     */
    public const DEFAULT_BYTE_MODE_ECODING = 'ISO-8859-1';

    /**
     * The original table is defined in the table 5 of JISX0510:2004 (p.19).
     */
    private const ALPHANUMERIC_TABLE = [
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  // 0x00-0x0f
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  // 0x10-0x1f
        36, -1, -1, -1, 37, 38, -1, -1, -1, -1, 39, 40, -1, 41, 42, 43,  // 0x20-0x2f
        0,   1,  2,  3,  4,  5,  6,  7,  8,  9, 44, -1, -1, -1, -1, -1,  // 0x30-0x3f
        -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,  // 0x40-0x4f
        25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1,  // 0x50-0x5f
    ];

    /**
     * Codec cache.
     *
     * @var array<string,ReedSolomonCodec>
     */
    private static $codecs = [];

    /**
     * Encodes "content" with the error correction level "ecLevel".
     */
    public static function encode(
        string $content,
        ErrorCorrectionLevel $ecLevel,
        string $encoding = self::DEFAULT_BYTE_MODE_ECODING,
        ?Version $forcedVersion = null
    ) : QrCode {
        // Pick an encoding mode appropriate for the content. Note that this
        // will not attempt to use multiple modes / segments even if that were
        // more efficient. Would be nice.
        $mode = self::chooseMode($content, $encoding);

        // This will store the header information, like mode and length, as well
        // as "header" segments like an ECI segment.
        $headerBits = new BitArray();

        // Append ECI segment if applicable
        if (Mode::BYTE() === $mode && self::DEFAULT_BYTE_MODE_ECODING !== $encoding) {
            $eci = CharacterSetEci::getCharacterSetEciByName($encoding);

            if (null !== $eci) {
                self::appendEci($eci, $headerBits);
            }
        }

        // (With ECI in place,) Write the mode marker
        self::appendModeInfo($mode, $headerBits);

        // Collect data within the main segment, separately, to count its size
        // if needed. Don't add it to main payload yet.
        $dataBits = new BitArray();
        self::appendBytes($content, $mode, $dataBits, $encoding);

        // Hard part: need to know version to know how many bits length takes.
        // But need to know how many bits it takes to know version. First we
        // take a guess at version by assuming version will be the minimum, 1:
        $provisionalBitsNeeded = $headerBits->getSize()
            + $mode->getCharacterCountBits(Version::getVersionForNumber(1))
            + $dataBits->getSize();
        $provisionalVersion = self::chooseVersion($provisionalBitsNeeded, $ecLevel);

        // Use that guess to calculate the right version. I am still not sure
        // this works in 100% of cases.
        $bitsNeeded = $headerBits->getSize()
            + $mode->getCharacterCountBits($provisionalVersion)
            + $dataBits->getSize();
        $version = self::chooseVersion($bitsNeeded, $ecLevel);

        if (null !== $forcedVersion) {
            // Forced version check
            if ($version->getVersionNumber() <= $forcedVersion->getVersionNumber()) {
                // Calculated minimum version is same or equal as forced version
                $version = $forcedVersion;
            } else {
                throw new WriterException(
                    'Invalid version! Calculated version: '
                    . $version->getVersionNumber()
                    . ', requested version: '
                    . $forcedVersion->getVersionNumber()
                );
            }
        }

        $headerAndDataBits = new BitArray();
        $headerAndDataBits->appendBitArray($headerBits);

        // Find "length" of main segment and write it.
        $numLetters = (Mode::BYTE() === $mode ? $dataBits->getSizeInBytes() : strlen($content));
        self::appendLengthInfo($numLetters, $version, $mode, $headerAndDataBits);

        // Put data together into the overall payload.
        $headerAndDataBits->appendBitArray($dataBits);
        $ecBlocks = $version->getEcBlocksForLevel($ecLevel);
        $numDataBytes = $version->getTotalCodewords() - $ecBlocks->getTotalEcCodewords();

        // Terminate the bits properly.
        self::terminateBits($numDataBytes, $headerAndDataBits);

        // Interleave data bits with error correction code.
        $finalBits = self::interleaveWithEcBytes(
            $headerAndDataBits,
            $version->getTotalCodewords(),
            $numDataBytes,
            $ecBlocks->getNumBlocks()
        );

        // Choose the mask pattern.
        $dimension = $version->getDimensionForVersion();
        $matrix = new ByteMatrix($dimension, $dimension);
        $maskPattern = self::chooseMaskPattern($finalBits, $ecLevel, $version, $matrix);

        // Build the matrix.
        MatrixUtil::buildMatrix($finalBits, $ecLevel, $version, $maskPattern, $matrix);

        return new QrCode($mode, $ecLevel, $version, $maskPattern, $matrix);
    }

    /**
     * Gets the alphanumeric code for a byte.
     */
    private static function getAlphanumericCode(int $code) : int
    {
        if (isset(self::ALPHANUMERIC_TABLE[$code])) {
            return self::ALPHANUMERIC_TABLE[$code];
        }

        return -1;
    }

    /**
     * Chooses the best mode for a given content.
     */
    private static function chooseMode(string $content, string $encoding = null) : Mode
    {
        if (null !== $encoding && 0 === strcasecmp($encoding, 'SHIFT-JIS')) {
            return self::isOnlyDoubleByteKanji($content) ? Mode::KANJI() : Mode::BYTE();
        }

        $hasNumeric = false;
        $hasAlphanumeric = false;
        $contentLength = strlen($content);

        for ($i = 0; $i < $contentLength; ++$i) {
            $char = $content[$i];

            if (ctype_digit($char)) {
                $hasNumeric = true;
            } elseif (-1 !== self::getAlphanumericCode(ord($char))) {
                $hasAlphanumeric = true;
            } else {
                return Mode::BYTE();
            }
        }

        if ($hasAlphanumeric) {
            return Mode::ALPHANUMERIC();
        } elseif ($hasNumeric) {
            return Mode::NUMERIC();
        }

        return Mode::BYTE();
    }

    /**
     * Calculates the mask penalty for a matrix.
     */
    private static function calculateMaskPenalty(ByteMatrix $matrix) : int
    {
        return (
            MaskUtil::applyMaskPenaltyRule1($matrix)
            + MaskUtil::applyMaskPenaltyRule2($matrix)
            + MaskUtil::applyMaskPenaltyRule3($matrix)
            + MaskUtil::applyMaskPenaltyRule4($matrix)
        );
    }

    /**
     * Checks if content only consists of double-byte kanji characters.
     */
    private static function isOnlyDoubleByteKanji(string $content) : bool
    {
        $bytes = @iconv('utf-8', 'SHIFT-JIS', $content);

        if (false === $bytes) {
            return false;
        }

        $length = strlen($bytes);

        if (0 !== $length % 2) {
            return false;
        }

        for ($i = 0; $i < $length; $i += 2) {
            $byte = $bytes[$i] & 0xff;

            if (($byte < 0x81 || $byte > 0x9f) && $byte < 0xe0 || $byte > 0xeb) {
                return false;
            }
        }

        return true;
    }

    /**
     * Chooses the best mask pattern for a matrix.
     */
    private static function chooseMaskPattern(
        BitArray $bits,
        ErrorCorrectionLevel $ecLevel,
        Version $version,
        ByteMatrix $matrix
    ) : int {
        $minPenalty = PHP_INT_MAX;
        $bestMaskPattern = -1;

        for ($maskPattern = 0; $maskPattern < QrCode::NUM_MASK_PATTERNS; ++$maskPattern) {
            MatrixUtil::buildMatrix($bits, $ecLevel, $version, $maskPattern, $matrix);
            $penalty = self::calculateMaskPenalty($matrix);

            if ($penalty < $minPenalty) {
                $minPenalty = $penalty;
                $bestMaskPattern = $maskPattern;
            }
        }

        return $bestMaskPattern;
    }

    /**
     * Chooses the best version for the input.
     *
     * @throws WriterException if data is too big
     */
    private static function chooseVersion(int $numInputBits, ErrorCorrectionLevel $ecLevel) : Version
    {
        for ($versionNum = 1; $versionNum <= 40; ++$versionNum) {
            $version = Version::getVersionForNumber($versionNum);
            $numBytes = $version->getTotalCodewords();

            $ecBlocks = $version->getEcBlocksForLevel($ecLevel);
            $numEcBytes = $ecBlocks->getTotalEcCodewords();

            $numDataBytes = $numBytes - $numEcBytes;
            $totalInputBytes = intdiv($numInputBits + 8, 8);

            if ($numDataBytes >= $totalInputBytes) {
                return $version;
            }
        }

        throw new WriterException('Data too big');
    }

    /**
     * Terminates the bits in a bit array.
     *
     * @throws WriterException if data bits cannot fit in the QR code
     * @throws WriterException if bits size does not equal the capacity
     */
    private static function terminateBits(int $numDataBytes, BitArray $bits) : void
    {
        $capacity = $numDataBytes << 3;

        if ($bits->getSize() > $capacity) {
            throw new WriterException('Data bits cannot fit in the QR code');
        }

        for ($i = 0; $i < 4 && $bits->getSize() < $capacity; ++$i) {
            $bits->appendBit(false);
        }

        $numBitsInLastByte = $bits->getSize() & 0x7;

        if ($numBitsInLastByte > 0) {
            for ($i = $numBitsInLastByte; $i < 8; ++$i) {
                $bits->appendBit(false);
            }
        }

        $numPaddingBytes = $numDataBytes - $bits->getSizeInBytes();

        for ($i = 0; $i < $numPaddingBytes; ++$i) {
            $bits->appendBits(0 === ($i & 0x1) ? 0xec : 0x11, 8);
        }

        if ($bits->getSize() !== $capacity) {
            throw new WriterException('Bits size does not equal capacity');
        }
    }

    /**
     * Gets number of data- and EC bytes for a block ID.
     *
     * @return int[]
     * @throws WriterException if block ID is too large
     * @throws WriterException if EC bytes mismatch
     * @throws WriterException if RS blocks mismatch
     * @throws WriterException if total bytes mismatch
     */
    private static function getNumDataBytesAndNumEcBytesForBlockId(
        int $numTotalBytes,
        int $numDataBytes,
        int $numRsBlocks,
        int $blockId
    ) : array {
        if ($blockId >= $numRsBlocks) {
            throw new WriterException('Block ID too large');
        }

        $numRsBlocksInGroup2 = $numTotalBytes % $numRsBlocks;
        $numRsBlocksInGroup1 = $numRsBlocks - $numRsBlocksInGroup2;
        $numTotalBytesInGroup1 = intdiv($numTotalBytes, $numRsBlocks);
        $numTotalBytesInGroup2 = $numTotalBytesInGroup1 + 1;
        $numDataBytesInGroup1 = intdiv($numDataBytes, $numRsBlocks);
        $numDataBytesInGroup2 = $numDataBytesInGroup1 + 1;
        $numEcBytesInGroup1 = $numTotalBytesInGroup1 - $numDataBytesInGroup1;
        $numEcBytesInGroup2 = $numTotalBytesInGroup2 - $numDataBytesInGroup2;

        if ($numEcBytesInGroup1 !== $numEcBytesInGroup2) {
            throw new WriterException('EC bytes mismatch');
        }

        if ($numRsBlocks !== $numRsBlocksInGroup1 + $numRsBlocksInGroup2) {
            throw new WriterException('RS blocks mismatch');
        }

        if ($numTotalBytes !==
            (($numDataBytesInGroup1 + $numEcBytesInGroup1) * $numRsBlocksInGroup1)
            + (($numDataBytesInGroup2 + $numEcBytesInGroup2) * $numRsBlocksInGroup2)
        ) {
            throw new WriterException('Total bytes mismatch');
        }

        if ($blockId < $numRsBlocksInGroup1) {
            return [$numDataBytesInGroup1, $numEcBytesInGroup1];
        } else {
            return [$numDataBytesInGroup2, $numEcBytesInGroup2];
        }
    }

    /**
     * Interleaves data with EC bytes.
     *
     * @throws WriterException if number of bits and data bytes does not match
     * @throws WriterException if data bytes does not match offset
     * @throws WriterException if an interleaving error occurs
     */
    private static function interleaveWithEcBytes(
        BitArray $bits,
        int $numTotalBytes,
        int $numDataBytes,
        int $numRsBlocks
    ) : BitArray {
        if ($bits->getSizeInBytes() !== $numDataBytes) {
            throw new WriterException('Number of bits and data bytes does not match');
        }

        $dataBytesOffset = 0;
        $maxNumDataBytes = 0;
        $maxNumEcBytes   = 0;

        $blocks = new SplFixedArray($numRsBlocks);

        for ($i = 0; $i < $numRsBlocks; ++$i) {
            list($numDataBytesInBlock, $numEcBytesInBlock) = self::getNumDataBytesAndNumEcBytesForBlockId(
                $numTotalBytes,
                $numDataBytes,
                $numRsBlocks,
                $i
            );

            $size = $numDataBytesInBlock;
            $dataBytes = $bits->toBytes(8 * $dataBytesOffset, $size);
            $ecBytes = self::generateEcBytes($dataBytes, $numEcBytesInBlock);
            $blocks[$i] = new BlockPair($dataBytes, $ecBytes);

            $maxNumDataBytes = max($maxNumDataBytes, $size);
            $maxNumEcBytes = max($maxNumEcBytes, count($ecBytes));
            $dataBytesOffset += $numDataBytesInBlock;
        }

        if ($numDataBytes !== $dataBytesOffset) {
            throw new WriterException('Data bytes does not match offset');
        }

        $result = new BitArray();

        for ($i = 0; $i < $maxNumDataBytes; ++$i) {
            foreach ($blocks as $block) {
                $dataBytes = $block->getDataBytes();

                if ($i < count($dataBytes)) {
                    $result->appendBits($dataBytes[$i], 8);
                }
            }
        }

        for ($i = 0; $i < $maxNumEcBytes; ++$i) {
            foreach ($blocks as $block) {
                $ecBytes = $block->getErrorCorrectionBytes();

                if ($i < count($ecBytes)) {
                    $result->appendBits($ecBytes[$i], 8);
                }
            }
        }

        if ($numTotalBytes !== $result->getSizeInBytes()) {
            throw new WriterException(
                'Interleaving error: ' . $numTotalBytes . ' and ' . $result->getSizeInBytes() . ' differ'
            );
        }

        return $result;
    }

    /**
     * Generates EC bytes for given data.
     *
     * @param  SplFixedArray<int> $dataBytes
     * @return SplFixedArray<int>
     */
    private static function generateEcBytes(SplFixedArray $dataBytes, int $numEcBytesInBlock) : SplFixedArray
    {
        $numDataBytes = count($dataBytes);
        $toEncode = new SplFixedArray($numDataBytes + $numEcBytesInBlock);

        for ($i = 0; $i < $numDataBytes; $i++) {
            $toEncode[$i] = $dataBytes[$i] & 0xff;
        }

        $ecBytes = new SplFixedArray($numEcBytesInBlock);
        $codec = self::getCodec($numDataBytes, $numEcBytesInBlock);
        $codec->encode($toEncode, $ecBytes);

        return $ecBytes;
    }

    /**
     * Gets an RS codec and caches it.
     */
    private static function getCodec(int $numDataBytes, int $numEcBytesInBlock) : ReedSolomonCodec
    {
        $cacheId = $numDataBytes . '-' . $numEcBytesInBlock;

        if (isset(self::$codecs[$cacheId])) {
            return self::$codecs[$cacheId];
        }

        return self::$codecs[$cacheId] = new ReedSolomonCodec(
            8,
            0x11d,
            0,
            1,
            $numEcBytesInBlock,
            255 - $numDataBytes - $numEcBytesInBlock
        );
    }

    /**
     * Appends mode information to a bit array.
     */
    private static function appendModeInfo(Mode $mode, BitArray $bits) : void
    {
        $bits->appendBits($mode->getBits(), 4);
    }

    /**
     * Appends length information to a bit array.
     *
     * @throws WriterException if num letters is bigger than expected
     */
    private static function appendLengthInfo(int $numLetters, Version $version, Mode $mode, BitArray $bits) : void
    {
        $numBits = $mode->getCharacterCountBits($version);

        if ($numLetters >= (1 << $numBits)) {
            throw new WriterException($numLetters . ' is bigger than ' . ((1 << $numBits) - 1));
        }

        $bits->appendBits($numLetters, $numBits);
    }

    /**
     * Appends bytes to a bit array in a specific mode.
     *
     * @throws WriterException if an invalid mode was supplied
     */
    private static function appendBytes(string $content, Mode $mode, BitArray $bits, string $encoding) : void
    {
        switch ($mode) {
            case Mode::NUMERIC():
                self::appendNumericBytes($content, $bits);
                break;

            case Mode::ALPHANUMERIC():
                self::appendAlphanumericBytes($content, $bits);
                break;

            case Mode::BYTE():
                self::append8BitBytes($content, $bits, $encoding);
                break;

            case Mode::KANJI():
                self::appendKanjiBytes($content, $bits);
                break;

            default:
                throw new WriterException('Invalid mode: ' . $mode);
        }
    }

    /**
     * Appends numeric bytes to a bit array.
     */
    private static function appendNumericBytes(string $content, BitArray $bits) : void
    {
        $length = strlen($content);
        $i = 0;

        while ($i < $length) {
            $num1 = (int) $content[$i];

            if ($i + 2 < $length) {
                // Encode three numeric letters in ten bits.
                $num2 = (int) $content[$i + 1];
                $num3 = (int) $content[$i + 2];
                $bits->appendBits($num1 * 100 + $num2 * 10 + $num3, 10);
                $i += 3;
            } elseif ($i + 1 < $length) {
                // Encode two numeric letters in seven bits.
                $num2 = (int) $content[$i + 1];
                $bits->appendBits($num1 * 10 + $num2, 7);
                $i += 2;
            } else {
                // Encode one numeric letter in four bits.
                $bits->appendBits($num1, 4);
                ++$i;
            }
        }
    }

    /**
     * Appends alpha-numeric bytes to a bit array.
     *
     * @throws WriterException if an invalid alphanumeric code was found
     */
    private static function appendAlphanumericBytes(string $content, BitArray $bits) : void
    {
        $length = strlen($content);
        $i = 0;

        while ($i < $length) {
            $code1 = self::getAlphanumericCode(ord($content[$i]));

            if (-1 === $code1) {
                throw new WriterException('Invalid alphanumeric code');
            }

            if ($i + 1 < $length) {
                $code2 = self::getAlphanumericCode(ord($content[$i + 1]));

                if (-1 === $code2) {
                    throw new WriterException('Invalid alphanumeric code');
                }

                // Encode two alphanumeric letters in 11 bits.
                $bits->appendBits($code1 * 45 + $code2, 11);
                $i += 2;
            } else {
                // Encode one alphanumeric letter in six bits.
                $bits->appendBits($code1, 6);
                ++$i;
            }
        }
    }

    /**
     * Appends regular 8-bit bytes to a bit array.
     *
     * @throws WriterException if content cannot be encoded to target encoding
     */
    private static function append8BitBytes(string $content, BitArray $bits, string $encoding) : void
    {
        $bytes = @iconv('utf-8', $encoding, $content);

        if (false === $bytes) {
            throw new WriterException('Could not encode content to ' . $encoding);
        }

        $length = strlen($bytes);

        for ($i = 0; $i < $length; $i++) {
            $bits->appendBits(ord($bytes[$i]), 8);
        }
    }

    /**
     * Appends KANJI bytes to a bit array.
     *
     * @throws WriterException if content does not seem to be encoded in SHIFT-JIS
     * @throws WriterException if an invalid byte sequence occurs
     */
    private static function appendKanjiBytes(string $content, BitArray $bits) : void
    {
        if (strlen($content) % 2 > 0) {
            // We just do a simple length check here. The for loop will check
            // individual characters.
            throw new WriterException('Content does not seem to be encoded in SHIFT-JIS');
        }

        $length = strlen($content);

        for ($i = 0; $i < $length; $i += 2) {
            $byte1 = ord($content[$i]) & 0xff;
            $byte2 = ord($content[$i + 1]) & 0xff;
            $code = ($byte1 << 8) | $byte2;

            if ($code >= 0x8140 && $code <= 0x9ffc) {
                $subtracted = $code - 0x8140;
            } elseif ($code >= 0xe040 && $code <= 0xebbf) {
                $subtracted = $code - 0xc140;
            } else {
                throw new WriterException('Invalid byte sequence');
            }

            $encoded = (($subtracted >> 8) * 0xc0) + ($subtracted & 0xff);

            $bits->appendBits($encoded, 13);
        }
    }

    /**
     * Appends ECI information to a bit array.
     */
    private static function appendEci(CharacterSetEci $eci, BitArray $bits) : void
    {
        $mode = Mode::ECI();
        $bits->appendBits($mode->getBits(), 4);
        $bits->appendBits($eci->getValue(), 8);
    }
}
PK{c�\=f�.�
�
*bacon/bacon-qr-code/src/Encoder/QrCode.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Encoder;

use BaconQrCode\Common\ErrorCorrectionLevel;
use BaconQrCode\Common\Mode;
use BaconQrCode\Common\Version;

/**
 * QR code.
 */
final class QrCode
{
    /**
     * Number of possible mask patterns.
     */
    public const NUM_MASK_PATTERNS = 8;

    /**
     * Mode of the QR code.
     *
     * @var Mode
     */
    private $mode;

    /**
     * EC level of the QR code.
     *
     * @var ErrorCorrectionLevel
     */
    private $errorCorrectionLevel;

    /**
     * Version of the QR code.
     *
     * @var Version
     */
    private $version;

    /**
     * Mask pattern of the QR code.
     *
     * @var int
     */
    private $maskPattern = -1;

    /**
     * Matrix of the QR code.
     *
     * @var ByteMatrix
     */
    private $matrix;

    public function __construct(
        Mode $mode,
        ErrorCorrectionLevel $errorCorrectionLevel,
        Version $version,
        int $maskPattern,
        ByteMatrix $matrix
    ) {
        $this->mode = $mode;
        $this->errorCorrectionLevel = $errorCorrectionLevel;
        $this->version = $version;
        $this->maskPattern = $maskPattern;
        $this->matrix = $matrix;
    }

    /**
     * Gets the mode.
     */
    public function getMode() : Mode
    {
        return $this->mode;
    }

    /**
     * Gets the EC level.
     */
    public function getErrorCorrectionLevel() : ErrorCorrectionLevel
    {
        return $this->errorCorrectionLevel;
    }

    /**
     * Gets the version.
     */
    public function getVersion() : Version
    {
        return $this->version;
    }

    /**
     * Gets the mask pattern.
     */
    public function getMaskPattern() : int
    {
        return $this->maskPattern;
    }

    /**
     * Gets the matrix.
     *
     * @return ByteMatrix
     */
    public function getMatrix()
    {
        return $this->matrix;
    }

    /**
     * Validates whether a mask pattern is valid.
     */
    public static function isValidMaskPattern(int $maskPattern) : bool
    {
        return $maskPattern > 0 && $maskPattern < self::NUM_MASK_PATTERNS;
    }

    /**
     * Returns a string representation of the QR code.
     */
    public function __toString() : string
    {
        $result = "<<\n"
                . ' mode: ' . $this->mode . "\n"
                . ' ecLevel: ' . $this->errorCorrectionLevel . "\n"
                . ' version: ' . $this->version . "\n"
                . ' maskPattern: ' . $this->maskPattern . "\n";

        if ($this->matrix === null) {
            $result .= " matrix: null\n";
        } else {
            $result .= " matrix:\n";
            $result .= $this->matrix;
        }

        $result .= ">>\n";

        return $result;
    }
}
PK{c�\��C�VV-bacon/bacon-qr-code/src/Encoder/BlockPair.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Encoder;

use SplFixedArray;

/**
 * Block pair.
 */
final class BlockPair
{
    /**
     * Data bytes in the block.
     *
     * @var SplFixedArray<int>
     */
    private $dataBytes;

    /**
     * Error correction bytes in the block.
     *
     * @var SplFixedArray<int>
     */
    private $errorCorrectionBytes;

    /**
     * Creates a new block pair.
     *
     * @param SplFixedArray<int> $data
     * @param SplFixedArray<int> $errorCorrection
     */
    public function __construct(SplFixedArray $data, SplFixedArray $errorCorrection)
    {
        $this->dataBytes = $data;
        $this->errorCorrectionBytes = $errorCorrection;
    }

    /**
     * Gets the data bytes.
     *
     * @return SplFixedArray<int>
     */
    public function getDataBytes() : SplFixedArray
    {
        return $this->dataBytes;
    }

    /**
     * Gets the error correction bytes.
     *
     * @return SplFixedArray<int>
     */
    public function getErrorCorrectionBytes() : SplFixedArray
    {
        return $this->errorCorrectionBytes;
    }
}
PK{c�\G��� � ,bacon/bacon-qr-code/src/Encoder/MaskUtil.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Encoder;

use BaconQrCode\Common\BitUtils;
use BaconQrCode\Exception\InvalidArgumentException;

/**
 * Mask utility.
 */
final class MaskUtil
{
    /**#@+
     * Penalty weights from section 6.8.2.1
     */
    const N1 = 3;
    const N2 = 3;
    const N3 = 40;
    const N4 = 10;
    /**#@-*/

    private function __construct()
    {
    }

    /**
     * Applies mask penalty rule 1 and returns the penalty.
     *
     * Finds repetitive cells with the same color and gives penalty to them.
     * Example: 00000 or 11111.
     */
    public static function applyMaskPenaltyRule1(ByteMatrix $matrix) : int
    {
        return (
            self::applyMaskPenaltyRule1Internal($matrix, true)
            + self::applyMaskPenaltyRule1Internal($matrix, false)
        );
    }

    /**
     * Applies mask penalty rule 2 and returns the penalty.
     *
     * Finds 2x2 blocks with the same color and gives penalty to them. This is
     * actually equivalent to the spec's rule, which is to find MxN blocks and
     * give a penalty proportional to (M-1)x(N-1), because this is the number of
     * 2x2 blocks inside such a block.
     */
    public static function applyMaskPenaltyRule2(ByteMatrix $matrix) : int
    {
        $penalty = 0;
        $array = $matrix->getArray();
        $width = $matrix->getWidth();
        $height = $matrix->getHeight();

        for ($y = 0; $y < $height - 1; ++$y) {
            for ($x = 0; $x < $width - 1; ++$x) {
                $value = $array[$y][$x];

                if ($value === $array[$y][$x + 1]
                    && $value === $array[$y + 1][$x]
                    && $value === $array[$y + 1][$x + 1]
                ) {
                    ++$penalty;
                }
            }
        }

        return self::N2 * $penalty;
    }

    /**
     * Applies mask penalty rule 3 and returns the penalty.
     *
     * Finds consecutive cells of 00001011101 or 10111010000, and gives penalty
     * to them. If we find patterns like 000010111010000, we give penalties
     * twice (i.e. 40 * 2).
     */
    public static function applyMaskPenaltyRule3(ByteMatrix $matrix) : int
    {
        $penalty = 0;
        $array = $matrix->getArray();
        $width = $matrix->getWidth();
        $height = $matrix->getHeight();

        for ($y = 0; $y < $height; ++$y) {
            for ($x = 0; $x < $width; ++$x) {
                if ($x + 6 < $width
                    && 1 === $array[$y][$x]
                    && 0 === $array[$y][$x + 1]
                    && 1 === $array[$y][$x + 2]
                    && 1 === $array[$y][$x + 3]
                    && 1 === $array[$y][$x + 4]
                    && 0 === $array[$y][$x + 5]
                    && 1 === $array[$y][$x + 6]
                    && (
                        (
                            $x + 10 < $width
                            && 0 === $array[$y][$x + 7]
                            && 0 === $array[$y][$x + 8]
                            && 0 === $array[$y][$x + 9]
                            && 0 === $array[$y][$x + 10]
                        )
                        || (
                            $x - 4 >= 0
                            && 0 === $array[$y][$x - 1]
                            && 0 === $array[$y][$x - 2]
                            && 0 === $array[$y][$x - 3]
                            && 0 === $array[$y][$x - 4]
                        )
                    )
                ) {
                    $penalty += self::N3;
                }

                if ($y + 6 < $height
                    && 1 === $array[$y][$x]
                    && 0 === $array[$y + 1][$x]
                    && 1 === $array[$y + 2][$x]
                    && 1 === $array[$y + 3][$x]
                    && 1 === $array[$y + 4][$x]
                    && 0 === $array[$y + 5][$x]
                    && 1 === $array[$y + 6][$x]
                    && (
                        (
                            $y + 10 < $height
                            && 0 === $array[$y + 7][$x]
                            && 0 === $array[$y + 8][$x]
                            && 0 === $array[$y + 9][$x]
                            && 0 === $array[$y + 10][$x]
                        )
                        || (
                            $y - 4 >= 0
                            && 0 === $array[$y - 1][$x]
                            && 0 === $array[$y - 2][$x]
                            && 0 === $array[$y - 3][$x]
                            && 0 === $array[$y - 4][$x]
                        )
                    )
                ) {
                    $penalty += self::N3;
                }
            }
        }

        return $penalty;
    }

    /**
     * Applies mask penalty rule 4 and returns the penalty.
     *
     * Calculates the ratio of dark cells and gives penalty if the ratio is far
     * from 50%. It gives 10 penalty for 5% distance.
     */
    public static function applyMaskPenaltyRule4(ByteMatrix $matrix) : int
    {
        $numDarkCells = 0;

        $array = $matrix->getArray();
        $width = $matrix->getWidth();
        $height = $matrix->getHeight();

        for ($y = 0; $y < $height; ++$y) {
            $arrayY = $array[$y];

            for ($x = 0; $x < $width; ++$x) {
                if (1 === $arrayY[$x]) {
                    ++$numDarkCells;
                }
            }
        }

        $numTotalCells = $height * $width;
        $darkRatio = $numDarkCells / $numTotalCells;
        $fixedPercentVariances = (int) (abs($darkRatio - 0.5) * 20);

        return $fixedPercentVariances * self::N4;
    }

    /**
     * Returns the mask bit for "getMaskPattern" at "x" and "y".
     *
     * See 8.8 of JISX0510:2004 for mask pattern conditions.
     *
     * @throws InvalidArgumentException if an invalid mask pattern was supplied
     */
    public static function getDataMaskBit(int $maskPattern, int $x, int $y) : bool
    {
        switch ($maskPattern) {
            case 0:
                $intermediate = ($y + $x) & 0x1;
                break;

            case 1:
                $intermediate = $y & 0x1;
                break;

            case 2:
                $intermediate = $x % 3;
                break;

            case 3:
                $intermediate = ($y + $x) % 3;
                break;

            case 4:
                $intermediate = (BitUtils::unsignedRightShift($y, 1) + (int) ($x / 3)) & 0x1;
                break;

            case 5:
                $temp = $y * $x;
                $intermediate = ($temp & 0x1) + ($temp % 3);
                break;

            case 6:
                $temp = $y * $x;
                $intermediate = (($temp & 0x1) + ($temp % 3)) & 0x1;
                break;

            case 7:
                $temp = $y * $x;
                $intermediate = (($temp % 3) + (($y + $x) & 0x1)) & 0x1;
                break;

            default:
                throw new InvalidArgumentException('Invalid mask pattern: ' . $maskPattern);
        }

        return 0 == $intermediate;
    }

    /**
     * Helper function for applyMaskPenaltyRule1.
     *
     * We need this for doing this calculation in both vertical and horizontal
     * orders respectively.
     */
    private static function applyMaskPenaltyRule1Internal(ByteMatrix $matrix, bool $isHorizontal) : int
    {
        $penalty = 0;
        $iLimit = $isHorizontal ? $matrix->getHeight() : $matrix->getWidth();
        $jLimit = $isHorizontal ? $matrix->getWidth() : $matrix->getHeight();
        $array = $matrix->getArray();

        for ($i = 0; $i < $iLimit; ++$i) {
            $numSameBitCells = 0;
            $prevBit = -1;

            for ($j = 0; $j < $jLimit; $j++) {
                $bit = $isHorizontal ? $array[$i][$j] : $array[$j][$i];

                if ($bit === $prevBit) {
                    ++$numSameBitCells;
                } else {
                    if ($numSameBitCells >= 5) {
                        $penalty += self::N1 + ($numSameBitCells - 5);
                    }

                    $numSameBitCells = 1;
                    $prevBit = $bit;
                }
            }

            if ($numSameBitCells >= 5) {
                $penalty += self::N1 + ($numSameBitCells - 5);
            }
        }

        return $penalty;
    }
}
PK{c�\m�����.bacon/bacon-qr-code/src/Encoder/ByteMatrix.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Encoder;

use SplFixedArray;
use Traversable;

/**
 * Byte matrix.
 */
final class ByteMatrix
{
    /**
     * Bytes in the matrix, represented as array.
     *
     * @var SplFixedArray<SplFixedArray<int>>
     */
    private $bytes;

    /**
     * Width of the matrix.
     *
     * @var int
     */
    private $width;

    /**
     * Height of the matrix.
     *
     * @var int
     */
    private $height;

    public function __construct(int $width, int $height)
    {
        $this->height = $height;
        $this->width = $width;
        $this->bytes = new SplFixedArray($height);

        for ($y = 0; $y < $height; ++$y) {
            $this->bytes[$y] = SplFixedArray::fromArray(array_fill(0, $width, 0));
        }
    }

    /**
     * Gets the width of the matrix.
     */
    public function getWidth() : int
    {
        return $this->width;
    }

    /**
     * Gets the height of the matrix.
     */
    public function getHeight() : int
    {
        return $this->height;
    }

    /**
     * Gets the internal representation of the matrix.
     *
     * @return SplFixedArray<SplFixedArray<int>>
     */
    public function getArray() : SplFixedArray
    {
        return $this->bytes;
    }

    /**
     * @return Traversable<int>
     */
    public function getBytes() : Traversable
    {
        foreach ($this->bytes as $row) {
            foreach ($row as $byte) {
                yield $byte;
            }
        }
    }

    /**
     * Gets the byte for a specific position.
     */
    public function get(int $x, int $y) : int
    {
        return $this->bytes[$y][$x];
    }

    /**
     * Sets the byte for a specific position.
     */
    public function set(int $x, int $y, int $value) : void
    {
        $this->bytes[$y][$x] = $value;
    }

    /**
     * Clears the matrix with a specific value.
     */
    public function clear(int $value) : void
    {
        for ($y = 0; $y < $this->height; ++$y) {
            for ($x = 0; $x < $this->width; ++$x) {
                $this->bytes[$y][$x] = $value;
            }
        }
    }

    public function __clone()
    {
        $this->bytes = clone $this->bytes;

        foreach ($this->bytes as $index => $row) {
            $this->bytes[$index] = clone $row;
        }
    }

    /**
     * Returns a string representation of the matrix.
     */
    public function __toString() : string
    {
        $result = '';

        for ($y = 0; $y < $this->height; $y++) {
            for ($x = 0; $x < $this->width; $x++) {
                switch ($this->bytes[$y][$x]) {
                    case 0:
                        $result .= ' 0';
                        break;

                    case 1:
                        $result .= ' 1';
                        break;

                    default:
                        $result .= '  ';
                        break;
                }
            }

            $result .= "\n";
        }

        return $result;
    }
}
PK{c�\�D�2bacon/bacon-qr-code/src/Renderer/ImageRenderer.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Renderer;

use BaconQrCode\Encoder\MatrixUtil;
use BaconQrCode\Encoder\QrCode;
use BaconQrCode\Exception\InvalidArgumentException;
use BaconQrCode\Renderer\Image\ImageBackEndInterface;
use BaconQrCode\Renderer\Path\Path;
use BaconQrCode\Renderer\RendererStyle\EyeFill;
use BaconQrCode\Renderer\RendererStyle\RendererStyle;

final class ImageRenderer implements RendererInterface
{
    /**
     * @var RendererStyle
     */
    private $rendererStyle;

    /**
     * @var ImageBackEndInterface
     */
    private $imageBackEnd;

    public function __construct(RendererStyle $rendererStyle, ImageBackEndInterface $imageBackEnd)
    {
        $this->rendererStyle = $rendererStyle;
        $this->imageBackEnd = $imageBackEnd;
    }

    /**
     * @throws InvalidArgumentException if matrix width doesn't match height
     */
    public function render(QrCode $qrCode) : string
    {
        $size = $this->rendererStyle->getSize();
        $margin = $this->rendererStyle->getMargin();
        $matrix = $qrCode->getMatrix();
        $matrixSize = $matrix->getWidth();

        if ($matrixSize !== $matrix->getHeight()) {
            throw new InvalidArgumentException('Matrix must have the same width and height');
        }

        $totalSize = $matrixSize + ($margin * 2);
        $moduleSize = $size / $totalSize;
        $fill = $this->rendererStyle->getFill();

        $this->imageBackEnd->new($size, $fill->getBackgroundColor());
        $this->imageBackEnd->scale((float) $moduleSize);
        $this->imageBackEnd->translate((float) $margin, (float) $margin);

        $module = $this->rendererStyle->getModule();
        $moduleMatrix = clone $matrix;
        MatrixUtil::removePositionDetectionPatterns($moduleMatrix);
        $modulePath = $this->drawEyes($matrixSize, $module->createPath($moduleMatrix));

        if ($fill->hasGradientFill()) {
            $this->imageBackEnd->drawPathWithGradient(
                $modulePath,
                $fill->getForegroundGradient(),
                0,
                0,
                $matrixSize,
                $matrixSize
            );
        } else {
            $this->imageBackEnd->drawPathWithColor($modulePath, $fill->getForegroundColor());
        }

        return $this->imageBackEnd->done();
    }

    private function drawEyes(int $matrixSize, Path $modulePath) : Path
    {
        $fill = $this->rendererStyle->getFill();

        $eye = $this->rendererStyle->getEye();
        $externalPath = $eye->getExternalPath();
        $internalPath = $eye->getInternalPath();

        $modulePath = $this->drawEye(
            $externalPath,
            $internalPath,
            $fill->getTopLeftEyeFill(),
            3.5,
            3.5,
            0,
            $modulePath
        );
        $modulePath = $this->drawEye(
            $externalPath,
            $internalPath,
            $fill->getTopRightEyeFill(),
            $matrixSize - 3.5,
            3.5,
            90,
            $modulePath
        );
        $modulePath = $this->drawEye(
            $externalPath,
            $internalPath,
            $fill->getBottomLeftEyeFill(),
            3.5,
            $matrixSize - 3.5,
            -90,
            $modulePath
        );

        return $modulePath;
    }

    private function drawEye(
        Path $externalPath,
        Path $internalPath,
        EyeFill $fill,
        float $xTranslation,
        float $yTranslation,
        int $rotation,
        Path $modulePath
    ) : Path {
        if ($fill->inheritsBothColors()) {
            return $modulePath
                ->append($externalPath->translate($xTranslation, $yTranslation))
                ->append($internalPath->translate($xTranslation, $yTranslation));
        }

        $this->imageBackEnd->push();
        $this->imageBackEnd->translate($xTranslation, $yTranslation);

        if (0 !== $rotation) {
            $this->imageBackEnd->rotate($rotation);
        }

        if ($fill->inheritsExternalColor()) {
            $modulePath = $modulePath->append($externalPath->translate($xTranslation, $yTranslation));
        } else {
            $this->imageBackEnd->drawPathWithColor($externalPath, $fill->getExternalColor());
        }

        if ($fill->inheritsInternalColor()) {
            $modulePath = $modulePath->append($internalPath->translate($xTranslation, $yTranslation));
        } else {
            $this->imageBackEnd->drawPathWithColor($internalPath, $fill->getInternalColor());
        }

        $this->imageBackEnd->pop();

        return $modulePath;
    }
}
PK{c�\[䘑��:bacon/bacon-qr-code/src/Renderer/RendererStyle/EyeFill.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Renderer\RendererStyle;

use BaconQrCode\Exception\RuntimeException;
use BaconQrCode\Renderer\Color\ColorInterface;

final class EyeFill
{
    /**
     * @var ColorInterface|null
     */
    private $externalColor;

    /**
     * @var ColorInterface|null
     */
    private $internalColor;

    /**
     * @var self|null
     */
    private static $inherit;

    public function __construct(?ColorInterface $externalColor, ?ColorInterface $internalColor)
    {
        $this->externalColor = $externalColor;
        $this->internalColor = $internalColor;
    }

    public static function uniform(ColorInterface $color) : self
    {
        return new self($color, $color);
    }

    public static function inherit() : self
    {
        return self::$inherit ?: self::$inherit = new self(null, null);
    }

    public function inheritsBothColors() : bool
    {
        return null === $this->externalColor && null === $this->internalColor;
    }

    public function inheritsExternalColor() : bool
    {
        return null === $this->externalColor;
    }

    public function inheritsInternalColor() : bool
    {
        return null === $this->internalColor;
    }

    public function getExternalColor() : ColorInterface
    {
        if (null === $this->externalColor) {
            throw new RuntimeException('External eye color inherits foreground color');
        }

        return $this->externalColor;
    }

    public function getInternalColor() : ColorInterface
    {
        if (null === $this->internalColor) {
            throw new RuntimeException('Internal eye color inherits foreground color');
        }

        return $this->internalColor;
    }
}
PK{c�\v��117bacon/bacon-qr-code/src/Renderer/RendererStyle/Fill.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Renderer\RendererStyle;

use BaconQrCode\Exception\RuntimeException;
use BaconQrCode\Renderer\Color\ColorInterface;
use BaconQrCode\Renderer\Color\Gray;

final class Fill
{
    /**
     * @var ColorInterface
     */
    private $backgroundColor;

    /**
     * @var ColorInterface|null
     */
    private $foregroundColor;

    /**
     * @var Gradient|null
     */
    private $foregroundGradient;

    /**
     * @var EyeFill
     */
    private $topLeftEyeFill;

    /**
     * @var EyeFill
     */
    private $topRightEyeFill;

    /**
     * @var EyeFill
     */
    private $bottomLeftEyeFill;

    /**
     * @var self|null
     */
    private static $default;

    private function __construct(
        ColorInterface $backgroundColor,
        ?ColorInterface $foregroundColor,
        ?Gradient $foregroundGradient,
        EyeFill $topLeftEyeFill,
        EyeFill $topRightEyeFill,
        EyeFill $bottomLeftEyeFill
    ) {
        $this->backgroundColor = $backgroundColor;
        $this->foregroundColor = $foregroundColor;
        $this->foregroundGradient = $foregroundGradient;
        $this->topLeftEyeFill = $topLeftEyeFill;
        $this->topRightEyeFill = $topRightEyeFill;
        $this->bottomLeftEyeFill = $bottomLeftEyeFill;
    }

    public static function default() : self
    {
        return self::$default ?: self::$default = self::uniformColor(new Gray(100), new Gray(0));
    }

    public static function withForegroundColor(
        ColorInterface $backgroundColor,
        ColorInterface $foregroundColor,
        EyeFill $topLeftEyeFill,
        EyeFill $topRightEyeFill,
        EyeFill $bottomLeftEyeFill
    ) : self {
        return new self(
            $backgroundColor,
            $foregroundColor,
            null,
            $topLeftEyeFill,
            $topRightEyeFill,
            $bottomLeftEyeFill
        );
    }

    public static function withForegroundGradient(
        ColorInterface $backgroundColor,
        Gradient $foregroundGradient,
        EyeFill $topLeftEyeFill,
        EyeFill $topRightEyeFill,
        EyeFill $bottomLeftEyeFill
    ) : self {
        return new self(
            $backgroundColor,
            null,
            $foregroundGradient,
            $topLeftEyeFill,
            $topRightEyeFill,
            $bottomLeftEyeFill
        );
    }

    public static function uniformColor(ColorInterface $backgroundColor, ColorInterface $foregroundColor) : self
    {
        return new self(
            $backgroundColor,
            $foregroundColor,
            null,
            EyeFill::inherit(),
            EyeFill::inherit(),
            EyeFill::inherit()
        );
    }

    public static function uniformGradient(ColorInterface $backgroundColor, Gradient $foregroundGradient) : self
    {
        return new self(
            $backgroundColor,
            null,
            $foregroundGradient,
            EyeFill::inherit(),
            EyeFill::inherit(),
            EyeFill::inherit()
        );
    }

    public function hasGradientFill() : bool
    {
        return null !== $this->foregroundGradient;
    }

    public function getBackgroundColor() : ColorInterface
    {
        return $this->backgroundColor;
    }

    public function getForegroundColor() : ColorInterface
    {
        if (null === $this->foregroundColor) {
            throw new RuntimeException('Fill uses a gradient, thus no foreground color is available');
        }

        return $this->foregroundColor;
    }

    public function getForegroundGradient() : Gradient
    {
        if (null === $this->foregroundGradient) {
            throw new RuntimeException('Fill uses a single color, thus no foreground gradient is available');
        }

        return $this->foregroundGradient;
    }

    public function getTopLeftEyeFill() : EyeFill
    {
        return $this->topLeftEyeFill;
    }

    public function getTopRightEyeFill() : EyeFill
    {
        return $this->topRightEyeFill;
    }

    public function getBottomLeftEyeFill() : EyeFill
    {
        return $this->bottomLeftEyeFill;
    }
}
PK{c�\;p+ݯ�@bacon/bacon-qr-code/src/Renderer/RendererStyle/RendererStyle.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Renderer\RendererStyle;

use BaconQrCode\Renderer\Eye\EyeInterface;
use BaconQrCode\Renderer\Eye\ModuleEye;
use BaconQrCode\Renderer\Module\ModuleInterface;
use BaconQrCode\Renderer\Module\SquareModule;

final class RendererStyle
{
    /**
     * @var int
     */
    private $size;

    /**
     * @var int
     */
    private $margin;

    /**
     * @var ModuleInterface
     */
    private $module;

    /**
     * @var EyeInterface|null
     */
    private $eye;

    /**
     * @var Fill
     */
    private $fill;

    public function __construct(
        int $size,
        int $margin = 4,
        ?ModuleInterface $module = null,
        ?EyeInterface $eye = null,
        ?Fill $fill = null
    ) {
        $this->margin = $margin;
        $this->size = $size;
        $this->module = $module ?: SquareModule::instance();
        $this->eye = $eye ?: new ModuleEye($this->module);
        $this->fill = $fill ?: Fill::default();
    }

    public function withSize(int $size) : self
    {
        $style = clone $this;
        $style->size = $size;
        return $style;
    }

    public function withMargin(int $margin) : self
    {
        $style = clone $this;
        $style->margin = $margin;
        return $style;
    }

    public function getSize() : int
    {
        return $this->size;
    }

    public function getMargin() : int
    {
        return $this->margin;
    }

    public function getModule() : ModuleInterface
    {
        return $this->module;
    }

    public function getEye() : EyeInterface
    {
        return $this->eye;
    }

    public function getFill() : Fill
    {
        return $this->fill;
    }
}
PK{c�\�g��ZZ;bacon/bacon-qr-code/src/Renderer/RendererStyle/Gradient.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Renderer\RendererStyle;

use BaconQrCode\Renderer\Color\ColorInterface;

final class Gradient
{
    /**
     * @var ColorInterface
     */
    private $startColor;

    /**
     * @var ColorInterface
     */
    private $endColor;

    /**
     * @var GradientType
     */
    private $type;

    public function __construct(ColorInterface $startColor, ColorInterface $endColor, GradientType $type)
    {
        $this->startColor = $startColor;
        $this->endColor = $endColor;
        $this->type = $type;
    }

    public function getStartColor() : ColorInterface
    {
        return $this->startColor;
    }

    public function getEndColor() : ColorInterface
    {
        return $this->endColor;
    }

    public function getType() : GradientType
    {
        return $this->type;
    }
}
PK{c�\#[�I?bacon/bacon-qr-code/src/Renderer/RendererStyle/GradientType.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Renderer\RendererStyle;

use DASPRiD\Enum\AbstractEnum;

/**
 * @method static self VERTICAL()
 * @method static self HORIZONTAL()
 * @method static self DIAGONAL()
 * @method static self INVERSE_DIAGONAL()
 * @method static self RADIAL()
 */
final class GradientType extends AbstractEnum
{
    protected const VERTICAL = null;
    protected const HORIZONTAL = null;
    protected const DIAGONAL = null;
    protected const INVERSE_DIAGONAL = null;
    protected const RADIAL = null;
}
PK{c�\5����6bacon/bacon-qr-code/src/Renderer/RendererInterface.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Renderer;

use BaconQrCode\Encoder\QrCode;

interface RendererInterface
{
    public function render(QrCode $qrCode) : string;
}
PK{c�\�`�s(s(>bacon/bacon-qr-code/src/Renderer/Image/ImagickImageBackEnd.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Renderer\Image;

use BaconQrCode\Exception\RuntimeException;
use BaconQrCode\Renderer\Color\Alpha;
use BaconQrCode\Renderer\Color\Cmyk;
use BaconQrCode\Renderer\Color\ColorInterface;
use BaconQrCode\Renderer\Color\Gray;
use BaconQrCode\Renderer\Color\Rgb;
use BaconQrCode\Renderer\Path\Close;
use BaconQrCode\Renderer\Path\Curve;
use BaconQrCode\Renderer\Path\EllipticArc;
use BaconQrCode\Renderer\Path\Line;
use BaconQrCode\Renderer\Path\Move;
use BaconQrCode\Renderer\Path\Path;
use BaconQrCode\Renderer\RendererStyle\Gradient;
use BaconQrCode\Renderer\RendererStyle\GradientType;
use Imagick;
use ImagickDraw;
use ImagickPixel;

final class ImagickImageBackEnd implements ImageBackEndInterface
{
    /**
     * @var string
     */
    private $imageFormat;

    /**
     * @var int
     */
    private $compressionQuality;

    /**
     * @var Imagick|null
     */
    private $image;

    /**
     * @var ImagickDraw|null
     */
    private $draw;

    /**
     * @var int|null
     */
    private $gradientCount;

    /**
     * @var TransformationMatrix[]|null
     */
    private $matrices;

    /**
     * @var int|null
     */
    private $matrixIndex;

    public function __construct(string $imageFormat = 'png', int $compressionQuality = 100)
    {
        if (! class_exists(Imagick::class)) {
            throw new RuntimeException('You need to install the imagick extension to use this back end');
        }

        $this->imageFormat = $imageFormat;
        $this->compressionQuality = $compressionQuality;
    }

    public function new(int $size, ColorInterface $backgroundColor) : void
    {
        $this->image = new Imagick();
        $this->image->newImage($size, $size, $this->getColorPixel($backgroundColor));
        $this->image->setImageFormat($this->imageFormat);
        $this->image->setCompressionQuality($this->compressionQuality);
        $this->draw = new ImagickDraw();
        $this->gradientCount = 0;
        $this->matrices = [new TransformationMatrix()];
        $this->matrixIndex = 0;
    }

    public function scale(float $size) : void
    {
        if (null === $this->draw) {
            throw new RuntimeException('No image has been started');
        }

        $this->draw->scale($size, $size);
        $this->matrices[$this->matrixIndex] = $this->matrices[$this->matrixIndex]
            ->multiply(TransformationMatrix::scale($size));
    }

    public function translate(float $x, float $y) : void
    {
        if (null === $this->draw) {
            throw new RuntimeException('No image has been started');
        }

        $this->draw->translate($x, $y);
        $this->matrices[$this->matrixIndex] = $this->matrices[$this->matrixIndex]
            ->multiply(TransformationMatrix::translate($x, $y));
    }

    public function rotate(int $degrees) : void
    {
        if (null === $this->draw) {
            throw new RuntimeException('No image has been started');
        }

        $this->draw->rotate($degrees);
        $this->matrices[$this->matrixIndex] = $this->matrices[$this->matrixIndex]
            ->multiply(TransformationMatrix::rotate($degrees));
    }

    public function push() : void
    {
        if (null === $this->draw) {
            throw new RuntimeException('No image has been started');
        }

        $this->draw->push();
        $this->matrices[++$this->matrixIndex] = $this->matrices[$this->matrixIndex - 1];
    }

    public function pop() : void
    {
        if (null === $this->draw) {
            throw new RuntimeException('No image has been started');
        }

        $this->draw->pop();
        unset($this->matrices[$this->matrixIndex--]);
    }

    public function drawPathWithColor(Path $path, ColorInterface $color) : void
    {
        if (null === $this->draw) {
            throw new RuntimeException('No image has been started');
        }

        $this->draw->setFillColor($this->getColorPixel($color));
        $this->drawPath($path);
    }

    public function drawPathWithGradient(
        Path $path,
        Gradient $gradient,
        float $x,
        float $y,
        float $width,
        float $height
    ) : void {
        if (null === $this->draw) {
            throw new RuntimeException('No image has been started');
        }

        $this->draw->setFillPatternURL('#' . $this->createGradientFill($gradient, $x, $y, $width, $height));
        $this->drawPath($path);
    }

    public function done() : string
    {
        if (null === $this->draw) {
            throw new RuntimeException('No image has been started');
        }

        $this->image->drawImage($this->draw);
        $blob = $this->image->getImageBlob();
        $this->draw->clear();
        $this->image->clear();
        $this->draw = null;
        $this->image = null;
        $this->gradientCount = null;

        return $blob;
    }

    private function drawPath(Path $path) : void
    {
        $this->draw->pathStart();

        foreach ($path as $op) {
            switch (true) {
                case $op instanceof Move:
                    $this->draw->pathMoveToAbsolute($op->getX(), $op->getY());
                    break;

                case $op instanceof Line:
                    $this->draw->pathLineToAbsolute($op->getX(), $op->getY());
                    break;

                case $op instanceof EllipticArc:
                    $this->draw->pathEllipticArcAbsolute(
                        $op->getXRadius(),
                        $op->getYRadius(),
                        $op->getXAxisAngle(),
                        $op->isLargeArc(),
                        $op->isSweep(),
                        $op->getX(),
                        $op->getY()
                    );
                    break;

                case $op instanceof Curve:
                    $this->draw->pathCurveToAbsolute(
                        $op->getX1(),
                        $op->getY1(),
                        $op->getX2(),
                        $op->getY2(),
                        $op->getX3(),
                        $op->getY3()
                    );
                    break;

                case $op instanceof Close:
                    $this->draw->pathClose();
                    break;

                default:
                    throw new RuntimeException('Unexpected draw operation: ' . get_class($op));
            }
        }

        $this->draw->pathFinish();
    }

    private function createGradientFill(Gradient $gradient, float $x, float $y, float $width, float $height) : string
    {
        list($width, $height) = $this->matrices[$this->matrixIndex]->apply($width, $height);

        $startColor = $this->getColorPixel($gradient->getStartColor())->getColorAsString();
        $endColor = $this->getColorPixel($gradient->getEndColor())->getColorAsString();
        $gradientImage = new Imagick();

        switch ($gradient->getType()) {
            case GradientType::HORIZONTAL():
                $gradientImage->newPseudoImage((int) $height, (int) $width, sprintf(
                    'gradient:%s-%s',
                    $startColor,
                    $endColor
                ));
                $gradientImage->rotateImage('transparent', -90);
                break;

            case GradientType::VERTICAL():
                $gradientImage->newPseudoImage((int) $width, (int) $height, sprintf(
                    'gradient:%s-%s',
                    $startColor,
                    $endColor
                ));
                break;

            case GradientType::DIAGONAL():
            case GradientType::INVERSE_DIAGONAL():
                $gradientImage->newPseudoImage((int) ($width * sqrt(2)), (int) ($height * sqrt(2)), sprintf(
                    'gradient:%s-%s',
                    $startColor,
                    $endColor
                ));

                if (GradientType::DIAGONAL() === $gradient->getType()) {
                    $gradientImage->rotateImage('transparent', -45);
                } else {
                    $gradientImage->rotateImage('transparent', -135);
                }

                $rotatedWidth = $gradientImage->getImageWidth();
                $rotatedHeight = $gradientImage->getImageHeight();

                $gradientImage->setImagePage($rotatedWidth, $rotatedHeight, 0, 0);
                $gradientImage->cropImage(
                    intdiv($rotatedWidth, 2) - 2,
                    intdiv($rotatedHeight, 2) - 2,
                    intdiv($rotatedWidth, 4) + 1,
                    intdiv($rotatedWidth, 4) + 1
                );
                break;

            case GradientType::RADIAL():
                $gradientImage->newPseudoImage((int) $width, (int) $height, sprintf(
                    'radial-gradient:%s-%s',
                    $startColor,
                    $endColor
                ));
                break;
        }

        $id = sprintf('g%d', ++$this->gradientCount);
        $this->draw->pushPattern($id, 0, 0, $width, $height);
        $this->draw->composite(Imagick::COMPOSITE_COPY, 0, 0, $width, $height, $gradientImage);
        $this->draw->popPattern();
        return $id;
    }

    private function getColorPixel(ColorInterface $color) : ImagickPixel
    {
        $alpha = 100;

        if ($color instanceof Alpha) {
            $alpha = $color->getAlpha();
            $color = $color->getBaseColor();
        }

        if ($color instanceof Rgb) {
            return new ImagickPixel(sprintf(
                'rgba(%d, %d, %d, %F)',
                $color->getRed(),
                $color->getGreen(),
                $color->getBlue(),
                $alpha / 100
            ));
        }

        if ($color instanceof Cmyk) {
            return new ImagickPixel(sprintf(
                'cmyka(%d, %d, %d, %d, %F)',
                $color->getCyan(),
                $color->getMagenta(),
                $color->getYellow(),
                $color->getBlack(),
                $alpha / 100
            ));
        }

        if ($color instanceof Gray) {
            return new ImagickPixel(sprintf(
                'graya(%d%%, %F)',
                $color->getGray(),
                $alpha / 100
            ));
        }

        return $this->getColorPixel(new Alpha($alpha, $color->toRgb()));
    }
}
PK{c�\<��1�.�.:bacon/bacon-qr-code/src/Renderer/Image/EpsImageBackEnd.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Renderer\Image;

use BaconQrCode\Exception\RuntimeException;
use BaconQrCode\Renderer\Color\Alpha;
use BaconQrCode\Renderer\Color\Cmyk;
use BaconQrCode\Renderer\Color\ColorInterface;
use BaconQrCode\Renderer\Color\Gray;
use BaconQrCode\Renderer\Color\Rgb;
use BaconQrCode\Renderer\Path\Close;
use BaconQrCode\Renderer\Path\Curve;
use BaconQrCode\Renderer\Path\EllipticArc;
use BaconQrCode\Renderer\Path\Line;
use BaconQrCode\Renderer\Path\Move;
use BaconQrCode\Renderer\Path\Path;
use BaconQrCode\Renderer\RendererStyle\Gradient;
use BaconQrCode\Renderer\RendererStyle\GradientType;

final class EpsImageBackEnd implements ImageBackEndInterface
{
    private const PRECISION = 3;

    /**
     * @var string|null
     */
    private $eps;

    public function new(int $size, ColorInterface $backgroundColor) : void
    {
        $this->eps = "%!PS-Adobe-3.0 EPSF-3.0\n"
            . "%%Creator: BaconQrCode\n"
            . sprintf("%%%%BoundingBox: 0 0 %d %d \n", $size, $size)
            . "%%BeginProlog\n"
            . "save\n"
            . "50 dict begin\n"
            . "/q { gsave } bind def\n"
            . "/Q { grestore } bind def\n"
            . "/s { scale } bind def\n"
            . "/t { translate } bind def\n"
            . "/r { rotate } bind def\n"
            . "/n { newpath } bind def\n"
            . "/m { moveto } bind def\n"
            . "/l { lineto } bind def\n"
            . "/c { curveto } bind def\n"
            . "/z { closepath } bind def\n"
            . "/f { eofill } bind def\n"
            . "/rgb { setrgbcolor } bind def\n"
            . "/cmyk { setcmykcolor } bind def\n"
            . "/gray { setgray } bind def\n"
            . "%%EndProlog\n"
            . "1 -1 s\n"
            . sprintf("0 -%d t\n", $size);

        if ($backgroundColor instanceof Alpha && 0 === $backgroundColor->getAlpha()) {
            return;
        }

        $this->eps .= wordwrap(
            '0 0 m'
            . sprintf(' %s 0 l', (string) $size)
            . sprintf(' %s %s l', (string) $size, (string) $size)
            . sprintf(' 0 %s l', (string) $size)
            . ' z'
            . ' ' .$this->getColorSetString($backgroundColor) . " f\n",
            75,
            "\n "
        );
    }

    public function scale(float $size) : void
    {
        if (null === $this->eps) {
            throw new RuntimeException('No image has been started');
        }

        $this->eps .= sprintf("%1\$s %1\$s s\n", round($size, self::PRECISION));
    }

    public function translate(float $x, float $y) : void
    {
        if (null === $this->eps) {
            throw new RuntimeException('No image has been started');
        }

        $this->eps .= sprintf("%s %s t\n", round($x, self::PRECISION), round($y, self::PRECISION));
    }

    public function rotate(int $degrees) : void
    {
        if (null === $this->eps) {
            throw new RuntimeException('No image has been started');
        }

        $this->eps .= sprintf("%d r\n", $degrees);
    }

    public function push() : void
    {
        if (null === $this->eps) {
            throw new RuntimeException('No image has been started');
        }

        $this->eps .= "q\n";
    }

    public function pop() : void
    {
        if (null === $this->eps) {
            throw new RuntimeException('No image has been started');
        }

        $this->eps .= "Q\n";
    }

    public function drawPathWithColor(Path $path, ColorInterface $color) : void
    {
        if (null === $this->eps) {
            throw new RuntimeException('No image has been started');
        }

        $fromX = 0;
        $fromY = 0;
        $this->eps .= wordwrap(
            'n '
            . $this->drawPathOperations($path, $fromX, $fromY)
            . ' ' . $this->getColorSetString($color) . " f\n",
            75,
            "\n "
        );
    }

    public function drawPathWithGradient(
        Path $path,
        Gradient $gradient,
        float $x,
        float $y,
        float $width,
        float $height
    ) : void {
        if (null === $this->eps) {
            throw new RuntimeException('No image has been started');
        }

        $fromX = 0;
        $fromY = 0;
        $this->eps .= wordwrap(
            'q n ' . $this->drawPathOperations($path, $fromX, $fromY) . "\n",
            75,
            "\n "
        );

        $this->createGradientFill($gradient, $x, $y, $width, $height);
    }

    public function done() : string
    {
        if (null === $this->eps) {
            throw new RuntimeException('No image has been started');
        }

        $this->eps .= "%%TRAILER\nend restore\n%%EOF";
        $blob = $this->eps;
        $this->eps = null;

        return $blob;
    }

    private function drawPathOperations(Iterable $ops, &$fromX, &$fromY) : string
    {
        $pathData = [];

        foreach ($ops as $op) {
            switch (true) {
                case $op instanceof Move:
                    $fromX = $toX = round($op->getX(), self::PRECISION);
                    $fromY = $toY = round($op->getY(), self::PRECISION);
                    $pathData[] = sprintf('%s %s m', $toX, $toY);
                    break;

                case $op instanceof Line:
                    $fromX = $toX = round($op->getX(), self::PRECISION);
                    $fromY = $toY = round($op->getY(), self::PRECISION);
                    $pathData[] = sprintf('%s %s l', $toX, $toY);
                    break;

                case $op instanceof EllipticArc:
                    $pathData[] = $this->drawPathOperations($op->toCurves($fromX, $fromY), $fromX, $fromY);
                    break;

                case $op instanceof Curve:
                    $x1 = round($op->getX1(), self::PRECISION);
                    $y1 = round($op->getY1(), self::PRECISION);
                    $x2 = round($op->getX2(), self::PRECISION);
                    $y2 = round($op->getY2(), self::PRECISION);
                    $fromX = $x3 = round($op->getX3(), self::PRECISION);
                    $fromY = $y3 = round($op->getY3(), self::PRECISION);
                    $pathData[] = sprintf('%s %s %s %s %s %s c', $x1, $y1, $x2, $y2, $x3, $y3);
                    break;

                case $op instanceof Close:
                    $pathData[] = 'z';
                    break;

                default:
                    throw new RuntimeException('Unexpected draw operation: ' . get_class($op));
            }
        }

        return implode(' ', $pathData);
    }

    private function createGradientFill(Gradient $gradient, float $x, float $y, float $width, float $height) : void
    {
        $startColor = $gradient->getStartColor();
        $endColor = $gradient->getEndColor();

        if ($startColor instanceof Alpha) {
            $startColor = $startColor->getBaseColor();
        }

        $startColorType = get_class($startColor);

        if (! in_array($startColorType, [Rgb::class, Cmyk::class, Gray::class])) {
            $startColorType = Cmyk::class;
            $startColor = $startColor->toCmyk();
        }

        if (get_class($endColor) !== $startColorType) {
            switch ($startColorType) {
                case Cmyk::class:
                    $endColor = $endColor->toCmyk();
                    break;

                case Rgb::class:
                    $endColor = $endColor->toRgb();
                    break;

                case Gray::class:
                    $endColor = $endColor->toGray();
                    break;
            }
        }

        $this->eps .= "eoclip\n<<\n";

        if ($gradient->getType() === GradientType::RADIAL()) {
            $this->eps .= " /ShadingType 3\n";
        } else {
            $this->eps .= " /ShadingType 2\n";
        }

        $this->eps .= " /Extend [ true true ]\n"
            . " /AntiAlias true\n";

        switch ($startColorType) {
            case Cmyk::class:
                $this->eps .= " /ColorSpace /DeviceCMYK\n";
                break;

            case Rgb::class:
                $this->eps .= " /ColorSpace /DeviceRGB\n";
                break;

            case Gray::class:
                $this->eps .= " /ColorSpace /DeviceGray\n";
                break;
        }

        switch ($gradient->getType()) {
            case GradientType::HORIZONTAL():
                $this->eps .= sprintf(
                    " /Coords [ %s %s %s %s ]\n",
                    round($x, self::PRECISION),
                    round($y, self::PRECISION),
                    round($x + $width, self::PRECISION),
                    round($y, self::PRECISION)
                );
                break;

            case GradientType::VERTICAL():
                $this->eps .= sprintf(
                    " /Coords [ %s %s %s %s ]\n",
                    round($x, self::PRECISION),
                    round($y, self::PRECISION),
                    round($x, self::PRECISION),
                    round($y + $height, self::PRECISION)
                );
                break;

            case GradientType::DIAGONAL():
                $this->eps .= sprintf(
                    " /Coords [ %s %s %s %s ]\n",
                    round($x, self::PRECISION),
                    round($y, self::PRECISION),
                    round($x + $width, self::PRECISION),
                    round($y + $height, self::PRECISION)
                );
                break;

            case GradientType::INVERSE_DIAGONAL():
                $this->eps .= sprintf(
                    " /Coords [ %s %s %s %s ]\n",
                    round($x, self::PRECISION),
                    round($y + $height, self::PRECISION),
                    round($x + $width, self::PRECISION),
                    round($y, self::PRECISION)
                );
                break;

            case GradientType::RADIAL():
                $centerX = ($x + $width) / 2;
                $centerY = ($y + $height) / 2;

                $this->eps .= sprintf(
                    " /Coords [ %s %s 0 %s %s %s ]\n",
                    round($centerX, self::PRECISION),
                    round($centerY, self::PRECISION),
                    round($centerX, self::PRECISION),
                    round($centerY, self::PRECISION),
                    round(max($width, $height) / 2, self::PRECISION)
                );
                break;
        }

        $this->eps .= " /Function\n"
            . " <<\n"
            . "  /FunctionType 2\n"
            . "  /Domain [ 0 1 ]\n"
            . sprintf("  /C0 [ %s ]\n", $this->getColorString($startColor))
            . sprintf("  /C1 [ %s ]\n", $this->getColorString($endColor))
            . "  /N 1\n"
            . " >>\n>>\nshfill\nQ\n";
    }

    private function getColorSetString(ColorInterface $color) : string
    {
        if ($color instanceof Rgb) {
            return $this->getColorString($color) . ' rgb';
        }

        if ($color instanceof Cmyk) {
            return $this->getColorString($color) . ' cmyk';
        }

        if ($color instanceof Gray) {
            return $this->getColorString($color) . ' gray';
        }

        return $this->getColorSetString($color->toCmyk());
    }

    private function getColorString(ColorInterface $color) : string
    {
        if ($color instanceof Rgb) {
            return sprintf('%s %s %s', $color->getRed() / 255, $color->getGreen() / 255, $color->getBlue() / 255);
        }

        if ($color instanceof Cmyk) {
            return sprintf(
                '%s %s %s %s',
                $color->getCyan() / 100,
                $color->getMagenta() / 100,
                $color->getYellow() / 100,
                $color->getBlack() / 100
            );
        }

        if ($color instanceof Gray) {
            return sprintf('%s', $color->getGray() / 100);
        }

        return $this->getColorString($color->toCmyk());
    }
}
PK{c�\ף`�T1T1:bacon/bacon-qr-code/src/Renderer/Image/SvgImageBackEnd.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Renderer\Image;

use BaconQrCode\Exception\RuntimeException;
use BaconQrCode\Renderer\Color\Alpha;
use BaconQrCode\Renderer\Color\ColorInterface;
use BaconQrCode\Renderer\Path\Close;
use BaconQrCode\Renderer\Path\Curve;
use BaconQrCode\Renderer\Path\EllipticArc;
use BaconQrCode\Renderer\Path\Line;
use BaconQrCode\Renderer\Path\Move;
use BaconQrCode\Renderer\Path\Path;
use BaconQrCode\Renderer\RendererStyle\Gradient;
use BaconQrCode\Renderer\RendererStyle\GradientType;
use XMLWriter;

final class SvgImageBackEnd implements ImageBackEndInterface
{
    private const PRECISION = 3;

    /**
     * @var XMLWriter|null
     */
    private $xmlWriter;

    /**
     * @var int[]|null
     */
    private $stack;

    /**
     * @var int|null
     */
    private $currentStack;

    /**
     * @var int|null
     */
    private $gradientCount;

    public function __construct()
    {
        if (! class_exists(XMLWriter::class)) {
            throw new RuntimeException('You need to install the libxml extension to use this back end');
        }
    }

    public function new(int $size, ColorInterface $backgroundColor) : void
    {
        $this->xmlWriter = new XMLWriter();
        $this->xmlWriter->openMemory();

        $this->xmlWriter->startDocument('1.0', 'UTF-8');
        $this->xmlWriter->startElement('svg');
        $this->xmlWriter->writeAttribute('xmlns', 'http://www.w3.org/2000/svg');
        $this->xmlWriter->writeAttribute('version', '1.1');
        $this->xmlWriter->writeAttribute('width', (string) $size);
        $this->xmlWriter->writeAttribute('height', (string) $size);
        $this->xmlWriter->writeAttribute('viewBox', '0 0 '. $size . ' ' . $size);

        $this->gradientCount = 0;
        $this->currentStack = 0;
        $this->stack[0] = 0;

        $alpha = 1;

        if ($backgroundColor instanceof Alpha) {
            $alpha = $backgroundColor->getAlpha() / 100;
        }

        if (0 === $alpha) {
            return;
        }

        $this->xmlWriter->startElement('rect');
        $this->xmlWriter->writeAttribute('x', '0');
        $this->xmlWriter->writeAttribute('y', '0');
        $this->xmlWriter->writeAttribute('width', (string) $size);
        $this->xmlWriter->writeAttribute('height', (string) $size);
        $this->xmlWriter->writeAttribute('fill', $this->getColorString($backgroundColor));

        if ($alpha < 1) {
            $this->xmlWriter->writeAttribute('fill-opacity', (string) $alpha);
        }

        $this->xmlWriter->endElement();
    }

    public function scale(float $size) : void
    {
        if (null === $this->xmlWriter) {
            throw new RuntimeException('No image has been started');
        }

        $this->xmlWriter->startElement('g');
        $this->xmlWriter->writeAttribute(
            'transform',
            sprintf('scale(%s)', round($size, self::PRECISION))
        );
        ++$this->stack[$this->currentStack];
    }

    public function translate(float $x, float $y) : void
    {
        if (null === $this->xmlWriter) {
            throw new RuntimeException('No image has been started');
        }

        $this->xmlWriter->startElement('g');
        $this->xmlWriter->writeAttribute(
            'transform',
            sprintf('translate(%s,%s)', round($x, self::PRECISION), round($y, self::PRECISION))
        );
        ++$this->stack[$this->currentStack];
    }

    public function rotate(int $degrees) : void
    {
        if (null === $this->xmlWriter) {
            throw new RuntimeException('No image has been started');
        }

        $this->xmlWriter->startElement('g');
        $this->xmlWriter->writeAttribute('transform', sprintf('rotate(%d)', $degrees));
        ++$this->stack[$this->currentStack];
    }

    public function push() : void
    {
        if (null === $this->xmlWriter) {
            throw new RuntimeException('No image has been started');
        }

        $this->xmlWriter->startElement('g');
        $this->stack[] = 1;
        ++$this->currentStack;
    }

    public function pop() : void
    {
        if (null === $this->xmlWriter) {
            throw new RuntimeException('No image has been started');
        }

        for ($i = 0; $i < $this->stack[$this->currentStack]; ++$i) {
            $this->xmlWriter->endElement();
        }

        array_pop($this->stack);
        --$this->currentStack;
    }

    public function drawPathWithColor(Path $path, ColorInterface $color) : void
    {
        if (null === $this->xmlWriter) {
            throw new RuntimeException('No image has been started');
        }

        $alpha = 1;

        if ($color instanceof Alpha) {
            $alpha = $color->getAlpha() / 100;
        }

        $this->startPathElement($path);
        $this->xmlWriter->writeAttribute('fill', $this->getColorString($color));

        if ($alpha < 1) {
            $this->xmlWriter->writeAttribute('fill-opacity', (string) $alpha);
        }

        $this->xmlWriter->endElement();
    }

    public function drawPathWithGradient(
        Path $path,
        Gradient $gradient,
        float $x,
        float $y,
        float $width,
        float $height
    ) : void {
        if (null === $this->xmlWriter) {
            throw new RuntimeException('No image has been started');
        }

        $gradientId = $this->createGradientFill($gradient, $x, $y, $width, $height);
        $this->startPathElement($path);
        $this->xmlWriter->writeAttribute('fill', 'url(#' . $gradientId . ')');
        $this->xmlWriter->endElement();
    }

    public function done() : string
    {
        if (null === $this->xmlWriter) {
            throw new RuntimeException('No image has been started');
        }

        foreach ($this->stack as $openElements) {
            for ($i = $openElements; $i > 0; --$i) {
                $this->xmlWriter->endElement();
            }
        }

        $this->xmlWriter->endDocument();
        $blob = $this->xmlWriter->outputMemory(true);
        $this->xmlWriter = null;
        $this->stack = null;
        $this->currentStack = null;
        $this->gradientCount = null;

        return $blob;
    }

    private function startPathElement(Path $path) : void
    {
        $pathData = [];

        foreach ($path as $op) {
            switch (true) {
                case $op instanceof Move:
                    $pathData[] = sprintf(
                        'M%s %s',
                        round($op->getX(), self::PRECISION),
                        round($op->getY(), self::PRECISION)
                    );
                    break;

                case $op instanceof Line:
                    $pathData[] = sprintf(
                        'L%s %s',
                        round($op->getX(), self::PRECISION),
                        round($op->getY(), self::PRECISION)
                    );
                    break;

                case $op instanceof EllipticArc:
                    $pathData[] = sprintf(
                        'A%s %s %s %u %u %s %s',
                        round($op->getXRadius(), self::PRECISION),
                        round($op->getYRadius(), self::PRECISION),
                        round($op->getXAxisAngle(), self::PRECISION),
                        $op->isLargeArc(),
                        $op->isSweep(),
                        round($op->getX(), self::PRECISION),
                        round($op->getY(), self::PRECISION)
                    );
                    break;

                case $op instanceof Curve:
                    $pathData[] = sprintf(
                        'C%s %s %s %s %s %s',
                        round($op->getX1(), self::PRECISION),
                        round($op->getY1(), self::PRECISION),
                        round($op->getX2(), self::PRECISION),
                        round($op->getY2(), self::PRECISION),
                        round($op->getX3(), self::PRECISION),
                        round($op->getY3(), self::PRECISION)
                    );
                    break;

                case $op instanceof Close:
                    $pathData[] = 'Z';
                    break;

                default:
                    throw new RuntimeException('Unexpected draw operation: ' . get_class($op));
            }
        }

        $this->xmlWriter->startElement('path');
        $this->xmlWriter->writeAttribute('fill-rule', 'evenodd');
        $this->xmlWriter->writeAttribute('d', implode('', $pathData));
    }

    private function createGradientFill(Gradient $gradient, float $x, float $y, float $width, float $height) : string
    {
        $this->xmlWriter->startElement('defs');

        $startColor = $gradient->getStartColor();
        $endColor = $gradient->getEndColor();

        if ($gradient->getType() === GradientType::RADIAL()) {
            $this->xmlWriter->startElement('radialGradient');
        } else {
            $this->xmlWriter->startElement('linearGradient');
        }

        $this->xmlWriter->writeAttribute('gradientUnits', 'userSpaceOnUse');

        switch ($gradient->getType()) {
            case GradientType::HORIZONTAL():
                $this->xmlWriter->writeAttribute('x1', (string) round($x, self::PRECISION));
                $this->xmlWriter->writeAttribute('y1', (string) round($y, self::PRECISION));
                $this->xmlWriter->writeAttribute('x2', (string) round($x + $width, self::PRECISION));
                $this->xmlWriter->writeAttribute('y2', (string) round($y, self::PRECISION));
                break;

            case GradientType::VERTICAL():
                $this->xmlWriter->writeAttribute('x1', (string) round($x, self::PRECISION));
                $this->xmlWriter->writeAttribute('y1', (string) round($y, self::PRECISION));
                $this->xmlWriter->writeAttribute('x2', (string) round($x, self::PRECISION));
                $this->xmlWriter->writeAttribute('y2', (string) round($y + $height, self::PRECISION));
                break;

            case GradientType::DIAGONAL():
                $this->xmlWriter->writeAttribute('x1', (string) round($x, self::PRECISION));
                $this->xmlWriter->writeAttribute('y1', (string) round($y, self::PRECISION));
                $this->xmlWriter->writeAttribute('x2', (string) round($x + $width, self::PRECISION));
                $this->xmlWriter->writeAttribute('y2', (string) round($y + $height, self::PRECISION));
                break;

            case GradientType::INVERSE_DIAGONAL():
                $this->xmlWriter->writeAttribute('x1', (string) round($x, self::PRECISION));
                $this->xmlWriter->writeAttribute('y1', (string) round($y + $height, self::PRECISION));
                $this->xmlWriter->writeAttribute('x2', (string) round($x + $width, self::PRECISION));
                $this->xmlWriter->writeAttribute('y2', (string) round($y, self::PRECISION));
                break;

            case GradientType::RADIAL():
                $this->xmlWriter->writeAttribute('cx', (string) round(($x + $width) / 2, self::PRECISION));
                $this->xmlWriter->writeAttribute('cy', (string) round(($y + $height) / 2, self::PRECISION));
                $this->xmlWriter->writeAttribute('r', (string) round(max($width, $height) / 2, self::PRECISION));
                break;
        }

        $id = sprintf('g%d', ++$this->gradientCount);
        $this->xmlWriter->writeAttribute('id', $id);

        $this->xmlWriter->startElement('stop');
        $this->xmlWriter->writeAttribute('offset', '0%');
        $this->xmlWriter->writeAttribute('stop-color', $this->getColorString($startColor));

        if ($startColor instanceof Alpha) {
            $this->xmlWriter->writeAttribute('stop-opacity', (string) $startColor->getAlpha());
        }

        $this->xmlWriter->endElement();

        $this->xmlWriter->startElement('stop');
        $this->xmlWriter->writeAttribute('offset', '100%');
        $this->xmlWriter->writeAttribute('stop-color', $this->getColorString($endColor));

        if ($endColor instanceof Alpha) {
            $this->xmlWriter->writeAttribute('stop-opacity', (string) $endColor->getAlpha());
        }

        $this->xmlWriter->endElement();

        $this->xmlWriter->endElement();
        $this->xmlWriter->endElement();

        return $id;
    }

    private function getColorString(ColorInterface $color) : string
    {
        $color = $color->toRgb();

        return sprintf(
            '#%02x%02x%02x',
            $color->getRed(),
            $color->getGreen(),
            $color->getBlue()
        );
    }
}
PK{c�\�j]���?bacon/bacon-qr-code/src/Renderer/Image/TransformationMatrix.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Renderer\Image;

final class TransformationMatrix
{
    /**
     * @var float[]
     */
    private $values;

    public function __construct()
    {
        $this->values = [1, 0, 0, 1, 0, 0];
    }

    public function multiply(self $other) : self
    {
        $matrix = new self();
        $matrix->values[0] = $this->values[0] * $other->values[0] + $this->values[2] * $other->values[1];
        $matrix->values[1] = $this->values[1] * $other->values[0] + $this->values[3] * $other->values[1];
        $matrix->values[2] = $this->values[0] * $other->values[2] + $this->values[2] * $other->values[3];
        $matrix->values[3] = $this->values[1] * $other->values[2] + $this->values[3] * $other->values[3];
        $matrix->values[4] = $this->values[0] * $other->values[4] + $this->values[2] * $other->values[5]
            + $this->values[4];
        $matrix->values[5] = $this->values[1] * $other->values[4] + $this->values[3] * $other->values[5]
            + $this->values[5];

        return $matrix;
    }

    public static function scale(float $size) : self
    {
        $matrix = new self();
        $matrix->values = [$size, 0, 0, $size, 0, 0];
        return $matrix;
    }

    public static function translate(float $x, float $y) : self
    {
        $matrix = new self();
        $matrix->values = [1, 0, 0, 1, $x, $y];
        return $matrix;
    }

    public static function rotate(int $degrees) : self
    {
        $matrix = new self();
        $rad = deg2rad($degrees);
        $matrix->values = [cos($rad), sin($rad), -sin($rad), cos($rad), 0, 0];
        return $matrix;
    }


    /**
     * Applies this matrix onto a point and returns the resulting viewport point.
     *
     * @return float[]
     */
    public function apply(float $x, float $y) : array
    {
        return [
            $x * $this->values[0] + $y * $this->values[2] + $this->values[4],
            $x * $this->values[1] + $y * $this->values[3] + $this->values[5],
        ];
    }
}
PK{c�\g^q�	�	@bacon/bacon-qr-code/src/Renderer/Image/ImageBackEndInterface.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Renderer\Image;

use BaconQrCode\Exception\RuntimeException;
use BaconQrCode\Renderer\Color\ColorInterface;
use BaconQrCode\Renderer\Path\Path;
use BaconQrCode\Renderer\RendererStyle\Gradient;

/**
 * Interface for back ends able to to produce path based images.
 */
interface ImageBackEndInterface
{
    /**
     * Starts a new image.
     *
     * If a previous image was already started, previous data get erased.
     */
    public function new(int $size, ColorInterface $backgroundColor) : void;

    /**
     * Transforms all following drawing operation coordinates by scaling them by a given factor.
     *
     * @throws RuntimeException if no image was started yet.
     */
    public function scale(float $size) : void;

    /**
     * Transforms all following drawing operation coordinates by translating them by a given amount.
     *
     * @throws RuntimeException if no image was started yet.
     */
    public function translate(float $x, float $y) : void;

    /**
     * Transforms all following drawing operation coordinates by rotating them by a given amount.
     *
     * @throws RuntimeException if no image was started yet.
     */
    public function rotate(int $degrees) : void;

    /**
     * Pushes the current coordinate transformation onto a stack.
     *
     * @throws RuntimeException if no image was started yet.
     */
    public function push() : void;

    /**
     * Pops the last coordinate transformation from a stack.
     *
     * @throws RuntimeException if no image was started yet.
     */
    public function pop() : void;

    /**
     * Draws a path with a given color.
     *
     * @throws RuntimeException if no image was started yet.
     */
    public function drawPathWithColor(Path $path, ColorInterface $color) : void;

    /**
     * Draws a path with a given gradient which spans the box described by the position and size.
     *
     * @throws RuntimeException if no image was started yet.
     */
    public function drawPathWithGradient(
        Path $path,
        Gradient $gradient,
        float $x,
        float $y,
        float $width,
        float $height
    ) : void;

    /**
     * Ends the image drawing operation and returns the resulting blob.
     *
     * This should reset the state of the back end and thus this method should only be callable once per image.
     *
     * @throws RuntimeException if no image was started yet.
     */
    public function done() : string;
}
PK{c�\=�3�
	
	/bacon/bacon-qr-code/src/Renderer/Color/Cmyk.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Renderer\Color;

use BaconQrCode\Exception;

final class Cmyk implements ColorInterface
{
    /**
     * @var int
     */
    private $cyan;

    /**
     * @var int
     */
    private $magenta;

    /**
     * @var int
     */
    private $yellow;

    /**
     * @var int
     */
    private $black;

    /**
     * @param int $cyan the cyan amount, 0 to 100
     * @param int $magenta the magenta amount, 0 to 100
     * @param int $yellow the yellow amount, 0 to 100
     * @param int $black the black amount, 0 to 100
     */
    public function __construct(int $cyan, int $magenta, int $yellow, int $black)
    {
        if ($cyan < 0 || $cyan > 100) {
            throw new Exception\InvalidArgumentException('Cyan must be between 0 and 100');
        }

        if ($magenta < 0 || $magenta > 100) {
            throw new Exception\InvalidArgumentException('Magenta must be between 0 and 100');
        }

        if ($yellow < 0 || $yellow > 100) {
            throw new Exception\InvalidArgumentException('Yellow must be between 0 and 100');
        }

        if ($black < 0 || $black > 100) {
            throw new Exception\InvalidArgumentException('Black must be between 0 and 100');
        }

        $this->cyan = $cyan;
        $this->magenta = $magenta;
        $this->yellow = $yellow;
        $this->black = $black;
    }

    public function getCyan() : int
    {
        return $this->cyan;
    }

    public function getMagenta() : int
    {
        return $this->magenta;
    }

    public function getYellow() : int
    {
        return $this->yellow;
    }

    public function getBlack() : int
    {
        return $this->black;
    }

    public function toRgb() : Rgb
    {
        $k = $this->black / 100;
        $c = (-$k * $this->cyan + $k * 100 + $this->cyan) / 100;
        $m = (-$k * $this->magenta + $k * 100 + $this->magenta) / 100;
        $y = (-$k * $this->yellow + $k * 100 + $this->yellow) / 100;

        return new Rgb(
            (int) (-$c * 255 + 255),
            (int) (-$m * 255 + 255),
            (int) (-$y * 255 + 255)
        );
    }

    public function toCmyk() : Cmyk
    {
        return $this;
    }

    public function toGray() : Gray
    {
        return $this->toRgb()->toGray();
    }
}
PK{c�\���mm9bacon/bacon-qr-code/src/Renderer/Color/ColorInterface.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Renderer\Color;

interface ColorInterface
{
    /**
     * Converts the color to RGB.
     */
    public function toRgb() : Rgb;

    /**
     * Converts the color to CMYK.
     */
    public function toCmyk() : Cmyk;

    /**
     * Converts the color to gray.
     */
    public function toGray() : Gray;
}
PK{c�\B�m���.bacon/bacon-qr-code/src/Renderer/Color/Rgb.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Renderer\Color;

use BaconQrCode\Exception;

final class Rgb implements ColorInterface
{
    /**
     * @var int
     */
    private $red;

    /**
     * @var int
     */
    private $green;

    /**
     * @var int
     */
    private $blue;

    /**
     * @param int $red the red amount of the color, 0 to 255
     * @param int $green the green amount of the color, 0 to 255
     * @param int $blue the blue amount of the color, 0 to 255
     */
    public function __construct(int $red, int $green, int $blue)
    {
        if ($red < 0 || $red > 255) {
            throw new Exception\InvalidArgumentException('Red must be between 0 and 255');
        }

        if ($green < 0 || $green > 255) {
            throw new Exception\InvalidArgumentException('Green must be between 0 and 255');
        }

        if ($blue < 0 || $blue > 255) {
            throw new Exception\InvalidArgumentException('Blue must be between 0 and 255');
        }

        $this->red = $red;
        $this->green = $green;
        $this->blue = $blue;
    }

    public function getRed() : int
    {
        return $this->red;
    }

    public function getGreen() : int
    {
        return $this->green;
    }

    public function getBlue() : int
    {
        return $this->blue;
    }

    public function toRgb() : Rgb
    {
        return $this;
    }

    public function toCmyk() : Cmyk
    {
        $c = 1 - ($this->red / 255);
        $m = 1 - ($this->green / 255);
        $y = 1 - ($this->blue / 255);
        $k = min($c, $m, $y);

        return new Cmyk(
            (int) (100 * ($c - $k) / (1 - $k)),
            (int) (100 * ($m - $k) / (1 - $k)),
            (int) (100 * ($y - $k) / (1 - $k)),
            (int) (100 * $k)
        );
    }

    public function toGray() : Gray
    {
        return new Gray((int) (($this->red * 0.21 + $this->green * 0.71 + $this->blue * 0.07) / 2.55));
    }
}
PK{c�\/�W:>>0bacon/bacon-qr-code/src/Renderer/Color/Alpha.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Renderer\Color;

use BaconQrCode\Exception;

final class Alpha implements ColorInterface
{
    /**
     * @var int
     */
    private $alpha;

    /**
     * @var ColorInterface
     */
    private $baseColor;

    /**
     * @param int $alpha the alpha value, 0 to 100
     */
    public function __construct(int $alpha, ColorInterface $baseColor)
    {
        if ($alpha < 0 || $alpha > 100) {
            throw new Exception\InvalidArgumentException('Alpha must be between 0 and 100');
        }

        $this->alpha = $alpha;
        $this->baseColor = $baseColor;
    }

    public function getAlpha() : int
    {
        return $this->alpha;
    }

    public function getBaseColor() : ColorInterface
    {
        return $this->baseColor;
    }

    public function toRgb() : Rgb
    {
        return $this->baseColor->toRgb();
    }

    public function toCmyk() : Cmyk
    {
        return $this->baseColor->toCmyk();
    }

    public function toGray() : Gray
    {
        return $this->baseColor->toGray();
    }
}
PK|c�\va��/bacon/bacon-qr-code/src/Renderer/Color/Gray.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Renderer\Color;

use BaconQrCode\Exception;

final class Gray implements ColorInterface
{
    /**
     * @var int
     */
    private $gray;

    /**
     * @param int $gray the gray value between 0 (black) and 100 (white)
     */
    public function __construct(int $gray)
    {
        if ($gray < 0 || $gray > 100) {
            throw new Exception\InvalidArgumentException('Gray must be between 0 and 100');
        }

        $this->gray = (int) $gray;
    }

    public function getGray() : int
    {
        return $this->gray;
    }

    public function toRgb() : Rgb
    {
        return new Rgb((int) ($this->gray * 2.55), (int) ($this->gray * 2.55), (int) ($this->gray * 2.55));
    }

    public function toCmyk() : Cmyk
    {
        return new Cmyk(0, 0, 0, 100 - $this->gray);
    }

    public function toGray() : Gray
    {
        return $this;
    }
}
PK|c�\��^�SS5bacon/bacon-qr-code/src/Renderer/Path/EllipticArc.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Renderer\Path;

final class EllipticArc implements OperationInterface
{
    private const ZERO_TOLERANCE = 1e-05;

    /**
     * @var float
     */
    private $xRadius;

    /**
     * @var float
     */
    private $yRadius;

    /**
     * @var float
     */
    private $xAxisAngle;

    /**
     * @var bool
     */
    private $largeArc;

    /**
     * @var bool
     */
    private $sweep;

    /**
     * @var float
     */
    private $x;

    /**
     * @var float
     */
    private $y;

    public function __construct(
        float $xRadius,
        float $yRadius,
        float $xAxisAngle,
        bool $largeArc,
        bool $sweep,
        float $x,
        float $y
    ) {
        $this->xRadius = abs($xRadius);
        $this->yRadius = abs($yRadius);
        $this->xAxisAngle = $xAxisAngle % 360;
        $this->largeArc = $largeArc;
        $this->sweep = $sweep;
        $this->x = $x;
        $this->y = $y;
    }

    public function getXRadius() : float
    {
        return $this->xRadius;
    }

    public function getYRadius() : float
    {
        return $this->yRadius;
    }

    public function getXAxisAngle() : float
    {
        return $this->xAxisAngle;
    }

    public function isLargeArc() : bool
    {
        return $this->largeArc;
    }

    public function isSweep() : bool
    {
        return $this->sweep;
    }

    public function getX() : float
    {
        return $this->x;
    }

    public function getY() : float
    {
        return $this->y;
    }

    /**
     * @return self
     */
    public function translate(float $x, float $y) : OperationInterface
    {
        return new self(
            $this->xRadius,
            $this->yRadius,
            $this->xAxisAngle,
            $this->largeArc,
            $this->sweep,
            $this->x + $x,
            $this->y + $y
        );
    }

    /**
     * Converts the elliptic arc to multiple curves.
     *
     * Since not all image back ends support elliptic arcs, this method allows to convert the arc into multiple curves
     * resembling the same result.
     *
     * @see https://mortoray.com/2017/02/16/rendering-an-svg-elliptical-arc-as-bezier-curves/
     * @return array<Curve|Line>
     */
    public function toCurves(float $fromX, float $fromY) : array
    {
        if (sqrt(($fromX - $this->x) ** 2 + ($fromY - $this->y) ** 2) < self::ZERO_TOLERANCE) {
            return [];
        }

        if ($this->xRadius < self::ZERO_TOLERANCE || $this->yRadius < self::ZERO_TOLERANCE) {
            return [new Line($this->x, $this->y)];
        }

        return $this->createCurves($fromX, $fromY);
    }

    /**
     * @return Curve[]
     */
    private function createCurves(float $fromX, float $fromY) : array
    {
        $xAngle = deg2rad($this->xAxisAngle);
        list($centerX, $centerY, $radiusX, $radiusY, $startAngle, $deltaAngle) =
            $this->calculateCenterPointParameters($fromX, $fromY, $xAngle);

        $s = $startAngle;
        $e = $s + $deltaAngle;
        $sign = ($e < $s) ? -1 : 1;
        $remain = abs($e - $s);
        $p1 = self::point($centerX, $centerY, $radiusX, $radiusY, $xAngle, $s);
        $curves = [];

        while ($remain > self::ZERO_TOLERANCE) {
            $step = min($remain, pi() / 2);
            $signStep = $step * $sign;
            $p2 = self::point($centerX, $centerY, $radiusX, $radiusY, $xAngle, $s + $signStep);

            $alphaT = tan($signStep / 2);
            $alpha = sin($signStep) * (sqrt(4 + 3 * $alphaT ** 2) - 1) / 3;
            $d1 = self::derivative($radiusX, $radiusY, $xAngle, $s);
            $d2 = self::derivative($radiusX, $radiusY, $xAngle, $s + $signStep);

            $curves[] = new Curve(
                $p1[0] + $alpha * $d1[0],
                $p1[1] + $alpha * $d1[1],
                $p2[0] - $alpha * $d2[0],
                $p2[1] - $alpha * $d2[1],
                $p2[0],
                $p2[1]
            );

            $s += $signStep;
            $remain -= $step;
            $p1 = $p2;
        }

        return $curves;
    }

    /**
     * @return float[]
     */
    private function calculateCenterPointParameters(float $fromX, float $fromY, float $xAngle)
    {
        $rX = $this->xRadius;
        $rY = $this->yRadius;

        // F.6.5.1
        $dx2 = ($fromX - $this->x) / 2;
        $dy2 = ($fromY - $this->y) / 2;
        $x1p = cos($xAngle) * $dx2 + sin($xAngle) * $dy2;
        $y1p = -sin($xAngle) * $dx2 + cos($xAngle) * $dy2;

        // F.6.5.2
        $rxs = $rX ** 2;
        $rys = $rY ** 2;
        $x1ps = $x1p ** 2;
        $y1ps = $y1p ** 2;
        $cr = $x1ps / $rxs + $y1ps / $rys;

        if ($cr > 1) {
            $s = sqrt($cr);
            $rX *= $s;
            $rY *= $s;
            $rxs = $rX ** 2;
            $rys = $rY ** 2;
        }

        $dq = ($rxs * $y1ps + $rys * $x1ps);
        $pq = ($rxs * $rys - $dq) / $dq;
        $q = sqrt(max(0, $pq));

        if ($this->largeArc === $this->sweep) {
            $q = -$q;
        }

        $cxp = $q * $rX * $y1p / $rY;
        $cyp = -$q * $rY * $x1p / $rX;

        // F.6.5.3
        $cx = cos($xAngle) * $cxp - sin($xAngle) * $cyp + ($fromX + $this->x) / 2;
        $cy = sin($xAngle) * $cxp + cos($xAngle) * $cyp + ($fromY + $this->y) / 2;

        // F.6.5.5
        $theta = self::angle(1, 0, ($x1p - $cxp) / $rX, ($y1p - $cyp) / $rY);

        // F.6.5.6
        $delta = self::angle(($x1p - $cxp) / $rX, ($y1p - $cyp) / $rY, (-$x1p - $cxp) / $rX, (-$y1p - $cyp) / $rY);
        $delta = fmod($delta, pi() * 2);

        if (! $this->sweep) {
            $delta -= 2 * pi();
        }

        return [$cx, $cy, $rX, $rY, $theta, $delta];
    }

    private static function angle(float $ux, float $uy, float $vx, float $vy) : float
    {
        // F.6.5.4
        $dot = $ux * $vx + $uy * $vy;
        $length = sqrt($ux ** 2 + $uy ** 2) * sqrt($vx ** 2 + $vy ** 2);
        $angle = acos(min(1, max(-1, $dot / $length)));

        if (($ux * $vy - $uy * $vx) < 0) {
            return -$angle;
        }

        return $angle;
    }

    /**
     * @return float[]
     */
    private static function point(
        float $centerX,
        float $centerY,
        float $radiusX,
        float $radiusY,
        float $xAngle,
        float $angle
    ) : array {
        return [
            $centerX + $radiusX * cos($xAngle) * cos($angle) - $radiusY * sin($xAngle) * sin($angle),
            $centerY + $radiusX * sin($xAngle) * cos($angle) + $radiusY * cos($xAngle) * sin($angle),
        ];
    }

    /**
     * @return float[]
     */
    private static function derivative(float $radiusX, float $radiusY, float $xAngle, float $angle) : array
    {
        return [
            -$radiusX * cos($xAngle) * sin($angle) - $radiusY * sin($xAngle) * cos($angle),
            -$radiusX * sin($xAngle) * sin($angle) + $radiusY * cos($xAngle) * cos($angle),
        ];
    }
}
PK|c�\3�����.bacon/bacon-qr-code/src/Renderer/Path/Line.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Renderer\Path;

final class Line implements OperationInterface
{
    /**
     * @var float
     */
    private $x;

    /**
     * @var float
     */
    private $y;

    public function __construct(float $x, float $y)
    {
        $this->x = $x;
        $this->y = $y;
    }

    public function getX() : float
    {
        return $this->x;
    }

    public function getY() : float
    {
        return $this->y;
    }

    /**
     * @return self
     */
    public function translate(float $x, float $y) : OperationInterface
    {
        return new self($this->x + $x, $this->y + $y);
    }
}
PK|c�\GO�9��/bacon/bacon-qr-code/src/Renderer/Path/Curve.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Renderer\Path;

final class Curve implements OperationInterface
{
    /**
     * @var float
     */
    private $x1;

    /**
     * @var float
     */
    private $y1;

    /**
     * @var float
     */
    private $x2;

    /**
     * @var float
     */
    private $y2;

    /**
     * @var float
     */
    private $x3;

    /**
     * @var float
     */
    private $y3;

    public function __construct(float $x1, float $y1, float $x2, float $y2, float $x3, float $y3)
    {
        $this->x1 = $x1;
        $this->y1 = $y1;
        $this->x2 = $x2;
        $this->y2 = $y2;
        $this->x3 = $x3;
        $this->y3 = $y3;
    }

    public function getX1() : float
    {
        return $this->x1;
    }

    public function getY1() : float
    {
        return $this->y1;
    }

    public function getX2() : float
    {
        return $this->x2;
    }

    public function getY2() : float
    {
        return $this->y2;
    }

    public function getX3() : float
    {
        return $this->x3;
    }

    public function getY3() : float
    {
        return $this->y3;
    }

    /**
     * @return self
     */
    public function translate(float $x, float $y) : OperationInterface
    {
        return new self(
            $this->x1 + $x,
            $this->y1 + $y,
            $this->x2 + $x,
            $this->y2 + $y,
            $this->x3 + $x,
            $this->y3 + $y
        );
    }
}
PK|c�\��M��.bacon/bacon-qr-code/src/Renderer/Path/Move.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Renderer\Path;

final class Move implements OperationInterface
{
    /**
     * @var float
     */
    private $x;

    /**
     * @var float
     */
    private $y;

    public function __construct(float $x, float $y)
    {
        $this->x = $x;
        $this->y = $y;
    }

    public function getX() : float
    {
        return $this->x;
    }

    public function getY() : float
    {
        return $this->y;
    }

    /**
     * @return self
     */
    public function translate(float $x, float $y) : OperationInterface
    {
        return new self($this->x + $x, $this->y + $y);
    }
}
PK|c�\�۶k��/bacon/bacon-qr-code/src/Renderer/Path/Close.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Renderer\Path;

final class Close implements OperationInterface
{
    /**
     * @var self|null
     */
    private static $instance;

    private function __construct()
    {
    }

    public static function instance() : self
    {
        return self::$instance ?: self::$instance = new self();
    }

    /**
     * @return self
     */
    public function translate(float $x, float $y) : OperationInterface
    {
        return $this;
    }
}
PK|c�\�����<bacon/bacon-qr-code/src/Renderer/Path/OperationInterface.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Renderer\Path;

interface OperationInterface
{
    /**
     * Translates the operation's coordinates.
     */
    public function translate(float $x, float $y) : self;
}
PK|c�\��uǤ	�	.bacon/bacon-qr-code/src/Renderer/Path/Path.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Renderer\Path;

use IteratorAggregate;
use Traversable;

/**
 * Internal Representation of a vector path.
 */
final class Path implements IteratorAggregate
{
    /**
     * @var OperationInterface[]
     */
    private $operations = [];

    /**
     * Moves the drawing operation to a certain position.
     */
    public function move(float $x, float $y) : self
    {
        $path = clone $this;
        $path->operations[] = new Move($x, $y);
        return $path;
    }

    /**
     * Draws a line from the current position to another position.
     */
    public function line(float $x, float $y) : self
    {
        $path = clone $this;
        $path->operations[] = new Line($x, $y);
        return $path;
    }

    /**
     * Draws an elliptic arc from the current position to another position.
     */
    public function ellipticArc(
        float $xRadius,
        float $yRadius,
        float $xAxisRotation,
        bool $largeArc,
        bool $sweep,
        float $x,
        float $y
    ) : self {
        $path = clone $this;
        $path->operations[] = new EllipticArc($xRadius, $yRadius, $xAxisRotation, $largeArc, $sweep, $x, $y);
        return $path;
    }

    /**
     * Draws a curve from the current position to another position.
     */
    public function curve(float $x1, float $y1, float $x2, float $y2, float $x3, float $y3) : self
    {
        $path = clone $this;
        $path->operations[] = new Curve($x1, $y1, $x2, $y2, $x3, $y3);
        return $path;
    }

    /**
     * Closes a sub-path.
     */
    public function close() : self
    {
        $path = clone $this;
        $path->operations[] = Close::instance();
        return $path;
    }

    /**
     * Appends another path to this one.
     */
    public function append(self $other) : self
    {
        $path = clone $this;
        $path->operations = array_merge($this->operations, $other->operations);
        return $path;
    }

    public function translate(float $x, float $y) : self
    {
        $path = new self();

        foreach ($this->operations as $operation) {
            $path->operations[] = $operation->translate($x, $y);
        }

        return $path;
    }

    /**
     * @return OperationInterface[]|Traversable
     */
    public function getIterator() : Traversable
    {
        foreach ($this->operations as $operation) {
            yield $operation;
        }
    }
}
PK|c�\6�翎�6bacon/bacon-qr-code/src/Renderer/PlainTextRenderer.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Renderer;

use BaconQrCode\Encoder\QrCode;
use BaconQrCode\Exception\InvalidArgumentException;

final class PlainTextRenderer implements RendererInterface
{
    /**
     * UTF-8 full block (U+2588)
     */
    private const FULL_BLOCK = "\xe2\x96\x88";

    /**
     * UTF-8 upper half block (U+2580)
     */
    private const UPPER_HALF_BLOCK = "\xe2\x96\x80";

    /**
     * UTF-8 lower half block (U+2584)
     */
    private const LOWER_HALF_BLOCK = "\xe2\x96\x84";

    /**
     * UTF-8 no-break space (U+00A0)
     */
    private const EMPTY_BLOCK = "\xc2\xa0";

    /**
     * @var int
     */
    private $margin;

    public function __construct(int $margin = 2)
    {
        $this->margin = $margin;
    }

    /**
     * @throws InvalidArgumentException if matrix width doesn't match height
     */
    public function render(QrCode $qrCode) : string
    {
        $matrix = $qrCode->getMatrix();
        $matrixSize = $matrix->getWidth();

        if ($matrixSize !== $matrix->getHeight()) {
            throw new InvalidArgumentException('Matrix must have the same width and height');
        }

        $rows = $matrix->getArray()->toArray();

        if (0 !== $matrixSize % 2) {
            $rows[] = array_fill(0, $matrixSize, 0);
        }

        $horizontalMargin = str_repeat(self::EMPTY_BLOCK, $this->margin);
        $result = str_repeat("\n", (int) ceil($this->margin / 2));

        for ($i = 0; $i < $matrixSize; $i += 2) {
            $result .= $horizontalMargin;

            $upperRow = $rows[$i];
            $lowerRow = $rows[$i + 1];

            for ($j = 0; $j < $matrixSize; ++$j) {
                $upperBit = $upperRow[$j];
                $lowerBit = $lowerRow[$j];

                if ($upperBit) {
                    $result .= $lowerBit ? self::FULL_BLOCK : self::UPPER_HALF_BLOCK;
                } else {
                    $result .= $lowerBit ? self::LOWER_HALF_BLOCK : self::EMPTY_BLOCK;
                }
            }

            $result .= $horizontalMargin . "\n";
        }

        $result .= str_repeat("\n", (int) ceil($this->margin / 2));

        return $result;
    }
}
PK|c�\��=��8bacon/bacon-qr-code/src/Renderer/Eye/SimpleCircleEye.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Renderer\Eye;

use BaconQrCode\Renderer\Path\Path;

/**
 * Renders the inner eye as a circle.
 */
final class SimpleCircleEye implements EyeInterface
{
    /**
     * @var self|null
     */
    private static $instance;

    private function __construct()
    {
    }

    public static function instance() : self
    {
        return self::$instance ?: self::$instance = new self();
    }

    public function getExternalPath() : Path
    {
        return (new Path())
            ->move(-3.5, -3.5)
            ->line(3.5, -3.5)
            ->line(3.5, 3.5)
            ->line(-3.5, 3.5)
            ->close()
            ->move(-2.5, -2.5)
            ->line(-2.5, 2.5)
            ->line(2.5, 2.5)
            ->line(2.5, -2.5)
            ->close()
        ;
    }

    public function getInternalPath() : Path
    {
        return (new Path())
            ->move(1.5, 0)
            ->ellipticArc(1.5, 1.5, 0., false, true, 0., 1.5)
            ->ellipticArc(1.5, 1.5, 0., false, true, -1.5, 0.)
            ->ellipticArc(1.5, 1.5, 0., false, true, 0., -1.5)
            ->ellipticArc(1.5, 1.5, 0., false, true, 1.5, 0.)
            ->close()
        ;
    }
}
PK|c�\_��
112bacon/bacon-qr-code/src/Renderer/Eye/SquareEye.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Renderer\Eye;

use BaconQrCode\Renderer\Path\Path;

/**
 * Renders the eyes in their default square shape.
 */
final class SquareEye implements EyeInterface
{
    /**
     * @var self|null
     */
    private static $instance;

    private function __construct()
    {
    }

    public static function instance() : self
    {
        return self::$instance ?: self::$instance = new self();
    }

    public function getExternalPath() : Path
    {
        return (new Path())
            ->move(-3.5, -3.5)
            ->line(3.5, -3.5)
            ->line(3.5, 3.5)
            ->line(-3.5, 3.5)
            ->close()
            ->move(-2.5, -2.5)
            ->line(-2.5, 2.5)
            ->line(2.5, 2.5)
            ->line(2.5, -2.5)
            ->close()
        ;
    }

    public function getInternalPath() : Path
    {
        return (new Path())
            ->move(-1.5, -1.5)
            ->line(1.5, -1.5)
            ->line(1.5, 1.5)
            ->line(-1.5, 1.5)
            ->close()
        ;
    }
}
PK|c�\�#�TT5bacon/bacon-qr-code/src/Renderer/Eye/EyeInterface.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Renderer\Eye;

use BaconQrCode\Renderer\Path\Path;

/**
 * Interface for describing the look of an eye.
 */
interface EyeInterface
{
    /**
     * Returns the path of the external eye element.
     *
     * The path origin point (0, 0) must be anchored at the middle of the path.
     */
    public function getExternalPath() : Path;

    /**
     * Returns the path of the internal eye element.
     *
     * The path origin point (0, 0) must be anchored at the middle of the path.
     */
    public function getInternalPath() : Path;
}
PK|c�\C"랫�2bacon/bacon-qr-code/src/Renderer/Eye/ModuleEye.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Renderer\Eye;

use BaconQrCode\Encoder\ByteMatrix;
use BaconQrCode\Renderer\Module\ModuleInterface;
use BaconQrCode\Renderer\Path\Path;

/**
 * Renders an eye based on a module renderer.
 */
final class ModuleEye implements EyeInterface
{
    /**
     * @var ModuleInterface
     */
    private $module;

    public function __construct(ModuleInterface $module)
    {
        $this->module = $module;
    }

    public function getExternalPath() : Path
    {
        $matrix = new ByteMatrix(7, 7);

        for ($x = 0; $x < 7; ++$x) {
            $matrix->set($x, 0, 1);
            $matrix->set($x, 6, 1);
        }

        for ($y = 1; $y < 6; ++$y) {
            $matrix->set(0, $y, 1);
            $matrix->set(6, $y, 1);
        }

        return $this->module->createPath($matrix)->translate(-3.5, -3.5);
    }

    public function getInternalPath() : Path
    {
        $matrix = new ByteMatrix(3, 3);

        for ($x = 0; $x < 3; ++$x) {
            for ($y = 0; $y < 3; ++$y) {
                $matrix->set($x, $y, 1);
            }
        }

        return $this->module->createPath($matrix)->translate(-1.5, -1.5);
    }
}
PK|c�\7�$��5bacon/bacon-qr-code/src/Renderer/Eye/CompositeEye.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Renderer\Eye;

use BaconQrCode\Renderer\Path\Path;

/**
 * Combines the style of two different eyes.
 */
final class CompositeEye implements EyeInterface
{
    /**
     * @var EyeInterface
     */
    private $externalEye;

    /**
     * @var EyeInterface
     */
    private $internalEye;

    public function __construct(EyeInterface $externalEye, EyeInterface $internalEye)
    {
        $this->externalEye = $externalEye;
        $this->internalEye = $internalEye;
    }

    public function getExternalPath() : Path
    {
        return $this->externalEye->getExternalPath();
    }

    public function getInternalPath() : Path
    {
        return $this->internalEye->getInternalPath();
    }
}
PK|c�\7A<y��;bacon/bacon-qr-code/src/Renderer/Module/ModuleInterface.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Renderer\Module;

use BaconQrCode\Encoder\ByteMatrix;
use BaconQrCode\Renderer\Path\Path;

/**
 * Interface describing how modules should be rendered.
 *
 * A module always receives a byte matrix (with values either being 1 or 0). It returns a path, where the origin
 * coordinate (0, 0) equals the top left corner of the first matrix value.
 */
interface ModuleInterface
{
    public function createPath(ByteMatrix $matrix) : Path;
}
PK|c�\F�݀��=bacon/bacon-qr-code/src/Renderer/Module/EdgeIterator/Edge.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Renderer\Module\EdgeIterator;

final class Edge
{
    /**
     * @var bool
     */
    private $positive;

    /**
     * @var array<int[]>
     */
    private $points = [];

    /**
     * @var array<int[]>|null
     */
    private $simplifiedPoints;

    /**
     * @var int
     */
    private $minX = PHP_INT_MAX;

    /**
     * @var int
     */
    private $minY = PHP_INT_MAX;

    /**
     * @var int
     */
    private $maxX = -1;

    /**
     * @var int
     */
    private $maxY = -1;

    public function __construct(bool $positive)
    {
        $this->positive = $positive;
    }

    public function addPoint(int $x, int $y) : void
    {
        $this->points[] = [$x, $y];
        $this->minX = min($this->minX, $x);
        $this->minY = min($this->minY, $y);
        $this->maxX = max($this->maxX, $x);
        $this->maxY = max($this->maxY, $y);
    }

    public function isPositive() : bool
    {
        return $this->positive;
    }

    /**
     * @return array<int[]>
     */
    public function getPoints() : array
    {
        return $this->points;
    }

    public function getMaxX() : int
    {
        return $this->maxX;
    }

    public function getSimplifiedPoints() : array
    {
        if (null !== $this->simplifiedPoints) {
            return $this->simplifiedPoints;
        }

        $points = [];
        $length = count($this->points);

        for ($i = 0; $i < $length; ++$i) {
            $previousPoint = $this->points[(0 === $i ? $length : $i) - 1];
            $nextPoint = $this->points[($length - 1 === $i ? -1 : $i) + 1];
            $currentPoint = $this->points[$i];

            if (($previousPoint[0] === $currentPoint[0] && $currentPoint[0] === $nextPoint[0])
                || ($previousPoint[1] === $currentPoint[1] && $currentPoint[1] === $nextPoint[1])
            ) {
                continue;
            }

            $points[] = $currentPoint;
        }

        return $this->simplifiedPoints = $points;
    }
}
PK|c�\%���iiEbacon/bacon-qr-code/src/Renderer/Module/EdgeIterator/EdgeIterator.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Renderer\Module\EdgeIterator;

use BaconQrCode\Encoder\ByteMatrix;
use IteratorAggregate;
use Traversable;

/**
 * Edge iterator based on potrace.
 */
final class EdgeIterator implements IteratorAggregate
{
    /**
     * @var int[]
     */
    private $bytes = [];

    /**
     * @var int
     */
    private $size;

    /**
     * @var int
     */
    private $width;

    /**
     * @var int
     */
    private $height;

    public function __construct(ByteMatrix $matrix)
    {
        $this->bytes = iterator_to_array($matrix->getBytes());
        $this->size = count($this->bytes);
        $this->width = $matrix->getWidth();
        $this->height = $matrix->getHeight();
    }

    /**
     * @return Traversable<Edge>
     */
    public function getIterator() : Traversable
    {
        $originalBytes = $this->bytes;
        $point = $this->findNext(0, 0);

        while (null !== $point) {
            $edge = $this->findEdge($point[0], $point[1]);
            $this->xorEdge($edge);

            yield $edge;

            $point = $this->findNext($point[0], $point[1]);
        }

        $this->bytes = $originalBytes;
    }

    /**
     * @return int[]|null
     */
    private function findNext(int $x, int $y) : ?array
    {
        $i = $this->width * $y + $x;

        while ($i < $this->size && 1 !== $this->bytes[$i]) {
            ++$i;
        }

        if ($i < $this->size) {
            return $this->pointOf($i);
        }

        return null;
    }

    private function findEdge(int $x, int $y) : Edge
    {
        $edge = new Edge($this->isSet($x, $y));
        $startX = $x;
        $startY = $y;
        $dirX = 0;
        $dirY = 1;

        while (true) {
            $edge->addPoint($x, $y);
            $x += $dirX;
            $y += $dirY;

            if ($x === $startX && $y === $startY) {
                break;
            }

            $left = $this->isSet($x + ($dirX + $dirY - 1 ) / 2, $y + ($dirY - $dirX - 1) / 2);
            $right = $this->isSet($x + ($dirX - $dirY - 1) / 2, $y + ($dirY + $dirX - 1) / 2);

            if ($right && ! $left) {
                $tmp = $dirX;
                $dirX = -$dirY;
                $dirY = $tmp;
            } elseif ($right) {
                $tmp = $dirX;
                $dirX = -$dirY;
                $dirY = $tmp;
            } elseif (! $left) {
                $tmp = $dirX;
                $dirX = $dirY;
                $dirY = -$tmp;
            }
        }

        return $edge;
    }

    private function xorEdge(Edge $path) : void
    {
        $points = $path->getPoints();
        $y1 = $points[0][1];
        $length = count($points);
        $maxX = $path->getMaxX();

        for ($i = 1; $i < $length; ++$i) {
            $y = $points[$i][1];

            if ($y === $y1) {
                continue;
            }

            $x = $points[$i][0];
            $minY = min($y1, $y);

            for ($j = $x; $j < $maxX; ++$j) {
                $this->flip($j, $minY);
            }

            $y1 = $y;
        }
    }

    private function isSet(int $x, int $y) : bool
    {
        return (
            $x >= 0
            && $x < $this->width
            && $y >= 0
            && $y < $this->height
        ) && 1 === $this->bytes[$this->width * $y + $x];
    }

    /**
     * @return int[]
     */
    private function pointOf(int $i) : array
    {
        $y = intdiv($i, $this->width);
        return [$i - $y * $this->width, $y];
    }

    private function flip(int $x, int $y) : void
    {
        $this->bytes[$this->width * $y + $x] = (
            $this->isSet($x, $y) ? 0 : 1
        );
    }
}
PK|c�\�xT}&&8bacon/bacon-qr-code/src/Renderer/Module/SquareModule.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Renderer\Module;

use BaconQrCode\Encoder\ByteMatrix;
use BaconQrCode\Renderer\Module\EdgeIterator\EdgeIterator;
use BaconQrCode\Renderer\Path\Path;

/**
 * Groups modules together to a single path.
 */
final class SquareModule implements ModuleInterface
{
    /**
     * @var self|null
     */
    private static $instance;

    private function __construct()
    {
    }

    public static function instance() : self
    {
        return self::$instance ?: self::$instance = new self();
    }

    public function createPath(ByteMatrix $matrix) : Path
    {
        $path = new Path();

        foreach (new EdgeIterator($matrix) as $edge) {
            $points = $edge->getSimplifiedPoints();
            $length = count($points);
            $path = $path->move($points[0][0], $points[0][1]);

            for ($i = 1; $i < $length; ++$i) {
                $path = $path->line($points[$i][0], $points[$i][1]);
            }

            $path = $path->close();
        }

        return $path;
    }
}
PK|c�\:	��||;bacon/bacon-qr-code/src/Renderer/Module/RoundnessModule.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Renderer\Module;

use BaconQrCode\Encoder\ByteMatrix;
use BaconQrCode\Exception\InvalidArgumentException;
use BaconQrCode\Renderer\Module\EdgeIterator\EdgeIterator;
use BaconQrCode\Renderer\Path\Path;

/**
 * Rounds the corners of module groups.
 */
final class RoundnessModule implements ModuleInterface
{
    public const STRONG = 1;
    public const MEDIUM = .5;
    public const SOFT = .25;

    /**
     * @var float
     */
    private $intensity;

    public function __construct(float $intensity)
    {
        if ($intensity <= 0 || $intensity > 1) {
            throw new InvalidArgumentException('Intensity must between 0 (exclusive) and 1 (inclusive)');
        }

        $this->intensity = $intensity / 2;
    }

    public function createPath(ByteMatrix $matrix) : Path
    {
        $path = new Path();

        foreach (new EdgeIterator($matrix) as $edge) {
            $points = $edge->getSimplifiedPoints();
            $length = count($points);

            $currentPoint = $points[0];
            $nextPoint = $points[1];
            $horizontal = ($currentPoint[1] === $nextPoint[1]);

            if ($horizontal) {
                $right = $nextPoint[0] > $currentPoint[0];
                $path = $path->move(
                    $currentPoint[0] + ($right ? $this->intensity : -$this->intensity),
                    $currentPoint[1]
                );
            } else {
                $up = $nextPoint[0] < $currentPoint[0];
                $path = $path->move(
                    $currentPoint[0],
                    $currentPoint[1] + ($up ? -$this->intensity : $this->intensity)
                );
            }

            for ($i = 1; $i <= $length; ++$i) {
                if ($i === $length) {
                    $previousPoint = $points[$length - 1];
                    $currentPoint = $points[0];
                    $nextPoint = $points[1];
                } else {
                    $previousPoint = $points[(0 === $i ? $length : $i) - 1];
                    $currentPoint = $points[$i];
                    $nextPoint = $points[($length - 1 === $i ? -1 : $i) + 1];
                }

                $horizontal = ($previousPoint[1] === $currentPoint[1]);

                if ($horizontal) {
                    $right = $previousPoint[0] < $currentPoint[0];
                    $up = $nextPoint[1] < $currentPoint[1];
                    $sweep = ($up xor $right);

                    if ($this->intensity < 0.5
                        || ($right && $previousPoint[0] !== $currentPoint[0] - 1)
                        || (! $right && $previousPoint[0] - 1 !== $currentPoint[0])
                    ) {
                        $path = $path->line(
                            $currentPoint[0] + ($right ? -$this->intensity : $this->intensity),
                            $currentPoint[1]
                        );
                    }

                    $path = $path->ellipticArc(
                        $this->intensity,
                        $this->intensity,
                        0,
                        false,
                        $sweep,
                        $currentPoint[0],
                        $currentPoint[1] + ($up ? -$this->intensity : $this->intensity)
                    );
                } else {
                    $up = $previousPoint[1] > $currentPoint[1];
                    $right = $nextPoint[0] > $currentPoint[0];
                    $sweep = ! ($up xor $right);

                    if ($this->intensity < 0.5
                        || ($up && $previousPoint[1] !== $currentPoint[1] + 1)
                        || (! $up && $previousPoint[0] + 1 !== $currentPoint[0])
                    ) {
                        $path = $path->line(
                            $currentPoint[0],
                            $currentPoint[1] + ($up ? $this->intensity : -$this->intensity)
                        );
                    }

                    $path = $path->ellipticArc(
                        $this->intensity,
                        $this->intensity,
                        0,
                        false,
                        $sweep,
                        $currentPoint[0] + ($right ? $this->intensity : -$this->intensity),
                        $currentPoint[1]
                    );
                }
            }

            $path = $path->close();
        }

        return $path;
    }
}
PK|c�\W�.!6bacon/bacon-qr-code/src/Renderer/Module/DotsModule.phpnu�[���<?php
declare(strict_types = 1);

namespace BaconQrCode\Renderer\Module;

use BaconQrCode\Encoder\ByteMatrix;
use BaconQrCode\Exception\InvalidArgumentException;
use BaconQrCode\Renderer\Path\Path;

/**
 * Renders individual modules as dots.
 */
final class DotsModule implements ModuleInterface
{
    public const LARGE = 1;
    public const MEDIUM = .8;
    public const SMALL = .6;

    /**
     * @var float
     */
    private $size;

    public function __construct(float $size)
    {
        if ($size <= 0 || $size > 1) {
            throw new InvalidArgumentException('Size must between 0 (exclusive) and 1 (inclusive)');
        }

        $this->size = $size;
    }

    public function createPath(ByteMatrix $matrix) : Path
    {
        $width = $matrix->getWidth();
        $height = $matrix->getHeight();
        $path = new Path();
        $halfSize = $this->size / 2;
        $margin = (1 - $this->size) / 2;

        for ($y = 0; $y < $height; ++$y) {
            for ($x = 0; $x < $width; ++$x) {
                if (! $matrix->get($x, $y)) {
                    continue;
                }

                $pathX = $x + $margin;
                $pathY = $y + $margin;

                $path = $path
                    ->move($pathX + $this->size, $pathY + $halfSize)
                    ->ellipticArc($halfSize, $halfSize, 0, false, true, $pathX + $halfSize, $pathY + $this->size)
                    ->ellipticArc($halfSize, $halfSize, 0, false, true, $pathX, $pathY + $halfSize)
                    ->ellipticArc($halfSize, $halfSize, 0, false, true, $pathX + $halfSize, $pathY)
                    ->ellipticArc($halfSize, $halfSize, 0, false, true, $pathX + $this->size, $pathY + $halfSize)
                    ->close()
                ;
            }
        }

        return $path;
    }
}
PK|c�\^�W�UU!bacon/bacon-qr-code/composer.jsonnu�[���{
    "name": "bacon/bacon-qr-code",
    "description": "BaconQrCode is a QR code generator for PHP.",
    "license" : "BSD-2-Clause",
    "homepage": "https://github.com/Bacon/BaconQrCode",
    "require": {
        "php": "^7.1 || ^8.0",
        "ext-iconv": "*",
        "dasprid/enum": "^1.0.3"
    },
    "suggest": {
        "ext-imagick": "to generate QR code images"
    },
    "authors": [
        {
            "name": "Ben Scholzen 'DASPRiD'",
            "email": "mail@dasprids.de",
            "homepage": "https://dasprids.de/",
            "role": "Developer"
        }
    ],
    "autoload": {
        "psr-4": {
            "BaconQrCode\\": "src/"
        }
    },
    "require-dev": {
        "phpunit/phpunit": "^7 | ^8 | ^9",
        "spatie/phpunit-snapshot-assertions": "^4.2.9",
        "squizlabs/php_codesniffer": "^3.4",
        "phly/keep-a-changelog": "^2.1"
    },
    "config": {
        "allow-plugins": {
            "ocramius/package-versions": true
        }
    },
    "archive": {
        "exclude": [
            "/test",
            "/phpunit.xml.dist"
        ]
    }
}
PK|c�\˷I�bacon/bacon-qr-code/LICENSEnu�[���Copyright (c) 2017, Ben Scholzen 'DASPRiD'
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
PK|c�\.ڌӞ�bacon/bacon-qr-code/README.mdnu�[���# QR Code generator

[![PHP CI](https://github.com/Bacon/BaconQrCode/actions/workflows/ci.yml/badge.svg)](https://github.com/Bacon/BaconQrCode/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/Bacon/BaconQrCode/branch/master/graph/badge.svg?token=rD0HcAiEEx)](https://codecov.io/gh/Bacon/BaconQrCode)
[![Latest Stable Version](https://poser.pugx.org/bacon/bacon-qr-code/v/stable)](https://packagist.org/packages/bacon/bacon-qr-code)
[![Total Downloads](https://poser.pugx.org/bacon/bacon-qr-code/downloads)](https://packagist.org/packages/bacon/bacon-qr-code)
[![License](https://poser.pugx.org/bacon/bacon-qr-code/license)](https://packagist.org/packages/bacon/bacon-qr-code)


## Introduction
BaconQrCode is a port of QR code portion of the ZXing library. It currently
only features the encoder part, but could later receive the decoder part as
well.

As the Reed Solomon codec implementation of the ZXing library performs quite
slow in PHP, it was exchanged with the implementation by Phil Karn.


## Example usage
```php
use BaconQrCode\Renderer\ImageRenderer;
use BaconQrCode\Renderer\Image\ImagickImageBackEnd;
use BaconQrCode\Renderer\RendererStyle\RendererStyle;
use BaconQrCode\Writer;

$renderer = new ImageRenderer(
    new RendererStyle(400),
    new ImagickImageBackEnd()
);
$writer = new Writer($renderer);
$writer->writeFile('Hello World!', 'qrcode.png');
```

## Available image renderer back ends
BaconQrCode comes with multiple back ends for rendering images. Currently included are the following:

- `ImagickImageBackEnd`: renders raster images using the Imagick library
- `SvgImageBackEnd`: renders SVG files using XMLWriter
- `EpsImageBackEnd`: renders EPS files
PK|c�\ty�33kolab/net_ldap3/.arcconfignu�[���{
    "phabricator.uri": "https://git.kolab.org"
}
PK|c�\�wF���kolab/net_ldap3/composer.jsonnu�[���{
	"name": "kolab/net_ldap3",
	"description": "A successor of the PEAR:Net_LDAP2 module providing advanced functionality for accessing LDAP directories",
	"type": "library",
	"keywords": ["pear", "ldap", "vlv"],
	"homepage": "http://git.kolab.org/pear/Net_LDAP3/",
	"license": "GPL-3.0+",
	"authors": [
		{
			"name": "Jeroen van Meeuwen",
			"email": "vanmeeuwen@kolabsys.com",
			"role": "Lead"
		},
		{
			"name": "Aleksander Machniak",
			"email": "machniak@kolabsys.com",
			"role": "Developer"
		},
		{
			"name": "Thomas Bruederli",
			"email": "roundcube@gmail.com",
			"role": "Developer"
		}
	],
	"require": {
		"php": ">=5.3.3",
		"pear/net_ldap2": ">=2.0.12"
	},
	"autoload": {
		"classmap": ["lib/"]
	}
}
PK|c�\|�wfK�K�kolab/net_ldap3/LICENSEnu�[���                    GNU GENERAL PUBLIC LICENSE
                       Version 3, 29 June 2007

 Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

                            Preamble

  The GNU General Public License is a free, copyleft license for
software and other kinds of works.

  The licenses for most software and other practical works are designed
to take away your freedom to share and change the works.  By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.  We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors.  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.

  To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights.  Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received.  You must make sure that they, too, receive
or can get the source code.  And you must show them these terms so they
know their rights.

  Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.

  For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software.  For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.

  Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so.  This is fundamentally incompatible with the aim of
protecting users' freedom to change the software.  The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable.  Therefore, we
have designed this version of the GPL to prohibit the practice for those
products.  If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.

  Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary.  To prevent this, the GPL assures that
patents cannot be used to render the program non-free.

  The precise terms and conditions for copying, distribution and
modification follow.

                       TERMS AND CONDITIONS

  0. Definitions.

  "This License" refers to version 3 of the GNU General Public License.

  "Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.

  "The Program" refers to any copyrightable work licensed under this
License.  Each licensee is addressed as "you".  "Licensees" and
"recipients" may be individuals or organizations.

  To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy.  The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.

  A "covered work" means either the unmodified Program or a work based
on the Program.

  To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy.  Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.

  To "convey" a work means any kind of propagation that enables other
parties to make or receive copies.  Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.

  An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License.  If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.

  1. Source Code.

  The "source code" for a work means the preferred form of the work
for making modifications to it.  "Object code" means any non-source
form of a work.

  A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.

  The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form.  A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.

  The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities.  However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work.  For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.

  The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.

  The Corresponding Source for a work in source code form is that
same work.

  2. Basic Permissions.

  All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met.  This License explicitly affirms your unlimited
permission to run the unmodified Program.  The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work.  This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.

  You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force.  You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright.  Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.

  Conveying under any other circumstances is permitted solely under
the conditions stated below.  Sublicensing is not allowed; section 10
makes it unnecessary.

  3. Protecting Users' Legal Rights From Anti-Circumvention Law.

  No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.

  When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.

  4. Conveying Verbatim Copies.

  You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.

  You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.

  5. Conveying Modified Source Versions.

  You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:

    a) The work must carry prominent notices stating that you modified
    it, and giving a relevant date.

    b) The work must carry prominent notices stating that it is
    released under this License and any conditions added under section
    7.  This requirement modifies the requirement in section 4 to
    "keep intact all notices".

    c) You must license the entire work, as a whole, under this
    License to anyone who comes into possession of a copy.  This
    License will therefore apply, along with any applicable section 7
    additional terms, to the whole of the work, and all its parts,
    regardless of how they are packaged.  This License gives no
    permission to license the work in any other way, but it does not
    invalidate such permission if you have separately received it.

    d) If the work has interactive user interfaces, each must display
    Appropriate Legal Notices; however, if the Program has interactive
    interfaces that do not display Appropriate Legal Notices, your
    work need not make them do so.

  A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit.  Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.

  6. Conveying Non-Source Forms.

  You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:

    a) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by the
    Corresponding Source fixed on a durable physical medium
    customarily used for software interchange.

    b) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by a
    written offer, valid for at least three years and valid for as
    long as you offer spare parts or customer support for that product
    model, to give anyone who possesses the object code either (1) a
    copy of the Corresponding Source for all the software in the
    product that is covered by this License, on a durable physical
    medium customarily used for software interchange, for a price no
    more than your reasonable cost of physically performing this
    conveying of source, or (2) access to copy the
    Corresponding Source from a network server at no charge.

    c) Convey individual copies of the object code with a copy of the
    written offer to provide the Corresponding Source.  This
    alternative is allowed only occasionally and noncommercially, and
    only if you received the object code with such an offer, in accord
    with subsection 6b.

    d) Convey the object code by offering access from a designated
    place (gratis or for a charge), and offer equivalent access to the
    Corresponding Source in the same way through the same place at no
    further charge.  You need not require recipients to copy the
    Corresponding Source along with the object code.  If the place to
    copy the object code is a network server, the Corresponding Source
    may be on a different server (operated by you or a third party)
    that supports equivalent copying facilities, provided you maintain
    clear directions next to the object code saying where to find the
    Corresponding Source.  Regardless of what server hosts the
    Corresponding Source, you remain obligated to ensure that it is
    available for as long as needed to satisfy these requirements.

    e) Convey the object code using peer-to-peer transmission, provided
    you inform other peers where the object code and Corresponding
    Source of the work are being offered to the general public at no
    charge under subsection 6d.

  A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.

  A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling.  In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage.  For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product.  A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.

  "Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source.  The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.

  If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information.  But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).

  The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed.  Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.

  Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.

  7. Additional Terms.

  "Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law.  If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.

  When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it.  (Additional permissions may be written to require their own
removal in certain cases when you modify the work.)  You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.

  Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:

    a) Disclaiming warranty or limiting liability differently from the
    terms of sections 15 and 16 of this License; or

    b) Requiring preservation of specified reasonable legal notices or
    author attributions in that material or in the Appropriate Legal
    Notices displayed by works containing it; or

    c) Prohibiting misrepresentation of the origin of that material, or
    requiring that modified versions of such material be marked in
    reasonable ways as different from the original version; or

    d) Limiting the use for publicity purposes of names of licensors or
    authors of the material; or

    e) Declining to grant rights under trademark law for use of some
    trade names, trademarks, or service marks; or

    f) Requiring indemnification of licensors and authors of that
    material by anyone who conveys the material (or modified versions of
    it) with contractual assumptions of liability to the recipient, for
    any liability that these contractual assumptions directly impose on
    those licensors and authors.

  All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10.  If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term.  If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.

  If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.

  Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.

  8. Termination.

  You may not propagate or modify a covered work except as expressly
provided under this License.  Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).

  However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.

  Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.

  Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License.  If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.

  9. Acceptance Not Required for Having Copies.

  You are not required to accept this License in order to receive or
run a copy of the Program.  Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance.  However,
nothing other than this License grants you permission to propagate or
modify any covered work.  These actions infringe copyright if you do
not accept this License.  Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.

  10. Automatic Licensing of Downstream Recipients.

  Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License.  You are not responsible
for enforcing compliance by third parties with this License.

  An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations.  If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.

  You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License.  For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.

  11. Patents.

  A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based.  The
work thus licensed is called the contributor's "contributor version".

  A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version.  For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.

  Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.

  In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement).  To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.

  If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients.  "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.

  If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.

  A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License.  You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.

  Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.

  12. No Surrender of Others' Freedom.

  If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all.  For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.

  13. Use with the GNU Affero General Public License.

  Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work.  The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.

  14. Revised Versions of this License.

  The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

  Each version is given a distinguishing version number.  If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation.  If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.

  If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.

  Later license versions may give you additional or different
permissions.  However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.

  15. Disclaimer of Warranty.

  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

  16. Limitation of Liability.

  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.

  17. Interpretation of Sections 15 and 16.

  If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.

                     END OF TERMS AND CONDITIONS

            How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

    <one line to give the program's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

Also add information on how to contact you by electronic and paper mail.

  If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:

    <program>  Copyright (C) <year>  <name of author>
    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
    This is free software, and you are welcome to redistribute it
    under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".

  You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.

  The GNU General Public License does not permit incorporating your program
into proprietary programs.  If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library.  If this is what you want to do, use the GNU Lesser General
Public License instead of this License.  But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
PK|c�\8*���!kolab/net_ldap3/lib/Net/LDAP3.phpnu�[���<?php

/*
 +-----------------------------------------------------------------------+
 | Net/LDAP3.php                                                         |
 |                                                                       |
 | Based on code created by the Roundcube Webmail team.                  |
 |                                                                       |
 | Copyright (C) 2006-2014, The Roundcube Dev Team                       |
 | Copyright (C) 2012-2014, Kolab Systems AG                             |
 |                                                                       |
 | This program is free software: you can redistribute it and/or modify  |
 | it under the terms of the GNU General Public License as published by  |
 | the Free Software Foundation, either version 3 of the License, or     |
 | (at your option) any later version.                                   |
 |                                                                       |
 | This program is distributed in the hope that it will be useful,       |
 | but WITHOUT ANY WARRANTY; without even the implied warranty of        |
 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the          |
 | GNU General Public License for more details.                          |
 |                                                                       |
 | You should have received a copy of the GNU General Public License     |
 | along with this program.  If not, see <http://www.gnu.org/licenses/>. |
 |                                                                       |
 | PURPOSE:                                                              |
 |   Provide advanced functionality for accessing LDAP directories       |
 |                                                                       |
 +-----------------------------------------------------------------------+
 | Authors: Thomas Bruederli <roundcube@gmail.com>                       |
 |          Aleksander Machniak <machniak@kolabsys.com>                  |
 |          Jeroen van Meeuwen <vanmeeuwen@kolabsys.com>                 |
 +-----------------------------------------------------------------------+
*/

require_once __DIR__ . '/LDAP3/Result.php';

/**
 * Model class to access a LDAP directories
 *
 * @package Net_LDAP3
 */
class Net_LDAP3
{
    const CONTROL_EFFECTIVE_RIGHTS = '1.3.6.1.4.1.42.2.27.9.5.2';
    const CONTROL_SORT_REQUEST     = '1.2.840.113556.1.4.473';
    const CONTROL_VLV_REQUEST      = '2.16.840.1.113730.3.4.9';
    const CONTROL_VLV_RESPONSE     = '2.16.840.1.113730.3.4.10';

    public $conn;
    public $vlv_active = false;

    private $attribute_level_rights_map = [
        'r' => 'read',
        's' => 'search',
        'w' => 'write',
        'o' => 'delete',
        'c' => 'compare',
        'W' => 'write',
        'O' => 'delete'
    ];

    private $entry_level_rights_map = [
        'a' => 'add',
        'd' => 'delete',
        'n' => 'modrdn',
        'v' => 'read'
    ];

    /*
     * Manipulate configuration through the config_set and config_get methods.
     * Available options:
     *       'debug'           => false,
     *       'hosts'           => array(),
     *       'port'            => 389,
     *       'use_tls'         => false,
     *       'ldap_version'    => 3,        // using LDAPv3
     *       'auth_method'     => '',       // SASL authentication method (for proxy auth), e.g. DIGEST-MD5
     *       'gssapi_cn'       => null      // Kerberos cache name (KRB5CCNAME) for SASL GSSAPI authentication
     *       'numsub_filter'   => '(objectClass=organizationalUnit)', // with VLV, we also use numSubOrdinates to query the total number of records. Set this filter to get all numSubOrdinates attributes for counting
     *       'referrals'       => false,    // Sets the LDAP_OPT_REFERRALS option. Mostly used in multi-domain Active Directory setups
     *       'network_timeout' => 10,       // The timeout (in seconds) for connect + bind arrempts. This is only supported in PHP >= 5.3.0 with OpenLDAP 2.x
     *       'sizelimit'       => 0,        // Enables you to limit the count of entries fetched. Setting this to 0 means no limit.
     *       'timelimit'       => 0,        // Sets the number of seconds how long is spend on the search. Setting this to 0 means no limit.
     *       'vlv'             => false,    // force VLV off
     *       'config_root_dn'  => 'cn=config',  // Root DN to read config (e.g. vlv indexes) from
     *       'service_bind_dn' => 'uid=kolab-service,ou=Special Users,dc=example,dc=org',
     *       'service_bind_pw' => 'Welcome2KolabSystems',
     *       'root_dn'         => 'dc=example,dc=org',
     */
    protected $config = [
        'sizelimit'      => 0,
        'timelimit'      => 0,
        'config_root_dn' => 'cn=config',
        'vlv'            => null,
    ];

    protected $debug_level = false;
    protected $list_page   = 1;
    protected $page_size   = 10;
    protected $icache      = [];
    protected $cache;

    // Use public method config_set('log_hook', $callback) to have $callback be
    // call_user_func'ed instead of the local log functions.
    protected $_log_hook;

    // Use public method config_set('config_get_hook', $callback) to have
    // $callback be call_user_func'ed instead of the local config_get function.
    protected $_config_get_hook;

    // Use public method config_set('config_set_hook', $callback) to have
    // $callback be call_user_func'ed instead of the local config_set function.
    protected $_config_set_hook;

    // Not Yet Implemented
    // Intended to allow hooking in for the purpose of caching.
    protected $_result_hook;

    // Runtime. These are not the variables you're looking for.
    protected $_current_bind_dn;
    protected $_current_bind_pw;
    protected $_current_host;
    protected $_metadata;
    protected $_vlv_indexes_and_searches;


    /**
     * Constructor
     *
     * @param array $config Configuration parameters. After initialization use
     *                      the config_set() method.
     */
    public function __construct($config = [])
    {
        if (!empty($config) && is_array($config)) {
            foreach ($config as $key => $value) {
                $setter = 'config_set_' . $key;
                if (method_exists($this, $setter)) {
                    $this->$setter($value);
                }
                else if (isset($this->$key)) {
                    $this->$key = $value;
                }
                else {
                    $this->config[$key] = $value;
                }
            }
        }
    }

    /**
     *  Add multiple entries to the directory information tree in one go.
     */
    public function add_entries($entries, $attributes = [])
    {
        // If $entries is an associative array, it's keys are DNs and its
        // values are the attributes for that DN.
        //
        // If $entries is a non-associative array, the attributes are expected
        // to be positional in $attributes.

        $result_set = [];

        if (array_keys($entries) == range(0, count($entries) - 1)) {
            // $entries is sequential
            if (count($entries) !== count($attributes)) {
                $this->_error("LDAP: Wrong entry/attribute count in " . __FUNCTION__);
                return false;
            }

            for ($i = 0; $i < count($entries); $i++) {
                $result_set[$i] = $this->add_entry($entries[$i], $attributes[$i]);
            }
        }
        else {
            // $entries is associative
            foreach ($entries as $entry_dn => $entry_attributes) {
                if (array_keys($attributes) !== range(0, count($attributes)-1)) {
                    // $attributes is associative as well, let's merge these
                    //
                    // $entry_attributes takes precedence, so is in the second
                    // position in array_merge()
                    $entry_attributes = array_merge($attributes, $entry_attributes);
                }

                $result_set[$entry_dn] = $this->add_entry($entry_dn, $entry_attributes);
            }
        }

        return $result_set;
    }

    /**
     * Add an entry to the directory information tree.
     */
    public function add_entry($entry_dn, $attributes)
    {
        // TODO:
        // - Get entry rdn attribute value from entry_dn and see if it exists in
        //   attributes -> issue warning if so (but not block the operation).
        $this->_debug("Entry DN", $entry_dn);
        $this->_debug("Attributes", $attributes);

        foreach ($attributes as $attr_name => $attr_value) {
            if (empty($attr_value)) {
                unset($attributes[$attr_name]);
            } else if (is_array($attr_value)) {
                $attributes[$attr_name] = array_values($attr_value);
            }
        }

        $this->_debug("C: Add $entry_dn: " . json_encode($attributes));

        if (!ldap_add($this->conn, $entry_dn, $attributes)) {
            $this->_debug("S: " . ldap_error($this->conn));
            $this->_warning("LDAP: Adding entry $entry_dn failed. " . ldap_error($this->conn));

            return false;
        }

        $this->_debug("S: OK");

        return true;
    }

    /**
     * Add replication agreements and initialize the consumer(s) for
     * $domain_root_dn.
     *
     * Searches the configured replicas for any of the current domain/config
     * databases, and uses this information to configure the additional
     * replication for the (new) domain database (at $domain_root_dn).
     *
     * Very specific to Netscape-based directory servers, and currently also
     * very specific to multi-master replication.
     */
    public function add_replication_agreements($domain_root_dn)
    {
        $replica_hosts = $this->list_replicas();

        if (empty($replica_hosts)) {
            return;
        }

        $result = $this->search($this->config_get('config_root_dn'), "(&(objectclass=nsDS5Replica)(nsDS5ReplicaType=3))", "sub");

        if (!$result) {
            $this->_debug("No replication configuration found.");
            return;
        }

        // Preserve the number of replicated databases we have, because the replication ID
        // can be calculated from the number of databases replicated, and the number of
        // servers.
        $num_replica_dbs = $result->count();
        $replicas        = $result->entries(true);
        $max_replica_agreements = 0;

        foreach ($replicas as $replica_dn => $replica_attrs) {
            $result = $this->search($replica_dn, "(objectclass=nsDS5ReplicationAgreement)", "sub");
            if ($result) {
                if ($max_replica_agreements < $result->count()) {
                    $max_replica_agreements    = $result->count();
                    $max_replica_agreements_dn = $replica_dn;
                }
            }
        }

        $max_repl_id = $num_replica_dbs * count($replica_hosts);

        $this->_debug("The current maximum replication ID is $max_repl_id");
        $this->_debug("The current maximum number of replication agreements for any database is $max_replica_agreements (for $max_replica_agreements_dn)");
        $this->_debug("With " . count($replica_hosts) . " replicas, the next is " . ($max_repl_id + 1) . " and the last one is " . ($max_repl_id + count($replica_hosts)));

        // Then add the replication agreements
        foreach ($replica_hosts as $num => $replica_host) {
            $ldap = new Net_LDAP3($this->config);
            $ldap->config_set('hosts', [$replica_host]);
            $ldap->connect();
            $ldap->bind($this->_current_bind_dn, $this->_current_bind_pw);

            $attrs = [
                'nsDS5ReplicaBindDN',
                'nsDS5ReplicaType',
                'nsds5ReplicaPurgeDelay',
                'nsDS5Flags'
            ];

            $replica_attrs = $ldap->get_entry_attributes($replica_dn, $attrs);
            $replica_attrs = array_merge((array) $replica_attrs, [
                'cn' => 'replica',
                'objectclass' => [
                    'top',
                    'nsds5replica',
                    'extensibleobject',
                ],
                'nsDS5ReplicaId'   => $max_repl_id + $num + 1,
                'nsDS5ReplicaRoot' => $domain_root_dn,
            ]);

            $new_replica_dn = 'cn=replica,cn="' . $domain_root_dn . '",cn=mapping tree,cn=config';

            $this->_debug("Adding $new_replica_dn to $replica_host with attributes: " . var_export($replica_attrs, true));

            $result = $ldap->add_entry($new_replica_dn, $replica_attrs);

            if (!$result) {
                $this->_error("LDAP: Could not add replication configuration to database for $domain_root_dn on $replica_host");
                continue;
            }

            $result = $ldap->search($replica_dn, "(objectclass=nsDS5ReplicationAgreement)", "sub");

            if (!$result) {
                $this->_error("LDAP: Host $replica_host does not have any replication agreements");
                continue;
            }

            $entries = $result->entries(true);
            $replica_agreement_tpl_dn = key($entries);

            $this->_debug("Using " . var_export($replica_agreement_tpl_dn, true) . " as the template for new replication agreements");

            foreach ($replica_hosts as $replicate_to_host) {
                // Skip the current server
                if ($replicate_to_host == $replica_host) {
                    continue;
                }

                $this->_debug("Adding a replication agreement for $domain_root_dn to $replicate_to_host on " . $replica_host);

                $attrs = [
                    'objectclass',
                    'nsDS5ReplicaBindDN',
                    'nsDS5ReplicaCredentials',
                    'nsDS5ReplicaTransportInfo',
                    'nsDS5ReplicaBindMethod'
                ];

                list($host, $port) = strpos($replicate_to_host, ':') !== false ? explode(':', $replicate_to_host) : [$replicate_to_host, 389];

                $replica_agreement_attrs = $ldap->get_entry_attributes($replica_agreement_tpl_dn, $attrs);
                $replica_agreement_attrs['cn'] = array_shift(explode('.', $replicate_to_host)) . str_replace(['dc=', ','], ['_', ''], $domain_root_dn);
                $replica_agreement_attrs['nsDS5ReplicaRoot'] = $domain_root_dn;
                $replica_agreement_attrs['nsDS5ReplicaHost'] = $host;
                $replica_agreement_attrs['nsDS5ReplicaPort'] = $port;
                $replica_agreement_dn = "cn=" . $replica_agreement_attrs['cn'] . "," . $new_replica_dn;

                $this->_debug("Adding $replica_agreement_dn to $replica_host with attributes: " . var_export($replica_agreement_attrs, true));

                $result = $ldap->add_entry($replica_agreement_dn, $replica_agreement_attrs);

                if (!$result) {
                    $this->_error("LDAP: Failed adding $replica_agreement_dn");
                }
            }
        }

        $server_id = implode('', array_diff($replica_hosts, $this->_server_id_not));

        $this->_debug("About to trigger consumer initialization for replicas on current 'parent': $server_id");

        $result = $this->search($this->config_get('config_root_dn'), "(&(objectclass=nsDS5ReplicationAgreement)(nsds5replicaroot=$domain_root_dn))", "sub");

        if ($result) {
            foreach ($result->entries(true) as $agreement_dn => $agreement_attrs) {
                $this->modify_entry_attributes(
                    $agreement_dn,
                    [
                        'replace' => [
                            'nsds5BeginReplicaRefresh' => 'start',
                        ],
                    ]
                );
            }
        }
    }

    public function attribute_details($attributes = [])
    {
        $schema = $this->init_schema();

        if (!$schema) {
            return [];
        }

        $attribs = $schema->getAll('attributes');

        $attributes_details = [];

        foreach ($attributes as $attribute) {
            if (array_key_exists($attribute, $attribs)) {
                $attrib_details = $attribs[$attribute];

                if (!empty($attrib_details['sup'])) {
                    foreach ($attrib_details['sup'] as $super_attrib) {
                        $_attrib_details = $attribs[$super_attrib];
                        if (is_array($_attrib_details)) {
                            $attrib_details = array_merge($_attrib_details, $attrib_details);
                        }
                    }
                }
            }
            else if (array_key_exists(strtolower($attribute), $attribs)) {
                $attrib_details = $attribs[strtolower($attribute)];

                if (!empty($attrib_details['sup'])) {
                    foreach ($attrib_details['sup'] as $super_attrib) {
                        $_attrib_details = $attribs[$super_attrib];
                        if (is_array($_attrib_details)) {
                            $attrib_details = array_merge($_attrib_details, $attrib_details);
                        }
                    }
                }
            }
            else {
                $this->_warning("LDAP: No schema details exist for attribute $attribute (which is strange)");
            }

            // The relevant parts only, please
            $attributes_details[$attribute] = [
                'type'        => !empty($attrib_details['single-value']) ? 'text' : 'list',
                'description' => $attrib_details['desc'],
                'syntax'      => $attrib_details['syntax'],
                'max-length'  => $attrib_details['max-length'] ?: false,
            ];
        }

        return $attributes_details;
    }

    public function attributes_allowed($objectclasses = [])
    {
        $this->_debug("Listing allowed_attributes for objectclasses", $objectclasses);

        if (!is_array($objectclasses) || empty($objectclasses)) {
            return false;
        }

        $schema = $this->init_schema();
        if (!$schema) {
            return false;
        }

        $may          = [];
        $must         = [];
        $superclasses = [];

        foreach ($objectclasses as $objectclass) {
            $superclass = $schema->superclass($objectclass);
            if (!empty($superclass)) {
                $superclasses = array_merge($superclass, $superclasses);
            }

            $_may  = $schema->may($objectclass);
            $_must = $schema->must($objectclass);

            if (is_array($_may)) {
                $may = array_merge($may, $_may);
            }
            if (is_array($_must)) {
                $must = array_merge($must, $_must);
            }
        }

        $may          = array_unique($may);
        $must         = array_unique($must);
        $superclasses = array_unique($superclasses);

        return ['may' => $may, 'must' => $must, 'super' => $superclasses];
    }

    public function classes_allowed()
    {
        $schema = $this->init_schema();
        if (!$schema) {
            return false;
        }

        $list    = $schema->getAll('objectclasses');
        $classes = [];

        foreach ($list as $class) {
            $classes[] = $class['name'];
        }

        return $classes;
    }

    /**
     * Bind connection with DN and password
     *
     * @param string $dn   Bind DN
     * @param string $pass Bind password
     *
     * @return boolean True on success, False on error
     */
    public function bind($bind_dn, $bind_pw)
    {
        if (!$this->conn) {
            return false;
        }

        if ($bind_dn == $this->_current_bind_dn) {
            return true;
        }

        $this->_debug("C: Bind [dn: $bind_dn]");

        if (@ldap_bind($this->conn, $bind_dn, $bind_pw)) {
            $this->_debug("S: OK");
            $this->_current_bind_dn = $bind_dn;
            $this->_current_bind_pw = $bind_pw;

            return true;
        }

        $this->_debug("S: ".ldap_error($this->conn));
        $this->_error("LDAP: Bind failed for dn=$bind_dn. ".ldap_error($this->conn));

        return false;
    }

    /**
     * Close connection to LDAP server
     */
    public function close()
    {
        if ($this->conn) {
            $this->_debug("C: Close");
            ldap_unbind($this->conn);

            $this->_current_bind_dn = null;
            $this->_current_bind_pw = null;
            $this->conn             = null;
        }
    }

    /**
     * Get the value of a configuration item.
     *
     * @param string $key     Configuration key
     * @param mixed  $default Default value to return
     */
    public function config_get($key, $default = null)
    {
        if (!empty($this->_config_get_hook)) {
            return call_user_func_array($this->_config_get_hook, [$key, $value]);
        }

        if (method_exists($this, "config_get_{$key}")) {
            return call_user_func([$this, "config_get_{$key}"], $value);
        }

        if (!isset($this->config[$key])) {
            return $default;
        }

        return $this->config[$key];
    }

    /**
     * Set a configuration item to value.
     *
     * @param string $key   Configuration key
     * @param mixed  $value Configuration value
     */
    public function config_set($key, $value = null)
    {
        if (is_array($key)) {
            foreach ($key as $k => $v) {
                $this->config_set($k, $v);
            }
            return;
        }

        if (!empty($this->_config_set_hook)) {
            call_user_func($this->_config_set_hook, [$key, $value]);
        }
        else if (method_exists($this, "config_set_{$key}")) {
            call_user_func_array([$this, "config_set_{$key}"], [$value]);
        }
        else if (property_exists($this, $key)) {
            $this->$key = $value;
        }
        else {
            // 'host' option is deprecated
            if ($key == 'host') {
                $this->config['hosts'] = (array) $value;
            }
            else {
                $this->config[$key] = $value;
            }
        }
    }

    /**
     * Establish a connection to the LDAP server
     */
    public function connect($host = null)
    {
        if (!function_exists('ldap_connect')) {
            $this->_error("No ldap support in this PHP installation");
            return false;
        }

        if (isset($this->conn) && $this->conn !== false) {
            $this->_debug("Connection already exists");
            return true;
        }

        $hosts = !empty($host) ? $host : $this->config_get('hosts', []);
        $port  = $this->config_get('port', 389);

        foreach ((array) $hosts as $host) {
            $ldap_uri = $this->_host2uri($host, $port);
            $this->_debug("C: Connect [{$ldap_uri}]");

            if ($lc = @ldap_connect($ldap_uri)) {
                if ($this->config_get('use_tls', false) === true) {
                    if (!ldap_start_tls($lc)) {
                        $this->_debug("S: Could not start TLS. " . ldap_error($lc));
                        continue;
                    }
                }

                $this->_debug("S: OK");

                $ldap_version = $this->config_get('ldap_version', 3);
                $timeout      = $this->config_get('network_timeout');
                $referrals    = $this->config_get('referrals');

                ldap_set_option($lc, LDAP_OPT_PROTOCOL_VERSION, $ldap_version);

                if ($timeout) {
                    ldap_set_option($lc, LDAP_OPT_NETWORK_TIMEOUT, $timeout);
                }

                if ($referrals !== null) {
                    ldap_set_option($lc, LDAP_OPT_REFERRALS, (bool) $referrals);
                }

                $this->_current_host = $ldap_uri;
                $this->conn          = $lc;

                break;
            }

            $this->_debug("S: NOT OK");
        }

        if (!isset($this->conn) || $this->conn === false) {
            $this->_error("Could not connect to LDAP");
            return false;
        }

        return true;
    }

    /**
     *   Shortcut to ldap_delete()
     */
    public function delete_entry($entry_dn)
    {
        $this->_debug("C: Delete $entry_dn");

        if (ldap_delete($this->conn, $entry_dn) === false) {
            $this->_debug("S: " . ldap_error($this->conn));
            $this->_warning("LDAP: Removing entry $entry_dn failed. " . ldap_error($this->conn));
            return false;
        }

        $this->_debug("S: OK");

        return true;
    }

    /**
     * Deletes specified entry and all entries in the tree
     */
    public function delete_entry_recursive($entry_dn)
    {
        // searching for sub entries, but not scope sub, just one level
        $result = $this->search($entry_dn, '(|(objectclass=*)(objectclass=ldapsubentry))', 'one');

        if ($result) {
            $entries = $result->entries(true);

            foreach (array_keys($entries) as $sub_dn) {
                if (!$this->delete_entry_recursive($sub_dn)) {
                    return false;
                }
            }
        }

        if (!$this->delete_entry($entry_dn)) {
            return false;
        }

        return true;
    }

    /**
     * Gets effective rights of an ldap entry
     */
    public function effective_rights($subject)
    {
        if (!in_array(self::CONTROL_EFFECTIVE_RIGHTS, $this->supported_controls())) {
            $this->_debug("LDAP: No getEffectiveRights control in supportedControls");
            return false;
        }

        $entry_dn = $this->entry_dn($subject);

        if (!$entry_dn) {
            $entry_dn = $this->config_get($subject . "_base_dn");
        }

        if (!$entry_dn) {
            $entry_dn = $this->config_get("base_dn");
        }

        if (!$entry_dn) {
            $entry_dn = $this->config_get("root_dn");
        }

        $this->_debug("effective_rights for subject $subject resolves to entry dn $entry_dn");

        if (PHP_VERSION_ID >= 70300) {
            // Note: This get_entry() have to request all attributes to be working
            $result = $this->get_entry($entry_dn, ['*'], [
                    [
                        'oid'        => self::CONTROL_EFFECTIVE_RIGHTS,
                        'value'      => 'dn:' . $this->_current_bind_dn,
                        'iscritical' => true,
                    ],
                ]);

            if (!empty($result)) {
                $attributes = [
                    'dn'                   => $entry_dn,
                    'attributelevelrights' => [],
                    'entrylevelrights'     => [],
                ];

                foreach (['aclrights', 'attributelevelrights', 'entrylevelrights'] as $attr_name) {
                    if (!empty($result[$attr_name])) {
                        $attr_value = $result[$attr_name];

                        switch ($attr_name) {
                        case 'aclrights':
                            $this->parse_aclrights($attributes, $attr_value);
                            break;
                        case 'attributelevelrights':
                            $attributes[$attr_name] = $this->parse_attribute_level_rights($attr_value);
                            break;
                        case 'entrylevelrights':
                            $attributes[$attr_name] = $this->parse_entry_level_rights($attr_value);
                            break;
                        }
                    }
                }

                $this->_debug("LDAP: Effective rights:" . var_export($attributes, true));

                return $attributes;
            }

            return false;
        }

        // Use ldapsearch command
        return $this->effective_rights_mozldap($entry_dn);
    }

    protected function effective_rights_mozldap($entry_dn)
    {
        $moz_ldapsearch = "/usr/lib64/mozldap/ldapsearch";
        if (!is_file($moz_ldapsearch)) {
            $moz_ldapsearch = "/usr/lib/mozldap/ldapsearch";
        }
        if (!is_file($moz_ldapsearch)) {
            $moz_ldapsearch = "/usr/bin/ldapsearch";
        }
        if (!is_file($moz_ldapsearch)) {
            $moz_ldapsearch = null;
        }

        if (empty($moz_ldapsearch)) {
            $this->_error("Mozilla LDAP C SDK binary ldapsearch not found, cannot get effective rights");
            return null;
        }

        $output = [];
        $command = [
            $moz_ldapsearch,
            '-x',
            '-h',
            parse_url($this->_current_host, PHP_URL_HOST),
            '-b',
            escapeshellarg($entry_dn),
            '-s',
            'base',
            '-D',
            escapeshellarg($this->_current_bind_dn),
            '-w',
            escapeshellarg($this->_current_bind_pw)
        ];

        if ($moz_ldapsearch != "/usr/bin/ldapsearch") {
            $command[] = '-p';
            $command[] = parse_url($this->_current_host, PHP_URL_PORT);
        }

        // TODO: Can we use -H instead of -h and -p?

        if ($this->vendor_name() == "Oracle Corporation") {
            // For Oracle DSEE
            $command[] = "-J";
            $command[] = escapeshellarg(self::CONTROL_EFFECTIVE_RIGHTS . ':true');
            $command[] = "-c";
            $command[] = escapeshellarg('dn:' . $this->_current_bind_dn);
        } else if ($moz_ldapsearch == "/usr/bin/ldapsearch") {
            // Modern 389 DS:
            $command[] = "-E";
            $command[] = escapeshellarg("!" . self::CONTROL_EFFECTIVE_RIGHTS . '=:dn:' . $this->_current_bind_dn);
        } else {
            // For 389 DS:
            $command[] = "-J";
            $command[] = escapeshellarg(self::CONTROL_EFFECTIVE_RIGHTS . ':true:dn:' . $this->_current_bind_dn);
        }

        // For both
        $command[] = '"(objectclass=*)"';
        $command[] = '"*"';

        if ($this->vendor_name() == "Oracle Corporation") {
            // Oracle DSEE
            $command[] = 'aclRights';
        }

        // remove password from debug log
        $command_debug     = $command;
        $command_debug[13] = '*';

        $command       = implode(' ', $command);
        $command_debug = implode(' ', $command_debug);

        $this->_debug("LDAP: Executing command: $command_debug");

        exec($command, $output, $return_code);

        $this->_debug("LDAP: Command output:" . var_export($output, true));
        $this->_debug("Return code: " . $return_code);

        if ($return_code) {
            $this->_error("Command $moz_ldapsearch returned error code: $return_code");
            return null;
        }

        $lines = [];
        foreach ($output as $line_num => $line) {
            if (substr($line, 0, 1) == " ") {
                $lines[count($lines)-1] .= trim($line);
            }
            else {
                $lines[] = trim($line);
            }
        }

        $attributes = [
            'dn'                   => $subject_dn,
            'attributelevelrights' => [],
            'entrylevelrights'     => [],
        ];

        if ($this->vendor_name() == "Oracle Corporation") {
            // Example for attribute level rights:
            // aclRights;attributeLevel;$attr:$right:$bool,$right:$bool
            // Example for entry level rights:
            // aclRights;entryLevel: add:1,delete:1,read:1,write:1,proxy:1
            foreach ($lines as $line) {
                $line_components = explode(':', $line);
                $attribute_name  = explode(';', array_shift($line_components));

                if ($attribute_name[0] === "aclRights") {
                    $this->parse_aclrights($attributes, $line);
                }
            }
        } else {
            foreach ($lines as $line) {
                $line_components = explode(':', $line);
                $attribute_name  = array_shift($line_components);
                $attribute_value = trim(implode(':', $line_components));

                switch ($attribute_name) {
                case "attributeLevelRights":
                    $attributes['attributelevelrights'] = $this->parse_attribute_level_rights($attribute_value);
                    break;
                case "entryLevelRights":
                    $attributes['entrylevelrights'] = $this->parse_entry_level_rights($attribute_value);
                    break;
                }
            }
        }

        return $attributes;
    }

    /**
     * Resolve entry data to entry DN
     *
     * @param string $subject    Entry string (e.g. entry DN or unique attribute value)
     * @param array  $attributes Additional attributes
     * @param string $base_dn    Optional base DN
     *
     * @return string Entry DN string
     */
    public function entry_dn($subject, $attributes = [], $base_dn = null)
    {
        $this->_debug("Net_LDAP3::entry_dn($subject)");
        $is_dn = ldap_explode_dn($subject, 1);

        if (is_array($is_dn) && array_key_exists("count", $is_dn) && $is_dn["count"] > 0) {
            $this->_debug("$subject is a dn");
            return $subject;
        }

        $this->_debug("$subject is not a dn");

        if (strlen($subject) < 32 || preg_match('/[^a-fA-F0-9-]/', $subject)) {
            $this->_debug("$subject is not a unique identifier");
            return;
        }

        $unique_attr = $this->config_get('unique_attribute', 'nsuniqueid');

        $this->_debug("Using unique_attribute " . var_export($unique_attr, true) . " at " . __FILE__ . ":" . __LINE__);

        $attributes  = array_merge([$unique_attr => $subject], (array) $attributes);
        $subject     = $this->entry_find_by_attribute($attributes, $base_dn);

        if (!empty($subject)) {
            return key($subject);
        }
    }

    public function entry_find_by_attribute($attributes, $base_dn = null)
    {
        $this->_debug("Net_LDAP3::entry_find_by_attribute(\$attributes, \$base_dn) called with base_dn", $base_dn, "and attributes", $attributes);

        if (empty($attributes) || !is_array($attributes)) {
            return false;
        }

        if (empty($attributes[key($attributes)])) {
            return false;
        }

        $filter = count($attributes) ? "(&" : "";

        foreach ($attributes as $key => $value) {
            $filter .= "(" . $key . "=" . $value . ")";
        }

        $filter .= count($attributes) ? ")" : "";

        if (empty($base_dn)) {
            $base_dn = $this->config_get('root_dn');
            $this->_debug("Using base_dn from domain " . $this->domain . ": " . $base_dn);
        }

        $result = $this->search($base_dn, $filter, 'sub', array_keys($attributes));

        if ($result && $result->count() > 0) {
            $this->_debug("Results found: " . implode(', ', array_keys($result->entries(true))));
            return $result->entries(true);
        }
        else {
            $this->_debug("No result");
            return false;
        }
    }

    public function find_user_groups($member_dn)
    {
        $groups  = [];
        $root_dn = $this->config_get('root_dn');

        // TODO: Do not query for both, it's either one or the other
        $entries = $this->search($root_dn, "(|" .
            "(&(objectclass=groupofnames)(member=$member_dn))" .
            "(&(objectclass=groupofuniquenames)(uniquemember=$member_dn))" .
            ")"
        );

        if ($entries) {
            $groups = array_keys($entries->entries(true));
        }

        return $groups;
    }

    public function get_entry_attribute($subject_dn, $attribute)
    {
        $entry = $this->get_entry_attributes($subject_dn, (array)$attribute);

        return $entry[strtolower($attribute)];
    }

    /**
     * Get a specific LDAP entry attributes
     *
     * @param string $dn         Record identifier
     * @param array  $attributes Attributes to return
     *
     * @return array Hash array (keys in lower case)
     */
    public function get_entry_attributes($subject_dn, $attributes)
    {
        $result = $this->get_entry($subject_dn, $attributes);

        if (!$result) {
            return [];
        }

        if (!in_array('*', $attributes) && !in_array('dn', $attributes)) {
            unset($result['dn']);
        }

        // change keys case for historical reasons
        return array_change_key_case($result, CASE_LOWER);
    }

    /**
     * Get a specific LDAP entry, identified by its DN
     *
     * @param string $dn         Record identifier
     * @param array  $attributes Attributes to return
     * @param array  $controls   LDAP Controls
     *
     * @return array Hash array (keys in original case)
     */
    public function get_entry($dn, $attributes = ["*"], $controls = [])
    {
        $rec = null;

        if ($this->conn) {
            $this->_debug("C: Read [dn: $dn] [(objectclass=*)]");

            if (!empty($controls)) {
                $ldap_result = @ldap_read($this->conn, $dn, '(objectclass=*)', $attributes, 0, -1, -1, LDAP_DEREF_NEVER, $controls);
            }
            else {
                $ldap_result = @ldap_read($this->conn, $dn, '(objectclass=*)', $attributes);
            }

            if ($ldap_result) {
                $this->_debug("S: OK");

                if ($entry = ldap_first_entry($this->conn, $ldap_result)) {
                    $rec = ldap_get_attributes($this->conn, $entry);
                }
            }
            else {
                $this->_debug("S: ".ldap_error($this->conn));
                $this->_warning("LDAP: Failed to read $dn. " . ldap_error($this->conn));
            }

            if (!empty($rec)) {
                $rec = self::normalize_entry($rec, true);
                if ($dn) {
                    $rec['dn'] = $dn; // Add in the dn for the entry.
                }
            }
        }

        return $rec;
    }

    public function list_replicas()
    {
        $this->_debug("Finding replicas for this server.");

        // Search any host that is a replica for the current host
        $replica_hosts = $this->config_get('replica_hosts', []);
        $root_dn       = $this->config_get('config_root_dn');

        if (!empty($replica_hosts)) {
            return $replica_hosts;
        }

        $ldap = new Net_LDAP3($this->config);
        $ldap->connect();
        $ldap->bind($this->_current_bind_dn, $this->_current_bind_pw);

        $host_attrs = ['nsds5replicahost', 'nsds5replicaport'];

        $result = $ldap->search($root_dn, '(objectclass=nsds5replicationagreement)', 'sub', $host_attrs);

        if (!$result) {
            $this->_debug("No replicas configured");
            return $replica_hosts;
        }

        $this->_debug("Replication agreements found: " . var_export($result->entries(true), true));

        foreach ($result->entries(true) as $dn => $attrs) {
            $host = $attrs['nsds5replicahost'] . ':' . !empty($attrs['nsds5replicaport']) ? $attrs['nsds5replicaport'] : 389;
            if (!in_array($host, $replica_hosts)) {
                $replica_hosts[] = $host;
            }
        }

        // $replica_hosts now holds the IDs of servers we are currently NOT
        // connected to. We might need this later in order to set
        $this->_server_id_not = $replica_hosts;

        $this->_debug("So far, we have the following replicas: " . var_export($replica_hosts, true));

        $ldap->close();

        foreach ($replica_hosts as $replica_host) {
            $ldap->config_set('hosts', [$replica_host]);
            $ldap->connect();
            $ldap->bind($this->_current_bind_dn, $this->_current_bind_pw);

            $result = $ldap->search($root_dn, '(objectclass=nsds5replicationagreement)', 'sub', $host_attrs);
            if (!$result) {
                $this->_debug("No replicas configured on $replica_host");
                $ldap->close();
                continue;
            }

            foreach ($result->entries(true) as $dn => $attrs) {
                $host = $attrs['nsds5replicahost'] . ':' . !empty($attrs['nsds5replicaport']) ? $attrs['nsds5replicaport'] : 389;
                if (!in_array($host, $replica_hosts)) {
                    $replica_hosts[] = $host;
                }
            }

            $ldap->close();
        }

        $this->config_set('replica_hosts', $replica_hosts);

        return $replica_hosts;
    }

    public function login($username, $password, $domain = null, &$attributes = null)
    {
        $this->_debug("Net_LDAP3::login($username,***,$domain)");

        $_bind_dn = $this->config_get('service_bind_dn');
        $_bind_pw = $this->config_get('service_bind_pw');

        if (empty($_bind_dn)) {
            $this->_debug("No valid service bind dn found.");
            return null;
        }

        if (empty($_bind_pw)) {
            $this->_debug("No valid service bind password found.");
            return null;
        }

        $bound = $this->bind($_bind_dn, $_bind_pw);

        if (!$bound) {
            $this->_debug("Could not bind with service bind credentials.");
            return null;
        }

        $entry_dn = $this->entry_dn($username);

        if (!empty($entry_dn)) {
            $bound = $this->bind($entry_dn, $password);

            if (!$bound) {
                $this->_error("LDAP: Could not bind with " . $entry_dn);
                return null;
            }

            // fetch user attributes if requested
            if (!empty($attributes)) {
                $attributes = $this->get_entry($entry_dn, $attributes);
            }

            return $entry_dn;
        }

        $base_dn = $this->config_get('root_dn');

        if (empty($base_dn)) {
            $this->_debug("Could not get a valid base dn to search.");
            return null;
        }

        $localpart = $username;

        if (empty($domain) ) {
            if (count(explode('@', $username)) > 1) {
                $_parts    = explode('@', $username);
                $localpart = $_parts[0];
                $domain    = $_parts[1];
            }
            else {
                $localpart = $username;
                $domain    = '';
            }
        }

        $realm  = $domain;
        $filter = $this->config_get("login_filter", null);

        if (empty($filter)) {
            $filter = $this->config_get("filter", null);
        }
        if (empty($filter)) {
            $filter = "(&(|(mail=%s)(mail=%U@%d)(alias=%s)(alias=%U@%d)(uid=%s))(objectclass=inetorgperson))";
        }

        $this->_debug("Net::LDAP3::login() original filter: " . $filter);

        $replace_patterns = [
            '/%s/' => $username,
            '/%d/' => $domain,
            '/%U/' => $localpart,
            '/%r/' => $realm
        ];

        $filter = preg_replace(array_keys($replace_patterns), array_values($replace_patterns), $filter);

        $this->_debug("Net::LDAP3::login() actual filter: " . $filter);

        $result = $this->search($base_dn, $filter, 'sub', $attributes);

        if (!$result) {
            $this->_debug("Could not search $base_dn with $filter");
            return null;
        }

        if ($result->count() > 1) {
            $this->_debug("Multiple entries found.");
            return null;
        }
        else if ($result->count() < 1) {
            $this->_debug("No entries found.");
            return null;
        }

        $entries  = $result->entries(true);
        $entry_dn = key($entries);

        $bound = $this->bind($entry_dn, $password);

        if (!$bound) {
            $this->_debug("Could not bind with " . $entry_dn);
            return null;
        }

        // replace attributes list with key-value data
        if (!empty($attributes)) {
            $attributes = $entries[$entry_dn];
        }

        return $entry_dn;
    }

    public function list_group_members($dn, $entry = null, $recurse = true)
    {
        $this->_debug("Net_LDAP3::list_group_members($dn)");

        if (is_array($entry) && in_array('objectclass', $entry)) {
            if (!in_array(['groupofnames', 'groupofuniquenames', 'groupofurls'], $entry['objectclass'])) {
                $this->_debug("Called list_group_members on a non-group!");
                return [];
            }
        }
        else {
            $entry = $this->get_entry_attributes($dn, ['member', 'uniquemember', 'memberurl', 'objectclass']);

            if (!$entry) {
                return [];
            }
        }

        $group_members = [];

        foreach ((array)$entry['objectclass'] as $objectclass) {
            switch (strtolower($objectclass)) {
                case "groupofnames":
                case "kolabgroupofnames":
                    $group_members = array_merge($group_members, $this->list_group_member($dn, $entry['member'], $recurse));
                    break;
                case "groupofuniquenames":
                case "kolabgroupofuniquenames":
                    $group_members = array_merge($group_members, $this->list_group_uniquemember($dn, $entry['uniquemember'], $recurse));
                    break;
                case "groupofurls":
                    $group_members = array_merge($group_members, $this->list_group_memberurl($dn, $entry['memberurl'], $recurse));
                    break;
            }
        }

        return array_values(array_filter($group_members));
    }

    public function modify_entry($subject_dn, $old_attrs, $new_attrs)
    {
        $this->_debug("OLD ATTRIBUTES", $old_attrs);
        $this->_debug("NEW ATTRIBUTES", $new_attrs);

        // TODO: Get $rdn_attr - we have type_id in $new_attrs
        $dn_components  = ldap_explode_dn($subject_dn, 0);
        $rdn_components = explode('=', $dn_components[0]);
        $rdn_attr       = $rdn_components[0];

        $this->_debug("Net_LDAP3::modify_entry() using rdn attribute: " . $rdn_attr);

        $mod_array = [
            'add'       => [], // For use with ldap_mod_add()
            'del'       => [], // For use with ldap_mod_del()
            'replace'   => [], // For use with ldap_mod_replace()
            'rename'    => [], // For use with ldap_rename()
        ];

        // This is me cheating. Remove this special attribute.
        if (array_key_exists('ou', $old_attrs) || array_key_exists('ou', $new_attrs)) {
            $old_ou = isset($old_attrs['ou']) && is_array($old_attrs['ou'])
                ? array_shift($old_attrs['ou']) : (isset($old_attrs['ou']) ? $old_attrs['ou'] : null);
            $new_ou = isset($new_attrs['ou']) && is_array($new_attrs['ou'])
                ? array_shift($new_attrs['ou']) : (isset($new_attrs['ou']) ? $new_attrs['ou'] : null);
            unset($old_attrs['ou']);
            unset($new_attrs['ou']);
        }
        else {
            $old_ou = null;
            $new_ou = null;
        }

        // Compare each attribute value of the old attrs with the corresponding value
        // in the new attrs, if any.
        foreach ($old_attrs as $attr => $old_attr_value) {
            if (is_array($old_attr_value)) {
                if (count($old_attr_value) == 1) {
                    $old_attrs[$attr] = $old_attr_value[0];
                    $old_attr_value = $old_attrs[$attr];
                }
            }

            if (array_key_exists($attr, $new_attrs)) {
                if (is_array($new_attrs[$attr])) {
                    if (count($new_attrs[$attr]) == 1) {
                        $new_attrs[$attr] = $new_attrs[$attr][0];
                    }
                }

                if (is_array($old_attrs[$attr]) && is_array($new_attrs[$attr])) {
                    $_sort1 = $new_attrs[$attr];
                    sort($_sort1);
                    $_sort2 = $old_attr_value;
                    sort($_sort2);
                }
                else {
                    $_sort1 = true;
                    $_sort2 = false;
                }

                if ($new_attrs[$attr] !== $old_attr_value && $_sort1 !== $_sort2) {
                    $this->_debug("Attribute $attr changed from " . var_export($old_attr_value, true) . " to " . var_export($new_attrs[$attr], true));
                    if ($attr === $rdn_attr) {
                        $this->_debug("This attribute is the RDN attribute. Let's see if it is multi-valued, and if the original still exists in the new value.");
                        if (is_array($old_attrs[$attr])) {
                            if (!is_array($new_attrs[$attr])) {
                                if (in_array($new_attrs[$attr], $old_attrs[$attr])) {
                                    // TODO: Need to remove all $old_attrs[$attr] values not equal to $new_attrs[$attr], and not equal to the current $rdn_attr value [0]

                                    $this->_debug("old attrs. is array, new attrs. is not array. new attr. exists in old attrs.");

                                    $rdn_attr_value  = array_shift($old_attrs[$attr]);
                                    $_attr_to_remove = [];

                                    foreach ($old_attrs[$attr] as $value) {
                                        if (strtolower($value) != strtolower($new_attrs[$attr])) {
                                            $_attr_to_remove[] = $value;
                                        }
                                    }

                                    $this->_debug("Adding to delete attribute $attr values:" . implode(', ', $_attr_to_remove));

                                    $mod_array['del'][$attr] = $_attr_to_remove;

                                    if (strtolower($new_attrs[$attr]) !== strtolower($rdn_attr_value)) {
                                        $this->_debug("new attrs is not the same as the old rdn value, issuing a rename");
                                        $mod_array['rename']['dn']      = $subject_dn;
                                        $mod_array['rename']['new_rdn'] = $rdn_attr . '=' . self::quote_string($new_attrs[$attr], true);
                                    }
                                }
                                else {
                                    $this->_debug("new attrs is not the same as any of the old rdn value, issuing a full rename");
                                    $mod_array['rename']['dn']      = $subject_dn;
                                    $mod_array['rename']['new_rdn'] = $rdn_attr . '=' . self::quote_string($new_attrs[$attr], true);
                                }
                            }
                            else {
                                // TODO: See if the rdn attr. value is still in $new_attrs[$attr]
                                if (in_array($old_attrs[$attr][0], $new_attrs[$attr])) {
                                    $this->_debug("Simply replacing attr $attr as rdn attr value is preserved.");
                                    $mod_array['replace'][$attr] = $new_attrs[$attr];
                                }
                                else {
                                    // TODO: This fails.
                                    $mod_array['rename']['dn']      = $subject_dn;
                                    $mod_array['rename']['new_rdn'] = $rdn_attr . '=' . self::quote_string($new_attrs[$attr][0], true);
                                    $mod_array['del'][$attr]        = $old_attrs[$attr][0];
                                }
                            }
                        }
                        else {
                            if (!is_array($new_attrs[$attr])) {
                                $this->_debug("Renaming " . $old_attrs[$attr] . " to " . $new_attrs[$attr]);
                                $mod_array['rename']['dn']      = $subject_dn;
                                $mod_array['rename']['new_rdn'] = $rdn_attr . '=' . self::quote_string($new_attrs[$attr], true);
                            }
                            else {
                                $this->_debug("Adding to replace");
                                // An additional attribute value is being supplied. Just replace and continue.
                                $mod_array['replace'][$attr] = $new_attrs[$attr];
                                continue;
                            }
                        }
                    }
                    else {
                        if (!isset($new_attrs[$attr]) || $new_attrs[$attr] === '' || (is_array($new_attrs[$attr]) && empty($new_attrs[$attr]))) {
                            switch ($attr) {
                                case "userpassword":
                                    break;
                                default:
                                    $this->_debug("Adding to del: $attr");
                                    $mod_array['del'][$attr] = (array)($old_attr_value);
                                    break;
                            }
                        }
                        else {
                            $this->_debug("Adding to replace: $attr");
                            $mod_array['replace'][$attr] = (array)($new_attrs[$attr]);
                        }
                    }
                }
                else {
                    $this->_debug("Attribute $attr unchanged");
                }
            }
            else {
                // TODO: Since we're not shipping the entire object back and forth, and only post
                // part of the data... we don't know what is actually removed (think modifiedtimestamp, etc.)
                $this->_debug("Group attribute $attr not mentioned in \$new_attrs..., but not explicitly removed... by assumption");
            }
        }

        foreach ($new_attrs as $attr => $value) {
            // OU's parent base dn
            if ($attr == 'base_dn') {
                continue;
            }

            if (is_array($value)) {
                if (count($value) == 1) {
                    $new_attrs[$attr] = $value[0];
                    $value = $new_attrs[$attr];
                }
            }

            if (array_key_exists($attr, $old_attrs)) {
                if (is_array($old_attrs[$attr])) {
                    if (count($old_attrs[$attr]) == 1) {
                        $old_attrs[$attr] = $old_attrs[$attr][0];
                    }
                }

                if (is_array($new_attrs[$attr]) && is_array($old_attrs[$attr])) {
                    $_sort1 = $old_attrs[$attr];
                    sort($_sort1);
                    $_sort2 = $value;
                    sort($_sort2);
                }
                else {
                    $_sort1 = true;
                    $_sort2 = false;
                }

                if ($value === null || $value === '' || (is_array($value) && empty($value))) {
                    if (!array_key_exists($attr, $mod_array['del'])) {
                        switch ($attr) {
                            case 'userpassword':
                                break;
                            default:
                                $this->_debug("Adding to del(2): $attr");
                                $mod_array['del'][$attr] = (array)($old_attrs[$attr]);
                                break;
                        }
                    }
                }
                else {
                    if (!($old_attrs[$attr] === $value) && !($attr === $rdn_attr) && !($_sort1 === $_sort2)) {
                        if (!array_key_exists($attr, $mod_array['replace'])) {
                            $this->_debug("Adding to replace(2): $attr");
                            $mod_array['replace'][$attr] = $value;
                        }
                    }
                }
            }
            else {
                if (!empty($value)) {
                    $mod_array['add'][$attr] = $value;
                }
            }
        }

        if (empty($old_ou)) {
            $subject_dn_components = ldap_explode_dn($subject_dn, 0);
            unset($subject_dn_components["count"]);
            $subject_rdn = array_shift($subject_dn_components);
            $old_ou      = implode(',', $subject_dn_components);
        }

        $subject_dn = self::unified_dn($subject_dn);
        $prefix     = self::unified_dn('ou=' . $old_ou) . ',';

        // object is an organizational unit
        if (strpos($subject_dn, $prefix) === 0) {
            $root = substr($subject_dn, strlen($prefix)); // remove ou=*,

            if ((!empty($new_attrs['base_dn']) && strtolower($new_attrs['base_dn']) !== strtolower($root))
                || (strtolower($old_ou) !== strtolower($new_ou))
            ) {
                if (!empty($new_attrs['base_dn'])) {
                    $root = $new_attrs['base_dn'];
                }

                $mod_array['rename']['new_parent'] = $root;
                $mod_array['rename']['dn']         = $subject_dn;
                $mod_array['rename']['new_rdn']    = 'ou=' . self::quote_string($new_ou, true);
            }
        }
        // not OU object, but changed ou attribute
        else if (!empty($old_ou) && !empty($new_ou)) {
            // unify DN strings for comparison
            $old_ou = self::unified_dn($old_ou);
            $new_ou = self::unified_dn($new_ou);

            if (strtolower($old_ou) !== strtolower($new_ou)) {
                $mod_array['rename']['new_parent'] = $new_ou;
                if (empty($mod_array['rename']['dn']) || empty($mod_array['rename']['new_rdn'])) {
                    $rdn_attr_value = self::quote_string($new_attrs[$rdn_attr], true);
                    $mod_array['rename']['dn']      = $subject_dn;
                    $mod_array['rename']['new_rdn'] = $rdn_attr . '=' . $rdn_attr_value;
                }
            }
        }

        $this->_debug($mod_array);

        $result = $this->modify_entry_attributes($subject_dn, $mod_array);

        if ($result) {
            return $mod_array;
        }
    }

    /**
     * Bind connection with (SASL-) user and password
     *
     * @param string $authc Authentication user
     * @param string $pass  Bind password
     * @param string $authz Autorization user
     *
     * @return boolean True on success, False on error
     */
    public function sasl_bind($authc = '', $pass = '', $authz = null)
    {
        if (!$this->conn) {
            return false;
        }

        if (!function_exists('ldap_sasl_bind')) {
            $this->_error("LDAP: Unable to bind. ldap_sasl_bind() not exists");
            return false;
        }

        if (!empty($authz)) {
            $authz = 'u:' . $authz;
        }

        $gssapi = $this->config_get('gssapi_cn');
        $method = $this->config_get('auth_method');

        if (empty($method)) {
            $method = 'DIGEST-MD5';
        }

        if ($gssapi && strcasecmp($method, 'GSSAPI') == 0) {
            putenv("KRB5CCNAME=$gssapi");
        }

        $this->_debug("C: Bind [mech: $method, authc: $authc, authz: $authz, gssapi: $gssapi]");

        if (ldap_sasl_bind($this->conn, null, $pass, $method, null, $authc, $authz)) {
            $this->_debug("S: OK");
            return true;
        }

        $this->_debug("S: ".ldap_error($this->conn));
        $this->_error("LDAP: Bind failed for authcid=$authc. ".ldap_error($this->conn));

        return false;
    }

    /**
     * Execute LDAP search
     *
     * @param string $base_dn    Base DN to use for searching
     * @param string $filter     Filter string to query
     * @param string $scope      The LDAP scope (list|sub|base)
     * @param array  $attrs      List of entry attributes to read
     * @param array  $prop       Hash array with query configuration properties:
     *   - sort:   array of sort attributes (has to be in sync with the VLV index)
     *   - search: search string used for VLV controls
     * @param bool   $count_only Set to true if only entry count is requested
     *
     * @return mixed Net_LDAP3_Result object or number of entries (if $count_only=true) or False on failure
     */
    public function search($base_dn, $filter = '(objectclass=*)', $scope = 'sub', $attrs = ['dn'], $props = [], $count_only = false)
    {
        $controls = null;

        if (!array_key_exists('sort', $props)) {
            $props['sort'] = false;
        }

        if (!$this->conn) {
            $this->_debug("No active connection for " . __CLASS__ . "::" . __FUNCTION__);
            return false;
        }

        // make sure attributes list is not empty
        if (empty($attrs)) {
            $attrs = ['dn'];
        }
        // make sure filter is not empty
        if (empty($filter)) {
            $filter = '(objectclass=*)';
        }

        $this->_debug("C: Search base dn: [$base_dn] scope [$scope] with filter [$filter]");

        $function = self::scope_to_function($scope, $ns_function);

        if (!$count_only && ($sort = $this->find_vlv($base_dn, $filter, $scope, $props['sort']))) {
            // when using VLV, we get the total count by...
            // ...either reading numSubOrdinates attribute
            if (($sub_filter = $this->config_get('numsub_filter')) &&
                ($result_count = @$ns_function($this->conn, $base_dn, $sub_filter, ['numSubOrdinates'], 0, 0, 0))
            ) {
                $counts = ldap_get_entries($this->conn, $result_count);
                for ($vlv_count = $j = 0; $j < $counts['count']; $j++) {
                    $vlv_count += $counts[$j]['numsubordinates'][0];
                }
                $this->_debug("D: total numsubordinates = " . $vlv_count);
            }
            // ...or by parsing the controls in the response, and if that's not supported
            // by fetching all records dn and counting them
            else if (PHP_VERSION_ID < 70305 && !function_exists('ldap_parse_virtuallist_control')) {
                // @FIXME: this search will ignore $props['search']
                $vlv_count = $this->search($base_dn, $filter, $scope, ['dn'], $props, true);
            }

            $controls = $this->_vlv_set_controls($sort, $this->list_page, $this->page_size,
                $this->_vlv_search($sort, $props['search']));

            $this->vlv_active = (bool) $controls;
        }
        else {
            $this->vlv_active = false;
        }

        $sizelimit = (int) $this->config['sizelimit'];
        $timelimit = (int) $this->config['timelimit'];
        $phplimit  = (int) @ini_get('max_execution_time');

        // set LDAP time limit to be (one second) less than PHP time limit
        // otherwise we have no chance to log the error below
        if ($phplimit && $timelimit >= $phplimit) {
            $timelimit = $phplimit - 1;
        }

        $this->_debug("Using function $function on scope $scope (\$ns_function is $ns_function)");

        if ($this->vlv_active) {
            if (!empty($this->additional_filter)) {
                $filter = "(&" . $filter . $this->additional_filter . ")";
                $this->_debug("C: (With VLV) Setting a filter (with additional filter) of " . $filter);
            }
            else {
                $this->_debug("C: (With VLV) Setting a filter (without additional filter) of " . $filter);
            }
        }
        else {
            if (!empty($this->additional_filter)) {
                $filter = "(&" . $filter . $this->additional_filter . ")";
            }
            $this->_debug("C: (Without VLV) Setting a filter of " . $filter);
        }

        $this->_debug("Executing search with return attributes: " . var_export($attrs, true));

        if (is_array($controls)) {
            $ldap_result = $function($this->conn, $base_dn, $filter, $attrs, 0, $sizelimit, $timelimit, LDAP_DEREF_NEVER, $controls);
        }
        else {
            $ldap_result = @$function($this->conn, $base_dn, $filter, $attrs, 0, $sizelimit, $timelimit);
        }

        if (!$ldap_result) {
            $this->_warning("LDAP: $function failed for dn=$base_dn. " . ldap_error($this->conn));
            return false;
        }

        // when running on a PHP with server controls support we can
        // retrieve the total count from the LDAP search result
        if ($this->vlv_active && (is_array($controls) || function_exists('ldap_parse_virtuallist_control'))) {
            if (ldap_parse_result($this->conn, $ldap_result, $errcode, $matcheddn, $errmsg, $referrals, $serverctrls)) {
                if (PHP_VERSION_ID >= 70300) {
                    $vlv_count   = (int) $serverctrls[self::CONTROL_VLV_RESPONSE]['value']['count'];
                    // FIXME: I don't know this is the same offset value as in ldap_parse_virtuallist_control() below
                    //        but anyway it looks like we do not use that value for anything
                    $last_offset = (int) $serverctrls[self::CONTROL_VLV_RESPONSE]['value']['target'];
                }
                else {
                    ldap_parse_virtuallist_control($this->conn, $serverctrls, $last_offset, $vlv_count, $vresult);
                }

                $this->_debug("S: VLV result: last_offset=$last_offset; content_count=$vlv_count");
            }
            else {
                $this->_debug("S: ".($errmsg ? $errmsg : ldap_error($this->conn)));
            }
        }
        else {
            $this->_debug("S: ".ldap_count_entries($this->conn, $ldap_result)." record(s) found");
        }

        $result = new Net_LDAP3_Result($this->conn, $base_dn, $filter, $scope, $ldap_result);

        if (isset($last_offset)) {
            $result->set('offset', $last_offset);
        }
        if (isset($vlv_count)) {
            $result->set('count', $vlv_count);
        }

        $result->set('vlv', $this->vlv_active);

        return $count_only ? $result->count() : $result;
    }

    /**
     * Similar to Net_LDAP3::search() but using a search array with multiple
     * keys and values that to continue to use the VLV but with an original
     * filter adding the search stuff to an additional filter.
     *
     * @see Net_LDAP3::search()
     */
    public function search_entries($base_dn, $filter = '(objectclass=*)', $scope = 'sub', $attrs = ['dn'], $props = [])
    {
        $this->_debug("Net_LDAP3::search_entries with search " . var_export($props, true));

        if (is_array($props['search']) && array_key_exists('params', $props['search'])) {
            $_search = $this->search_filter($props['search']);
            $this->_debug("C: Search filter: $_search");

            if (!empty($_search)) {
                $this->additional_filter = $_search;
            }
            else {
                $this->additional_filter = "(|";

                foreach ($props['search'] as $attr => $value) {
                    $this->additional_filter .= "(" . $attr . "=" . $this->_fuzzy_search_prefix() . $value . $this->_fuzzy_search_suffix() . ")";
                }

                $this->additional_filter .= ")";
            }

            $this->_debug("C: Setting an additional filter " . $this->additional_filter);
        }

        $search = $this->search($base_dn, $filter, $scope, $attrs, $props);

        $this->additional_filter = null;

        if (!$search) {
            $this->_debug("Net_LDAP3: Search did not succeed!");
            return false;
        }

        return $search;
    }

    /**
     * Create LDAP search filter string according to defined parameters.
     */
    public function search_filter($search)
    {
        if (empty($search) || !is_array($search) || empty($search['params'])) {
            return null;
        }

        $operators = ['=', '~=', '>=', '<='];
        $filter    = '';

        foreach ((array) $search['params'] as $field => $param) {
            $value = (array) $param['value'];
            $type = isset($param['type']) ? strval($param['type']) : null;

            switch ($type) {
                case 'prefix':
                    $prefix = '';
                    $suffix = '*';
                    break;

                case 'suffix':
                    $prefix = '*';
                    $suffix = '';
                    break;

                case 'exact':
                case '=':
                case '~=':
                case '>=':
                case '<=':
                    $prefix = '';
                    $suffix = '';

                    // this is a common query to find entry by DN, make sure
                    // it is a unified DN so special characters are handled correctly
                    if ($field == 'entrydn') {
                        $value = array_map(['Net_LDAP3', 'unified_dn'], $value);
                    }

                    break;

                case 'exists':
                    $prefix = '*';
                    $suffix = '';
                    $param['value'] = '';
                    break;

                case 'both':
                default:
                    $prefix = '*';
                    $suffix = '*';
                    break;
            }

            $operator = $type && in_array($type, $operators) ? $type : '=';

            if (count($value) < 2) {
                $value = array_pop($value);
            }

            if (is_array($value)) {
                $val_filter = [];
                foreach ($value as $val) {
                    $val          = self::quote_string($val);
                    $val_filter[] = "(" . $field . $operator . $prefix . $val . $suffix . ")";
                }
                $filter .= "(|" . implode($val_filter) . ")";
            }
            else {
                $value = self::quote_string($value);
                $filter .= "(" . $field . $operator . $prefix . $value . $suffix . ")";
            }
        }

        // join search parameters with specified operator ('OR' or 'AND')
        if (!empty($search['params']) && count($search['params']) > 1) {
            $operator = !empty($search['operator']) && $search['operator'] == 'AND' ? '&' : '|';
            $filter = '(' . $operator . $filter . ')';
        }

        return $filter;
    }

    /**
     * Set properties for VLV-based paging
     *
     * @param number $page Page number to list (starting at 1)
     * @param number $size Number of entries to display on one page
     */
    public function set_vlv_page($page, $size = 10)
    {
        $this->list_page = $page;
        $this->page_size = $size;
    }

    /**
     * Turn an LDAP entry into a regular PHP array with attributes as keys.
     *
     * @param array $entry Attributes array as retrieved from ldap_get_attributes() or ldap_get_entries()
     * @param bool  $flat  Convert one-element-array values into strings
     *
     * @return array Hash array with attributes as keys
     */
    public static function normalize_entry($entry, $flat = false)
    {
        $rec = [];

        for ($i=0; $i < $entry['count']; $i++) {
            $attr = $entry[$i];
            $_attr = strtolower($attr);

            for ($j=0; $j < $entry[$attr]['count']; $j++) {
                $rec[$_attr][$j] = $entry[$attr][$j];
            }

            if ($flat && count($rec[$_attr]) == 1) {
                $rec[$_attr] = $rec[$_attr][0];
            }
        }

        return $rec;
    }

    /**
     * Normalize a ldap result by converting entry attribute arrays into single values
     */
    public static function normalize_result($_result)
    {
        if (!is_array($_result)) {
            return [];
        }

        $result = [];

        for ($x = 0; $x < $_result['count']; $x++) {
            $dn    = $_result[$x]['dn'];
            $entry = self::normalize_entry($_result[$x], true);

            if (!empty($entry['objectclass'])) {
                if (is_array($entry['objectclass'])) {
                    $entry['objectclass'] = array_map('strtolower', $entry['objectclass']);
                }
                else {
                    $entry['objectclass'] = strtolower($entry['objectclass']);
                }
            }

            $result[$dn] = $entry;
        }

        return $result;
    }

    public static function scopeint2str($scope)
    {
        switch ($scope) {
            case 2:
                return 'sub';

            case 1:
                return 'one';

            case 0:
                return 'base';

            default:
                $this->_debug("Scope $scope is not a valid scope integer");
        }
    }

    /**
     * Choose the right PHP function according to scope property
     *
     * @param string $scope         The LDAP scope (sub|base|list)
     * @param string $ns_function   Function to be used for numSubOrdinates queries
     * @return string  PHP function to be used to query directory
     */
    public static function scope_to_function($scope, &$ns_function = null)
    {
        switch ($scope) {
            case 'sub':
                $function = $ns_function = 'ldap_search';
                break;
            case 'base':
                $function = $ns_function = 'ldap_read';
                break;
            case 'one':
            case 'list':
            default:
                $function    = 'ldap_list';
                $ns_function = 'ldap_read';
                break;
        }

        return $function;
    }

    private function config_set_config_get_hook($callback)
    {
        $this->_config_get_hook = $callback;
    }

    private function config_set_config_set_hook($callback)
    {
        $this->_config_set_hook = $callback;
    }

    /**
     * Sets the debug level both for this class and the ldap connection.
     */
    private function config_set_debug($value)
    {
        $this->config['debug'] = (bool) $value;
        ldap_set_option(null, LDAP_OPT_DEBUG_LEVEL, (int) $value);
    }

    /**
     *  Sets a log hook that is called with every log message in this module.
     */
    private function config_set_log_hook($callback)
    {
        $this->_log_hook = $callback;
    }

    /**
     * Find a matching VLV
     */
    protected function find_vlv($base_dn, $filter, $scope, $sort_attrs = null)
    {
        if ($scope == 'base') {
            return false;
        }

        $vlv_indexes = $this->find_vlv_indexes_and_searches();

        if (empty($vlv_indexes)) {
            return false;
        }

        $this->_debug("Existing vlv index and search information", $vlv_indexes);

        $filter  = strtolower($filter);
        $base_dn = self::unified_dn($base_dn);

        foreach ($vlv_indexes as $vlv_index) {
            if (!empty($vlv_index[$base_dn])) {
                $this->_debug("Found a VLV for base_dn: " . $base_dn);
                if ($vlv_index[$base_dn]['filter'] == $filter) {
                    if ($vlv_index[$base_dn]['scope'] == $scope) {
                        $this->_debug("Scope and filter matches");

                        // Not passing any sort attributes means you don't care
                        if (!empty($sort_attrs)) {
                            $sort_attrs = array_map('strtolower', (array) $sort_attrs);

                            foreach ($vlv_index[$base_dn]['sort'] as $sss_config) {
                                $sss_config = array_map('strtolower', $sss_config);
                                if (count(array_intersect($sort_attrs, $sss_config)) == count($sort_attrs)) {
                                    $this->_debug("Sorting matches");

                                    return $sort_attrs;
                                }
                            }

                            $this->_debug("Sorting does not match");
                        }
                        else {
                            $sort = array_filter((array) $vlv_index[$base_dn]['sort'][0]);
                            $this->_debug("Sorting unimportant");

                            return $sort;
                        }
                    }
                    else {
                        $this->_debug("Scope does not match");
                    }
                }
                else {
                    $this->_debug("Filter does not match");
                }
            }
        }

        return false;
    }

    /**
     * Return VLV indexes and searches including necessary configuration
     * details.
     */
    protected function find_vlv_indexes_and_searches()
    {
        // Use of Virtual List View control has been specifically disabled.
        if ($this->config['vlv'] === false) {
            return false;
        }

        // Virtual List View control has been configured in kolab.conf, for example;
        //
        // [ldap]
        // vlv = [
        //         {
        //                 'ou=People,dc=example,dc=org': {
        //                         'scope': 'sub',
        //                         'filter': '(objectclass=inetorgperson)',
        //                         'sort' : [
        //                                 [
        //                                         'displayname',
        //                                         'sn',
        //                                         'givenname',
        //                                         'cn'
        //                                     ]
        //                             ]
        //                     }
        //             },
        //         {
        //                 'ou=Groups,dc=example,dc=org': {
        //                         'scope': 'sub',
        //                         'filter': '(objectclass=groupofuniquenames)',
        //                         'sort' : [
        //                                 [
        //                                         'cn'
        //                                     ]
        //                             ]
        //                     }
        //             },
        //     ]
        //
        if (is_array($this->config['vlv'])) {
            return $this->config['vlv'];
        }

        // We have done this dance before.
        if ($this->_vlv_indexes_and_searches !== null) {
            return $this->_vlv_indexes_and_searches;
        }

        $this->_vlv_indexes_and_searches = [];

        $config_root_dn = $this->config_get('config_root_dn');

        if (empty($config_root_dn)) {
            return [];
        }

        if ($cached_config = $this->get_cache_data('vlvconfig')) {
            $this->_vlv_indexes_and_searches = $cached_config;
            return $this->_vlv_indexes_and_searches;
        }

        $search_filter = '(objectclass=vlvsearch)';
        $index_filter  = '(objectclass=vlvindex)';

        $this->_debug("C: Search base dn: [$config_root_dn] scope [sub] with filter [$search_filter]");

        $search_result = ldap_search($this->conn, $config_root_dn, $search_filter, ['*'], 0, 0, 0);

        if ($search_result === false) {
            $this->_debug("S: " . ldap_error($this->conn));
            return [];
        }

        $this->_debug("S: " . ldap_count_entries($this->conn, $search_result) . " record(s) found");

        $vlv_searches = new Net_LDAP3_Result($this->conn, $config_root_dn, $search_filter, 'sub', $search_result);

        if ($vlv_searches->count() < 1) {
            return [];
        }

        foreach ($vlv_searches->entries(true) as $vlv_search_dn => $vlv_search_attrs) {
            // The attributes we are interested in are as follows:
            $_vlv_base_dn = self::unified_dn($vlv_search_attrs['vlvbase']);
            $_vlv_scope   = $vlv_search_attrs['vlvscope'];
            $_vlv_filter  = $vlv_search_attrs['vlvfilter'];

            $this->_debug("C: Search base dn: [$vlv_search_dn] scope [sub] with filter [$index_filter]");

            // Multiple indexes may exist
            $index_result = ldap_search($this->conn, $vlv_search_dn, $index_filter, ['*'], 0, 0, 0);

            if ($index_result === false) {
                $this->_debug("S: " . ldap_error($this->conn));
                continue;
            }

            $this->_debug("S: " . ldap_count_entries($this->conn, $index_result) . " record(s) found");

            $vlv_indexes = new Net_LDAP3_Result($this->conn, $vlv_search_dn, $index_filter, 'sub', $index_result);
            $vlv_indexes = $vlv_indexes->entries(true);

            // Reset this one for each VLV search.
            $_vlv_sort = [];

            foreach ($vlv_indexes as $vlv_index_dn => $vlv_index_attrs) {
                $_vlv_sort[] = explode(' ', trim($vlv_index_attrs['vlvsort']));
            }

            $this->_vlv_indexes_and_searches[] = [
                $_vlv_base_dn => [
                    'scope'  => self::scopeint2str($_vlv_scope),
                    'filter' => strtolower($_vlv_filter),
                    'sort'   => $_vlv_sort,
                ],
            ];
        }

        // cache this
        $this->set_cache_data('vlvconfig', $this->_vlv_indexes_and_searches);

        return $this->_vlv_indexes_and_searches;
    }

    private function init_schema()
    {
        // use PEAR include if autoloading failed
        if (!class_exists('Net_LDAP2')) {
            require_once('Net/LDAP2.php');
        }

        $port = $this->config_get('port', 389);
        $tls  = $this->config_get('use_tls', false);

        foreach ((array) $this->config_get('hosts') as $host) {
            $ldap_uri = $this->_host2uri($host, $port);
            $this->_debug("C: Connect [{$ldap_uri}]");

            $_ldap_cfg = [
                'host'   => $host,
                'port'   => $port,
                'tls'    => $tls,
                'version' => 3,
                'binddn' => $this->config_get('service_bind_dn'),
                'bindpw' => $this->config_get('service_bind_pw')
            ];

            $_ldap_schema_cache_cfg = [
                'max_age' => 86400,
                'path' => sprintf(
                    '%s/%s-Net_LDAP2_Schema.cache',
                    sys_get_temp_dir() ?: '/tmp',
                    str_replace(['://', '/'], ':', $ldap_uri)
                ),
            ];

            $_ldap = Net_LDAP2::connect($_ldap_cfg);

            if (!is_a($_ldap, 'Net_LDAP2_Error')) {
                $this->_debug("S: OK");
                break;
            }

            $this->_debug("S: NOT OK");
            $this->_debug($_ldap->getMessage());
        }

        if (is_a($_ldap, 'Net_LDAP2_Error')) {
            return null;
        }

        $_ldap_schema_cache = new Net_LDAP2_SimpleFileSchemaCache($_ldap_schema_cache_cfg);

        $_ldap->registerSchemaCache($_ldap_schema_cache);

        // TODO: We should learn what LDAP tech. we're running against.
        // Perhaps with a scope base objectclass recognize rootdse entry
        $schema_root_dn = $this->config_get('schema_root_dn');

        if (!$schema_root_dn) {
            $_schema = $_ldap->schema();
        }

        return $_schema;
    }

    protected function list_group_member($dn, $members, $recurse = true)
    {
        $this->_debug("Net_LDAP3::list_group_member($dn)");

        $members       = (array) $members;
        $group_members = [];

        // remove possible 'count' item
        unset($members['count']);

        // Use the member attributes to return an array of member ldap objects
        // NOTE that the member attribute is supposed to contain a DN
        foreach ($members as $member) {
            $member_entry = $this->get_entry_attributes($member, ['member', 'uniquemember', 'memberurl', 'objectclass']);

            if (empty($member_entry)) {
                continue;
            }

            $group_members[$member] = $member;

            if ($recurse) {
                // Nested groups
                $group_group_members = $this->list_group_members($member, $member_entry);
                if ($group_group_members) {
                    $group_members = array_merge($group_group_members, $group_members);
                }
            }
        }

        return array_filter($group_members);
    }

    protected function list_group_uniquemember($dn, $uniquemembers, $recurse = true)
    {
        $this->_debug("Net_LDAP3::list_group_uniquemember($dn)", $entry);

        $uniquemembers = (array) $uniquemembers;
        $group_members = [];

        // remove possible 'count' item
        unset($uniquemembers['count']);

        foreach ($uniquemembers as $member) {
            $member_entry = $this->get_entry_attributes($member, ['member', 'uniquemember', 'memberurl', 'objectclass']);

            if (empty($member_entry)) {
                continue;
            }

            $group_members[$member] = $member;

            if ($recurse) {
                // Nested groups
                $group_group_members = $this->list_group_members($member, $member_entry);
                if ($group_group_members) {
                    $group_members = array_merge($group_group_members, $group_members);
                }
            }
        }

        return array_filter($group_members);
    }

    protected function list_group_memberurl($dn, $memberurls, $recurse = true)
    {
        $this->_debug("Net_LDAP3::list_group_memberurl($dn)");

        $group_members = [];
        $memberurls    = (array) $memberurls;
        $attributes    = ['member', 'uniquemember', 'memberurl', 'objectclass'];

        // remove possible 'count' item
        unset($memberurls['count']);

        foreach ($memberurls as $url) {
            $ldap_uri = $this->parse_memberurl($url);
            $result   = $this->search($ldap_uri[3], $ldap_uri[6], 'sub', $attributes);

            if (!$result) {
                continue;
            }

            foreach ($result->entries(true) as $entry_dn => $_entry) {
                $group_members[$entry_dn] = $entry_dn;
                $this->_debug("Found {$entry_dn}");

                if ($recurse) {
                    // Nested group
                    $group_group_members = $this->list_group_members($entry_dn, $_entry);
                    if ($group_group_members) {
                        $group_members = array_merge($group_members, $group_group_members);
                    }
                }
            }
        }

        return array_filter($group_members);
    }

    /**
     * memberUrl attribute parser
     *
     * @param string $url URL string
     *
     * @return array URL elements
     */
    protected function parse_memberurl($url)
    {
        preg_match('/(.*):\/\/(.*)\/(.*)\?(.*)\?(.*)\?(.*)/', $url, $matches);
        return $matches;
    }

    protected function modify_entry_attributes($subject_dn, $attributes)
    {
        if (is_array($attributes['rename']) && !empty($attributes['rename'])) {
            $olddn      = $attributes['rename']['dn'];
            $newrdn     = $attributes['rename']['new_rdn'];
            $new_parent = isset($attributes['rename']['new_parent']) ? $attributes['rename']['new_parent'] : null;

            $this->_debug("C: Rename $olddn to $newrdn,$new_parent");

            // Note: for some reason the operation fails if RDN contains special characters
            // and last argument of ldap_rename() is set to TRUE. That's why we use FALSE.
            // However, we need to modify RDN attribute value later, otherwise it
            // will contain an array of previous and current values
            for ($i = 1; $i >= 0; $i--) {
                $result = ldap_rename($this->conn, $olddn, $newrdn, $new_parent, $i == 1);
                if ($result) {
                    break;
                }
            }

            if ($result) {
                $this->_debug("S: OK");

                if ($new_parent) {
                    $subject_dn = $newrdn . ',' . $new_parent;
                }
                else {
                    $old_parent_dn_components = ldap_explode_dn($olddn, 0);
                    unset($old_parent_dn_components['count']);
                    $old_rdn       = array_shift($old_parent_dn_components);
                    $old_parent_dn = implode(',', $old_parent_dn_components);
                    $subject_dn    = $newrdn . ',' . $old_parent_dn;
                }

                // modify RDN attribute value, see note above
                if (!$i && empty($attributes['replace'][$attr])) {
                    list($attr, $val) = explode('=', $newrdn, 2);
                    $attributes['replace'][$attr] = self::quote_string($val, true, true);
                }
            }
            else {
                $this->_debug("S: " . ldap_error($this->conn));
                $this->_warning("LDAP: Failed to rename $olddn to $newrdn,$new_parent. " . ldap_error($this->conn));
                return false;
            }
        }

        if (is_array($attributes['replace']) && !empty($attributes['replace'])) {
            $this->_debug("C: Mod-Replace $subject_dn: " . json_encode($attributes['replace']));

            $result = ldap_mod_replace($this->conn, $subject_dn, $attributes['replace']);

            if ($result) {
                $this->_debug("S: OK");
            }
            else {
                $this->_debug("S: " . ldap_error($this->conn));
                $this->_warning("LDAP: Failed to replace attributes on $subject_dn: " . $this->_encode_attrs($attributes['replace']));
                return false;
            }
        }

        if (is_array($attributes['del']) && !empty($attributes['del'])) {
            $this->_debug("C: Mod-Delete $subject_dn: " . json_encode($attributes['del']));

            $result = ldap_mod_del($this->conn, $subject_dn, $attributes['del']);

            if ($result) {
                $this->_debug("S: OK");
            }
            else {
                $this->_debug("S: " . ldap_error($this->conn));
                $this->_warning("LDAP: Failed to delete attributes on $subject_dn: " . $this->_encode_attrs($attributes['del']));
                return false;
            }
        }

        if (is_array($attributes['add']) && !empty($attributes['add'])) {
            $this->_debug("C: Mod-Add $subject_dn: " . json_encode($attributes['add']));

            $result = ldap_mod_add($this->conn, $subject_dn, $attributes['add']);

            if ($result) {
                $this->_debug("S: OK");
            }
            else {
                $this->_debug("S: " . ldap_error($this->conn));
                $this->_warning("LDAP: Failed to add attributes on $subject_dn: " . $this->_encode_attrs($attributes['add']));
                return false;
            }
        }

        return true;
    }

    protected function parse_aclrights(&$attributes, $attribute_value)
    {
        $components      = explode(':', $attribute_value);
        $_acl_target     = array_shift($components);
        $_acl_value      = trim(implode(':', $components));
        $_acl_components = explode(';', $_acl_target);

        switch ($_acl_components[1]) {
            case "entryLevel":
                $attributes['entrylevelrights'] = [];
                $_acl_value = explode(',', $_acl_value);

                foreach ($_acl_value as $right) {
                    list($method, $bool) = explode(':', $right);
                    if ($bool == "1" && !in_array($method, $attributes['entrylevelrights'])) {
                        $attributes['entrylevelrights'][] = $method;
                    }
                }

                break;

            case "attributeLevel":
                $attributes['attributelevelrights'][$_acl_components[2]] = [];
                $_acl_value = explode(',', $_acl_value);

                foreach ($_acl_value as $right) {
                    list($method, $bool) = explode(':', $right);
                    if ($bool == "1" && !in_array($method, $attributes['attributelevelrights'][$_acl_components[2]])) {
                        $attributes['attributelevelrights'][$_acl_components[2]][] = $method;
                    }
                }

                break;

            default:
                break;
        }
    }

    protected function parse_attribute_level_rights($attribute_value)
    {
        $attribute_value  = str_replace(", ", ",", $attribute_value);
        $attribute_values = explode(",", $attribute_value);
        $attribute_value  = [];

        foreach ($attribute_values as $access_right) {
            $access_right_components = explode(":", $access_right);
            $access_attribute        = strtolower(array_shift($access_right_components));
            $access_value            = array_shift($access_right_components);

            $attribute_value[$access_attribute] = [];

            for ($i = 0; $i < strlen($access_value); $i++) {
                $method = $this->attribute_level_rights_map[substr($access_value, $i, 1)];

                if (!in_array($method, $attribute_value[$access_attribute])) {
                    $attribute_value[$access_attribute][] = $method;
                }
            }
        }

        return $attribute_value;
    }

    protected function parse_entry_level_rights($attribute_value)
    {
        $_attribute_value = [];

        for ($i = 0; $i < strlen($attribute_value); $i++) {
            $method = $this->entry_level_rights_map[substr($attribute_value, $i, 1)];

            if (!in_array($method, $_attribute_value)) {
                $_attribute_value[] = $method;
            }
        }

        return $_attribute_value;
    }

    protected function supported_controls()
    {
        $metadata = $this->server_metadata();

        return (array) $metadata['supportedcontrol'];
    }

    protected function vendor_name()
    {
        $metadata = $this->server_metadata();

        return (string) $metadata['vendorname'];
    }

    protected function server_metadata()
    {
        if ($this->_metadata === null) {
            $this->_debug("Obtaining LDAP server metadata");

            $result = $this->get_entry('', ['vendorname', 'supportedcontrol']);

            if ($result) {
                $this->_metadata = $result;
                $this->_debug("LDAP Server metadata: " . var_export($result, true));
            }
            else {
                $this->_metadata = [];
                $this->_warning("LDAP: Failed to get server metadata");
            }
        }

        return $this->_metadata;
    }

    protected function _alert()
    {
        $this->__log(LOG_ALERT, func_get_args());
    }

    protected function _critical()
    {
        $this->__log(LOG_CRIT, func_get_args());
    }

    protected function _debug()
    {
        $this->__log(LOG_DEBUG, func_get_args());
    }

    protected function _emergency()
    {
        $this->__log(LOG_EMERG, func_get_args());
    }

    protected function _error()
    {
        $this->__log(LOG_ERR, func_get_args());
    }

    protected function _info()
    {
        $this->__log(LOG_INFO, func_get_args());
    }

    protected function _notice()
    {
        $this->__log(LOG_NOTICE, func_get_args());
    }

    protected function _warning()
    {
        $this->__log(LOG_WARNING, func_get_args());
    }

    /**
     *  Log a message.
     */
    private function __log($level, $args)
    {
        $msg = [];

        foreach ($args as $arg) {
            $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
        }

        if (!empty($this->_log_hook)) {
            call_user_func_array($this->_log_hook, [$level, $msg]);
            return;
        }

        if ($this->debug_level > 0) {
            syslog($level, implode("\n", $msg));
        }
    }

    /**
     * Convert attributes array into a string for logging
     * Remove plain text passwords
     */
    private function _encode_attrs($attributes)
    {
        if (isset($attributes['userpassword'])) {
            $attributes['userpassword'] = '*';
        }

        return json_encode($attributes);
    }

    /**
     * Quotes attribute value string
     *
     * @param string $str     Attribute value
     * @param bool   $dn      True if the attribute is a DN
     * @param bool   $reverse Do reverse replacement
     *
     * @return string Quoted string
     */
    public static function quote_string($str, $is_dn = false, $reverse = false)
    {
        // take first entry if array given
        if (is_array($str)) {
            $str = reset($str);
        }

        if ($is_dn) {
            $replace = [
                ',' => '\2c',
                '=' => '\3d',
                '+' => '\2b',
                '<' => '\3c',
                '>' => '\3e',
                ';' => '\3b',
                "\\"=> '\5c',
                '"' => '\22',
                '#' => '\23'
            ];
        }
        else {
            $replace = [
                '*' => '\2a',
                '(' => '\28',
                ')' => '\29',
                "\\" => '\5c',
                '/' => '\2f'
            ];
        }

        if ($reverse) {
            return str_replace(array_values($replace), array_keys($replace), $str);
        }

        return strtr($str, $replace);
    }

    /**
     * Unify DN string for comparison
     *
     * @para string $str DN string
     *
     * @return string Unified DN string
     */
    public static function unified_dn($str)
    {
        $result = [];

        foreach (explode(',', $str) as $token) {
            list($attr, $value) = strpos($token, '=') !== false ? explode('=', $token, 2) : [$token, ''];

            $pos = 0;
            while (preg_match('/\\\\[0-9a-fA-F]{2}/', $value, $matches, PREG_OFFSET_CAPTURE, $pos)) {
                $char  = chr(hexdec(substr($matches[0][0], 1)));
                $pos   = $matches[0][1];
                $value = substr_replace($value, $char, $pos, 3);
                $pos += 1;
            }

            $result[] = $attr . '=' . self::quote_string($value, true);
        }

        return implode(',', $result);
    }

    private function _fuzzy_search_prefix()
    {
        switch ($this->config_get("fuzzy_search", 2)) {
            case 2:
                return "*";
                break;
            case 1:
            case 0:
            default:
                return "";
                break;
        }
    }

    private function _fuzzy_search_suffix()
    {
        switch ($this->config_get("fuzzy_search", 2)) {
            case 2:
                return "*";
                break;
            case 1:
                return "*";
            case 0:
            default:
                return "";
                break;
        }
    }

    /**
     * Return the search string value to be used in VLV controls
     *
     * @param array        $sort   List of attributes in vlv index
     * @param array|string $search Search string or attribute => value hash
     *
     * @return string Search string
     */
    private function _vlv_search($sort, $search)
    {
        if (!empty($this->additional_filter)) {
            $this->_debug("Not setting a VLV search filter because we already have a filter");
            return;
        }

        if (empty($search)) {
            return;
        }

        foreach ((array) $search as $attr => $value) {
            if ($attr && !in_array(strtolower($attr), $sort)) {
                $this->_debug("Cannot use VLV search using attribute not indexed: $attr (not in " . var_export($sort, true) . ")");
                return;
            }
            else {
                return $value . $this->_fuzzy_search_suffix();
            }
        }
    }

    /**
     * Set server controls for Virtual List View (paginated listing)
     */
    private function _vlv_set_controls($sort, $list_page, $page_size, $search = null)
    {
        $sort_ctrl = [
            'oid'   => self::CONTROL_SORT_REQUEST,
            'value' => self::_sort_ber_encode($sort)
        ];

        if (!empty($search)) {
            $this->_debug("_vlv_set_controls to include search: " . var_export($search, true));
        }

        $vlv_ctrl  = [
            'oid' => self::CONTROL_VLV_REQUEST,
            'value' => self::_vlv_ber_encode(
                    $offset = ($list_page-1) * $page_size + 1,
                    $page_size,
                    $search
            ),
            'iscritical' => true
        ];

        $this->_debug("C: set controls sort=" . join(' ', unpack('H'.(strlen($sort_ctrl['value'])*2), $sort_ctrl['value']))
            . " (" . implode(',', (array) $sort) . ");"
            . " vlv=" . join(' ', (unpack('H'.(strlen($vlv_ctrl['value'])*2), $vlv_ctrl['value']))) . " ($offset/$page_size)");

        $controls = [$sort_ctrl, $vlv_ctrl];

        if (PHP_VERSION_ID >= 70305) {
            return $controls;
        }

        if (!ldap_set_option($this->conn, LDAP_OPT_SERVER_CONTROLS, $controls)) {
            $this->_debug("S: ".ldap_error($this->conn));
            return false;
        }

        return true;
    }

    /**
     * create ber encoding for sort control
     *
     * @param array List of cols to sort by
     * @return string BER encoded option value
     */
    private static function _sort_ber_encode($sortcols)
    {
        $str = '';
        foreach (array_reverse((array)$sortcols) as $col) {
            $ber_val = self::_string2hex($col);

            // 30 = ber sequence with a length of octet value
            // 04 = octet string with a length of the ascii value
            $oct = self::_ber_addseq($ber_val, '04');
            $str = self::_ber_addseq($oct, '30') . $str;
        }

        // now tack on sequence identifier and length
        $str = self::_ber_addseq($str, '30');

        return pack('H'.strlen($str), $str);
    }

    /**
     * Generate BER encoded string for Virtual List View option
     *
     * @param integer List offset (first record)
     * @param integer Records per page
     * @return string BER encoded option value
     */
    private static function _vlv_ber_encode($offset, $rpp, $search = '')
    {
        // This string is ber-encoded, php will prefix this value with:
        // 04 (octet string) and 10 (length of 16 bytes)
        // the code behind this string is broken down as follows:
        // 30 = ber sequence with a length of 0e (14) bytes following
        // 02 = type integer (in two's complement form) with 2 bytes following (beforeCount): 01 00 (ie 0)
        // 02 = type integer (in two's complement form) with 2 bytes following (afterCount):  01 18 (ie 25-1=24)
        // a0 = type context-specific/constructed with a length of 06 (6) bytes following
        // 02 = type integer with 2 bytes following (offset): 01 01 (ie 1)
        // 02 = type integer with 2 bytes following (contentCount):  01 00

        // whith a search string present:
        // 81 = type context-specific/constructed with a length of 04 (4) bytes following (the length will change here)
        // 81 indicates a user string is present where as a a0 indicates just a offset search
        // 81 = type context-specific/constructed with a length of 06 (6) bytes following

        // the following info was taken from the ISO/IEC 8825-1:2003 x.690 standard re: the
        // encoding of integer values (note: these values are in
        // two-complement form so since offset will never be negative bit 8 of the
        // leftmost octet should never by set to 1):
        // 8.3.2: If the contents octets of an integer value encoding consist
        // of more than one octet, then the bits of the first octet (rightmost) and bit 8
        // of the second (to the left of first octet) octet:
        // a) shall not all be ones; and
        // b) shall not all be zero

        if ($search) {
            $search  = preg_replace('/[^-[:alpha:] ,.()0-9]+/', '', $search);
            $ber_val = self::_string2hex($search);
            $str     = self::_ber_addseq($ber_val, '81');
        }
        else {
            // construct the string from right to left
            $str = "020100"; # contentCount

            // returns encoded integer value in hex format
            $ber_val = self::_ber_encode_int($offset);

            // calculate octet length of $ber_val
            $str = self::_ber_addseq($ber_val, '02') . $str;

            // now compute length over $str
            $str = self::_ber_addseq($str, 'a0');
        }

        // now tack on records per page
        $str = "020100" . self::_ber_addseq(self::_ber_encode_int($rpp-1), '02') . $str;

        // now tack on sequence identifier and length
        $str = self::_ber_addseq($str, '30');

        return pack('H'.strlen($str), $str);
    }

    /**
     * Add BER sequence with correct length and the given identifier
     */
    private static function _ber_addseq($str, $identifier)
    {
        $len = dechex(strlen($str)/2);
        if (strlen($len) % 2 != 0) {
            $len = '0'.$len;
        }

        return $identifier . $len . $str;
    }

    /**
     * Returns BER encoded integer value in hex format
     */
    private static function _ber_encode_int($offset)
    {
        $val    = dechex($offset);
        $prefix = '';

        // check if bit 8 of high byte is 1
        if (preg_match('/^[89abcdef]/', $val)) {
            $prefix = '00';
        }

        if (strlen($val)%2 != 0) {
            $prefix .= '0';
        }

        return $prefix . $val;
    }

    /**
     * Returns ascii string encoded in hex
     */
    private static function _string2hex($str)
    {
        return implode(unpack("H*", $str));
    }

    /**
     * Convert LDAP host/port into URI
     */
    private static function _host2uri($host, $port = null)
    {
        if (stripos($host, 'ldapi://') === 0) {
            return $host;
        }

        if (strpos($host, '://') === false) {
            $host = ($port == 636 ? 'ldaps' : 'ldap') . '://' . $host;
        }

        if ($port && !preg_match('/:[0-9]+$/', $host)) {
            $host .= ':' . $port;
        }

        return $host;
    }

    /**
     * Get global handle for cache access
     *
     * @return object Cache object
     */
    public function get_cache()
    {
        if ($this->cache === true) {
            // no memcache support in PHP
            if (!class_exists('Memcache')) {
                $this->cache = false;
                return false;
            }

            // add all configured hosts to pool
            $pconnect = $this->config_get('memcache_pconnect');
            $hosts    = $this->config_get('memcache_hosts');

            if ($hosts) {
                $this->cache        = new Memcache;
                $this->mc_available = 0;

                $hosts = explode(',', $hosts);
                foreach ($hosts as $host) {
                    $host = trim($host);
                    if (substr($host, 0, 7) != 'unix://') {
                        list($host, $port) = explode(':', $host);
                        if (!$port) $port = 11211;
                    }
                    else {
                        $port = 0;
                    }

                    $this->mc_available += intval($this->cache->addServer(
                        $host, $port, $pconnect, 1, 1, 15, false, [$this, 'memcache_failure']));
                }

                // test connection and failover (will result in $this->mc_available == 0 on complete failure)
                $this->cache->increment('__CONNECTIONTEST__', 1);  // NOP if key doesn't exist
            }

            if (!$this->mc_available) {
                $this->cache = false;
            }
        }

        return $this->cache;
    }

    /**
     * Callback for memcache failure
     */
    public function memcache_failure($host, $port)
    {
        static $seen = [];

        // only report once
        if (!$seen["$host:$port"]++) {
            $this->mc_available--;
            $this->_error("LDAP: Memcache failure on host $host:$port");
        }
    }

    /**
     * Get cached data
     *
     * @param string $key Cache key
     *
     * @return mixed Cached value
     */
    public function get_cache_data($key)
    {
        if ($cache = $this->get_cache()) {
            return $cache->get($key);
        }
    }

    /**
     * Store cached data
     *
     * @param string $key  Cache key
     * @param mixed  $data Data
     * @param int    $ttl  Cache TTL in seconds
     *
     * @return bool False on failure or when cache is disabled, True if data was saved succesfully
     */
    public function set_cache_data($key, $data, $ttl = 3600)
    {
        if ($cache = $this->get_cache()) {
            $flags = defined('MEMCACHE_COMPRESSED') ? MEMCACHE_COMPRESSED : 0;
            if (!method_exists($cache, 'replace') || !$cache->replace($key, $data, $flags, $ttl)) {
                return $cache->set($key, $data, $flags, $ttl);
            }
            else {
                return true;
            }
        }

        return false;
    }

    /**
     * Translate a domain name into it's corresponding root dn.
     *
     * @param string $domain Domain name
     *
     * @return string|bool Domain root DN or False on error
     */
    public function domain_root_dn($domain)
    {
        if (empty($domain)) {
            return false;
        }

        $ckey = 'domain.root::' . $domain;
        if (array_key_exists($ckey, $this->icache)) {
            return $this->icache[$ckey];
        }

        $this->_debug("Net_LDAP3::domain_root_dn($domain)");

        if ($entry_attrs = $this->find_domain($domain)) {
            $name_attribute = $this->config_get('domain_name_attribute');

            if (empty($name_attribute)) {
                $name_attribute = 'associateddomain';
            }

            if (is_array($entry_attrs)) {
                if (!empty($entry_attrs['inetdomainbasedn'])) {
                    $domain_root_dn = $entry_attrs['inetdomainbasedn'];
                }
                else {
                    if (is_array($entry_attrs[$name_attribute])) {
                        $domain_root_dn = $this->_standard_root_dn($entry_attrs[$name_attribute][0]);
                    }
                    else {
                        $domain_root_dn = $this->_standard_root_dn($entry_attrs[$name_attribute]);
                    }
                }
            }
        }

        if (empty($domain_root_dn)) {
            $domain_root_dn = $this->_standard_root_dn($domain);
        }

        $this->_debug("Net_LDAP3::domain_root_dn() result: $domain_root_dn");

        return $this->icache[$ckey] = $domain_root_dn;
    }

    /**
     * Find domain by name
     *
     * @param string $domain     Domain name
     * @param array  $attributes Result attributes
     *
     * @return array|bool Domain attributes (plus 'dn' attribute) or False if not found
     */
    public function find_domain($domain, $attributes = ['*'])
    {
        if (empty($domain)) {
            return false;
        }

        $ckey  = 'domain::' . $domain;
        $ickey = $ckey . '::' . md5(implode(',', $attributes));

        if (array_key_exists($ickey, $this->icache)) {
            return $this->icache[$ickey];
        }

        $this->_debug("Net_LDAP3::find_domain($domain)");

        // use cache
        $domain_dn = $this->get_cache_data($ckey);

        if ($domain_dn) {
            $result = $this->get_entry_attributes($domain_dn, $attributes);

            if (!empty($result)) {
                $result['dn'] = $domain_dn;
            }
            else {
                $result = false;
            }
        }
        else if ($domain_base_dn = $this->config_get('domain_base_dn')) {
            $domain_filter  = $this->config_get('domain_filter');

            if (strpos($domain_filter, '%s') !== false) {
                $domain_filter = str_replace('%s', self::quote_string($domain), $domain_filter);
            }
            else {
                $name_attribute = $this->config_get('domain_name_attribute');
                if (empty($name_attribute)) {
                    $name_attribute = 'associateddomain';
                }

                $domain_filter = "(&" . $domain_filter . "(" . $name_attribute . "=" . self::quote_string($domain) . "))";
            }

            if ($result = $this->search($domain_base_dn, $domain_filter, 'sub', $attributes)) {
                $result       = $result->entries(true);
                $domain_dn    = key($result);

                if (empty($domain_dn)) {
                    $result = false;
                }
                else {
                    $result       = $result[$domain_dn];
                    $result['dn'] = $domain_dn;

                    // cache domain DN
                    $this->set_cache_data($ckey, $domain_dn);
                }
            }
        }

        $this->_debug("Net_LDAP3::find_domain() result: " . var_export($result, true));

        return $this->icache[$ickey] = $result;
    }

    /**
     * From a domain name, such as 'kanarip.com', create a standard root
     * dn, such as 'dc=kanarip,dc=com'.
     *
     * As the parameter $associatedDomains, either pass it an array (such
     * as may have been returned by ldap_get_entries() or perhaps
     * ldap_list()), where the function will assume the first value
     * ($array[0]) to be the uber-level domain name, or pass it a string
     * such as 'kanarip.nl'.
     *
     * @return string
     */
    protected function _standard_root_dn($associatedDomains)
    {
        if (is_array($associatedDomains)) {
            // Usually, the associatedDomain in position 0 is the naming attribute associatedDomain
            if ($associatedDomains['count'] > 1) {
                // Issue a debug message here
                $relevant_associatedDomain = $associatedDomains[0];
            }
            else {
                $relevant_associatedDomain = $associatedDomains[0];
            }
        }
        else {
            $relevant_associatedDomain = $associatedDomains;
        }

        return 'dc=' . implode(',dc=', explode('.', $relevant_associatedDomain));
    }
}
PK|c�\{�_���pear/net_ldap2/composer.jsonnu�[���{
    "name": "pear/net_ldap2",
    "description": "Object oriented interface for searching and manipulating LDAP-entries",
    "license": "LGPL-3.0",
    "type": "library",
    "keywords": [
        "pear",
        "ldap"
    ],
    "homepage": "http://pear.php.net/package/Net_LDAP2",
    "support": {
        "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Net_LDAP2",
        "source": "https://github.com/pear/Net_LDAP2"
    },
    "require": {
        "pear/pear-core-minimal": "^1.10.1",
        "ext-ldap": "*"
    },
    "include-path": ["."],
    "autoload": {
        "classmap": [
            "Net/"
        ]
    },
    "require-dev": {
        "phpunit/phpunit": "^9 || ^10.3"
    }
}
PK|c�\�����pear/net_ldap2/LICENSEnu�[���                 GNU LESSER GENERAL PUBLIC LICENSE
                       Version 3, 29 June 2007

 Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.


  This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.

  0. Additional Definitions.

  As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.

  "The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.

  An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.

  A "Combined Work" is a work produced by combining or linking an
Application with the Library.  The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".

  The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.

  The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.

  1. Exception to Section 3 of the GNU GPL.

  You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.

  2. Conveying Modified Versions.

  If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:

   a) under this License, provided that you make a good faith effort to
   ensure that, in the event an Application does not supply the
   function or data, the facility still operates, and performs
   whatever part of its purpose remains meaningful, or

   b) under the GNU GPL, with none of the additional permissions of
   this License applicable to that copy.

  3. Object Code Incorporating Material from Library Header Files.

  The object code form of an Application may incorporate material from
a header file that is part of the Library.  You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:

   a) Give prominent notice with each copy of the object code that the
   Library is used in it and that the Library and its use are
   covered by this License.

   b) Accompany the object code with a copy of the GNU GPL and this license
   document.

  4. Combined Works.

  You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:

   a) Give prominent notice with each copy of the Combined Work that
   the Library is used in it and that the Library and its use are
   covered by this License.

   b) Accompany the Combined Work with a copy of the GNU GPL and this license
   document.

   c) For a Combined Work that displays copyright notices during
   execution, include the copyright notice for the Library among
   these notices, as well as a reference directing the user to the
   copies of the GNU GPL and this license document.

   d) Do one of the following:

       0) Convey the Minimal Corresponding Source under the terms of this
       License, and the Corresponding Application Code in a form
       suitable for, and under terms that permit, the user to
       recombine or relink the Application with a modified version of
       the Linked Version to produce a modified Combined Work, in the
       manner specified by section 6 of the GNU GPL for conveying
       Corresponding Source.

       1) Use a suitable shared library mechanism for linking with the
       Library.  A suitable mechanism is one that (a) uses at run time
       a copy of the Library already present on the user's computer
       system, and (b) will operate properly with a modified version
       of the Library that is interface-compatible with the Linked
       Version.

   e) Provide Installation Information, but only if you would otherwise
   be required to provide such information under section 6 of the
   GNU GPL, and only to the extent that such information is
   necessary to install and execute a modified version of the
   Combined Work produced by recombining or relinking the
   Application with a modified version of the Linked Version. (If
   you use option 4d0, the Installation Information must accompany
   the Minimal Corresponding Source and Corresponding Application
   Code. If you use option 4d1, you must provide the Installation
   Information in the manner specified by section 6 of the GNU GPL
   for conveying Corresponding Source.)

  5. Combined Libraries.

  You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:

   a) Accompany the combined library with a copy of the same work based
   on the Library, uncombined with any other library facilities,
   conveyed under the terms of this License.

   b) Give prominent notice with the combined library that part of it
   is a work based on the Library, and explaining where to find the
   accompanying uncombined form of the same work.

  6. Revised Versions of the GNU Lesser General Public License.

  The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.

  Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.

  If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.PK|c�\������pear/net_ldap2/package.xmlnu�[���<?xml version="1.0" encoding="UTF-8"?>
<package packagerversion="1.4.11" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
http://pear.php.net/dtd/tasks-1.0.xsd
http://pear.php.net/dtd/package-2.0
http://pear.php.net/dtd/package-2.0.xsd">
    <name>Net_LDAP2</name>
    <channel>pear.php.net</channel>
    <extends>Net_LDAP</extends>
    <summary>Object oriented interface for searching and manipulating LDAP-entries</summary>
    <description>Net_LDAP2 is the successor of Net_LDAP which is a clone of Perls Net::LDAP
                object interface to directory servers. It does contain most of Net::LDAPs
                features but has some own too.
                 With Net_LDAP2 you have:
                 * A simple object-oriented interface to connections, searches entries and filters.
                 * Support for TLS and LDAP v3.
                 * Simple modification, deletion and creation of LDAP entries.
                 * Support for schema handling.

                 Net_LDAP2 layers itself on top of PHP's existing ldap extensions.
    </description>
    <lead>
        <name>Benedikt Hallinger</name>
        <user>beni</user>
        <email>beni@php.net</email>
        <active>yes</active>
    </lead>

    <!-- Information for this release -->
    <date>2023-10-12</date>
    <version>
        <release>2.3.0</release>
        <api>2.2.0</api>
    </version>
    <stability>
        <release>stable</release>
        <api>stable</api>
    </stability>
    <license>LGPLv3 License</license>
    <notes>
     * Support PHP 8.1
     * Fix unit tests on PHP 8.0.29
     * Make tests compatible with PHPUnit 9
    </notes>
    <contents>
        <dir name="/">
       <dir name="Net">
            <file name="LDAP2.php" role="php" />
            <dir name="LDAP2">
                <file name="Entry.php" role="php" />
                <file name="Filter.php" role="php" />
                <file name="RootDSE.php" role="php" />
                <file name="Schema.php" role="php" />
                <file name="Search.php" role="php" />
                <file name="Util.php" role="php" />
                <file name="LDIF.php" role="php" />
                <file name="SchemaCache.interface.php" role="php" />
                <file name="SimpleFileSchemaCache.php" role="php" />
            </dir> <!-- /LDAP2 -->
          </dir> <!-- /Net -->
          <dir name="doc">
                <file name="manual.html" role="doc" />
                <file name="README.txt" role="doc" />
                <file name="RootDSE.txt" role="doc" />
                <file name="Schema.txt" role="doc" />
                <file name="utf8.txt" role="doc" />
                <file name="examples/connecting.php" role="doc" />
                <file name="examples/fetch_entry.php" role="doc" />
                <file name="examples/search_entries.php" role="doc" />
                <file name="examples/add_entry.php" role="doc" />
                <file name="examples/modify_entry.php" role="doc" />
                <file name="examples/modify_entry2.php" role="doc" />
                <file name="examples/schema_cache.php" role="doc" />
          </dir> <!-- /doc -->
          <dir name="tests">
                <file name="phpunit.xml" role="test" />
                <file name="Net_LDAP2_TestBase.php" role="test" />
                <file name="Net_LDAP2_EntryTest.php" role="test" />
                <file name="Net_LDAP2_FilterTest.php" role="test" />
                <file name="Net_LDAP2_RootDSETest.php" role="test" />
                <file name="Net_LDAP2_SearchTest.php" role="test" />
                <file name="Net_LDAP2Test.php" role="test" />
                <file name="Net_LDAP2_UtilTest.php" role="test" />
                <file name="Net_LDAP2_LDIFTest.php" role="test" />
                <file name="ldapconfig.ini.dist" role="test" />
                <file name="ldapldifconfig.ini.dist" role="test" />
                <file name="ldif_data/base.ldif" role="test" />
                <file name="ldif_data/malformed_syntax.ldif" role="test" />
                <file name="ldif_data/malformed_syntax.ldif" role="test" />
                <file name="ldif_data/malformed_wrapping.ldif" role="test" />
                <file name="ldif_data/slapd.conf" role="test" />
                <file name="ldif_data/sorted_w40.ldif" role="test" />
                <file name="ldif_data/sorted_w50.ldif" role="test" />
                <file name="ldif_data/unsorted_w30.ldif" role="test" />
                <file name="ldif_data/unsorted_w50.ldif" role="test" />
                <file name="ldif_data/unsorted_w50_WIN.ldif" role="test" />
                <file name="ldif_data/changes.ldif" role="test" />
          </dir> <!-- /tests -->
        </dir> <!-- / -->
    </contents>
    <dependencies>
        <required>
            <php>
                <min>7.4</min>
            </php>
            <pearinstaller>
                <min>1.10.1</min>
            </pearinstaller>
            <extension>
                <name>ldap</name>
            </extension>
        </required>
    </dependencies>
    <phprelease />

    <!-- CHANGELOG -->
    <changelog>
        <release>
            <version>
                <release>0.1</release>
                <api>0.1</api>
            </version>
            <stability>
                <release>beta</release>
                <api>beta</api>
            </stability>
            <date>2003-06-23</date>
            <license>LGPL License</license>
            <notes>Initial release
            </notes>
        </release>
        <release>
            <version>
                <release>0.2</release>
                <api>0.2</api>
            </version>
            <stability>
                <release>beta</release>
                <api>beta</api>
            </stability>
            <date>2003-08-23</date>
            <license>LGPL License</license>
            <notes>Fixed a lot of bugs that jumped in during the pearification process
            </notes>
        </release>
        <release>
            <version>
                <release>0.3</release>
                <api>0.3</api>
            </version>
            <stability>
                <release>beta</release>
                <api>beta</api>
            </stability>
            <date>2003-09-21</date>
            <license>LGPL License</license>
            <notes>More bug squashing! Much better errorhandling in the -&gt;search() function.
                   Also, all errors that create a Pear_error now includes the errornumber if
                   appropriate (i.e. it was an ldap generated error).
            </notes>
        </release>
        <release>
            <version>
                <release>0.4</release>
                <api>0.4</api>
            </version>
            <stability>
                <release>beta</release>
                <api>beta</api>
            </stability>
            <date>2003-10-01</date>
            <license>LGPL License</license>
            <notes>Many more bugfixes. Jan Wagner fixed the shift_entry function.
                   Also a new Net_LDAP_Entry::modify function has been added that goes far making a simple way to modify entries.
            </notes>
        </release>
        <release>
            <version>
                <release>0.5</release>
                <api>0.5</api>
            </version>
            <stability>
                <release>beta</release>
                <api>beta</api>
            </stability>
            <date>2003-10-11</date>
            <license>LGPL License</license>
            <notes>Jan Wagner Contributed a new RootDSE object and a Schema object and some fixes to the Net_LDAP::search() method
                   The new Net_ldap_entry::modify() method seems to work very nice now.
            </notes>
        </release>
        <release>
            <version>
                <release>0.6</release>
                <api>0.6</api>
            </version>
            <stability>
                <release>beta</release>
                <api>beta</api>
            </stability>
            <date>2003-10-17</date>
            <license>LGPL License</license>
            <notes>New Net_LDAP::ArrayUTF8Decode and Net_LDAP::ArrayUTF8Encode functions. These are used by the Net_LDAP::Entry objects to ensure that things work ok.
            </notes>
        </release>
        <release>
            <version>
                <release>0.6.3</release>
                <api>0.6.3</api>
            </version>
            <stability>
                <release>beta</release>
                <api>beta</api>
            </stability>
            <date>2003-11-12</date>
            <license>LGPL License</license>
            <notes>It seems that 0.6.2 was out too fast. So this is mainly a bugfix release:
                   - Removed remaining Net_LDAP::UTF8Encode and Net_LDAP::UTF8Decode calls in Net_LDAP_Entry,
                     which stopped attributes() and get_entry() from working
                   - The UTF8 functions somehow got outside the Net_LDAP class ... FIXED.
                   - The usuage example of the last release was wrong. We decided to move UTF8 handling into Net_LDAP.
                     Handling should be done this way:

                         $attr = $ldap-&gt;utf8Encode($attr);
                         $entry-&gt;modify($attr);
                         $attr = $ldap-&gt;utf8Decode( $entry-&gt;attributes() );
                   - This means Net_LDAP_Util is useless right now, but will be extended in the future.
                   - Jan did a complete overhaul of the phpdoc stuff. Everything seems to be fine now with phpDocumentor.
            </notes>
        </release>
        <release>
            <date>2007-02-05</date>
            <version>
                <release>0.7.0</release>
                <api>0.7.0</api>
            </version>
            <stability>
                <release>beta</release>
                <api>beta</api>
            </stability>
            <license>LGPL License</license>
            <notes>This long awaited release of Net_LDAP features more stability and new functionality.
                   The main changes are:
                   - Rewrite of much of the code (including some api changes!)
                   - LOTS of fixed bugs!
                   - New class for easy filter handling (Net_LDAP_Filter)
                   - Sorting support for searchresults (including multivalued sorting!)
                   - Searched Entries can now be fetched as_struct() (array)!
                   - Some memory optimizations

                   Please note also that Net_LDAPs configuration changed slightly. Please see $_config in LDAP.php for the new parameters.
            </notes>
        </release>
        <release>
            <date>2007-02-23</date>
            <version>
                <release>0.7.1</release>
                <api>0.7.0</api>
            </version>
            <stability>
                <release>beta</release>
                <api>beta</api>
            </stability>
            <license>LGPL License</license>
            <notes>This is not just a bugfix release of 0.7.0 but also introduces some internal optimisations:
                   - Fixed a connection bug whith LDAP V3 only servers
                   - clearer sanitizing of the host config parameter
            </notes>
        </release>
        <release>
            <date>2007-05-07</date>
            <version>
                <release>0.7.2</release>
                <api>0.7.2</api>
            </version>
            <stability>
                <release>beta</release>
                <api>beta</api>
            </stability>
            <license>LGPL License</license>
            <notes>This release features some internal code movements to be more compatible to PERL::Net_LDAP.
               The movements include:
                  * Removed UTF8 en-/decoding stuff from Net_LDAP_Utils class since this was moved to Net_LDAP class in 0.6.6
                  * Moved Filter encoding from Net_LDAP_Filter to Net_LDAP_Util
                  * Moved ldap_explode_dn_escaped() from Net_LDAP_Entry to Net_LDAP_Util
                  * Added perls functions from Net_LDAP::Util to our Util class, but they still need some work
               Please note that ldap_explode_dn_escaped() is not available from Net_LDAP_Entry anymore.

               Additionally some new functionality has been introduced:
                  * You can now apply regular expressions directly to a entrys attributes
                    and don't need to fetch the attribute values manually.
                  * Net_LDAP_Schema can check if a attributes syntax is binary

               The following bugs have been resolved:
                  * Connections to LDAP servers that forbid anonymous binds are possible again
                  * The JPEG attribute is now properly returned as binary value instead of string
                  * If the array describing selected attributes in searches didn't contain consecutive keys, there was a problem sometimes
                  * Some PHP5 return issues
            </notes>
        </release>
        <release>
            <date>2007-06-12</date>
            <version>
               <release>0.7.3</release>
               <api>0.7.2</api>
            </version>
            <stability>
                <release>beta</release>
                <api>beta</api>
            </stability>
            <license>LGPL License</license>
            <notes>This release introduces some example files showing you in detail how to work with Net_LDAP.
                   Additionally, a bug at recursive deletion of an entry is fixed and the Net_LDAP_Filter
                   class is slightly optimized.
             </notes>
        </release>
        <release>
                <date>2007-06-20</date>
                <version>
                    <release>1.0.0RC1</release>
                    <api>1.0.0RC1</api>
                </version>
                <stability>
                    <release>beta</release>
                    <api>beta</api>
                </stability>
                <license>LGPL License</license>
                <notes>Again some small Bugfixes, most notably a bug within $ldap->modify() that occured when using the
                    combined 'changes' array.
                    Besides that, $search->popEntry() and the corresponding alias pop_entry() has been implemented.
                    Net_LDAP_Util::unescape_filter_value() is available too now and Net_LDAP_Util::escape_filter_value()
                    can handle ASCII chars smaller than 32. Above that, Net_LDAP_Util::canonical_dn() has been fully implemented.
                    A new method createFresh() was added to Net_LDAP_Entry, so creation of initial entries is more
                    standardized and clearer.
                    A new example is available, describing the $ldap->modify() method.
                    The add_entry.php example was updated, it shows the use of Net_LDAP_Entry::createFresh().
                    $ldap->add() links unlinked entries now to the connection used for the add.
                    Some new additional utility functions are available in Net_LDAP_Util to assist you in handling attributes and dns.
                    The LDAP-Rename command now uses this functions to deal with DN escaping issues.
                    Please note that ldap_explode_dn_escaped() is not available from Net_LDAP_Util anymore; it got superseeded by Net_LDAP_Util::ldap_explode_dn().
                </notes>
        </release>
        <release>
            <date>2007-06-28</date>
            <version>
                <release>1.0.0RC2</release>
                <api>1.0.0RC2</api>
            </version>
            <stability>
                <release>beta</release>
                <api>beta</api>
            </stability>
            <license>LGPL License</license>
            <notes>Net_LDAP->dnExists() uses the Util class now, which makes it safer.
                A new move() method is available from Net_LDAP.
                Please note, that the copy() method was removed from the Net_LDAP_Entry class since
                people would expect attribute moving because of the overall API of Net_LDAP.
                Instead use the more failsafer copy() from Net_LDAP.
            </notes>
        </release>
        <release>
            <date>2007-07-24</date>
            <version>
                <release>1.0.0RC3</release>
                <api>1.0.0RC3</api>
            </version>
            <stability>
                <release>beta</release>
                <api>beta</api>
            </stability>
            <license>LGPL License</license>
            <notes>
                Fixed a bug with dnExists() that was caused mainly by bad behavior of Net_LDAP_UTIL::ldap_explode_dn().
                Fixed a bug with call time pass-by-reference if calling $entry->update(); however this inflicted a API change:
                The parameter $ldap is not available anymore, you need to use $entry->setLDAP() prior update now if you want to change the LDAP
                object. This brought us a more logical API now, since Entry operations should be performed by the Net_LDAP object.
            </notes>
        </release>
        <release>
            <date>2007-09-18</date>
            <version>
                <release>1.0.0RC4</release>
                <api>1.0.0RC4</api>
            </version>
            <stability>
                <release>beta</release>
                <api>beta</api>
            </stability>
            <license>LGPL License</license>
            <notes>
                - Fixed some minor bugs of RC3
                - Reintroduced $ldap parameter for
                $entry-&lt;update(), but it is not prefferred to use this way.
                    The Parameter is there for perl interface compatibility
            </notes>
        </release>
        <release>
            <date>2007-10-29</date>
            <version>
                <release>1.0.0</release>
                <api>1.0.0</api>
            </version>
            <stability>
                <release>stable</release>
                <api>stable</api>
            </stability>
            <license>LGPL License</license>
            <notes>
                After more than four years of development, we are very proud to announce the
                        ~ FIRST STABLE Net_LDAP RELEASE 1.0.0 ~
                Net_LDAP ist tested now and should be stable enough for production use.
                The API is finished so far, no changes should be neccessary in the future.

                Changes to Release candidate 4:
                    - Implemented PHPUnit tests
                    - Fixed some minor bugs of RC4 (including the schema loading warning-generation)
                    - Fixed several bugs in Net_LDAP_Util
                    - Improved Net_LDAP_Filter and Net_LDAP_Util error handling and code cleanness
                    - Completely implemented Net_LDAP_Filter perl interface
                    - Improved several doc comments and fixed some spelling errors
            </notes>
        </release>
        <release>
            <date>2008-01-14</date>
            <version>
                <release>1.1.0a1</release>
                <api>1.1.0a1</api>
            </version>
            <stability>
                <release>beta</release>
                <api>beta</api>
            </stability>
            <license>LGPL License</license>
            <notes>
                * Added LDIF reading and writing support
                * Fixed minor issues of 1.0.0 release
            </notes>
        </release>
        <release>
            <date>2008-01-21</date>
            <version>
                <release>1.1.0a2</release>
                <api>1.1.0a2</api>
            </version>
            <stability>
                <release>beta</release>
                <api>beta</api>
            </stability>
            <license>LGPL License</license>
            <notes>
                * Added parseLines() to Net_LDAP_LDIF for more convinience
                * Added some handy methods to Net_LDAP_Entry
                * Enhanced tests
            </notes>
        </release>
        <release>
            <date>2008-02-27</date>
            <version>
                <release>1.1.0</release>
                <api>1.1.0</api>
            </version>
            <stability>
                <release>stable</release>
                <api>stable</api>
            </stability>
            <license>LGPL License</license>
            <notes>* Fixed a little bug at cross directory move
            * Fixed a bug when deleting a subtree containing several subentries that failed if
            one called dnExists() prior calling delete()
            * Fixed some minor bugs at NeT_LDAP->move() and Net_LDAP->dnExists()
            * Added Net_LDAP tests
            * Changed API of Net_LDAP->copy() to only accept Net_LDAP_Entry objects, because with DNs
            Attribute values will be lost
            /!\ This is the last release of Net_LDAP supporting PHP4 /!\
            </notes>
        </release>
        <release>
            <date>2008-03-19</date>
            <version>
                <release>2.0.0RC1</release>
                <api>2.0.0RC1</api>
            </version>
            <stability>
                <release>beta</release>
                <api>beta</api>
            </stability>
            <license>LGPL License</license>
            <notes>/!\ This release is PHP5 only, replacing the Net_LDAP package.
                    If you still need PHP4 support, use Net_LDAP instead.
                * Implemented iterable search results so one can use foreach() with Net_LDAP2_Search objects
                * Fixed a problem with Net_LDAP2_LDIF and files with DOS line endings
   </notes>
  </release>
  <release>
   <date>2008-03-20</date>
   <version>
    <release>2.0.0RC2</release>
    <api>2.0.0RC2</api>
   </version>
   <stability>
    <release>beta</release>
    <api>beta</api>
   </stability>
   <license>LGPL License</license>
   <notes>
* Implemented PHP5 language stuff (thanks to Torsten Roehr for his helpful patches)
* Changed Net_LDAP2-&gt;_markAsNew() to public access, since this is required by the api
and may be useful to developers too
* Changed API to create schema object, there is now a factory. Net_LDAP2-&gt;schema() calls
that factory now instead of fetching the Schema itself
* Changed API to create rootDSE object, there is now a factory. Net_LDAP2-&gt;rootDSE() calls
that factory now instead of fetching the rootDSE itself
* Net_LDAP2_Entry has a new factory constructor: createConnected() can be used to
establish a new Net_LDAP2_Entry object that represents an already existing entry inside
some directory
   </notes>
  </release>
  <release>
   <date>2008-06-04</date>
   <version>
    <release>2.0.0RC3</release>
    <api>2.0.0RC3</api>
   </version>
   <stability>
    <release>beta</release>
    <api>beta</api>
   </stability>
   <license>LGPL License</license>
   <notes>
* New constructor factory for Entry objects: createExisting()
* Several small improvements
* New method Entry-&gt;isNew()
* Net_LDAP2-&gt;search() and Net_LDAP2-&gt;dnExists() can handle entry objects now
* Added &quot;present&quot; matching rule as stated by RFC 2254 (is an alias of the former &quot;any&quot;)
* Bugfix in filter class for approx matching and not combination
* Bugfix for Schema-&gt;isBinary() bug if unknown attribute type is requested
   </notes>
  </release>
  <release>
   <date>2008-10-16</date>
   <version>
    <release>2.0.0RC4</release>
    <api>2.0.0RC4</api>
   </version>
   <stability>
    <release>beta</release>
    <api>beta</api>
   </stability>
   <license>LGPL License</license>
   <notes>
* Net_LDAP2_Filter::create*() methods are declared static now
* Net_LDAP2 is able to reconnect now in case link is down during operation (thanks Del)
* Complex updates fail: under some circumstances, $entry-&gt;update() will fail. This is
caused by mandatory attributes set and the internal behaviour of Net_LDAP2. A
workaround is documented in the code and in the user manual.
* Bugfixes in Dels patches. Soemtimes there where endless loops and deleting entries
did not always succeed.
* Bugfix to reset unicodePwd (Active Directory): a new $force parameter was introduced to Net_LDAP2_Entry-&gt;replace()
that forces &quot;replace&quot; mode. If not set and attribute is empty (or could not be read like in the AD case)
replace() resulted in Net_LDAP2 thinking it should add the attribute. This can now be overriden.
* Bugfix in unit tests: the Net_LDAP2Test suite had huge memory consumtion caused by a little error in
Net_LDAP2-&gt;checkLDAPExtension(). If that method is called before any Net_LDAP class was instanciated, a PEAR
error is returned instead of the documented Net_LDAP2_Error which causes the unit test to plot out very much
debug information.
   </notes>
  </release>
  <release>
   <date>2009-01-09</date>
   <version>
    <release>2.0.0RC5</release>
    <api>2.0.0RC5</api>
   </version>
   <stability>
    <release>beta</release>
    <api>beta</api>
   </stability>
   <license>LGPL License</license>
   <notes>
* Fixed a little issue with repetive adding the same attribute value
* Fixed Bug #14886 that caused problems with OpenLDAP and V3 only connects
* Fixed Bug #14903 and #15494, now bind attempt is also encrypted if TLS is requested
* Fixed issue with repetitve adding or deleting values causing Net_LDAP to send the same change multiple times
* Fixed Bug #15364 that caused a problem with setting the ldap version if only one version is supported by server
   </notes>
  </release>
  <release>
   <date>2009-05-08</date>
   <version>
    <release>2.0.0RC6</release>
    <api>2.0.0RC6</api>
   </version>
   <stability>
    <release>beta</release>
    <api>beta</api>
   </stability>
   <license>LGPL License</license>
   <notes>
* Bugfix in LDIF writer concerning needless base64 encoding of values
* New schema caching facility
* PHPCS fixes, some comment changes and general code cleanup
   </notes>
  </release>
  <release>
   <date>2009-05-28</date>
   <version>
    <release>2.0.0</release>
    <api>2.0.0</api>
   </version>
   <stability>
    <release>stable</release>
    <api>stable</api>
   </stability>
   <license>LGPL License</license>
   <notes>
* Fixed bug #16242 (arguments for createFresh in wrong order when calling Net_LDAP2_Entry::createFresh())
* Fixed bug #16253 (strict checking of isError())
   </notes>
  </release>
  <release>
   <date>2009-06-15</date>
   <version>
    <release>2.0.1</release>
    <api>2.0.0</api>
   </version>
   <stability>
    <release>stable</release>
    <api>stable</api>
   </stability>
   <license>LGPL License</license>
   <notes>
* Fixed Bugs #16272 and #16278 (Problem in starttls function)
   </notes>
  </release>
  <release>
   <date>2009-06-29</date>
   <version>
    <release>2.0.2</release>
    <api>2.0.0</api>
   </version>
   <stability>
    <release>stable</release>
    <api>stable</api>
   </stability>
   <license>LGPL License</license>
   <notes>
* Fixed some bugs that rendered the new schema caching facility unusable
   </notes>
  </release>
  <release>
   <date>2009-07-03</date>
   <version>
    <release>2.0.3</release>
    <api>2.0.0</api>
   </version>
   <stability>
    <release>stable</release>
    <api>stable</api>
   </stability>
   <license>LGPL License</license>
   <notes>
* Fixed LDAP RFC-1777 violation: bind has to be performed prior setting LDAP version
* Fixed wrong version reported from version()
   </notes>
  </release>
  <release>
   <date>2009-07-08</date>
   <version>
    <release>2.0.4</release>
    <api>2.0.0</api>
   </version>
   <stability>
    <release>stable</release>
    <api>stable</api>
   </stability>
   <license>LGPL License</license>
   <notes>
* Fixed Bug #16404 (Bind fails at OpenLDAP with protocol error)
   </notes>
  </release>
  <release>
   <date>2009-07-14</date>
   <version>
    <release>2.0.5</release>
    <api>2.0.0</api>
   </version>
   <stability>
    <release>stable</release>
    <api>stable</api>
   </stability>
   <license>LGPL License</license>
   <notes>
* Fixed bug #16438 (SimplefileSchemaCache could not be configured due to wrong var name)
   </notes>
  </release>
  <release>
   <date>2009-08-04</date>
   <version>
    <release>2.0.6</release>
    <api>2.0.0</api>
   </version>
   <stability>
    <release>stable</release>
    <api>stable</api>
   </stability>
   <license>LGPLv3 License</license>
   <notes>
* switched LICENSE to LGPL v3
* added some documentation
   </notes>
  </release>
  <release>
   <date>2009-10-28</date>
   <version>
    <release>2.0.7</release>
    <api>2.0.0</api>
   </version>
   <stability>
    <release>stable</release>
    <api>stable</api>
   </stability>
   <license>LGPLv3 License</license>
   <notes>
* Corrected bug #16738 (Problem with Net_LDAP2_Filter::parse() with complex filter, when first subfilter was an combined filter too)
   </notes>
  </release>
  <release>
   <version>
    <release>2.0.8</release>
    <api>2.0.0</api>
   </version>
   <stability>
    <release>stable</release>
    <api>stable</api>
   </stability>
   <date>2010-02-12</date>
   <license>LGPLv3 License</license>
   <notes>
* Fixed Bug #16940 (Net_LDAP2::startTLS should ignore errors before ldap_start_tls() being called)
* Fixed Bug #17023 (improper handling of wrapped lines in LDIF files)
   </notes>
  </release>
  <release>
   <date>2010-02-16</date>
   <version>
    <release>2.0.8</release>
    <api>2.0.0</api>
   </version>
   <stability>
    <release>stable</release>
    <api>stable</api>
   </stability>
   <license>LGPLv3 License</license>
   <notes>
* Fixed Bug #16940 (Net_LDAP2::startTLS should ignore errors before ldap_start_tls() being called)
* Fixed Bug #17023 (improper handling of wrapped lines in LDIF files)
* Fixed Bug #17057 (problem with parsing certain NOT-Filters)
   </notes>
  </release>
  <release>
    <date>2010-02-16</date>
    <version>
        <release>2.0.9</release>
        <api>2.0.0</api>
    </version>
    <stability>
        <release>stable</release>
        <api>stable</api>
    </stability>
    <license>LGPLv3 License</license>
    <notes>
        * fixed package (package.xml was unclean so 2.0.8 wouldnt install)
    </notes>
  </release>
  <release>
    <date>2010-08-23</date>
      <version>
          <release>2.0.10</release>
          <api>2.0.0</api>
      </version>
      <stability>
          <release>stable</release>
          <api>stable</api>
      </stability>
      <license>LGPLv3 License</license>
      <notes>
* Added schema handling methods to make schema checks more easily accessible
* Bugfix for #17245. The check in the code was not working properly. Schema checking is considered the users responsibility.
  If now an attribute is requested that is not set at the entry, an empty string is returned.
* Bugfix for #17770. Some Net_LDAP2 files were included with relative path ("Util.php"), not absolute ("Net/LDAP2/Util.php").
* Bugfix for #17314. LDIF support for attributes with modifiers ("attr1;binary").
      </notes>
  </release>
  <release>
    <date>2011-01-19</date>
    <version>
        <release>2.0.11</release>
        <api>2.0.0</api>
    </version>
    <stability>
        <release>stable</release>
        <api>stable</api>
    </stability>
    <license>LGPLv3 License</license>
    <notes>
* (doc issue) Fix for #17861: Missing komma in example
* Fix for #18202: Adding attributes to a Fresh Entry saving and laterly updating fails
    </notes>
  </release>
  <release>
    <date>2011-10-27</date>
    <version>
        <release>2.0.12</release>
        <api>2.0.0</api>
    </version>
    <stability>
        <release>stable</release>
        <api>stable</api>
    </stability>
    <license>LGPLv3 License</license>
    <notes>
        * inmproved performance with large search results
        * Fixed some minor issues with Net_LDAP2_Filter and Net_LDAP2->dnExists()
        * Added NOT filter to Net_LDAP2_Filter::create() so negating is more easily now
    </notes>
  </release>
  <release>
    <date>2013-12-09</date>
    <version>
        <release>2.1.0</release>
        <api>2.0.0</api>
    </version>
    <stability>
        <release>stable</release>
        <api>stable</api>
    </stability>
    <license>LGPLv3 License</license>
    <notes>
        * New feature: Filter::matches() can do simple filtering on entry sets (supported: equals, contain, begin, end, any; NOT, AND, OR. Filtering is simple based on regexp, no schema checks and matchRules yet!)
        * Fixed minor bugs in Filter, LDAP and Entry class
        * Util::split_attribute_string(): Added support for extended match operators from filters
        * Util::split_attribute_string(): Added support for delimeter retrieval
    </notes>
  </release>

  <release>
    <date>2015-10-30</date>
    <version>
        <release>2.2.0</release>
        <api>2.2.0</api>
    </version>
    <stability>
        <release>stable</release>
        <api>stable</api>
    </stability>
    <license>LGPLv3 License</license>
    <notes>
     * Fix bug #20969: Fatal error with PEAR 1.10.0 / constructor visiblity
     * Add support for PHP 7
     * Improve unit tests
    </notes>
  </release>
  <release>
      <date>2015-10-30</date>
    <version>
        <release>2.2.0</release>
        <api>2.2.0</api>
    </version>
    <stability>
        <release>stable</release>
        <api>stable</api>
    </stability>
    <license>LGPLv3 License</license>
    <notes>
     * Fix bug #20969: Fatal error with PEAR 1.10.0 / constructor visiblity
     * Add support for PHP 7
     * Improve unit tests
    </notes>
  </release>

  <release>
    <date>2023-02-23</date>
    <version>
        <release>2.2.1</release>
        <api>2.2.0</api>
    </version>
    <stability>
        <release>stable</release>
        <api>stable</api>
    </stability>
    <license>LGPLv3 License</license>
    <notes>
     * Fix: remove unused $ber property
    </notes>
  </release>

 </changelog>
</package>
PK|c�\���=\\pear/net_ldap2/Net/LDAP2.phpnu�[���<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
/**
* File containing the Net_LDAP2 interface class.
*
* PHP version 5
*
* @category  Net
* @package   Net_LDAP2
* @author    Tarjej Huse <tarjei@bergfald.no>
* @author    Jan Wagner <wagner@netsols.de>
* @author    Del <del@babel.com.au>
* @author    Benedikt Hallinger <beni@php.net>
* @copyright 2003-2007 Tarjej Huse, Jan Wagner, Del Elson, Benedikt Hallinger
* @license   http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
* @version   SVN: $Id$
* @link      http://pear.php.net/package/Net_LDAP2/
*/

/**
* Package includes.
*/
require_once 'PEAR.php';
require_once 'Net/LDAP2/RootDSE.php';
require_once 'Net/LDAP2/Schema.php';
require_once 'Net/LDAP2/Entry.php';
require_once 'Net/LDAP2/Search.php';
require_once 'Net/LDAP2/Util.php';
require_once 'Net/LDAP2/Filter.php';
require_once 'Net/LDAP2/LDIF.php';
require_once 'Net/LDAP2/SchemaCache.interface.php';
require_once 'Net/LDAP2/SimpleFileSchemaCache.php';

/**
*  Error constants for errors that are not LDAP errors.
*/
define('NET_LDAP2_ERROR', 1000);

/**
* Net_LDAP2 Version
*/
define('NET_LDAP2_VERSION', '2.1.0');

/**
* Net_LDAP2 - manipulate LDAP servers the right way!
*
* @category  Net
* @package   Net_LDAP2
* @author    Tarjej Huse <tarjei@bergfald.no>
* @author    Jan Wagner <wagner@netsols.de>
* @author    Del <del@babel.com.au>
* @author    Benedikt Hallinger <beni@php.net>
* @copyright 2003-2007 Tarjej Huse, Jan Wagner, Del Elson, Benedikt Hallinger
* @license   http://www.gnu.org/copyleft/lesser.html LGPL
* @link      http://pear.php.net/package/Net_LDAP2/
*/
class Net_LDAP2 extends PEAR
{
    /**
    * Class configuration array
    *
    * host     = the ldap host to connect to
    *            (may be an array of several hosts to try)
    * port     = the server port
    * version  = ldap version (defaults to v 3)
    * starttls = when set, ldap_start_tls() is run after connecting.
    * bindpw   = no explanation needed
    * binddn   = the DN to bind as.
    * basedn   = ldap base
    * options  = hash of ldap options to set (opt => val)
    * filter   = default search filter
    * scope    = default search scope
    *
    * Newly added in 2.0.0RC4, for auto-reconnect:
    * auto_reconnect  = if set to true then the class will automatically
    *                   attempt to reconnect to the LDAP server in certain
    *                   failure conditionswhen attempting a search, or other
    *                   LDAP operation.  Defaults to false.  Note that if you
    *                   set this to true, calls to search() may block
    *                   indefinitely if there is a catastrophic server failure.
    * min_backoff     = minimum reconnection delay period (in seconds).
    * current_backoff = initial reconnection delay period (in seconds).
    * max_backoff     = maximum reconnection delay period (in seconds).
    *
    * @access protected
    * @var array
    */
    protected $_config = array('host'            => 'localhost',
                               'port'            => 389,
                               'version'         => 3,
                               'starttls'        => false,
                               'binddn'          => '',
                               'bindpw'          => '',
                               'basedn'          => '',
                               'options'         => array(),
                               'filter'          => '(objectClass=*)',
                               'scope'           => 'sub',
                               'auto_reconnect'  => false,
                               'min_backoff'     => 1,
                               'current_backoff' => 1,
                               'max_backoff'     => 32);

    /**
    * List of hosts we try to establish a connection to
    *
    * @access protected
    * @var array
    */
    protected $_host_list = array();

    /**
    * List of hosts that are known to be down.
    *
    * @access protected
    * @var array
    */
    protected $_down_host_list = array();

    /**
    * LDAP resource link.
    *
    * @access protected
    * @var resource
    */
    protected $_link = false;

    /**
    * Net_LDAP2_Schema object
    *
    * This gets set and returned by {@link schema()}
    *
    * @access protected
    * @var object Net_LDAP2_Schema
    */
    protected $_schema = null;

    /**
    * Schema cacher function callback
    *
    * @see registerSchemaCache()
    * @var string
    */
    protected $_schema_cache = null;

    /**
    * Cache for attribute encoding checks
    *
    * @access protected
    * @var array Hash with attribute names as key and boolean value
    *            to determine whether they should be utf8 encoded or not.
    */
    protected $_schemaAttrs = array();

    /**
    * Cache for rootDSE objects
    *
    * Hash with requested rootDSE attr names as key and rootDSE object as value
    *
    * Since the RootDSE object itself may request a rootDSE object,
    * {@link rootDse()} caches successful requests.
    * Internally, Net_LDAP2 needs several lookups to this object, so
    * caching increases performance significally.
    *
    * @access protected
    * @var array
    */
    protected $_rootDSE_cache = array();

    /**
    * Returns the Net_LDAP2 Release version, may be called statically
    *
    * @static
    * @return string Net_LDAP2 version
    */
    public static function getVersion()
    {
        return NET_LDAP2_VERSION;
    }

    /**
    * Configure Net_LDAP2, connect and bind
    *
    * Use this method as starting point of using Net_LDAP2
    * to establish a connection to your LDAP server.
    *
    * Static function that returns either an error object or the new Net_LDAP2
    * object. Something like a factory. Takes a config array with the needed
    * parameters.
    *
    * @param array $config Configuration array
    *
    * @access public
    * @return Net_LDAP2_Error|Net_LDAP2   Net_LDAP2_Error or Net_LDAP2 object
    */
    public static function connect($config = array())
    {
        $ldap_check = self::checkLDAPExtension();
        if (self::iserror($ldap_check)) {
            return $ldap_check;
        }

        @$obj = new Net_LDAP2($config);

        // todo? better errorhandling for setConfig()?

        // connect and bind with credentials in config
        $err = $obj->bind();
        if (self::isError($err)) {
            return $err;
        }

        return $obj;
    }

    /**
    * Net_LDAP2 constructor
    *
    * Sets the config array
    *
    * Please note that the usual way of getting Net_LDAP2 to work is
    * to call something like:
    * <code>$ldap = Net_LDAP2::connect($ldap_config);</code>
    *
    * @param array $config Configuration array
    *
    * @access protected
    * @return void
    * @see $_config
    */
    public function __construct($config = array())
    {
        parent::__construct('Net_LDAP2_Error');
        $this->setConfig($config);
    }

    /**
    * Sets the internal configuration array
    *
    * @param array $config Configuration array
    *
    * @access protected
    * @return void
    */
    protected function setConfig($config)
    {
        //
        // Parameter check -- probably should raise an error here if config
        // is not an array.
        //
        if (! is_array($config)) {
            return;
        }

        foreach ($config as $k => $v) {
            if (isset($this->_config[$k])) {
                $this->_config[$k] = $v;
            } else {
                // map old (Net_LDAP2) parms to new ones
                switch($k) {
                case "dn":
                    $this->_config["binddn"] = $v;
                    break;
                case "password":
                    $this->_config["bindpw"] = $v;
                    break;
                case "tls":
                    $this->_config["starttls"] = $v;
                    break;
                case "base":
                    $this->_config["basedn"] = $v;
                    break;
                }
            }
        }

        //
        // Ensure the host list is an array.
        //
        if (is_array($this->_config['host'])) {
            $this->_host_list = $this->_config['host'];
        } else {
            if (strlen($this->_config['host']) > 0) {
                $this->_host_list = array($this->_config['host']);
            } else {
                $this->_host_list = array();
                // ^ this will cause an error in performConnect(),
                // so the user is notified about the failure
            }
        }

        //
        // Reset the down host list, which seems like a sensible thing to do
        // if the config is being reset for some reason.
        //
        $this->_down_host_list = array();
    }

    /**
    * Bind or rebind to the ldap-server
    *
    * This function binds with the given dn and password to the server. In case
    * no connection has been made yet, it will be started and startTLS issued
    * if appropiate.
    *
    * The internal bind configuration is not being updated, so if you call
    * bind() without parameters, you can rebind with the credentials
    * provided at first connecting to the server.
    *
    * @param string $dn       Distinguished name for binding
    * @param string $password Password for binding
    *
    * @access public
    * @return Net_LDAP2_Error|true    Net_LDAP2_Error object or true
    */
    public function bind($dn = null, $password = null)
    {
        // fetch current bind credentials
        if (is_null($dn)) {
            $dn = $this->_config["binddn"];
        }
        if (is_null($password)) {
            $password = $this->_config["bindpw"];
        }

        // Connect first, if we haven't so far.
        // This will also bind us to the server.
        if ($this->_link === false) {
            // store old credentials so we can revert them later
            // then overwrite config with new bind credentials
            $olddn = $this->_config["binddn"];
            $oldpw = $this->_config["bindpw"];

            // overwrite bind credentials in config
            // so performConnect() knows about them
            $this->_config["binddn"] = $dn;
            $this->_config["bindpw"] = $password;

            // try to connect with provided credentials
            $msg = $this->performConnect();

            // reset to previous config
            $this->_config["binddn"] = $olddn;
            $this->_config["bindpw"] = $oldpw;

            // see if bind worked
            if (self::isError($msg)) {
                return $msg;
            }
        } else {
            // do the requested bind as we are
            // asked to bind manually
            if (is_null($dn)) {
                // anonymous bind
                $msg = @ldap_bind($this->_link);
            } else {
                // privileged bind
                $msg = @ldap_bind($this->_link, $dn, $password);
            }
            if (false === $msg) {
                return PEAR::raiseError("Bind failed: " .
                                        @ldap_error($this->_link),
                                        @ldap_errno($this->_link));
            }
        }
        return true;
    }

    /**
    * Connect to the ldap-server
    *
    * This function connects to the LDAP server specified in
    * the configuration, binds and set up the LDAP protocol as needed.
    *
    * @access protected
    * @return Net_LDAP2_Error|true    Net_LDAP2_Error object or true
    */
    protected function performConnect()
    {
        // Note: Connecting is briefly described in RFC1777.
        // Basicly it works like this:
        //  1. set up TCP connection
        //  2. secure that connection if neccessary
        //  3a. setLDAPVersion to tell server which version we want to speak
        //  3b. perform bind
        //  3c. setLDAPVersion to tell server which version we want to speak
        //      together with a test for supported versions
        //  4. set additional protocol options

        // Return true if we are already connected.
        if ($this->_link !== false) {
            return true;
        }

        // Connnect to the LDAP server if we are not connected.  Note that
        // with some LDAP clients, ldapperformConnect returns a link value even
        // if no connection is made.  We need to do at least one anonymous
        // bind to ensure that a connection is actually valid.
        //
        // Ref: http://www.php.net/manual/en/function.ldap-connect.php

        // Default error message in case all connection attempts
        // fail but no message is set
        $current_error = new PEAR_Error('Unknown connection error');

        // Catch empty $_host_list arrays.
        if (!is_array($this->_host_list) || count($this->_host_list) == 0) {
            $current_error = PEAR::raiseError('No Servers configured! Please '.
               'pass in an array of servers to Net_LDAP2');
            return $current_error;
        }

        // Cycle through the host list.
        foreach ($this->_host_list as $host) {

            // Ensure we have a valid string for host name
            if (is_array($host)) {
                $current_error = PEAR::raiseError('No Servers configured! '.
                   'Please pass in an one dimensional array of servers to '.
                   'Net_LDAP2! (multidimensional array detected!)');
                continue;
            }

            // Skip this host if it is known to be down.
            if (in_array($host, $this->_down_host_list)) {
                continue;
            }

            // Record the host that we are actually connecting to in case
            // we need it later.
            $this->_config['host'] = $host;

            // Attempt a connection.
            $this->_link = @ldap_connect($host, $this->_config['port']);
            if (false === $this->_link) {
                $current_error = PEAR::raiseError('Could not connect to ' .
                    $host . ':' . $this->_config['port']);
                $this->_down_host_list[] = $host;
                continue;
            }

            // If we're supposed to use TLS, do so before we try to bind,
            // as some strict servers only allow binding via secure connections
            if ($this->_config["starttls"] === true) {
                if (self::isError($msg = $this->startTLS())) {
                    $current_error           = $msg;
                    $this->_link             = false;
                    $this->_down_host_list[] = $host;
                    continue;
                }
            }

            // Try to set the configured LDAP version on the connection if LDAP
            // server needs that before binding (eg OpenLDAP).
            // This could be necessary since rfc-1777 states that the protocol version
            // has to be set at the bind request.
            // We use force here which means that the test in the rootDSE is skipped;
            // this is neccessary, because some strict LDAP servers only allow to
            // read the LDAP rootDSE (which tells us the supported protocol versions)
            // with authenticated clients.
            // This may fail in which case we try again after binding.
            // In this case, most probably the bind() or setLDAPVersion()-call
            // below will also fail, providing error messages.
            $version_set = false;
            $ignored_err = $this->setLDAPVersion(0, true);
            if (!self::isError($ignored_err)) {
                $version_set = true;
            }

            // Attempt to bind to the server. If we have credentials configured,
            // we try to use them, otherwise its an anonymous bind.
            // As stated by RFC-1777, the bind request should be the first
            // operation to be performed after the connection is established.
            // This may give an protocol error if the server does not support
            // V2 binds and the above call to setLDAPVersion() failed.
            // In case the above call failed, we try an V2 bind here and set the
            // version afterwards (with checking to the rootDSE).
            $msg = $this->bind();
            if (self::isError($msg)) {
                // The bind failed, discard link and save error msg.
                // Then record the host as down and try next one
                if ($msg->getCode() == 0x02 && !$version_set) {
                    // provide a finer grained error message
                    // if protocol error arieses because of invalid version
                    $msg = new Net_LDAP2_Error($msg->getMessage().
                        " (could not set LDAP protocol version to ".
                        $this->_config['version'].")",
                        $msg->getCode());
                }
                $this->_link             = false;
                $current_error           = $msg;
                $this->_down_host_list[] = $host;
                continue;
            }

            // Set desired LDAP version if not successfully set before.
            // Here, a check against the rootDSE is performed, so we get a
            // error message if the server does not support the version.
            // The rootDSE entry should tell us which LDAP versions are
            // supported. However, some strict LDAP servers only allow
            // bound suers to read the rootDSE.
            if (!$version_set) {
                if (self::isError($msg = $this->setLDAPVersion())) {
                    $current_error           = $msg;
                    $this->_link             = false;
                    $this->_down_host_list[] = $host;
                    continue;
                }
            }

            // Set LDAP parameters, now we know we have a valid connection.
            if (isset($this->_config['options']) &&
                is_array($this->_config['options']) &&
                count($this->_config['options'])) {
                foreach ($this->_config['options'] as $opt => $val) {
                    $err = $this->setOption($opt, $val);
                    if (self::isError($err)) {
                        $current_error           = $err;
                        $this->_link             = false;
                        $this->_down_host_list[] = $host;
                        continue 2;
                    }
                }
            }

            // At this stage we have connected, bound, and set up options,
            // so we have a known good LDAP server.  Time to go home.
            return true;
        }


        // All connection attempts have failed, return the last error.
        return $current_error;
    }

    /**
    * Reconnect to the ldap-server.
    *
    * In case the connection to the LDAP
    * service has dropped out for some reason, this function will reconnect,
    * and re-bind if a bind has been attempted in the past.  It is probably
    * most useful when the server list provided to the new() or connect()
    * function is an array rather than a single host name, because in that
    * case it will be able to connect to a failover or secondary server in
    * case the primary server goes down.
    *
    * This doesn't return anything, it just tries to re-establish
    * the current connection.  It will sleep for the current backoff
    * period (seconds) before attempting the connect, and if the
    * connection fails it will double the backoff period, but not
    * try again.  If you want to ensure a reconnection during a
    * transient period of server downtime then you need to call this
    * function in a loop.
    *
    * @access protected
    * @return Net_LDAP2_Error|true    Net_LDAP2_Error object or true
    */
    protected function performReconnect()
    {

        // Return true if we are already connected.
        if ($this->_link !== false) {
            return true;
        }

        // Default error message in case all connection attempts
        // fail but no message is set
        $current_error = new PEAR_Error('Unknown connection error');

        // Sleep for a backoff period in seconds.
        sleep($this->_config['current_backoff']);

        // Retry all available connections.
        $this->_down_host_list = array();
        $msg = $this->performConnect();

        // Bail out if that fails.
        if (self::isError($msg)) {
            $this->_config['current_backoff'] =
               $this->_config['current_backoff'] * 2;
            if ($this->_config['current_backoff'] > $this->_config['max_backoff']) {
                $this->_config['current_backoff'] = $this->_config['max_backoff'];
            }
            return $msg;
        }

        // Now we should be able to safely (re-)bind.
        $msg = $this->bind();
        if (self::isError($msg)) {
            $this->_config['current_backoff'] = $this->_config['current_backoff'] * 2;
            if ($this->_config['current_backoff'] > $this->_config['max_backoff']) {
                $this->_config['current_backoff'] = $this->_config['max_backoff'];
            }

            // _config['host'] should have had the last connected host stored in it
            // by performConnect().  Since we are unable to bind to that host we can safely
            // assume that it is down or has some other problem.
            $this->_down_host_list[] = $this->_config['host'];
            return $msg;
        }

        // At this stage we have connected, bound, and set up options,
        // so we have a known good LDAP server. Time to go home.
        $this->_config['current_backoff'] = $this->_config['min_backoff'];
        return true;
    }

    /**
    * Starts an encrypted session
    *
    * @access public
    * @return Net_LDAP2_Error|true    Net_LDAP2_Error object or true
    */
    public function startTLS()
    {
        /* Test to see if the server supports TLS first.
           This is done via testing the extensions offered by the server.
           The OID 1.3.6.1.4.1.1466.20037 tells us, if TLS is supported.
           Note, that not all servers allow to feth either the rootDSE or
           attributes over an unencrypted channel, so we must ignore errors. */
        $rootDSE = $this->rootDse();
        if (self::isError($rootDSE)) {
            /* IGNORE this error, because server may refuse fetching the
               RootDSE over an unencrypted connection. */
            //return $this->raiseError("Unable to fetch rootDSE entry ".
            //"to see if TLS is supoported: ".$rootDSE->getMessage(), $rootDSE->getCode());
        } else {
            /* Fetch suceeded, see, if the server supports TLS. Again, we
               ignore errors, because the server may refuse to return
               attributes over unencryted connections. */
            $supported_extensions = $rootDSE->getValue('supportedExtension');
            if (self::isError($supported_extensions)) {
                /* IGNORE error, because server may refuse attribute
                   returning over an unencrypted connection. */
                //return $this->raiseError("Unable to fetch rootDSE attribute 'supportedExtension' ".
                //"to see if TLS is supoported: ".$supported_extensions->getMessage(), $supported_extensions->getCode());
            } else {
                // fetch succeedet, lets see if the server supports it.
                // if not, then drop an error. If supported, then do nothing,
                // because then we try to issue TLS afterwards.
                if (!in_array('1.3.6.1.4.1.1466.20037', $supported_extensions)) {
                    return $this->raiseError("Server reports that it does not support TLS.");
                 }
            }
        }

        // Try to establish TLS.
        if (false === @ldap_start_tls($this->_link)) {
            // Starting TLS failed. This may be an error, or because
            // the server does not support it but did not enable us to
            // detect that above.
            return $this->raiseError("TLS could not be started: " .
                                    @ldap_error($this->_link),
                                    @ldap_errno($this->_link));
        } else {
            return true; // TLS is started now.
        }
    }

    /**
    * alias function of startTLS() for perl-ldap interface
    *
    * @return void
    * @see startTLS()
    */
    public function start_tls()
    {
        $args = func_get_args();
        return call_user_func_array(array( $this, 'startTLS' ), $args);
    }

    /**
    * Close LDAP connection.
    *
    * Closes the connection. Use this when the session is over.
    *
    * @return void
    */
    public function done()
    {
        $this->_Net_LDAP2();
    }

    /**
    * Alias for {@link done()}
    *
    * @return void
    * @see done()
    */
    public function disconnect()
    {
        $this->done();
    }

    /**
    * Destructor
    *
    * @access protected
    */
    public function _Net_LDAP2()
    {
        @ldap_close($this->_link);
    }

    /**
    * Add a new entryobject to a directory.
    *
    * Use add to add a new Net_LDAP2_Entry object to the directory.
    * This also links the entry to the connection used for the add,
    * if it was a fresh entry ({@link Net_LDAP2_Entry::createFresh()})
    *
    * @param Net_LDAP2_Entry $entry Net_LDAP2_Entry
    *
    * @return Net_LDAP2_Error|true    Net_LDAP2_Error object or true
    */
    public function add($entry)
    {
        if (!$entry instanceof Net_LDAP2_Entry) {
            return PEAR::raiseError('Parameter to Net_LDAP2::add() must be a Net_LDAP2_Entry object.');
        }

        // Continue attempting the add operation in a loop until we
        // get a success, a definitive failure, or the world ends.
        $foo = 0;
        while (true) {
            $link = $this->getLink();

            if ($link === false) {
                // We do not have a successful connection yet.  The call to
                // getLink() would have kept trying if we wanted one.  Go
                // home now.
                return PEAR::raiseError("Could not add entry " . $entry->dn() .
                       " no valid LDAP connection could be found.");
            }

            if (@ldap_add($link, $entry->dn(), $entry->getValues())) {
                // entry successfully added, we should update its $ldap reference
                // in case it is not set so far (fresh entry)
                if (!$entry->getLDAP() instanceof Net_LDAP2) {
                    $entry->setLDAP($this);
                }
                // store, that the entry is present inside the directory
                $entry->markAsNew(false);
                return true;
            } else {
                // We have a failure.  What type?  We may be able to reconnect
                // and try again.
                $error_code = @ldap_errno($link);
                $error_name = Net_LDAP2::errorMessage($error_code);

                if (($error_name === 'LDAP_OPERATIONS_ERROR') &&
                    ($this->_config['auto_reconnect'])) {

                    // The server has become disconnected before trying the
                    // operation.  We should try again, possibly with a different
                    // server.
                    $this->_link = false;
                    $this->performReconnect();
                } else {
                    // Errors other than the above catched are just passed
                    // back to the user so he may react upon them.
                    return PEAR::raiseError("Could not add entry " . $entry->dn() . " " .
                                            $error_name,
                                            $error_code);
                }
            }
        }
    }

    /**
    * Delete an entry from the directory
    *
    * The object may either be a string representing the dn or a Net_LDAP2_Entry
    * object. When the boolean paramter recursive is set, all subentries of the
    * entry will be deleted as well.
    *
    * @param string|Net_LDAP2_Entry $dn        DN-string or Net_LDAP2_Entry
    * @param boolean                $recursive Should we delete all children recursive as well?
    *
    * @access public
    * @return Net_LDAP2_Error|true    Net_LDAP2_Error object or true
    */
    public function delete($dn, $recursive = false)
    {
        if ($dn instanceof Net_LDAP2_Entry) {
             $dn = $dn->dn();
        }
        if (false === is_string($dn)) {
            return PEAR::raiseError("Parameter is not a string nor an entry object!");
        }
        // Recursive delete searches for children and calls delete for them
        if ($recursive) {
            $result = @ldap_list($this->_link, $dn, '(objectClass=*)', array(null), 0, 0);
            if (@ldap_count_entries($this->_link, $result)) {
                $subentry = @ldap_first_entry($this->_link, $result);
                $this->delete(@ldap_get_dn($this->_link, $subentry), true);
                while ($subentry = @ldap_next_entry($this->_link, $subentry)) {
                    $this->delete(@ldap_get_dn($this->_link, $subentry), true);
                }
            }
        }

        // Continue attempting the delete operation in a loop until we
        // get a success, a definitive failure, or the world ends.
        while (true) {
            $link = $this->getLink();

            if ($link === false) {
                // We do not have a successful connection yet.  The call to
                // getLink() would have kept trying if we wanted one.  Go
                // home now.
                return PEAR::raiseError("Could not add entry " . $dn .
                       " no valid LDAP connection could be found.");
            }

            if (@ldap_delete($link, $dn)) {
                // entry successfully deleted.
                return true;
            } else {
                // We have a failure.  What type?
                // We may be able to reconnect and try again.
                $error_code = @ldap_errno($link);
                $error_name = Net_LDAP2::errorMessage($error_code);

                if ((Net_LDAP2::errorMessage($error_code) === 'LDAP_OPERATIONS_ERROR') &&
                    ($this->_config['auto_reconnect'])) {
                    // The server has become disconnected before trying the
                    // operation.  We should try again, possibly with a
                    // different server.
                    $this->_link = false;
                    $this->performReconnect();

                } elseif ($error_code == 66) {
                    // Subentries present, server refused to delete.
                    // Deleting subentries is the clients responsibility, but
                    // since the user may not know of the subentries, we do not
                    // force that here but instead notify the developer so he
                    // may take actions himself.
                    return PEAR::raiseError("Could not delete entry $dn because of subentries. Use the recursive parameter to delete them.");

                } else {
                    // Errors other than the above catched are just passed
                    // back to the user so he may react upon them.
                    return PEAR::raiseError("Could not delete entry " . $dn . " " .
                                            $error_name,
                                            $error_code);
                }
            }
        }
    }

    /**
    * Modify an ldapentry directly on the server
    *
    * This one takes the DN or a Net_LDAP2_Entry object and an array of actions.
    * This array should be something like this:
    *
    * array('add' => array('attribute1' => array('val1', 'val2'),
    *                      'attribute2' => array('val1')),
    *       'delete' => array('attribute1'),
    *       'replace' => array('attribute1' => array('val1')),
    *       'changes' => array('add' => ...,
    *                          'replace' => ...,
    *                          'delete' => array('attribute1', 'attribute2' => array('val1')))
    *
    * The changes array is there so the order of operations can be influenced
    * (the operations are done in order of appearance).
    * The order of execution is as following:
    *   1. adds from 'add' array
    *   2. deletes from 'delete' array
    *   3. replaces from 'replace' array
    *   4. changes (add, replace, delete) in order of appearance
    * All subarrays (add, replace, delete, changes) may be given at the same time.
    *
    * The function calls the corresponding functions of an Net_LDAP2_Entry
    * object. A detailed description of array structures can be found there.
    *
    * Unlike the modification methods provided by the Net_LDAP2_Entry object,
    * this method will instantly carry out an update() after each operation,
    * thus modifying "directly" on the server.
    *
    * @param string|Net_LDAP2_Entry $entry DN-string or Net_LDAP2_Entry
    * @param array                  $parms Array of changes
    *
    * @access public
    * @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
    */
    public function modify($entry, $parms = array())
    {
        if (is_string($entry)) {
            $entry = $this->getEntry($entry);
            if (self::isError($entry)) {
                return $entry;
            }
        }
        if (!$entry instanceof Net_LDAP2_Entry) {
            return PEAR::raiseError("Parameter is not a string nor an entry object!");
        }

        // Perform changes mentioned separately
        foreach (array('add', 'delete', 'replace') as $action) {
            if (isset($parms[$action])) {
                $msg = $entry->$action($parms[$action]);
                if (self::isError($msg)) {
                    return $msg;
                }
                $entry->setLDAP($this);

                // Because the @ldap functions are called inside Net_LDAP2_Entry::update(),
                // we have to trap the error codes issued from that if we want to support
                // reconnection.
                while (true) {
                    $msg = $entry->update();

                    if (self::isError($msg)) {
                        // We have a failure.  What type?  We may be able to reconnect
                        // and try again.
                        $error_code = $msg->getCode();
                        $error_name = Net_LDAP2::errorMessage($error_code);

                        if ((Net_LDAP2::errorMessage($error_code) === 'LDAP_OPERATIONS_ERROR') &&
                            ($this->_config['auto_reconnect'])) {

                            // The server has become disconnected before trying the
                            // operation.  We should try again, possibly with a different
                            // server.
                            $this->_link = false;
                            $this->performReconnect();

                        } else {

                            // Errors other than the above catched are just passed
                            // back to the user so he may react upon them.
                            return PEAR::raiseError("Could not modify entry: ".$msg->getMessage());
                        }
                    } else {
                        // modification succeedet, evaluate next change
                        break;
                    }
                }
            }
        }

        // perform combined changes in 'changes' array
        if (isset($parms['changes']) && is_array($parms['changes'])) {
            foreach ($parms['changes'] as $action => $value) {

                // Because the @ldap functions are called inside Net_LDAP2_Entry::update,
                // we have to trap the error codes issued from that if we want to support
                // reconnection.
                while (true) {
                    $msg = $this->modify($entry, array($action => $value));

                    if (self::isError($msg)) {
                        // We have a failure.  What type?  We may be able to reconnect
                        // and try again.
                        $error_code = $msg->getCode();
                        $error_name = Net_LDAP2::errorMessage($error_code);

                        if ((Net_LDAP2::errorMessage($error_code) === 'LDAP_OPERATIONS_ERROR') &&
                            ($this->_config['auto_reconnect'])) {

                            // The server has become disconnected before trying the
                            // operation.  We should try again, possibly with a different
                            // server.
                            $this->_link = false;
                            $this->performReconnect();

                        } else {
                            // Errors other than the above catched are just passed
                            // back to the user so he may react upon them.
                            return $msg;
                        }
                    } else {
                        // modification succeedet, evaluate next change
                        break;
                    }
                }
            }
        }

        return true;
    }

    /**
    * Run a ldap search query
    *
    * Search is used to query the ldap-database.
    * $base and $filter may be ommitted. The one from config will
    * then be used. $base is either a DN-string or an Net_LDAP2_Entry
    * object in which case its DN willb e used.
    *
    * Params may contain:
    *
    * scope: The scope which will be used for searching
    *        base - Just one entry
    *        sub  - The whole tree
    *        one  - Immediately below $base
    * sizelimit: Limit the number of entries returned (default: 0 = unlimited),
    * timelimit: Limit the time spent for searching (default: 0 = unlimited),
    * attrsonly: If true, the search will only return the attribute names,
    * attributes: Array of attribute names, which the entry should contain.
    *             It is good practice to limit this to just the ones you need.
    * [NOT IMPLEMENTED]
    * deref: By default aliases are dereferenced to locate the base object for the search, but not when
    *        searching subordinates of the base object. This may be changed by specifying one of the
    *        following values:
    *
    *        never  - Do not dereference aliases in searching or in locating the base object of the search.
    *        search - Dereference aliases in subordinates of the base object in searching, but not in
    *                locating the base object of the search.
    *        find
    *        always
    *
    * Please note, that you cannot override server side limitations to sizelimit
    * and timelimit: You can always only lower a given limit.
    *
    * @param string|Net_LDAP2_Entry  $base   LDAP searchbase
    * @param string|Net_LDAP2_Filter $filter LDAP search filter or a Net_LDAP2_Filter object
    * @param array                   $params Array of options
    *
    * @access public
    * @return Net_LDAP2_Search|Net_LDAP2_Error Net_LDAP2_Search object or Net_LDAP2_Error object
    * @todo implement search controls (sorting etc)
    */
    public function search($base = null, $filter = null, $params = array())
    {
        if (is_null($base)) {
            $base = $this->_config['basedn'];
        }
        if ($base instanceof Net_LDAP2_Entry) {
            $base = $base->dn(); // fetch DN of entry, making searchbase relative to the entry
        }
        if (is_null($filter)) {
            $filter = $this->_config['filter'];
        }
        if ($filter instanceof Net_LDAP2_Filter) {
            $filter = $filter->asString(); // convert Net_LDAP2_Filter to string representation
        }
        if (PEAR::isError($filter)) {
            return $filter;
        }
        if (PEAR::isError($base)) {
            return $base;
        }

        /* setting searchparameters  */
        (isset($params['sizelimit']))  ? $sizelimit  = $params['sizelimit']  : $sizelimit = 0;
        (isset($params['timelimit']))  ? $timelimit  = $params['timelimit']  : $timelimit = 0;
        (isset($params['attrsonly']))  ? $attrsonly  = $params['attrsonly']  : $attrsonly = 0;
        (isset($params['attributes'])) ? $attributes = $params['attributes'] : $attributes = array();

        // Ensure $attributes to be an array in case only one
        // attribute name was given as string
        if (!is_array($attributes)) {
            $attributes = array($attributes);
        }

        // reorganize the $attributes array index keys
        // sometimes there are problems with not consecutive indexes
        $attributes = array_values($attributes);

        // scoping makes searches faster!
        $scope = (isset($params['scope']) ? $params['scope'] : $this->_config['scope']);

        switch ($scope) {
        case 'one':
            $search_function = 'ldap_list';
            break;
        case 'base':
            $search_function = 'ldap_read';
            break;
        default:
            $search_function = 'ldap_search';
        }

        // Continue attempting the search operation until we get a success
        // or a definitive failure.
        while (true) {
            $link = $this->getLink();
            $search = @call_user_func($search_function,
                                      $link,
                                      $base,
                                      $filter,
                                      $attributes,
                                      $attrsonly,
                                      $sizelimit,
                                      $timelimit);

            if ($err = @ldap_errno($link)) {
                if ($err == 32) {
                    // Errorcode 32 = no such object, i.e. a nullresult.
                    return $obj = new Net_LDAP2_Search ($search, $this, $attributes);
                } elseif ($err == 4) {
                    // Errorcode 4 = sizelimit exeeded.
                    return $obj = new Net_LDAP2_Search ($search, $this, $attributes);
                } elseif ($err == 87) {
                    // bad search filter
                    return $this->raiseError(Net_LDAP2::errorMessage($err) . "($filter)", $err);
                } elseif (($err == 1) && ($this->_config['auto_reconnect'])) {
                    // Errorcode 1 = LDAP_OPERATIONS_ERROR but we can try a reconnect.
                    $this->_link = false;
                    $this->performReconnect();
                } else {
                    $msg = "\nParameters:\nBase: $base\nFilter: $filter\nScope: $scope";
                    return $this->raiseError(Net_LDAP2::errorMessage($err) . $msg, $err);
                }
            } else {
                return $obj = new Net_LDAP2_Search($search, $this, $attributes);
            }
        }
    }

    /**
    * Set an LDAP option
    *
    * @param string $option Option to set
    * @param mixed  $value  Value to set Option to
    *
    * @access public
    * @return Net_LDAP2_Error|true    Net_LDAP2_Error object or true
    */
    public function setOption($option, $value)
    {
        if ($this->_link) {
            if (defined($option)) {
                if (@ldap_set_option($this->_link, constant($option), $value)) {
                    return true;
                } else {
                    $err = @ldap_errno($this->_link);
                    if ($err) {
                        $msg = @ldap_err2str($err);
                    } else {
                        $err = NET_LDAP2_ERROR;
                        $msg = Net_LDAP2::errorMessage($err);
                    }
                    return $this->raiseError($msg, $err);
                }
            } else {
                return $this->raiseError("Unkown Option requested");
            }
        } else {
            return $this->raiseError("Could not set LDAP option: No LDAP connection");
        }
    }

    /**
    * Get an LDAP option value
    *
    * @param string $option Option to get
    *
    * @access public
    * @return Net_LDAP2_Error|string Net_LDAP2_Error or option value
    */
    public function getOption($option)
    {
        if ($this->_link) {
            if (defined($option)) {
                if (@ldap_get_option($this->_link, constant($option), $value)) {
                    return $value;
                } else {
                    $err = @ldap_errno($this->_link);
                    if ($err) {
                        $msg = @ldap_err2str($err);
                    } else {
                        $err = NET_LDAP2_ERROR;
                        $msg = Net_LDAP2::errorMessage($err);
                    }
                    return $this->raiseError($msg, $err);
                }
            } else {
                $this->raiseError("Unkown Option requested");
            }
        } else {
            $this->raiseError("No LDAP connection");
        }
    }

    /**
    * Get the LDAP_PROTOCOL_VERSION that is used on the connection.
    *
    * A lot of ldap functionality is defined by what protocol version the ldap server speaks.
    * This might be 2 or 3.
    *
    * @return int
    */
    public function getLDAPVersion()
    {
        if ($this->_link) {
            $version = $this->getOption("LDAP_OPT_PROTOCOL_VERSION");
        } else {
            $version = $this->_config['version'];
        }
        return $version;
    }

    /**
    * Set the LDAP_PROTOCOL_VERSION that is used on the connection.
    *
    * @param int     $version LDAP-version that should be used
    * @param boolean $force   If set to true, the check against the rootDSE will be skipped
    *
    * @return Net_LDAP2_Error|true    Net_LDAP2_Error object or true
    * @todo Checking via the rootDSE takes much time - why? fetching and instanciation is quick!
    */
    public function setLDAPVersion($version = 0, $force = false)
    {
        if (!$version) {
            $version = $this->_config['version'];
        }

        //
        // Check to see if the server supports this version first.
        //
        // Todo: Why is this so horribly slow?
        // $this->rootDse() is very fast, as well as Net_LDAP2_RootDSE::fetch()
        // seems like a problem at copiyng the object inside PHP??
        // Additionally, this is not always reproducable...
        //
        if (!$force) {
            $rootDSE = $this->rootDse();
            if ($rootDSE instanceof Net_LDAP2_Error) {
                return $rootDSE;
            } else {
                $supported_versions = $rootDSE->getValue('supportedLDAPVersion');
                if (is_string($supported_versions)) {
                    $supported_versions = array($supported_versions);
                }
                $check_ok = in_array($version, $supported_versions);
            }
        }

        if ($force || $check_ok) {
            return $this->setOption("LDAP_OPT_PROTOCOL_VERSION", $version);
        } else {
            return $this->raiseError("LDAP Server does not support protocol version " . $version);
        }
    }


    /**
    * Tells if a DN does exist in the directory
    *
    * @param string|Net_LDAP2_Entry $dn The DN of the object to test
    *
    * @return boolean|Net_LDAP2_Error
    */
    public function dnExists($dn)
    {
        if (PEAR::isError($dn)) {
            return $dn;
        }
        if ($dn instanceof Net_LDAP2_Entry) {
             $dn = $dn->dn();
        }
        if (false === is_string($dn)) {
            return PEAR::raiseError('Parameter $dn is not a string nor an entry object!');
        }

        // search LDAP for that DN by performing a baselevel search for any
        // object. We can only find the DN in question this way, or nothing.
        $s_opts = array(
            'scope'      => 'base',
            'sizelimit'  => 1,
            'attributes' => '1.1' // select no attrs
        );
        $search = $this->search($dn, '(objectClass=*)', $s_opts);

        if (self::isError($search)) {
            return $search;
        }

        // retun wehter the DN exists; that is, we found an entry
        return ($search->count() == 0)? false : true;
    }


    /**
    * Get a specific entry based on the DN
    *
    * @param string $dn   DN of the entry that should be fetched
    * @param array  $attr Array of Attributes to select. If ommitted, all attributes are fetched.
    *
    * @return Net_LDAP2_Entry|Net_LDAP2_Error    Reference to a Net_LDAP2_Entry object or Net_LDAP2_Error object
    * @todo Maybe check against the shema should be done to be sure the attribute type exists
    */
    public function getEntry($dn, $attr = array())
    {
        if (!is_array($attr)) {
            $attr = array($attr);
        }
        $result = $this->search($dn, '(objectClass=*)',
                                array('scope' => 'base', 'attributes' => $attr));
        if (self::isError($result)) {
            return $result;
        } elseif ($result->count() == 0) {
            return PEAR::raiseError('Could not fetch entry '.$dn.': no entry found');
        }
        $entry = $result->shiftEntry();
        if (false == $entry) {
            return PEAR::raiseError('Could not fetch entry (error retrieving entry from search result)');
        }
        return $entry;
    }

    /**
    * Rename or move an entry
    *
    * This method will instantly carry out an update() after the move,
    * so the entry is moved instantly.
    * You can pass an optional Net_LDAP2 object. In this case, a cross directory
    * move will be performed which deletes the entry in the source (THIS) directory
    * and adds it in the directory $target_ldap.
    * A cross directory move will switch the Entrys internal LDAP reference so
    * updates to the entry will go to the new directory.
    *
    * Note that if you want to do a cross directory move, you need to
    * pass an Net_LDAP2_Entry object, otherwise the attributes will be empty.
    *
    * @param string|Net_LDAP2_Entry $entry       Entry DN or Entry object
    * @param string                 $newdn       New location
    * @param Net_LDAP2              $target_ldap (optional) Target directory for cross server move; should be passed via reference
    * @param boolean                $deleteoldrdn (optional) if false the old RDN value(s) is retained as non-distinguishided values of the entry.
    *
    * @return Net_LDAP2_Error|true
    */
    public function move($entry, $newdn, $target_ldap = null, $deleteoldrdn = true)
    {
        if (is_string($entry)) {
            $entry_o = $this->getEntry($entry);
        } else {
            $entry_o = $entry;
        }
        if (!$entry_o instanceof Net_LDAP2_Entry) {
            return PEAR::raiseError('Parameter $entry is expected to be a Net_LDAP2_Entry object! (If DN was passed, conversion failed)');
        }
        if (null !== $target_ldap && !$target_ldap instanceof Net_LDAP2) {
            return PEAR::raiseError('Parameter $target_ldap is expected to be a Net_LDAP2 object!');
        }

        if ($target_ldap && $target_ldap !== $this) {
            // cross directory move
            if (is_string($entry)) {
                return PEAR::raiseError('Unable to perform cross directory move: operation requires a Net_LDAP2_Entry object');
            }
            if ($target_ldap->dnExists($newdn)) {
                return PEAR::raiseError('Unable to perform cross directory move: entry does exist in target directory');
            }
            $entry_o->dn($newdn);
            $res = $target_ldap->add($entry_o);
            if (self::isError($res)) {
                return PEAR::raiseError('Unable to perform cross directory move: '.$res->getMessage().' in target directory');
            }
            $res = $this->delete($entry_o->currentDN());
            if (self::isError($res)) {
                $res2 = $target_ldap->delete($entry_o); // undo add
                if (self::isError($res2)) {
                    $add_error_string = 'Additionally, the deletion (undo add) of $entry in target directory failed.';
                }
                return PEAR::raiseError('Unable to perform cross directory move: '.$res->getMessage().' in source directory. '.$add_error_string);
            }
            $entry_o->setLDAP($target_ldap);
            return true;
        } else {
            // local move
            $entry_o->dn($newdn);
            $entry_o->setLDAP($this);
            return $entry_o->update($deleteoldrdn);
        }
    }

    /**
    * Copy an entry to a new location
    *
    * The entry will be immediately copied.
    * Please note that only attributes you have
    * selected will be copied.
    *
    * @param Net_LDAP2_Entry $entry Entry object
    * @param string          $newdn  New FQF-DN of the entry
    *
    * @return Net_LDAP2_Error|Net_LDAP2_Entry Error Message or reference to the copied entry
    */
    public function copy($entry, $newdn)
    {
        if (!$entry instanceof Net_LDAP2_Entry) {
            return PEAR::raiseError('Parameter $entry is expected to be a Net_LDAP2_Entry object!');
        }

        $newentry = Net_LDAP2_Entry::createFresh($newdn, $entry->getValues());
        $result   = $this->add($newentry);

        if ($result instanceof Net_LDAP2_Error) {
            return $result;
        } else {
            return $newentry;
        }
    }


    /**
    * Returns the string for an ldap errorcode.
    *
    * Made to be able to make better errorhandling
    * Function based on DB::errorMessage()
    * Tip: The best description of the errorcodes is found here:
    * http://www.directory-info.com/LDAP2/LDAPErrorCodes.html
    *
    * @param int $errorcode Error code
    *
    * @return string The errorstring for the error.
    */
    public static function errorMessage($errorcode)
    {
        $errorMessages = array(
                              0x00 => "LDAP_SUCCESS",
                              0x01 => "LDAP_OPERATIONS_ERROR",
                              0x02 => "LDAP_PROTOCOL_ERROR",
                              0x03 => "LDAP_TIMELIMIT_EXCEEDED",
                              0x04 => "LDAP_SIZELIMIT_EXCEEDED",
                              0x05 => "LDAP_COMPARE_FALSE",
                              0x06 => "LDAP_COMPARE_TRUE",
                              0x07 => "LDAP_AUTH_METHOD_NOT_SUPPORTED",
                              0x08 => "LDAP_STRONG_AUTH_REQUIRED",
                              0x09 => "LDAP_PARTIAL_RESULTS",
                              0x0a => "LDAP_REFERRAL",
                              0x0b => "LDAP_ADMINLIMIT_EXCEEDED",
                              0x0c => "LDAP_UNAVAILABLE_CRITICAL_EXTENSION",
                              0x0d => "LDAP_CONFIDENTIALITY_REQUIRED",
                              0x0e => "LDAP_SASL_BIND_INPROGRESS",
                              0x10 => "LDAP_NO_SUCH_ATTRIBUTE",
                              0x11 => "LDAP_UNDEFINED_TYPE",
                              0x12 => "LDAP_INAPPROPRIATE_MATCHING",
                              0x13 => "LDAP_CONSTRAINT_VIOLATION",
                              0x14 => "LDAP_TYPE_OR_VALUE_EXISTS",
                              0x15 => "LDAP_INVALID_SYNTAX",
                              0x20 => "LDAP_NO_SUCH_OBJECT",
                              0x21 => "LDAP_ALIAS_PROBLEM",
                              0x22 => "LDAP_INVALID_DN_SYNTAX",
                              0x23 => "LDAP_IS_LEAF",
                              0x24 => "LDAP_ALIAS_DEREF_PROBLEM",
                              0x30 => "LDAP_INAPPROPRIATE_AUTH",
                              0x31 => "LDAP_INVALID_CREDENTIALS",
                              0x32 => "LDAP_INSUFFICIENT_ACCESS",
                              0x33 => "LDAP_BUSY",
                              0x34 => "LDAP_UNAVAILABLE",
                              0x35 => "LDAP_UNWILLING_TO_PERFORM",
                              0x36 => "LDAP_LOOP_DETECT",
                              0x3C => "LDAP_SORT_CONTROL_MISSING",
                              0x3D => "LDAP_INDEX_RANGE_ERROR",
                              0x40 => "LDAP_NAMING_VIOLATION",
                              0x41 => "LDAP_OBJECT_CLASS_VIOLATION",
                              0x42 => "LDAP_NOT_ALLOWED_ON_NONLEAF",
                              0x43 => "LDAP_NOT_ALLOWED_ON_RDN",
                              0x44 => "LDAP_ALREADY_EXISTS",
                              0x45 => "LDAP_NO_OBJECT_CLASS_MODS",
                              0x46 => "LDAP_RESULTS_TOO_LARGE",
                              0x47 => "LDAP_AFFECTS_MULTIPLE_DSAS",
                              0x50 => "LDAP_OTHER",
                              0x51 => "LDAP_SERVER_DOWN",
                              0x52 => "LDAP_LOCAL_ERROR",
                              0x53 => "LDAP_ENCODING_ERROR",
                              0x54 => "LDAP_DECODING_ERROR",
                              0x55 => "LDAP_TIMEOUT",
                              0x56 => "LDAP_AUTH_UNKNOWN",
                              0x57 => "LDAP_FILTER_ERROR",
                              0x58 => "LDAP_USER_CANCELLED",
                              0x59 => "LDAP_PARAM_ERROR",
                              0x5a => "LDAP_NO_MEMORY",
                              0x5b => "LDAP_CONNECT_ERROR",
                              0x5c => "LDAP_NOT_SUPPORTED",
                              0x5d => "LDAP_CONTROL_NOT_FOUND",
                              0x5e => "LDAP_NO_RESULTS_RETURNED",
                              0x5f => "LDAP_MORE_RESULTS_TO_RETURN",
                              0x60 => "LDAP_CLIENT_LOOP",
                              0x61 => "LDAP_REFERRAL_LIMIT_EXCEEDED",
                              1000 => "Unknown Net_LDAP2 Error"
                              );

         return isset($errorMessages[$errorcode]) ?
            $errorMessages[$errorcode] :
            $errorMessages[NET_LDAP2_ERROR] . ' (' . $errorcode . ')';
    }

    /**
    * Gets a rootDSE object
    *
    * This either fetches a fresh rootDSE object or returns it from
    * the internal cache for performance reasons, if possible.
    *
    * @param array $attrs Array of attributes to search for
    *
    * @access public
    * @return Net_LDAP2_Error|Net_LDAP2_RootDSE Net_LDAP2_Error or Net_LDAP2_RootDSE object
    */
    public function rootDse($attrs = null)
    {
        if ($attrs !== null && !is_array($attrs)) {
            return PEAR::raiseError('Parameter $attr is expected to be an array!');
        }

        $attrs_signature = serialize($attrs);

        // see if we need to fetch a fresh object, or if we already
        // requested this object with the same attributes
        if (true || !array_key_exists($attrs_signature, $this->_rootDSE_cache)) {
            $rootdse = Net_LDAP2_RootDSE::fetch($this, $attrs);
            if ($rootdse instanceof Net_LDAP2_Error) {
                return $rootdse;
            }

            // search was ok, store rootDSE in cache
            $this->_rootDSE_cache[$attrs_signature] = $rootdse;
        }
        return $this->_rootDSE_cache[$attrs_signature];
    }

    /**
    * Alias function of rootDse() for perl-ldap interface
    *
    * @access public
    * @see rootDse()
    * @return Net_LDAP2_Error|Net_LDAP2_RootDSE
    */
    public function root_dse()
    {
        $args = func_get_args();
        return call_user_func_array(array($this, 'rootDse'), $args);
    }

    /**
    * Get a schema object
    *
    * @param string $dn (optional) Subschema entry dn
    *
    * @access public
    * @return Net_LDAP2_Schema|Net_LDAP2_Error  Net_LDAP2_Schema or Net_LDAP2_Error object
    */
    public function schema($dn = null)
    {
        // Schema caching by Knut-Olav Hoven
        // If a schema caching object is registered, we use that to fetch
        // a schema object.
        // See registerSchemaCache() for more info on this.
        if ($this->_schema === null) {
            if ($this->_schema_cache) {
               $cached_schema = $this->_schema_cache->loadSchema();
               if ($cached_schema instanceof Net_LDAP2_Error) {
                   return $cached_schema; // route error to client
               } else {
                   if ($cached_schema instanceof Net_LDAP2_Schema) {
                       $this->_schema = $cached_schema;
                   }
               }
            }
        }

        // Fetch schema, if not tried before and no cached version available.
        // If we are already fetching the schema, we will skip fetching.
        if ($this->_schema === null) {
            // store a temporary error message so subsequent calls to schema() can
            // detect, that we are fetching the schema already.
            // Otherwise we will get an infinite loop at Net_LDAP2_Schema::fetch()
            $this->_schema = new Net_LDAP2_Error('Schema not initialized');
            $this->_schema = Net_LDAP2_Schema::fetch($this, $dn);

            // If schema caching is active, advise the cache to store the schema
            if ($this->_schema_cache) {
                $caching_result = $this->_schema_cache->storeSchema($this->_schema);
                if ($caching_result instanceof Net_LDAP2_Error) {
                    return $caching_result; // route error to client
                }
            }
        }
        return $this->_schema;
    }

    /**
    * Enable/disable persistent schema caching
    *
    * Sometimes it might be useful to allow your scripts to cache
    * the schema information on disk, so the schema is not fetched
    * every time the script runs which could make your scripts run
    * faster.
    *
    * This method allows you to register a custom object that
    * implements your schema cache. Please see the SchemaCache interface
    * (SchemaCache.interface.php) for informations on how to implement this.
    * To unregister the cache, pass null as $cache parameter.
    *
    * For ease of use, Net_LDAP2 provides a simple file based cache
    * which is used in the example below. You may use this, for example,
    * to store the schema in a linux tmpfs which results in the schema
    * beeing cached inside the RAM which allows nearly instant access.
    * <code>
    *    // Create the simple file cache object that comes along with Net_LDAP2
    *    $mySchemaCache_cfg = array(
    *      'path'    =>  '/tmp/Net_LDAP2_Schema.cache',
    *      'max_age' =>  86400   // max age is 24 hours (in seconds)
    *    );
    *    $mySchemaCache = new Net_LDAP2_SimpleFileSchemaCache($mySchemaCache_cfg);
    *    $ldap = new Net_LDAP2::connect(...);
    *    $ldap->registerSchemaCache($mySchemaCache); // enable caching
    *    // now each call to $ldap->schema() will get the schema from disk!
    * </code>
    *
    * @param Net_LDAP2_SchemaCache|null $cache Object implementing the Net_LDAP2_SchemaCache interface
    *
    * @return true|Net_LDAP2_Error
    */
    public function registerSchemaCache($cache) {
        if (is_null($cache)
        || (is_object($cache) && in_array('Net_LDAP2_SchemaCache', class_implements($cache))) ) {
            $this->_schema_cache = $cache;
            return true;
        } else {
            return new Net_LDAP2_Error('Custom schema caching object is either no '.
                'valid object or does not implement the Net_LDAP2_SchemaCache interface!');
        }
    }


    /**
    * Checks if phps ldap-extension is loaded
    *
    * If it is not loaded, it tries to load it manually using PHPs dl().
    * It knows both windows-dll and *nix-so.
    *
    * @static
    * @return Net_LDAP2_Error|true
    */
    public static function checkLDAPExtension()
    {
        if (!extension_loaded('ldap') && !@dl('ldap.' . PHP_SHLIB_SUFFIX)) {
            return new Net_LDAP2_Error("It seems that you do not have the ldap-extension installed. Please install it before using the Net_LDAP2 package.");
        } else {
            return true;
        }
    }

    /**
    * Encodes given attributes from ISO-8859-1 to UTF-8 if needed by schema
    *
    * This function takes attributes in an array and then checks against the schema if they need
    * UTF8 encoding. If that is so, they will be encoded. An encoded array will be returned and
    * can be used for adding or modifying.
    *
    * $attributes is expected to be an array with keys describing
    * the attribute names and the values as the value of this attribute:
    * <code>$attributes = array('cn' => 'foo', 'attr2' => array('mv1', 'mv2'));</code>
    *
    * @param array $attributes Array of attributes
    *
    * @access public
    * @return array|Net_LDAP2_Error Array of UTF8 encoded attributes or Error
    */
    public function utf8Encode($attributes)
    {
        return $this->utf8($attributes, 'utf8_encode');
    }

    /**
    * Decodes the given attribute values from UTF-8 to ISO-8859-1 if needed by schema
    *
    * $attributes is expected to be an array with keys describing
    * the attribute names and the values as the value of this attribute:
    * <code>$attributes = array('cn' => 'foo', 'attr2' => array('mv1', 'mv2'));</code>
    *
    * @param array $attributes Array of attributes
    *
    * @access public
    * @see utf8Encode()
    * @return array|Net_LDAP2_Error Array with decoded attribute values or Error
    */
    public function utf8Decode($attributes)
    {
        return $this->utf8($attributes, 'utf8_decode');
    }

    /**
    * Encodes or decodes UTF-8/ISO-8859-1 attribute values if needed by schema
    *
    * @param array $attributes Array of attributes
    * @param array $function   Function to apply to attribute values
    *
    * @access protected
    * @return array|Net_LDAP2_Error Array of attributes with function applied to values or Error
    */
    protected function utf8($attributes, $function)
    {
        if (!is_array($attributes) || array_key_exists(0, $attributes)) {
            return PEAR::raiseError('Parameter $attributes is expected to be an associative array');
        }

        if (!$this->_schema) {
            $this->_schema = $this->schema();
        }

        if (!$this->_link || self::isError($this->_schema) || !function_exists($function)) {
            return $attributes;
        }

        if (is_array($attributes) && count($attributes) > 0) {

            foreach ($attributes as $k => $v) {

                if (!isset($this->_schemaAttrs[$k])) {

                    $attr = $this->_schema->get('attribute', $k);
                    if (self::isError($attr)) {
                        continue;
                    }

                    // Encoding is needed if this is a DIR_STR. We assume also
                    // needed encoding in case the schema contains no syntax
                    // information (he does not need to, see rfc2252, 4.2)
                    if (!array_key_exists('syntax', $attr) || false !== strpos($attr['syntax'], '1.3.6.1.4.1.1466.115.121.1.15')) {
                        $encode = true;
                    } else {
                        $encode = false;
                    }
                    $this->_schemaAttrs[$k] = $encode;

                } else {
                    $encode = $this->_schemaAttrs[$k];
                }

                if ($encode) {
                    if (is_array($v)) {
                        foreach ($v as $ak => $av) {
                            $v[$ak] = call_user_func($function, $av);
                        }
                    } else {
                        $v = call_user_func($function, $v);
                    }
                }
                $attributes[$k] = $v;
            }
        }
        return $attributes;
    }

    /**
    * Get the LDAP link resource.  It will loop attempting to
    * re-establish the connection if the connection attempt fails and
    * auto_reconnect has been turned on (see the _config array documentation).
    *
    * @access public
    * @return resource|\LDAP\Connection LDAP link (resource on PHP < 8.1)
    */
    public function getLink()
    {
        if ($this->_config['auto_reconnect']) {
            while (true) {
                //
                // Return the link handle if we are already connected.  Otherwise
                // try to reconnect.
                //
                if ($this->_link !== false) {
                    return $this->_link;
                } else {
                    $this->performReconnect();
                }
            }
        }
        return $this->_link;
    }
}

/**
* Net_LDAP2_Error implements a class for reporting portable LDAP error messages.
*
* @category Net
* @package  Net_LDAP2
* @author   Tarjej Huse <tarjei@bergfald.no>
* @license  http://www.gnu.org/copyleft/lesser.html LGPL
* @link     http://pear.php.net/package/Net_LDAP22/
*/
class Net_LDAP2_Error extends PEAR_Error
{
    /**
     * Net_LDAP2_Error constructor.
     *
     * @param string  $message   String with error message.
     * @param integer $code      Net_LDAP2 error code
     * @param integer $mode      what "error mode" to operate in
     * @param mixed   $level     what error level to use for $mode & PEAR_ERROR_TRIGGER
     * @param mixed   $debuginfo additional debug info, such as the last query
     *
     * @access public
     * @see PEAR_Error
     */
    public function __construct($message = 'Net_LDAP2_Error', $code = NET_LDAP2_ERROR, $mode = PEAR_ERROR_RETURN,
                         $level = E_USER_NOTICE, $debuginfo = null)
    {
        if (is_int($code)) {
            parent::__construct($message . ': ' . Net_LDAP2::errorMessage($code), $code, $mode, $level, $debuginfo);
        } else {
            parent::__construct("$message: $code", NET_LDAP2_ERROR, $mode, $level, $debuginfo);
        }
    }
}

?>
PK|c�\7�(1K1K#pear/net_ldap2/Net/LDAP2/Search.phpnu�[���<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
/**
* File containing the Net_LDAP2_Search interface class.
*
* PHP version 5
*
* @category  Net
* @package   Net_LDAP2
* @author    Tarjej Huse <tarjei@bergfald.no>
* @author    Benedikt Hallinger <beni@php.net>
* @copyright 2009 Tarjej Huse, Benedikt Hallinger
* @license   http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
* @version   SVN: $Id$
* @link      http://pear.php.net/package/Net_LDAP2/
*/

/**
* Includes
*/
require_once 'PEAR.php';

/**
* Result set of an LDAP search
*
* @category Net
* @package  Net_LDAP2
* @author   Tarjej Huse <tarjei@bergfald.no>
* @author   Benedikt Hallinger <beni@php.net>
* @license  http://www.gnu.org/copyleft/lesser.html LGPL
* @link     http://pear.php.net/package/Net_LDAP22/
*/
class Net_LDAP2_Search extends PEAR implements Iterator
{
    /**
    * Search result identifier
    *
    * @access protected
    * @var resource
    */
    protected $_search;

    /**
    * LDAP resource link
    *
    * @access protected
    * @var resource
    */
    protected $_link;

    /**
    * Net_LDAP2 object
    *
    * A reference of the Net_LDAP2 object for passing to Net_LDAP2_Entry
    *
    * @access protected
    * @var object Net_LDAP2
    */
    protected $_ldap;

    /**
    * Result entry identifier
    *
    * @access protected
    * @var resource
    */
    protected $_entry = null;

    /**
    * The errorcode the search got
    *
    * Some errorcodes might be of interest, but might not be best handled as errors.
    * examples: 4 - LDAP_SIZELIMIT_EXCEEDED - indicates a huge search.
    *               Incomplete results are returned. If you just want to check if there's anything in the search.
    *               than this is a point to handle.
    *           32 - no such object - search here returns a count of 0.
    *
    * @access protected
    * @var int
    */
    protected $_errorCode = 0; // if not set - sucess!

    /**
    * Cache for all entries already fetched from iterator interface
    *
    * @access protected
    * @var array
    */
    protected $_iteratorCache = array();

    /**
    * What attributes we searched for
    *
    * The $attributes array contains the names of the searched attributes and gets
    * passed from $Net_LDAP2->search() so the Net_LDAP2_Search object can tell
    * what attributes was searched for ({@link searchedAttrs())
    *
    * This variable gets set from the constructor and returned
    * from {@link searchedAttrs()}
    *
    * @access protected
    * @var array
    */
    protected $_searchedAttrs = array();

    /**
    * Cache variable for storing entries fetched internally
    *
    * This currently is not used by all functions and need consolidation.
    *
    * @access protected
    * @var array
    */
    protected $_entry_cache = false;

    /**
    * Cache variable for count()
    *
    * @see count()
    * @access protected
    * @var int
    */
    protected $_count_cache = null;

    /**
    * Constructor
    *
    * @param resource           $search    Search result identifier
    * @param Net_LDAP2|resource $ldap      Net_LDAP2 object or just a LDAP-Link resource
    * @param array              $attributes (optional) Array with searched attribute names. (see {@link $_searchedAttrs})
    *
    * @access public
    */
    public function __construct($search, $ldap, $attributes = array())
    {
        parent::__construct('Net_LDAP2_Error');

        $this->setSearch($search);

        if ($ldap instanceof Net_LDAP2) {
            $this->_ldap = $ldap;
            $this->setLink($this->_ldap->getLink());
        } else {
            $this->setLink($ldap);
        }

        $this->_errorCode = @ldap_errno($this->_link);

        if (is_array($attributes) && !empty($attributes)) {
            $this->_searchedAttrs = $attributes;
        }
    }

    /**
    * Returns an array of entry objects.
    *
    * @return array Array of entry objects.
    */
    public function entries()
    {
        $entries = array();

        if (false === $this->_entry_cache) {
            // cache is empty: fetch from LDAP
            while ($entry = $this->shiftEntry()) {
                $entries[] = $entry;
            }
            $this->_entry_cache = $entries; // store result in cache
        }

        return $this->_entry_cache;
    }

    /**
    * Get the next entry in the searchresult from LDAP server.
    *
    * This will return a valid Net_LDAP2_Entry object or false, so
    * you can use this method to easily iterate over the entries inside
    * a while loop.
    *
    * @return Net_LDAP2_Entry|false  Reference to Net_LDAP2_Entry object or false
    */
    public function shiftEntry()
    {
        if (is_null($this->_entry)) {
            if(!$this->_entry = @ldap_first_entry($this->_link, $this->_search)) {
                $false = false;
                return $false;
            }
            $entry = Net_LDAP2_Entry::createConnected($this->_ldap, $this->_entry);
            if ($entry instanceof PEAR_Error) $entry = false;

        } else if ($this->_entry === false) {
            //no more results
            return false;

        } else {
            if (!$this->_entry = @ldap_next_entry($this->_link, $this->_entry)) {
                $false = false;
                return $false;
            }
            $entry = Net_LDAP2_Entry::createConnected($this->_ldap, $this->_entry);
            if ($entry instanceof PEAR_Error) $entry = false;
        }
        return $entry;
    }

    /**
    * Alias function of shiftEntry() for perl-ldap interface
    *
    * @see shiftEntry()
    * @return Net_LDAP2_Entry|false
    */
    public function shift_entry()
    {
        $args = func_get_args();
        return call_user_func_array(array( $this, 'shiftEntry' ), $args);
    }

    /**
    * Retrieve the next entry in the searchresult, but starting from last entry
    *
    * This is the opposite to {@link shiftEntry()} and is also very useful
    * to be used inside a while loop.
    *
    * @return Net_LDAP2_Entry|false
    */
    public function popEntry()
    {
        if (false === $this->_entry_cache) {
            // fetch entries into cache if not done so far
            $this->_entry_cache = $this->entries();
        }

        $return = array_pop($this->_entry_cache);
        return (null === $return)? false : $return;
    }

    /**
    * Alias function of popEntry() for perl-ldap interface
    *
    * @see popEntry()
    * @return Net_LDAP2_Entry|false
    */
    public function pop_entry()
    {
        $args = func_get_args();
        return call_user_func_array(array( $this, 'popEntry' ), $args);
    }

    /**
    * Return entries sorted as array
    *
    * This returns a array with sorted entries and the values.
    * Sorting is done with PHPs {@link array_multisort()}.
    * This method relies on {@link as_struct()} to fetch the raw data of the entries.
    *
    * Please note that attribute names are case sensitive!
    *
    * Usage example:
    * <code>
    *   // to sort entries first by location, then by surename, but descending:
    *   $entries = $search->sorted_as_struct(array('locality','sn'), SORT_DESC);
    * </code>
    *
    * @param array $attrs Array of attribute names to sort; order from left to right.
    * @param int   $order Ordering direction, either constant SORT_ASC or SORT_DESC
    *
    * @return array|Net_LDAP2_Error   Array with sorted entries or error
    * @todo what about server side sorting as specified in http://www.ietf.org/rfc/rfc2891.txt?
    */
    public function sorted_as_struct($attrs = array('cn'), $order = SORT_ASC)
    {
        /*
        * Old Code, suitable and fast for single valued sorting
        * This code should be used if we know that single valued sorting is desired,
        * but we need some method to get that knowledge...
        */
        /*
        $attrs = array_reverse($attrs);
        foreach ($attrs as $attribute) {
            if (!ldap_sort($this->_link, $this->_search, $attribute)){
                $this->raiseError("Sorting failed for Attribute " . $attribute);
            }
        }

        $results = ldap_get_entries($this->_link, $this->_search);

        unset($results['count']); //for tidier output
        if ($order) {
            return array_reverse($results);
        } else {
            return $results;
        }*/

        /*
        * New code: complete "client side" sorting
        */
        // first some parameterchecks
        if (!is_array($attrs)) {
            return PEAR::raiseError("Sorting failed: Parameterlist must be an array!");
        }
        if ($order != SORT_ASC && $order != SORT_DESC) {
            return PEAR::raiseError("Sorting failed: sorting direction not understood! (neither constant SORT_ASC nor SORT_DESC)");
        }

        // fetch the entries data
        $entries = $this->as_struct();

        // now sort each entries attribute values
        // this is neccessary because later we can only sort by one value,
        // so we need the highest or lowest attribute now, depending on the
        // selected ordering for that specific attribute
        foreach ($entries as $dn => $entry) {
            foreach ($entry as $attr_name => $attr_values) {
                sort($entries[$dn][$attr_name]);
                if ($order == SORT_DESC) {
                    array_reverse($entries[$dn][$attr_name]);
                }
            }
        }

        // reformat entrys array for later use with array_multisort()
        $to_sort = array(); // <- will be a numeric array similar to ldap_get_entries
        foreach ($entries as $dn => $entry_attr) {
            $row       = array();
            $row['dn'] = $dn;
            foreach ($entry_attr as $attr_name => $attr_values) {
                $row[$attr_name] = $attr_values;
            }
            $to_sort[] = $row;
        }

        // Build columns for array_multisort()
        // each requested attribute is one row
        $columns = array();
        foreach ($attrs as $attr_name) {
            foreach ($to_sort as $key => $row) {
                $columns[$attr_name][$key] =& $to_sort[$key][$attr_name][0];
            }
        }

        // sort the colums with array_multisort, if there is something
        // to sort and if we have requested sort columns
        if (!empty($to_sort) && !empty($columns)) {
            $sort_params = '';
            foreach ($attrs as $attr_name) {
                $sort_params .= '$columns[\''.$attr_name.'\'], '.$order.', ';
            }
            eval("array_multisort($sort_params \$to_sort);"); // perform sorting
        }

        return $to_sort;
    }

    /**
    * Return entries sorted as objects
    *
    * This returns a array with sorted Net_LDAP2_Entry objects.
    * The sorting is actually done with {@link sorted_as_struct()}.
    *
    * Please note that attribute names are case sensitive!
    * Also note, that it is (depending on server capabilitys) possible to let
    * the server sort your results. This happens through search controls
    * and is described in detail at {@link http://www.ietf.org/rfc/rfc2891.txt}
    *
    * Usage example:
    * <code>
    *   // to sort entries first by location, then by surename, but descending:
    *   $entries = $search->sorted(array('locality','sn'), SORT_DESC);
    * </code>
    *
    * @param array $attrs Array of sort attributes to sort; order from left to right.
    * @param int   $order Ordering direction, either constant SORT_ASC or SORT_DESC
    *
    * @return array|Net_LDAP2_Error   Array with sorted Net_LDAP2_Entries or error
    * @todo Entry object construction could be faster. Maybe we could use one of the factorys instead of fetching the entry again
    */
    public function sorted($attrs = array('cn'), $order = SORT_ASC)
    {
        $return = array();
        $sorted = $this->sorted_as_struct($attrs, $order);
        if (PEAR::isError($sorted)) {
            return $sorted;
        }
        foreach ($sorted as $key => $row) {
            $entry = $this->_ldap->getEntry($row['dn'], $this->searchedAttrs());
            if (!PEAR::isError($entry)) {
                array_push($return, $entry);
            } else {
                return $entry;
            }
        }
        return $return;
    }

    /**
    * Return entries as array
    *
    * This method returns the entries and the selected attributes values as
    * array.
    * The first array level contains all found entries where the keys are the
    * DNs of the entries. The second level arrays contian the entries attributes
    * such that the keys is the lowercased name of the attribute and the values
    * are stored in another indexed array. Note that the attribute values are stored
    * in an array even if there is no or just one value.
    *
    * The array has the following structure:
    * <code>
    * $return = array(
    *           'cn=foo,dc=example,dc=com' => array(
    *                                                'sn'       => array('foo'),
    *                                                'multival' => array('val1', 'val2', 'valN')
    *                                             )
    *           'cn=bar,dc=example,dc=com' => array(
    *                                                'sn'       => array('bar'),
    *                                                'multival' => array('val1', 'valN')
    *                                             )
    *           )
    * </code>
    *
    * @return array      associative result array as described above
    */
    public function as_struct()
    {
        $return  = array();
        $entries = $this->entries();
        foreach ($entries as $entry) {
            $attrs            = array();
            $entry_attributes = $entry->attributes();
            foreach ($entry_attributes as $attr_name) {
                $attr_values = $entry->getValue($attr_name, 'all');
                if (!is_array($attr_values)) {
                    $attr_values = array($attr_values);
                }
                $attrs[$attr_name] = $attr_values;
            }
            $return[$entry->dn()] = $attrs;
        }
        return $return;
    }

    /**
    * Set the search objects resource link
    *
    * @param resource $search Search result identifier
    *
    * @access public
    * @return void
    */
    public function setSearch($search)
    {
        $this->_search = $search;
    }

    /**
    * Set the ldap ressource link
    *
    * @param resource $link Link identifier
    *
    * @access public
    * @return void
    */
    public function setLink($link)
    {
        $this->_link = $link;
    }

    /**
    * Returns the number of entries in the searchresult
    *
    * @return int Number of entries in search.
    */
    public function count()
    {
        // this catches the situation where OL returned errno 32 = no such object!
        if (!$this->_search) {
            return 0;
        }
        // ldap_count_entries is slow (see pear bug #18752) with large results,
        // so we cache the result internally.
        if ($this->_count_cache === null) {
            $this->_count_cache = @ldap_count_entries($this->_link, $this->_search);
        }

        return $this->_count_cache;
    }

    /**
    * Get the errorcode the object got in its search.
    *
    * @return int The ldap error number.
    */
    public function getErrorCode()
    {
        return $this->_errorCode;
    }

    /**
    * Destructor
    *
    * @access protected
    */
    public function _Net_LDAP2_Search()
    {
        if ($this->_search !== false) {
            @ldap_free_result($this->_search);
        }
    }

    /**
    * Closes search result
    *
    * @return void
    */
    public function done()
    {
        $this->_Net_LDAP2_Search();
    }

    /**
    * Return the attribute names this search selected
    *
    * @return array
    * @see $_searchedAttrs
    * @access protected
    */
    protected function searchedAttrs()
    {
        return $this->_searchedAttrs;
    }

    /**
    * Tells if this search exceeds a sizelimit
    *
    * @return boolean
    */
    public function sizeLimitExceeded()
    {
        return ($this->getErrorCode() == 4);
    }


    /*
    * SPL Iterator interface methods.
    * This interface allows to use Net_LDAP2_Search
    * objects directly inside a foreach loop!
    */
    /**
    * SPL Iterator interface: Return the current element.
    *
    * The SPL Iterator interface allows you to fetch entries inside
    * a foreach() loop: <code>foreach ($search as $dn => $entry) { ...</code>
    *
    * Of course, you may call {@link current()}, {@link key()}, {@link next()},
    * {@link rewind()} and {@link valid()} yourself.
    *
    * If the search throwed an error, it returns false.
    * False is also returned, if the end is reached
    * In case no call to next() was made, we will issue one,
    * thus returning the first entry.
    *
    * @return Net_LDAP2_Entry|false
    */
    public function current()
    {
        if (count($this->_iteratorCache) == 0) {
            $this->next();
            reset($this->_iteratorCache);
        }
        $entry = current($this->_iteratorCache);
        return ($entry instanceof Net_LDAP2_Entry)? $entry : false;
    }

    /**
    * SPL Iterator interface: Return the identifying key (DN) of the current entry.
    *
    * @see current()
    * @return string|false DN of the current entry; false in case no entry is returned by current()
    */
    public function key()
    {
        $entry = $this->current();
        return ($entry instanceof Net_LDAP2_Entry)? $entry->dn() :false;
    }

    /**
    * SPL Iterator interface: Move forward to next entry.
    *
    * After a call to {@link next()}, {@link current()} will return
    * the next entry in the result set.
    *
    * @see current()
    * @return void
    */
    public function next()
    {
        // fetch next entry.
        // if we have no entrys anymore, we add false (which is
        // returned by shiftEntry()) so current() will complain.
        if (count($this->_iteratorCache) - 1 <= $this->count()) {
            $this->_iteratorCache[] = $this->shiftEntry();
        }

        // move on array pointer to current element.
        // even if we have added all entries, this will
        // ensure proper operation in case we rewind()
        next($this->_iteratorCache);
    }

    /**
    * SPL Iterator interface:  Check if there is a current element after calls to {@link rewind()} or {@link next()}.
    *
    * Used to check if we've iterated to the end of the collection.
    *
    * @see current()
    * @return boolean FALSE if there's nothing more to iterate over
    */
    public function valid()
    {
        return ($this->current() instanceof Net_LDAP2_Entry);
    }

    /**
    * SPL Iterator interface: Rewind the Iterator to the first element.
    *
    * After rewinding, {@link current()} will return the first entry in the result set.
    *
    * @see current()
    * @return void
    */
    public function rewind()
    {
        reset($this->_iteratorCache);
    }
}

?>
PK|c�\W�NN$pear/net_ldap2/Net/LDAP2/RootDSE.phpnu�[���<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
/**
* File containing the Net_LDAP2_RootDSE interface class.
*
* PHP version 5
*
* @category  Net
* @package   Net_LDAP2
* @author    Jan Wagner <wagner@netsols.de>
* @copyright 2009 Jan Wagner
* @license   http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
* @version   SVN: $Id$
* @link      http://pear.php.net/package/Net_LDAP2/
*/

/**
* Includes
*/
require_once 'PEAR.php';

/**
* Getting the rootDSE entry of a LDAP server
*
* @category Net
* @package  Net_LDAP2
* @author   Jan Wagner <wagner@netsols.de>
* @license  http://www.gnu.org/copyleft/lesser.html LGPL
* @link     http://pear.php.net/package/Net_LDAP22/
*/
class Net_LDAP2_RootDSE extends PEAR
{
    /**
    * @access protected
    * @var object Net_LDAP2_Entry
    **/
    protected $_entry;

    /**
    * Class constructor
    *
    * @param Net_LDAP2_Entry &$entry Net_LDAP2_Entry object of the RootDSE
    */
    public function __construct(&$entry)
    {
        $this->_entry = $entry;
    }

    /**
    * Fetches a RootDSE object from an LDAP connection
    *
    * @param Net_LDAP2 $ldap  Directory from which the RootDSE should be fetched
    * @param array     $attrs Array of attributes to search for
    *
    * @access static
    * @return Net_LDAP2_RootDSE|Net_LDAP2_Error
    */
    public static function fetch($ldap, $attrs = null)
    {
        if (!$ldap instanceof Net_LDAP2) {
            return PEAR::raiseError("Unable to fetch Schema: Parameter \$ldap must be a Net_LDAP2 object!");
        }

        if (is_array($attrs) && count($attrs) > 0 ) {
            $attributes = $attrs;
        } else {
            $attributes = array('vendorName',
                                'vendorVersion',
                                'namingContexts',
                                'altServer',
                                'supportedExtension',
                                'supportedControl',
                                'supportedSASLMechanisms',
                                'supportedLDAPVersion',
                                'subschemaSubentry' );
        }
        $result = $ldap->search('', '(objectClass=*)', array('attributes' => $attributes, 'scope' => 'base'));
        if (self::isError($result)) {
            return $result;
        }
        $entry = $result->shiftEntry();
        if (false === $entry) {
            return PEAR::raiseError('Could not fetch RootDSE entry');
        }
        $ret = new Net_LDAP2_RootDSE($entry);
        return $ret;
    }

    /**
    * Gets the requested attribute value
    *
    * Same usuage as {@link Net_LDAP2_Entry::getValue()}
    *
    * @param string $attr    Attribute name
    * @param array  $options Array of options
    *
    * @access public
    * @return mixed Net_LDAP2_Error object or attribute values
    * @see Net_LDAP2_Entry::get_value()
    */
    public function getValue($attr = '', $options = '')
    {
        return $this->_entry->get_value($attr, $options);
    }

    /**
    * Alias function of getValue() for perl-ldap interface
    *
    * @see getValue()
    * @return mixed
    */
    public function get_value()
    {
        $args = func_get_args();
        return call_user_func_array(array( &$this, 'getValue' ), $args);
    }

    /**
    * Determines if the extension is supported
    *
    * @param array $oids Array of oids to check
    *
    * @access public
    * @return boolean
    */
    public function supportedExtension($oids)
    {
        return $this->checkAttr($oids, 'supportedExtension');
    }

    /**
    * Alias function of supportedExtension() for perl-ldap interface
    *
    * @see supportedExtension()
    * @return boolean
    */
    public function supported_extension()
    {
        $args = func_get_args();
        return call_user_func_array(array( &$this, 'supportedExtension'), $args);
    }

    /**
    * Determines if the version is supported
    *
    * @param array $versions Versions to check
    *
    * @access public
    * @return boolean
    */
    public function supportedVersion($versions)
    {
        return $this->checkAttr($versions, 'supportedLDAPVersion');
    }

    /**
    * Alias function of supportedVersion() for perl-ldap interface
    *
    * @see supportedVersion()
    * @return boolean
    */
    public function supported_version()
    {
        $args = func_get_args();
        return call_user_func_array(array(&$this, 'supportedVersion'), $args);
    }

    /**
    * Determines if the control is supported
    *
    * @param array $oids Control oids to check
    *
    * @access public
    * @return boolean
    */
    public function supportedControl($oids)
    {
        return $this->checkAttr($oids, 'supportedControl');
    }

    /**
    * Alias function of supportedControl() for perl-ldap interface
    *
    * @see supportedControl()
    * @return boolean
    */
    public function supported_control()
    {
        $args = func_get_args();
        return call_user_func_array(array(&$this, 'supportedControl' ), $args);
    }

    /**
    * Determines if the sasl mechanism is supported
    *
    * @param array $mechlist SASL mechanisms to check
    *
    * @access public
    * @return boolean
    */
    public function supportedSASLMechanism($mechlist)
    {
        return $this->checkAttr($mechlist, 'supportedSASLMechanisms');
    }

    /**
    * Alias function of supportedSASLMechanism() for perl-ldap interface
    *
    * @see supportedSASLMechanism()
    * @return boolean
    */
    public function supported_sasl_mechanism()
    {
        $args = func_get_args();
        return call_user_func_array(array(&$this, 'supportedSASLMechanism'), $args);
    }

    /**
    * Checks for existance of value in attribute
    *
    * @param array  $values values to check
    * @param string $attr   attribute name
    *
    * @access protected
    * @return boolean
    */
    protected function checkAttr($values, $attr)
    {
        if (!is_array($values)) $values = array($values);

        foreach ($values as $value) {
            if (!@in_array($value, $this->get_value($attr, 'all'))) {
                return false;
            }
        }
        return true;
    }
}

?>
PK|c�\�U/�2pear/net_ldap2/Net/LDAP2/SimpleFileSchemaCache.phpnu�[���<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
/**
* File containing the example simple file based Schema Caching class.
*
* PHP version 5
*
* @category  Net
* @package   Net_LDAP2
* @author    Benedikt Hallinger <beni@php.net>
* @copyright 2009 Benedikt Hallinger
* @license   http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
* @version   SVN: $Id$
* @link      http://pear.php.net/package/Net_LDAP2/
*/

/**
* A simple file based schema cacher with cache aging.
*
* Once the cache is too old, the loadSchema() method will return false, so
* Net_LDAP2 will fetch a fresh object from the LDAP server that will
* overwrite the current (outdated) old cache.
*/
class Net_LDAP2_SimpleFileSchemaCache implements Net_LDAP2_SchemaCache
{
    /**
    * Internal config of this cache
    *
    * @see Net_LDAP2_SimpleFileSchemaCache()
    * @var array
    */
    protected $config = array(
        'path'    => '/tmp/Net_LDAP_Schema.cache',
        'max_age' => 1200
    );

    /**
    * Initialize the simple cache
    *
    * Config is as following:
    *  path     Complete path to the cache file.
    *  max_age  Maximum age of cache in seconds, 0 means "endlessly".
    *
    * @param array $cfg Config array
    */
    public function __construct($cfg)
    {
    	foreach ($cfg as $key => $value) {
			if (array_key_exists($key, $this->config)) {
				if (gettype($this->config[$key]) != gettype($value)) {
					$this->getCore()->dropFatalError(__CLASS__.": Could not set config! Key $key does not match type ".gettype($this->config[$key])."!");
				}
				$this->config[$key] = $value;
			} else {
				$this->getCore()->dropFatalError(__CLASS__.": Could not set config! Key $key is not defined!");
			}
		}
    }

    /**
    * Return the schema object from the cache
    *
    * If file is existent and cache has not expired yet,
    * then the cache is deserialized and returned.
    *
    * @return Net_LDAP2_Schema|Net_LDAP2_Error|false
    */
    public function loadSchema()
    {
         $return = false; // Net_LDAP2 will load schema from LDAP
         if (file_exists($this->config['path'])) {
             $cache_maxage = filemtime($this->config['path']) + $this->config['max_age'];
             if (time() <= $cache_maxage || $this->config['max_age'] == 0) {
                 $return = unserialize(file_get_contents($this->config['path']));
             }
         }
         return $return;
    }

    /**
    * Store a schema object in the cache
    *
    * This method will be called, if Net_LDAP2 has fetched a fresh
    * schema object from LDAP and wants to init or refresh the cache.
    *
    * To invalidate the cache and cause Net_LDAP2 to refresh the cache,
    * you can call this method with null or false as value.
    * The next call to $ldap->schema() will then refresh the caches object.
    *
    * @param mixed $schema The object that should be cached
    * @return true|Net_LDAP2_Error|false
    */
    public function storeSchema($schema) {
        file_put_contents($this->config['path'], serialize($schema));
        return true;
    }
}
PK|c�\
~��R�R#pear/net_ldap2/Net/LDAP2/Schema.phpnu�[���<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
/**
* File containing the Net_LDAP2_Schema interface class.
*
* PHP version 5
*
* @category  Net
* @package   Net_LDAP2
* @author    Jan Wagner <wagner@netsols.de>
* @author    Benedikt Hallinger <beni@php.net>
* @copyright 2009 Jan Wagner, Benedikt Hallinger
* @license   http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
* @version   SVN: $Id$
* @link      http://pear.php.net/package/Net_LDAP2/
* @todo see the comment at the end of the file
*/

/**
* Includes
*/
require_once 'PEAR.php';

/**
* Syntax definitions
*
* Please don't forget to add binary attributes to isBinary() below
* to support proper value fetching from Net_LDAP2_Entry
*/
define('NET_LDAP2_SYNTAX_BOOLEAN',            '1.3.6.1.4.1.1466.115.121.1.7');
define('NET_LDAP2_SYNTAX_DIRECTORY_STRING',   '1.3.6.1.4.1.1466.115.121.1.15');
define('NET_LDAP2_SYNTAX_DISTINGUISHED_NAME', '1.3.6.1.4.1.1466.115.121.1.12');
define('NET_LDAP2_SYNTAX_INTEGER',            '1.3.6.1.4.1.1466.115.121.1.27');
define('NET_LDAP2_SYNTAX_JPEG',               '1.3.6.1.4.1.1466.115.121.1.28');
define('NET_LDAP2_SYNTAX_NUMERIC_STRING',     '1.3.6.1.4.1.1466.115.121.1.36');
define('NET_LDAP2_SYNTAX_OID',                '1.3.6.1.4.1.1466.115.121.1.38');
define('NET_LDAP2_SYNTAX_OCTET_STRING',       '1.3.6.1.4.1.1466.115.121.1.40');

/**
* Load an LDAP Schema and provide information
*
* This class takes a Subschema entry, parses this information
* and makes it available in an array. Most of the code has been
* inspired by perl-ldap( http://perl-ldap.sourceforge.net).
* You will find portions of their implementation in here.
*
* @category Net
* @package  Net_LDAP2
* @author   Jan Wagner <wagner@netsols.de>
* @author   Benedikt Hallinger <beni@php.net>
* @license  http://www.gnu.org/copyleft/lesser.html LGPL
* @link     http://pear.php.net/package/Net_LDAP22/
*/
class Net_LDAP2_Schema extends PEAR
{
    /**
    * Map of entry types to ldap attributes of subschema entry
    *
    * @access public
    * @var array
    */
    public $types = array(
            'attribute'        => 'attributeTypes',
            'ditcontentrule'   => 'dITContentRules',
            'ditstructurerule' => 'dITStructureRules',
            'matchingrule'     => 'matchingRules',
            'matchingruleuse'  => 'matchingRuleUse',
            'nameform'         => 'nameForms',
            'objectclass'      => 'objectClasses',
            'syntax'           => 'ldapSyntaxes'
        );

    /**
    * Array of entries belonging to this type
    *
    * @access protected
    * @var array
    */
    protected $_attributeTypes    = array();
    protected $_matchingRules     = array();
    protected $_matchingRuleUse   = array();
    protected $_ldapSyntaxes      = array();
    protected $_objectClasses     = array();
    protected $_dITContentRules   = array();
    protected $_dITStructureRules = array();
    protected $_nameForms         = array();


    /**
    * hash of all fetched oids
    *
    * @access protected
    * @var array
    */
    protected $_oids = array();

    /**
    * Tells if the schema is initialized
    *
    * @access protected
    * @var boolean
    * @see parse(), get()
    */
    protected $_initialized = false;


    /**
    * Constructor of the class
    *
    * @access protected
    */
    public function __construct()
    {
        parent::__construct('Net_LDAP2_Error'); // default error class
    }

    /**
    * Fetch the Schema from an LDAP connection
    *
    * @param Net_LDAP2 $ldap LDAP connection
    * @param string    $dn   (optional) Subschema entry dn
    *
    * @access public
    * @return Net_LDAP2_Schema|NET_LDAP2_Error
    */
    public static function fetch($ldap, $dn = null)
    {
        if (!$ldap instanceof Net_LDAP2) {
            return PEAR::raiseError("Unable to fetch Schema: Parameter \$ldap must be a Net_LDAP2 object!");
        }

        $schema_o = new Net_LDAP2_Schema();

        if (is_null($dn)) {
            // get the subschema entry via root dse
            $dse = $ldap->rootDSE(array('subschemaSubentry'));
            if (false == Net_LDAP2::isError($dse)) {
                $base = $dse->getValue('subschemaSubentry', 'single');
                if (!Net_LDAP2::isError($base)) {
                    $dn = $base;
                }
            }
        }

        // Support for buggy LDAP servers (e.g. Siemens DirX 6.x) that incorrectly
        // call this entry subSchemaSubentry instead of subschemaSubentry.
        // Note the correct case/spelling as per RFC 2251.
        if (is_null($dn)) {
            // get the subschema entry via root dse
            $dse = $ldap->rootDSE(array('subSchemaSubentry'));
            if (false == Net_LDAP2::isError($dse)) {
                $base = $dse->getValue('subSchemaSubentry', 'single');
                if (!Net_LDAP2::isError($base)) {
                    $dn = $base;
                }
            }
        }

        // Final fallback case where there is no subschemaSubentry attribute
        // in the root DSE (this is a bug for an LDAP v3 server so report this
        // to your LDAP vendor if you get this far).
        if (is_null($dn)) {
            $dn = 'cn=Subschema';
        }

        // fetch the subschema entry
        $result = $ldap->search($dn, '(objectClass=*)',
                                array('attributes' => array_values($schema_o->types),
                                        'scope' => 'base'));
        if (Net_LDAP2::isError($result)) {
            return PEAR::raiseError('Could not fetch Subschema entry: '.$result->getMessage());
        }

        $entry = $result->shiftEntry();
        if (!$entry instanceof Net_LDAP2_Entry) {
            if ($entry instanceof Net_LDAP2_Error) {
                return PEAR::raiseError('Could not fetch Subschema entry: '.$entry->getMessage());
            } else {
                return PEAR::raiseError('Could not fetch Subschema entry (search returned '.$result->count().' entries. Check parameter \'basedn\')');
            }
        }

        $schema_o->parse($entry);
        return $schema_o;
    }

    /**
    * Return a hash of entries for the given type
    *
    * Returns a hash of entry for the givene type. Types may be:
    * objectclasses, attributes, ditcontentrules, ditstructurerules, matchingrules,
    * matchingruleuses, nameforms, syntaxes
    *
    * @param string $type Type to fetch
    *
    * @access public
    * @return array|Net_LDAP2_Error Array or Net_LDAP2_Error
    */
    public function &getAll($type)
    {
        $map = array('objectclasses'     => &$this->_objectClasses,
                     'attributes'        => &$this->_attributeTypes,
                     'ditcontentrules'   => &$this->_dITContentRules,
                     'ditstructurerules' => &$this->_dITStructureRules,
                     'matchingrules'     => &$this->_matchingRules,
                     'matchingruleuses'  => &$this->_matchingRuleUse,
                     'nameforms'         => &$this->_nameForms,
                     'syntaxes'          => &$this->_ldapSyntaxes );

        $key = strtolower($type);
        $ret = ((key_exists($key, $map)) ? $map[$key] : PEAR::raiseError("Unknown type $type"));
        return $ret;
    }

    /**
    * Return a specific entry
    *
    * @param string $type Type of name
    * @param string $name Name or OID to fetch
    *
    * @access public
    * @return mixed Entry or Net_LDAP2_Error
    */
    public function &get($type, $name)
    {
        if ($this->_initialized) {
            $type = strtolower($type);
            if (false == key_exists($type, $this->types)) {
                return PEAR::raiseError("No such type $type");
            }

            $name     = strtolower($name);
            $type_var = &$this->{'_' . $this->types[$type]};

            if (key_exists($name, $type_var)) {
                return $type_var[$name];
            } elseif (key_exists($name, $this->_oids) && $this->_oids[$name]['type'] == $type) {
                return $this->_oids[$name];
            } else {
                return PEAR::raiseError("Could not find $type $name");
            }
        } else {
            $return = null;
            return $return;
        }
    }


    /**
    * Fetches attributes that MAY be present in the given objectclass
    *
    * @param string $oc Name or OID of objectclass
    *
    * @access public
    * @return array|Net_LDAP2_Error Array with attributes or Net_LDAP2_Error
    */
    public function may($oc)
    {
        return $this->_getAttr($oc, 'may');
    }

    /**
    * Fetches attributes that MUST be present in the given objectclass
    *
    * @param string $oc Name or OID of objectclass
    *
    * @access public
    * @return array|Net_LDAP2_Error Array with attributes or Net_LDAP2_Error
    */
    public function must($oc)
    {
        return $this->_getAttr($oc, 'must');
    }

    /**
    * Fetches the given attribute from the given objectclass
    *
    * @param string $oc   Name or OID of objectclass
    * @param string $attr Name of attribute to fetch
    *
    * @access protected
    * @return array|Net_LDAP2_Error The attribute or Net_LDAP2_Error
    */
    protected function _getAttr($oc, $attr)
    {
        $oc = strtolower($oc);
        if (key_exists($oc, $this->_objectClasses) && key_exists($attr, $this->_objectClasses[$oc])) {
            return $this->_objectClasses[$oc][$attr];
        } elseif (key_exists($oc, $this->_oids) &&
                $this->_oids[$oc]['type'] == 'objectclass' &&
                key_exists($attr, $this->_oids[$oc])) {
            return $this->_oids[$oc][$attr];
        } else {
            return PEAR::raiseError("Could not find $attr attributes for $oc ");
        }
    }

    /**
    * Returns the name(s) of the immediate superclass(es)
    *
    * @param string $oc Name or OID of objectclass
    *
    * @access public
    * @return array|Net_LDAP2_Error  Array of names or Net_LDAP2_Error
    */
    public function superclass($oc)
    {
        $o = $this->get('objectclass', $oc);
        if (Net_LDAP2::isError($o)) {
            return $o;
        }
        return (key_exists('sup', $o) ? $o['sup'] : array());
    }

    /**
    * Parses the schema of the given Subschema entry
    *
    * @param Net_LDAP2_Entry &$entry Subschema entry
    *
    * @access public
    * @return void
    */
    public function parse(&$entry)
    {
        foreach ($this->types as $type => $attr) {
            // initialize map type to entry
            $type_var          = '_' . $attr;
            $this->{$type_var} = array();

            // get values for this type
            if ($entry->exists($attr)) {
                $values = $entry->getValue($attr);
                if (is_array($values)) {
                    foreach ($values as $value) {

                        unset($schema_entry); // this was a real mess without it

                        // get the schema entry
                        $schema_entry = $this->_parse_entry($value);

                        // set the type
                        $schema_entry['type'] = $type;

                        // save a ref in $_oids
                        $this->_oids[$schema_entry['oid']] = &$schema_entry;

                        // save refs for all names in type map
                        $names = $schema_entry['aliases'];
                        array_push($names, $schema_entry['name']);
                        foreach ($names as $name) {
                            $this->{$type_var}[strtolower($name)] = &$schema_entry;
                        }
                    }
                }
            }
        }
        $this->_initialized = true;
    }

    /**
    * Parses an attribute value into a schema entry
    *
    * @param string $value Attribute value
    *
    * @access protected
    * @return array|false Schema entry array or false
    */
    protected function &_parse_entry($value)
    {
        // tokens that have no value associated
        $noValue = array('single-value',
                         'obsolete',
                         'collective',
                         'no-user-modification',
                         'abstract',
                         'structural',
                         'auxiliary');

        // tokens that can have multiple values
        $multiValue = array('must', 'may', 'sup');

        $schema_entry = array('aliases' => array()); // initilization

        $tokens = $this->_tokenize($value); // get an array of tokens

        // remove surrounding brackets
        if ($tokens[0] == '(') array_shift($tokens);
        if ($tokens[count($tokens) - 1] == ')') array_pop($tokens); // -1 doesnt work on arrays :-(

        $schema_entry['oid'] = array_shift($tokens); // first token is the oid

        // cycle over the tokens until none are left
        while (count($tokens) > 0) {
            $token = strtolower(array_shift($tokens));
            if (in_array($token, $noValue)) {
                $schema_entry[$token] = 1; // single value token
            } else {
                // this one follows a string or a list if it is multivalued
                if (($schema_entry[$token] = array_shift($tokens)) == '(') {
                    // this creates the list of values and cycles through the tokens
                    // until the end of the list is reached ')'
                    $schema_entry[$token] = array();
                    while ($tmp = array_shift($tokens)) {
                        if ($tmp == ')') break;
                        if ($tmp != '$') array_push($schema_entry[$token], $tmp);
                    }
                }
                // create a array if the value should be multivalued but was not
                if (in_array($token, $multiValue) && !is_array($schema_entry[$token])) {
                    $schema_entry[$token] = array($schema_entry[$token]);
                }
            }
        }
        // get max length from syntax
        if (key_exists('syntax', $schema_entry)) {
            if (preg_match('/{(\d+)}/', $schema_entry['syntax'], $matches)) {
                $schema_entry['max_length'] = $matches[1];
            }
        }
        // force a name
        if (empty($schema_entry['name'])) {
            $schema_entry['name'] = $schema_entry['oid'];
        }
        // make one name the default and put the other ones into aliases
        if (is_array($schema_entry['name'])) {
            $aliases                 = $schema_entry['name'];
            $schema_entry['name']    = array_shift($aliases);
            $schema_entry['aliases'] = $aliases;
        }
        return $schema_entry;
    }

    /**
    * Tokenizes the given value into an array of tokens
    *
    * @param string $value String to parse
    *
    * @access protected
    * @return array Array of tokens
    */
    protected function _tokenize($value)
    {
        $tokens  = array();       // array of tokens
        $matches = array();       // matches[0] full pattern match, [1,2,3] subpatterns

        // this one is taken from perl-ldap, modified for php
        $pattern = "/\s* (?:([()]) | ([^'\s()]+) | '((?:[^']+|'[^\s)])*)') \s*/x";

        /**
         * This one matches one big pattern wherin only one of the three subpatterns matched
         * We are interested in the subpatterns that matched. If it matched its value will be
         * non-empty and so it is a token. Tokens may be round brackets, a string, or a string
         * enclosed by '
         */
        preg_match_all($pattern, $value, $matches);

        for ($i = 0; $i < count($matches[0]); $i++) {     // number of tokens (full pattern match)
            for ($j = 1; $j < 4; $j++) {                  // each subpattern
                if (null != trim($matches[$j][$i])) {     // pattern match in this subpattern
                    $tokens[$i] = trim($matches[$j][$i]); // this is the token
                }
            }
        }
        return $tokens;
    }

    /**
    * Returns wether a attribute syntax is binary or not
    *
    * This method gets used by Net_LDAP2_Entry to decide which
    * PHP function needs to be used to fetch the value in the
    * proper format (e.g. binary or string)
    *
    * @param string $attribute The name of the attribute (eg.: 'sn')
    *
    * @access public
    * @return boolean
    */
    public function isBinary($attribute)
    {
        $return = false; // default to false

        // This list contains all syntax that should be treaten as
        // containing binary values
        // The Syntax Definitons go into constants at the top of this page
        $syntax_binary = array(
                           NET_LDAP2_SYNTAX_OCTET_STRING,
                           NET_LDAP2_SYNTAX_JPEG
                         );

        // Check Syntax
        $attr_s = $this->get('attribute', $attribute);
        if (Net_LDAP2::isError($attr_s)) {
            // Attribute not found in schema
            $return = false; // consider attr not binary
        } elseif (isset($attr_s['syntax']) && in_array($attr_s['syntax'], $syntax_binary)) {
            // Syntax is defined as binary in schema
            $return = true;
        } else {
            // Syntax not defined as binary, or not found
            // if attribute is a subtype, check superior attribute syntaxes
            if (isset($attr_s['sup'])) {
                foreach ($attr_s['sup'] as $superattr) {
                    $return = $this->isBinary($superattr);
                    if ($return) {
                        break; // stop checking parents since we are binary
                    }
                }
            }
        }

        return $return;
    }

    /**
    * See if an schema element exists
    *
    * @param string $type Type of name, see get()
    * @param string $name Name or OID
    *
    * @return boolean
    */
    public function exists($type, $name)
    {
        $entry = $this->get($type, $name);
        if ($entry instanceof Net_LDAP2_ERROR) {
                return false;
        } else {
            return true;
        }
    }

    /**
    * See if an attribute is defined in the schema
    *
    * @param string $attribute Name or OID of the attribute
    * @return boolean
    */
    public function attributeExists($attribute)
    {
        return $this->exists('attribute', $attribute);
    }

    /**
    * See if an objectClass is defined in the schema
    *
    * @param string $ocl Name or OID of the objectClass
    * @return boolean
    */
    public function objectClassExists($ocl)
    {
        return $this->exists('objectclass', $ocl);
    }


    /**
    * See to which ObjectClasses an attribute is assigned
    *
    * The objectclasses are sorted into the keys 'may' and 'must'.
    *
    * @param string $attribute Name or OID of the attribute
    *
    * @return array|Net_LDAP2_Error Associative array with OCL names or Error
    */
    public function getAssignedOCLs($attribute)
    {
        $may  = array();
        $must = array();

        // Test if the attribute type is defined in the schema,
        // if so, retrieve real name for lookups
        $attr_entry = $this->get('attribute', $attribute);
        if ($attr_entry instanceof Net_LDAP2_ERROR) {
            return PEAR::raiseError("Attribute $attribute not defined in schema: ".$attr_entry->getMessage());
        } else {
            $attribute = $attr_entry['name'];
        }


        // We need to get all defined OCLs for this.
        $ocls = $this->getAll('objectclasses');
        foreach ($ocls as $ocl => $ocl_data) {
            // Fetch the may and must attrs and see if our searched attr is contained.
            // If so, record it in the corresponding array.
            $ocl_may_attrs  = $this->may($ocl);
            $ocl_must_attrs = $this->must($ocl);
            if (is_array($ocl_may_attrs) && in_array($attribute, $ocl_may_attrs)) {
                array_push($may, $ocl_data['name']);
            }
            if (is_array($ocl_must_attrs) && in_array($attribute, $ocl_must_attrs)) {
                array_push($must, $ocl_data['name']);
            }
        }

        return array('may' => $may, 'must' => $must);
    }

    /**
    * See if an attribute is available in a set of objectClasses
    *
    * @param string $attribute Attribute name or OID
    * @param array $ocls       Names of OCLs to check for
    *
    * @return boolean TRUE, if the attribute is defined for at least one of the OCLs
    */
    public function checkAttribute($attribute, $ocls)
    {
        foreach ($ocls as $ocl) {
            $ocl_entry = $this->get('objectclass', $ocl);
            $ocl_may_attrs  = $this->may($ocl);
            $ocl_must_attrs = $this->must($ocl);
            if (is_array($ocl_may_attrs) && in_array($attribute, $ocl_may_attrs)) {
                return true;
            }
            if (is_array($ocl_must_attrs) && in_array($attribute, $ocl_must_attrs)) {
                return true;
            }
        }
        return false; // no ocl for the ocls found.
    }
}
?>PK|c�\�r�B����!pear/net_ldap2/Net/LDAP2/LDIF.phpnu�[���<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
/**
* File containing the Net_LDAP2_LDIF interface class.
*
* PHP version 5
*
* @category  Net
* @package   Net_LDAP2
* @author    Benedikt Hallinger <beni@php.net>
* @copyright 2009 Benedikt Hallinger
* @license   http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
* @version   SVN: $Id$
* @link      http://pear.php.net/package/Net_LDAP2/
*/

/**
* Includes
*/
require_once 'PEAR.php';
require_once 'Net/LDAP2.php';
require_once 'Net/LDAP2/Entry.php';
require_once 'Net/LDAP2/Util.php';

/**
* LDIF capabilitys for Net_LDAP2, closely taken from PERLs Net::LDAP
*
* It provides a means to convert between Net_LDAP2_Entry objects and LDAP entries
* represented in LDIF format files. Reading and writing are supported and may
* manipulate single entries or lists of entries.
*
* Usage example:
* <code>
* // Read and parse an ldif-file into Net_LDAP2_Entry objects
* // and print out the DNs. Store the entries for later use.
* require 'Net/LDAP2/LDIF.php';
* $options = array(
*       'onerror' => 'die'
* );
* $entries = array();
* $ldif = new Net_LDAP2_LDIF('test.ldif', 'r', $options);
* do {
*       $entry = $ldif->read_entry();
*       $dn    = $entry->dn();
*       echo " done building entry: $dn\n";
*       array_push($entries, $entry);
* } while (!$ldif->eof());
* $ldif->done();
*
*
* // write those entries to another file
* $ldif = new Net_LDAP2_LDIF('test.out.ldif', 'w', $options);
* $ldif->write_entry($entries);
* $ldif->done();
* </code>
*
* @category Net
* @package  Net_LDAP2
* @author   Benedikt Hallinger <beni@php.net>
* @license  http://www.gnu.org/copyleft/lesser.html LGPL
* @link     http://pear.php.net/package/Net_LDAP22/
* @see      http://www.ietf.org/rfc/rfc2849.txt
* @todo     Error handling should be PEARified
* @todo     LDAPv3 controls are not implemented yet
*/
class Net_LDAP2_LDIF extends PEAR
{
    /**
    * Options
    *
    * @access protected
    * @var array
    */
    protected $_options = array('encode'    => 'base64',
                                'onerror'   => null,
                                'change'    => 0,
                                'lowercase' => 0,
                                'sort'      => 0,
                                'version'   => null,
                                'wrap'      => 78,
                                'raw'       => ''
                               );

    /**
    * Errorcache
    *
    * @access protected
    * @var array
    */
    protected $_error = array('error' => null,
                              'line'  => 0
                             );

    /**
    * Filehandle for read/write
    *
    * @access protected
    * @var array
    */
    protected $_FH = null;

    /**
    * Says, if we opened the filehandle ourselves
    *
    * @access protected
    * @var array
    */
    protected $_FH_opened = false;

    /**
    * Linecounter for input file handle
    *
    * @access protected
    * @var array
    */
    protected $_input_line = 0;

    /**
    * counter for processed entries
    *
    * @access protected
    * @var int
    */
    protected $_entrynum = 0;

    /**
    * Mode we are working in
    *
    * Either 'r', 'a' or 'w'
    *
    * @access protected
    * @var string
    */
    protected $_mode = false;

    /**
    * Tells, if the LDIF version string was already written
    *
    * @access protected
    * @var boolean
    */
    protected $_version_written = false;

    /**
    * Cache for lines that have build the current entry
    *
    * @access protected
    * @var boolean
    */
    protected $_lines_cur = array();

    /**
    * Cache for lines that will build the next entry
    *
    * @access protected
    * @var boolean
    */
    protected $_lines_next = array();

    /**
    * Open LDIF file for reading or for writing
    *
    * new (FILE):
    * Open the file read-only. FILE may be the name of a file
    * or an already open filehandle.
    * If the file doesn't exist, it will be created if in write mode.
    *
    * new (FILE, MODE, OPTIONS):
    *     Open the file with the given MODE (see PHPs fopen()), eg "w" or "a".
    *     FILE may be the name of a file or an already open filehandle.
    *     PERLs Net_LDAP2 "FILE|" mode does not work curently.
    *
    *     OPTIONS is an associative array and may contain:
    *       encode => 'none' | 'canonical' | 'base64'
    *         Some DN values in LDIF cannot be written verbatim and have to be encoded in some way:
    *         'none'       No encoding.
    *         'canonical'  See "canonical_dn()" in Net::LDAP::Util.
    *         'base64'     Use base64. (default, this differs from the Perl interface.
    *                                   The perl default is "none"!)
    *
    *       onerror => 'die' | 'warn' | NULL
    *         Specify what happens when an error is detected.
    *         'die'  Net_LDAP2_LDIF will croak with an appropriate message.
    *         'warn' Net_LDAP2_LDIF will warn (echo) with an appropriate message.
    *         NULL   Net_LDAP2_LDIF will not warn (default), use error().
    *
    *       change => 1
    *         Write entry changes to the LDIF file instead of the entries itself. I.e. write LDAP
    *         operations acting on the entries to the file instead of the entries contents.
    *         This writes the changes usually carried out by an update() to the LDIF file.
    *
    *       lowercase => 1
    *         Convert attribute names to lowercase when writing.
    *
    *       sort => 1
    *         Sort attribute names when writing entries according to the rule:
    *         objectclass first then all other attributes alphabetically sorted by attribute name
    *
    *       version => '1'
    *         Set the LDIF version to write to the resulting LDIF file.
    *         According to RFC 2849 currently the only legal value for this option is 1.
    *         When this option is set Net_LDAP2_LDIF tries to adhere more strictly to
    *         the LDIF specification in RFC2489 in a few places.
    *         The default is NULL meaning no version information is written to the LDIF file.
    *
    *       wrap => 78
    *         Number of columns where output line wrapping shall occur.
    *         Default is 78. Setting it to 40 or lower inhibits wrapping.
    *
    *       raw => REGEX
    *         Use REGEX to denote the names of attributes that are to be
    *         considered binary in search results if writing entries.
    *         Example: raw => "/(?i:^jpegPhoto|;binary)/i"
    *
    * @param string|ressource $file    Filename or filehandle
    * @param string           $mode    Mode to open filename
    * @param array            $options Options like described above
    */
    public function __construct($file, $mode = 'r', $options = array())
    {
        parent::__construct('Net_LDAP2_Error'); // default error class

        // First, parse options
        // todo: maybe implement further checks on possible values
        foreach ($options as $option => $value) {
            if (!array_key_exists($option, $this->_options)) {
                $this->dropError('Net_LDAP2_LDIF error: option '.$option.' not known!');
                return;
            } else {
                $this->_options[$option] = strtolower($value);
            }
        }

        // setup LDIF class
        $this->version($this->_options['version']);

        // setup file mode
        if (!preg_match('/^[rwa]\+?$/', $mode)) {
            $this->dropError('Net_LDAP2_LDIF error: file mode '.$mode.' not supported!');
        } else {
            $this->_mode = $mode;

            // setup filehandle
            if (is_resource($file)) {
                // TODO: checks on mode possible?
                $this->_FH =& $file;
            } else {
                $imode = substr($this->_mode, 0, 1);
                if ($imode == 'r') {
                    if (!file_exists($file)) {
                        $this->dropError('Unable to open '.$file.' for read: file not found');
                        $this->_mode = false;
                    }
                    if (!is_readable($file)) {
                        $this->dropError('Unable to open '.$file.' for read: permission denied');
                        $this->_mode = false;
                    }
                }

                if (($imode == 'w' || $imode == 'a')) {
                    if (file_exists($file)) {
                        if (!is_writable($file)) {
                            $this->dropError('Unable to open '.$file.' for write: permission denied');
                            $this->_mode = false;
                        }
                    } else {
                        if (!@touch($file)) {
                            $this->dropError('Unable to create '.$file.' for write: permission denied');
                            $this->_mode = false;
                        }
                    }
                }

                if ($this->_mode) {
                    $this->_FH = @fopen($file, $this->_mode);
                    if (false === $this->_FH) {
                        // Fallback; should never be reached if tests above are good enough!
                        $this->dropError('Net_LDAP2_LDIF error: Could not open file '.$file);
                    } else {
                        $this->_FH_opened = true;
                    }
                }
            }
        }
    }

    /**
    * Read one entry from the file and return it as a Net::LDAP::Entry object.
    *
    * @return Net_LDAP2_Entry
    */
    public function read_entry()
    {
        // read fresh lines, set them as current lines and create the entry
        $attrs = $this->next_lines(true);
        if (count($attrs) > 0) {
            $this->_lines_cur = $attrs;
        }
        return $this->current_entry();
    }

    /**
    * Returns true when the end of the file is reached.
    *
    * @return boolean
    */
    public function eof()
    {
        return feof($this->_FH);
    }

    /**
    * Write the entry or entries to the LDIF file.
    *
    * If you want to build an LDIF file containing several entries AND
    * you want to call write_entry() several times, you must open the filehandle
    * in append mode ("a"), otherwise you will always get the last entry only.
    *
    * @param Net_LDAP2_Entry|array $entries Entry or array of entries
    *
    * @return void
    * @todo implement operations on whole entries (adding a whole entry)
    */
    public function write_entry($entries)
    {
        if (!is_array($entries)) {
            $entries = array($entries);
        }

        foreach ($entries as $entry) {
            $this->_entrynum++;
            if (!$entry instanceof Net_LDAP2_Entry) {
                $this->dropError('Net_LDAP2_LDIF error: entry '.$this->_entrynum.' is not an Net_LDAP2_Entry object');
            } else {
                if ($this->_options['change']) {
                    // LDIF change mode
                    // fetch change information from entry
                    $entry_attrs_changes = $entry->getChanges();
                    $num_of_changes      = count($entry_attrs_changes['add'])
                                           + count($entry_attrs_changes['replace'])
                                           + count($entry_attrs_changes['delete']);


                    $is_changed = ($num_of_changes > 0 || $entry->willBeDeleted() || $entry->willBeMoved());

                    // write version if not done yet
                    // also write DN of entry
                    if ($is_changed) {
                        if (!$this->_version_written) {
                            $this->write_version();
                        }
                        $this->writeDN($entry->currentDN());
                    }

                    // process changes
                    // TODO: consider DN add!
                    if ($entry->willBeDeleted()) {
                        $this->writeLine("changetype: delete".PHP_EOL);
                    } elseif ($entry->willBeMoved()) {
                        $this->writeLine("changetype: modrdn".PHP_EOL);
                        $olddn     = Net_LDAP2_Util::ldap_explode_dn($entry->currentDN(), array('casefold' => 'none')); // maybe gives a bug if using multivalued RDNs
                        $oldrdn    = array_shift($olddn);
                        $oldparent = implode(',', $olddn);
                        $newdn     = Net_LDAP2_Util::ldap_explode_dn($entry->dn(), array('casefold' => 'none')); // maybe gives a bug if using multivalued RDNs
                        $rdn       = array_shift($newdn);
                        $parent    = implode(',', $newdn);
                        $this->writeLine("newrdn: ".$rdn.PHP_EOL);
                        $this->writeLine("deleteoldrdn: 1".PHP_EOL);
                        if ($parent !== $oldparent) {
                            $this->writeLine("newsuperior: ".$parent.PHP_EOL);
                        }
                        // TODO: What if the entry has attribute changes as well?
                        //       I think we should check for that and make a dummy
                        //       entry with the changes that is written to the LDIF file
                    } elseif ($num_of_changes > 0) {
                        // write attribute change data
                        $this->writeLine("changetype: modify".PHP_EOL);
                        foreach ($entry_attrs_changes as $changetype => $entry_attrs) {
                            foreach ($entry_attrs as $attr_name => $attr_values) {
                                $this->writeLine("$changetype: $attr_name".PHP_EOL);
                                if ($attr_values !== null) $this->writeAttribute($attr_name, $attr_values, $changetype);
                                $this->writeLine("-".PHP_EOL);
                            }
                        }
                    }

                    // finish this entrys data if we had changes
                    if ($is_changed) {
                        $this->finishEntry();
                    }
                } else {
                    // LDIF-content mode
                    // fetch attributes for further processing
                    $entry_attrs = $entry->getValues();

                    // sort and put objectclass-attrs to first position
                    if ($this->_options['sort']) {
                        ksort($entry_attrs);
                        if (array_key_exists('objectclass', $entry_attrs)) {
                            $oc = $entry_attrs['objectclass'];
                            unset($entry_attrs['objectclass']);
                            $entry_attrs = array_merge(array('objectclass' => $oc), $entry_attrs);
                        }
                    }

                    // write data
                    if (!$this->_version_written) {
                        $this->write_version();
                    }
                    $this->writeDN($entry->dn());
                    foreach ($entry_attrs as $attr_name => $attr_values) {
                        $this->writeAttribute($attr_name, $attr_values);
                    }
                    $this->finishEntry();
                }
            }
        }
    }

    /**
    * Write version to LDIF
    *
    * If the object's version is defined, this method allows to explicitely write the version before an entry is written.
    * If not called explicitely, it gets called automatically when writing the first entry.
    *
    * @return void
    */
    public function write_version()
    {
        $this->_version_written = true;
        if (!is_null($this->version())) {
            return $this->writeLine('version: '.$this->version().PHP_EOL, 'Net_LDAP2_LDIF error: unable to write version');
        }
    }

    /**
    * Get or set LDIF version
    *
    * If called without arguments it returns the version of the LDIF file or NULL if no version has been set.
    * If called with an argument it sets the LDIF version to VERSION.
    * According to RFC 2849 currently the only legal value for VERSION is 1.
    *
    * @param int $version (optional) LDIF version to set
    *
    * @return int
    */
    public function version($version = null)
    {
        if ($version !== null) {
            if ($version != 1) {
                $this->dropError('Net_LDAP2_LDIF error: illegal LDIF version set');
            } else {
                $this->_options['version'] = $version;
            }
        }
        return $this->_options['version'];
    }

    /**
    * Returns the file handle the Net_LDAP2_LDIF object reads from or writes to.
    *
    * You can, for example, use this to fetch the content of the LDIF file yourself
    *
    * @return null|resource
    */
    public function &handle()
    {
        if (!is_resource($this->_FH)) {
            $this->dropError('Net_LDAP2_LDIF error: invalid file resource');
            $null = null;
            return $null;
        } else {
            return $this->_FH;
        }
    }

    /**
    * Clean up
    *
    * This method signals that the LDIF object is no longer needed.
    * You can use this to free up some memory and close the file handle.
    * The file handle is only closed, if it was opened from Net_LDAP2_LDIF.
    *
    * @return void
    */
    public function done()
    {
        // close FH if we opened it
        if ($this->_FH_opened) {
            fclose($this->handle());
        }

        // free variables
        foreach (get_object_vars($this) as $name => $value) {
            unset($this->$name);
        }
    }

    /**
    * Returns last error message if error was found.
    *
    * Example:
    * <code>
    *  $ldif->someAction();
    *  if ($ldif->error()) {
    *     echo "Error: ".$ldif->error()." at input line: ".$ldif->error_lines();
    *  }
    * </code>
    *
    * @param boolean $as_string If set to true, only the message is returned
    *
    * @return false|Net_LDAP2_Error
    */
    public function error($as_string = false)
    {
        if (Net_LDAP2::isError($this->_error['error'])) {
            return ($as_string)? $this->_error['error']->getMessage() : $this->_error['error'];
        } else {
            return false;
        }
    }

    /**
    * Returns lines that resulted in error.
    *
    * Perl returns an array of faulty lines in list context,
    * but we always just return an int because of PHPs language.
    *
    * @return int
    */
    public function error_lines()
    {
        return $this->_error['line'];
    }

    /**
    * Returns the current Net::LDAP::Entry object.
    *
    * @return Net_LDAP2_Entry|false
    */
    public function current_entry()
    {
        return $this->parseLines($this->current_lines());
    }

    /**
    * Parse LDIF lines of one entry into an Net_LDAP2_Entry object
    *
    * @param array $lines LDIF lines for one entry
    *
    * @return Net_LDAP2_Entry|false Net_LDAP2_Entry object for those lines
    * @todo what about file inclusions and urls? "jpegphoto:< file:///usr/local/directory/photos/fiona.jpg"
    */
    public function parseLines($lines)
    {
        // parse lines into an array of attributes and build the entry
        $attributes = array();
        $dn = false;
        foreach ($lines as $line) {
            if (preg_match('/^(\w+(;binary)?)(:|::|:<)\s(.+)$/', $line, $matches)) {
                $attr  =& $matches[1] . $matches[2];
                $delim =& $matches[3];
                $data  =& $matches[4];

                if ($delim == ':') {
                    // normal data
                    $attributes[$attr][] = $data;
                } elseif ($delim == '::') {
                    // base64 data
                    $attributes[$attr][] = base64_decode($data);
                } elseif ($delim == ':<') {
                    // file inclusion
                    // TODO: Is this the job of the LDAP-client or the server?
                    $this->dropError('File inclusions are currently not supported');
                    //$attributes[$attr][] = ...;
                } else {
                    // since the pattern above, the delimeter cannot be something else.
                    $this->dropError('Net_LDAP2_LDIF parsing error: invalid syntax at parsing entry line: '.$line);
                    continue;
                }

                if (strtolower($attr) == 'dn') {
                    // DN line detected
                    $dn = $attributes[$attr][0];  // save possibly decoded DN
                    unset($attributes[$attr]);    // remove wrongly added "dn: " attribute
                }
            } else {
                // line not in "attr: value" format -> ignore
                // maybe we should rise an error here, but this should be covered by
                // next_lines() already. A problem arises, if users try to feed data of
                // several entries to this method - the resulting entry will
                // get wrong attributes. However, this is already mentioned in the
                // methods documentation above.
            }
        }

        if (false === $dn) {
            $this->dropError('Net_LDAP2_LDIF parsing error: unable to detect DN for entry');
            return false;
        } else {
            $newentry = Net_LDAP2_Entry::createFresh($dn, $attributes);
            return $newentry;
        }
    }

    /**
    * Returns the lines that generated the current Net::LDAP::Entry object.
    *
    * Note that this returns an empty array if no lines have been read so far.
    *
    * @return array Array of lines
    */
    public function current_lines()
    {
        return $this->_lines_cur;
    }

    /**
    * Returns the lines that will generate the next Net::LDAP::Entry object.
    *
    * If you set $force to TRUE then you can iterate over the lines that build
    * up entries manually. Otherwise, iterating is done using {@link read_entry()}.
    * Force will move the file pointer forward, thus returning the next entries lines.
    *
    * Wrapped lines will be unwrapped. Comments are stripped.
    *
    * @param boolean $force Set this to true if you want to iterate over the lines manually
    *
    * @return array
    */
    public function next_lines($force = false)
    {
        // if we already have those lines, just return them, otherwise read
        if (count($this->_lines_next) == 0 || $force) {
            $this->_lines_next = array(); // empty in case something was left (if used $force)
            $entry_done        = false;
            $fh                = &$this->handle();
            $commentmode       = false; // if we are in an comment, for wrapping purposes
            $datalines_read    = 0;     // how many lines with data we have read

            while (!$entry_done && !$this->eof()) {
                $this->_input_line++;
                // Read line. Remove line endings, we want only data;
                // this is okay since ending spaces should be encoded
                $data = rtrim(fgets($fh));
                if ($data === false) {
                    // error only, if EOF not reached after fgets() call
                    if (!$this->eof()) {
                        $this->dropError('Net_LDAP2_LDIF error: error reading from file at input line '.$this->_input_line, $this->_input_line);
                    }
                    break;
                } else {
                    if (count($this->_lines_next) > 0 && preg_match('/^$/', $data)) {
                        // Entry is finished if we have an empty line after we had data
                        $entry_done = true;

                        // Look ahead if the next EOF is nearby. Comments and empty
                        // lines at the file end may cause problems otherwise
                        $current_pos = ftell($fh);
                        $data        = fgets($fh);
                        while (!feof($fh)) {
                            if (preg_match('/^\s*$/', $data) || preg_match('/^#/', $data)) {
                                // only empty lines or comments, continue to seek
                                // TODO: Known bug: Wrappings for comments are okay but are treaten as
                                //       error, since we do not honor comment mode here.
                                //       This should be a very theoretically case, however
                                //       i am willing to fix this if really necessary.
                                $this->_input_line++;
                                $current_pos = ftell($fh);
                                $data        = fgets($fh);
                            } else {
                                // Data found if non emtpy line and not a comment!!
                                // Rewind to position prior last read and stop lookahead
                                fseek($fh, $current_pos);
                                break;
                            }
                        }
                        // now we have either the file pointer at the beginning of
                        // a new data position or at the end of file causing feof() to return true

                    } else {
                        // build lines
                        if (preg_match('/^version:\s(.+)$/', $data, $match)) {
                            // version statement, set version
                            $this->version($match[1]);
                        } elseif (preg_match('/^\w+(;binary)?::?\s.+$/', $data)) {
                            // normal attribute: add line
                            $commentmode         = false;
                            $this->_lines_next[] = trim($data);
                            $datalines_read++;
                        } elseif (preg_match('/^\s(.+)$/', $data, $matches)) {
                            // wrapped data: unwrap if not in comment mode
                            // note that the \s above is some more liberal than
                            // the RFC requests as it also matches tabs etc.
                            if (!$commentmode) {
                                if ($datalines_read == 0) {
                                    // first line of entry: wrapped data is illegal
                                    $this->dropError('Net_LDAP2_LDIF error: illegal wrapping at input line '.$this->_input_line, $this->_input_line);
                                } else {
                                    $last                = array_pop($this->_lines_next);
                                    $last                = $last.$matches[1];
                                    $this->_lines_next[] = $last;
                                    $datalines_read++;
                                }
                            }
                        } elseif (preg_match('/^#/', $data)) {
                            // LDIF comments
                            $commentmode = true;
                        } elseif (preg_match('/^\s*$/', $data)) {
                            // empty line but we had no data for this
                            // entry, so just ignore this line
                            $commentmode = false;
                        } else {
                            $this->dropError('Net_LDAP2_LDIF error: invalid syntax at input line '.$this->_input_line, $this->_input_line);
                            continue;
                        }

                    }
                }
            }
        }
        return $this->_lines_next;
    }

    /**
    * Convert an attribute and value to LDIF string representation
    *
    * It honors correct encoding of values according to RFC 2849.
    * Line wrapping will occur at the configured maximum but only if
    * the value is greater than 40 chars.
    *
    * @param string $attr_name  Name of the attribute
    * @param string $attr_value Value of the attribute
    *
    * @access protected
    * @return string LDIF string for that attribute and value
    */
    protected function convertAttribute($attr_name, $attr_value)
    {
        // Handle empty attribute or process
        if (strlen($attr_value) == 0) {
            $attr_value = " ";
        } else {
            $base64 = false;
            // ASCII-chars that are NOT safe for the
            // start and for being inside the value.
            // These are the int values of those chars.
            $unsafe_init = array(0, 10, 13, 32, 58, 60);
            $unsafe      = array(0, 10, 13);

            // Test for illegal init char
            $init_ord = ord(substr($attr_value, 0, 1));
            if ($init_ord > 127 || in_array($init_ord, $unsafe_init)) {
                $base64 = true;
            }

            // Test for illegal content char
            for ($i = 0; $i < strlen($attr_value); $i++) {
                $char_ord = ord(substr($attr_value, $i, 1));
                if ($char_ord > 127 || in_array($char_ord, $unsafe)) {
                    $base64 = true;
                }
            }

            // Test for ending space
            if (substr($attr_value, -1) == ' ') {
                $base64 = true;
            }

            // If converting is needed, do it
            // Either we have some special chars or a matching "raw" regex
            if ($base64 || ($this->_options['raw'] && preg_match($this->_options['raw'], $attr_name))) {
                $attr_name .= ':';
                $attr_value = base64_encode($attr_value);
            }

            // Lowercase attr names if requested
            if ($this->_options['lowercase']) $attr_name = strtolower($attr_name);

            // Handle line wrapping
            if ($this->_options['wrap'] > 40 && strlen($attr_value) > $this->_options['wrap']) {
                $attr_value = wordwrap($attr_value, $this->_options['wrap'], PHP_EOL." ", true);
            }
        }

        return $attr_name.': '.$attr_value;
    }

    /**
    * Convert an entries DN to LDIF string representation
    *
    * It honors correct encoding of values according to RFC 2849.
    *
    * @param string $dn UTF8-Encoded DN
    *
    * @access protected
    * @return string LDIF string for that DN
    * @todo I am not sure, if the UTF8 stuff is correctly handled right now
    */
    protected function convertDN($dn)
    {
        $base64 = false;
        // ASCII-chars that are NOT safe for the
        // start and for being inside the dn.
        // These are the int values of those chars.
        $unsafe_init = array(0, 10, 13, 32, 58, 60);
        $unsafe      = array(0, 10, 13);

        // Test for illegal init char
        $init_ord = ord(substr($dn, 0, 1));
        if ($init_ord >= 127 || in_array($init_ord, $unsafe_init)) {
            $base64 = true;
        }

        // Test for illegal content char
        for ($i = 0; $i < strlen($dn); $i++) {
            $char = substr($dn, $i, 1);
            if (ord($char) >= 127 || in_array($init_ord, $unsafe)) {
                $base64 = true;
            }
        }

        // Test for ending space
        if (substr($dn, -1) == ' ') {
            $base64 = true;
        }

        // if converting is needed, do it
        return ($base64)? 'dn:: '.base64_encode($dn) : 'dn: '.$dn;
    }

    /**
    * Writes an attribute to the filehandle
    *
    * @param string       $attr_name   Name of the attribute
    * @param string|array $attr_values Single attribute value or array with attribute values
    *
    * @access protected
    * @return void
    */
    protected function writeAttribute($attr_name, $attr_values)
    {
        // write out attribute content
        if (!is_array($attr_values)) {
            $attr_values = array($attr_values);
        }
        foreach ($attr_values as $attr_val) {
            $line = $this->convertAttribute($attr_name, $attr_val).PHP_EOL;
            $this->writeLine($line, 'Net_LDAP2_LDIF error: unable to write attribute '.$attr_name.' of entry '.$this->_entrynum);
        }
    }

    /**
    * Writes a DN to the filehandle
    *
    * @param string $dn DN to write
    *
    * @access protected
    * @return void
    */
    protected function writeDN($dn)
    {
        // prepare DN
        if ($this->_options['encode'] == 'base64') {
            $dn = $this->convertDN($dn).PHP_EOL;
        } elseif ($this->_options['encode'] == 'canonical') {
            $dn = Net_LDAP2_Util::canonical_dn($dn, array('casefold' => 'none')).PHP_EOL;
        } else {
            $dn = $dn.PHP_EOL;
        }
        $this->writeLine($dn, 'Net_LDAP2_LDIF error: unable to write DN of entry '.$this->_entrynum);
    }

    /**
    * Finishes an LDIF entry
    *
    * @access protected
    * @return void
    */
    protected function finishEntry()
    {
        $this->writeLine(PHP_EOL, 'Net_LDAP2_LDIF error: unable to close entry '.$this->_entrynum);
    }

    /**
    * Just write an arbitary line to the filehandle
    *
    * @param string $line  Content to write
    * @param string $error If error occurs, drop this message
    *
    * @access protected
    * @return true|false
    */
    protected function writeLine($line, $error = 'Net_LDAP2_LDIF error: unable to write to filehandle')
    {
        if (is_resource($this->handle()) && fwrite($this->handle(), $line, strlen($line)) === false) {
            $this->dropError($error);
            return false;
        } else {
            return true;
        }
    }

    /**
    * Optionally raises an error and pushes the error on the error cache
    *
    * @param string $msg  Errortext
    * @param int    $line Line in the LDIF that caused the error
    *
    * @access protected
    * @return void
    */
    protected function dropError($msg, $line = null)
    {
        $this->_error['error'] = new Net_LDAP2_Error($msg);
        if ($line !== null) $this->_error['line'] = $line;

        if ($this->_options['onerror'] == 'die') {
            die($msg.PHP_EOL);
        } elseif ($this->_options['onerror'] == 'warn') {
            echo $msg.PHP_EOL;
        }
    }
}
?>
PK|c�\s|��k�k#pear/net_ldap2/Net/LDAP2/Filter.phpnu�[���<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
/**
* File containing the Net_LDAP2_Filter interface class.
*
* PHP version 5
*
* @category  Net
* @package   Net_LDAP2
* @author    Benedikt Hallinger <beni@php.net>
* @copyright 2009 Benedikt Hallinger
* @license   http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
* @version   SVN: $Id$
* @link      http://pear.php.net/package/Net_LDAP2/
*/

/**
* Includes
*/
require_once 'PEAR.php';
require_once 'Net/LDAP2/Util.php';
require_once 'Net/LDAP2/Entry.php';

/**
* Object representation of a part of a LDAP filter.
*
* This Class is not completely compatible to the PERL interface!
*
* The purpose of this class is, that users can easily build LDAP filters
* without having to worry about right escaping etc.
* A Filter is built using several independent filter objects
* which are combined afterwards. This object works in two
* modes, depending how the object is created.
* If the object is created using the {@link create()} method, then this is a leaf-object.
* If the object is created using the {@link combine()} method, then this is a container object.
*
* LDAP filters are defined in RFC-2254 and can be found under
* {@link http://www.ietf.org/rfc/rfc2254.txt}
*
* Here a quick copy&paste example:
* <code>
* $filter0 = Net_LDAP2_Filter::create('stars', 'equals', '***');
* $filter_not0 = Net_LDAP2_Filter::combine('not', $filter0);
*
* $filter1 = Net_LDAP2_Filter::create('gn', 'begins', 'bar');
* $filter2 = Net_LDAP2_Filter::create('gn', 'ends', 'baz');
* $filter_comp = Net_LDAP2_Filter::combine('or',array($filter_not0, $filter1, $filter2));
*
* echo $filter_comp->asString();
* // This will output: (|(!(stars=\0x5c0x2a\0x5c0x2a\0x5c0x2a))(gn=bar*)(gn=*baz))
* // The stars in $filter0 are treaten as real stars unless you disable escaping.
* </code>
*
* @category Net
* @package  Net_LDAP2
* @author   Benedikt Hallinger <beni@php.net>
* @license  http://www.gnu.org/copyleft/lesser.html LGPL
* @link     http://pear.php.net/package/Net_LDAP2/
*/
class Net_LDAP2_Filter extends PEAR
{
    /**
    * Storage for combination of filters
    *
    * This variable holds a array of filter objects
    * that should be combined by this filter object.
    *
    * @access protected
    * @var array
    */
    protected $_subfilters = array();

    /**
    * Match of this filter
    *
    * If this is a leaf filter, then a matching rule is stored,
    * if it is a container, then it is a logical operator
    *
    * @access protected
    * @var string
    */
    protected $_match;

    /**
    * Single filter
    *
    * If we operate in leaf filter mode,
    * then the constructing method stores
    * the filter representation here
    *
    * @acces private
    * @var string
    */
    protected $_filter;

    /**
    * Create a new Net_LDAP2_Filter object and parse $filter.
    *
    * This is for PERL Net::LDAP interface.
    * Construction of Net_LDAP2_Filter objects should happen through either
    * {@link create()} or {@link combine()} which give you more control.
    * However, you may use the perl iterface if you already have generated filters.
    *
    * @param string $filter LDAP filter string
    *
    * @see parse()
    */
    public function __construct($filter = false)
    {
        // The optional parameter must remain here, because otherwise create() crashes
        if (false !== $filter) {
            $filter_o = self::parse($filter);
            if (PEAR::isError($filter_o)) {
                $this->_filter = $filter_o; // assign error, so asString() can report it
            } else {
                $this->_filter = $filter_o->asString();
            }
        }
    }

    /**
    * Constructor of a new part of a LDAP filter.
    *
    * The following matching rules exists:
    *    - equals:         One of the attributes values is exactly $value
    *                      Please note that case sensitiviness is depends on the
    *                      attributes syntax configured in the server.
    *    - begins:         One of the attributes values must begin with $value
    *    - ends:           One of the attributes values must end with $value
    *    - contains:       One of the attributes values must contain $value
    *    - present | any:  The attribute can contain any value but must be existent
    *    - greater:        The attributes value is greater than $value
    *    - less:           The attributes value is less than $value
    *    - greaterOrEqual: The attributes value is greater or equal than $value
    *    - lessOrEqual:    The attributes value is less or equal than $value
    *    - approx:         One of the attributes values is similar to $value
    *
    * Negation ("not") can be done by prepending the above operators with the
    * "not" or "!" keyword, see example below. 
    *
    * If $escape is set to true (default) then $value will be escaped
    * properly. If it is set to false then $value will be treaten as raw filter value string.
    * You should escape yourself using {@link Net_LDAP2_Util::escape_filter_value()}!
    *
    * Examples:
    * <code>
    *   // This will find entries that contain an attribute "sn" that ends with "foobar":
    *   $filter = Net_LDAP2_Filter::create('sn', 'ends', 'foobar');
    *
    *   // This will find entries that contain an attribute "sn" that has any value set:
    *   $filter = Net_LDAP2_Filter::create('sn', 'any');
    *
    *   // This will build a negated equals filter:
    *   $filter = Net_LDAP2_Filter::create('sn', 'not equals', 'foobar');
    * </code>
    *
    * @param string  $attr_name Name of the attribute the filter should apply to
    * @param string  $match     Matching rule (equals, begins, ends, contains, greater, less, greaterOrEqual, lessOrEqual, approx, any)
    * @param string  $value     (optional) if given, then this is used as a filter
    * @param boolean $escape    Should $value be escaped? (default: yes, see {@link Net_LDAP2_Util::escape_filter_value()} for detailed information)
    *
    * @return Net_LDAP2_Filter|Net_LDAP2_Error
    */
    public static function create($attr_name, $match, $value = '', $escape = true)
    {
        $leaf_filter = new Net_LDAP2_Filter();
        if ($escape) {
            $array = Net_LDAP2_Util::escape_filter_value(array($value));
            $value = $array[0];
        }

        $match = strtolower($match);

        // detect negation
        $neg_matches   = array();
        $negate_filter = false;
        if (preg_match('/^(?:not|!)[\s_-](.+)/', $match, $neg_matches)) {
            $negate_filter = true;
            $match         = $neg_matches[1];
        }

        // build basic filter
        switch ($match) {
        case 'equals':
        case '=':
        case '==':
            $leaf_filter->_filter = '(' . $attr_name . '=' . $value . ')';
            break;
        case 'begins':
            $leaf_filter->_filter = '(' . $attr_name . '=' . $value . '*)';
            break;
        case 'ends':
            $leaf_filter->_filter = '(' . $attr_name . '=*' . $value . ')';
            break;
        case 'contains':
            $leaf_filter->_filter = '(' . $attr_name . '=*' . $value . '*)';
            break;
        case 'greater':
        case '>':
            $leaf_filter->_filter = '(' . $attr_name . '>' . $value . ')';
            break;
        case 'less':
        case '<':
            $leaf_filter->_filter = '(' . $attr_name . '<' . $value . ')';
            break;
        case 'greaterorequal':
        case '>=':
            $leaf_filter->_filter = '(' . $attr_name . '>=' . $value . ')';
            break;
        case 'lessorequal':
        case '<=':
            $leaf_filter->_filter = '(' . $attr_name . '<=' . $value . ')';
            break;
        case 'approx':
        case '~=':
            $leaf_filter->_filter = '(' . $attr_name . '~=' . $value . ')';
            break;
        case 'any':
        case 'present': // alias that may improve user code readability
            $leaf_filter->_filter = '(' . $attr_name . '=*)';
            break;
        default:
            return PEAR::raiseError('Net_LDAP2_Filter create error: matching rule "' . $match . '" not known!');
        }
        
        // negate if requested
        if ($negate_filter) {
           $leaf_filter = Net_LDAP2_Filter::combine('!', $leaf_filter);
        }

        return $leaf_filter;
    }

    /**
    * Combine two or more filter objects using a logical operator
    *
    * This static method combines two or more filter objects and returns one single
    * filter object that contains all the others.
    * Call this method statically: $filter = Net_LDAP2_Filter::combine('or', array($filter1, $filter2))
    * If the array contains filter strings instead of filter objects, we will try to parse them.
    *
    * @param string                 $log_op  The locical operator. May be "and", "or", "not" or the subsequent logical equivalents "&", "|", "!"
    * @param array|Net_LDAP2_Filter $filters array with Net_LDAP2_Filter objects
    *
    * @return Net_LDAP2_Filter|Net_LDAP2_Error
    * @static
    */
    public static function &combine($log_op, $filters)
    {
        if (PEAR::isError($filters)) {
            return $filters;
        }

        // substitude named operators to logical operators
        if ($log_op == 'and') $log_op = '&';
        if ($log_op == 'or')  $log_op = '|';
        if ($log_op == 'not') $log_op = '!';

        // tests for sane operation
        if ($log_op == '!') {
            // Not-combination, here we only accept one filter object or filter string
            if ($filters instanceof Net_LDAP2_Filter) {
                $filters = array($filters); // force array
            } elseif (is_string($filters)) {
                $filter_o = self::parse($filters);
                if (PEAR::isError($filter_o)) {
                    $err = PEAR::raiseError('Net_LDAP2_Filter combine error: '.$filter_o->getMessage());
                    return $err;
                } else {
                    $filters = array($filter_o);
                }
            } elseif (is_array($filters)) {
                if (count($filters) != 1) {
                    $err = PEAR::raiseError('Net_LDAP2_Filter combine error: operator is "not" but $filter is an array!');
                    return $err;
                } elseif (!($filters[0] instanceof Net_LDAP2_Filter)) {
                     $err = PEAR::raiseError('Net_LDAP2_Filter combine error: operator is "not" but $filter is not a valid Net_LDAP2_Filter nor a filter string!');
                     return $err;
                }
            } else {
                $err = PEAR::raiseError('Net_LDAP2_Filter combine error: operator is "not" but $filter is not a valid Net_LDAP2_Filter nor a filter string!');
                return $err;
            }
        } elseif ($log_op == '&' || $log_op == '|') {
            if (!is_array($filters) || count($filters) < 2) {
                $err = PEAR::raiseError('Net_LDAP2_Filter combine error: parameter $filters is not an array or contains less than two Net_LDAP2_Filter objects!');
                return $err;
            }
        } else {
            $err = PEAR::raiseError('Net_LDAP2_Filter combine error: logical operator is not known!');
            return $err;
        }

        $combined_filter = new Net_LDAP2_Filter();
        foreach ($filters as $key => $testfilter) {     // check for errors
            if (PEAR::isError($testfilter)) {
                return $testfilter;
            } elseif (is_string($testfilter)) {
                // string found, try to parse into an filter object
                $filter_o = self::parse($testfilter);
                if (PEAR::isError($filter_o)) {
                    return $filter_o;
                } else {
                    $filters[$key] = $filter_o;
                }
            } elseif (!$testfilter instanceof Net_LDAP2_Filter) {
                $err = PEAR::raiseError('Net_LDAP2_Filter combine error: invalid object passed in array $filters!');
                return $err;
            }
        }

        $combined_filter->_subfilters = $filters;
        $combined_filter->_match      = $log_op;
        return $combined_filter;
    }

    /**
    * Parse FILTER into a Net_LDAP2_Filter object
    *
    * This parses an filter string into Net_LDAP2_Filter objects.
    *
    * @param string $FILTER The filter string
    *
    * @access static
    * @return Net_LDAP2_Filter|Net_LDAP2_Error
    * @todo Leaf-mode: Do we need to escape at all? what about *-chars?check for the need of encoding values, tackle problems (see code comments)
    */
    public static function parse($FILTER)
    {
        if (preg_match('/^\((.+?)\)$/', $FILTER, $matches)) {
            // Check for right bracket syntax: count of unescaped opening
            // brackets must match count of unescaped closing brackets.
            // At this stage we may have:
            //   1. one filter component with already removed outer brackets
            //   2. one or more subfilter components
            $c_openbracks  = preg_match_all('/(?<!\\\\)\(/' , $matches[1], $notrelevant);
            $c_closebracks = preg_match_all('/(?<!\\\\)\)/' , $matches[1], $notrelevant);
            if ($c_openbracks != $c_closebracks) {
                return PEAR::raiseError("Filter parsing error: invalid filter syntax - opening brackets do not match close brackets!");
            }

            if (in_array(substr($matches[1], 0, 1), array('!', '|', '&'))) {
                // Subfilter processing: pass subfilters to parse() and combine
                // the objects using the logical operator detected
                // we have now something like "&(...)(...)(...)" but at least one part ("!(...)").
                // Each subfilter could be an arbitary complex subfilter.

                // extract logical operator and filter arguments
                $log_op              = substr($matches[1], 0, 1);
                $remaining_component = substr($matches[1], 1);

                // split $remaining_component into individual subfilters
                // we cannot use split() for this, because we do not know the
                // complexiness of the subfilter. Thus, we look trough the filter
                // string and just recognize ending filters at the first level.
                // We record the index number of the char and use that information
                // later to split the string.
                $sub_index_pos = array();
                $prev_char     = ''; // previous character looked at
                $level         = 0;  // denotes the current bracket level we are,
                                     //   >1 is too deep, 1 is ok, 0 is outside any
                                     //   subcomponent
                for ($curpos = 0; $curpos < strlen($remaining_component); $curpos++) {
                    $cur_char = substr($remaining_component, $curpos, 1);

                    // rise/lower bracket level
                    if ($cur_char == '(' && $prev_char != '\\') {
                        $level++;
                    } elseif  ($cur_char == ')' && $prev_char != '\\') {
                        $level--;
                    }

                    if ($cur_char == '(' && $prev_char == ')' && $level == 1) {
                        array_push($sub_index_pos, $curpos); // mark the position for splitting
                    }
                    $prev_char = $cur_char;
                }

                // now perform the splits. To get also the last part, we
                // need to add the "END" index to the split array
                array_push($sub_index_pos, strlen($remaining_component));
                $subfilters = array();
                $oldpos = 0;
                foreach ($sub_index_pos as $s_pos) {
                    $str_part = substr($remaining_component, $oldpos, $s_pos - $oldpos);
                    array_push($subfilters, $str_part);
                    $oldpos = $s_pos;
                }

                // some error checking...
                if (count($subfilters) == 1) {
                    // only one subfilter found
                } elseif (count($subfilters) > 1) {
                    // several subfilters found
                    if ($log_op == "!") {
                        return PEAR::raiseError("Filter parsing error: invalid filter syntax - NOT operator detected but several arguments given!");
                    }
                } else {
                    // this should not happen unless the user specified a wrong filter
                    return PEAR::raiseError("Filter parsing error: invalid filter syntax - got operator '$log_op' but no argument!");
                }

                // Now parse the subfilters into objects and combine them using the operator
                $subfilters_o = array();
                foreach ($subfilters as $s_s) {
                    $o = self::parse($s_s);
                    if (PEAR::isError($o)) {
                        return $o;
                    } else {
                        array_push($subfilters_o, self::parse($s_s));
                    }
                }

                $filter_o = self::combine($log_op, $subfilters_o);
                return $filter_o;

            } else {
                // This is one leaf filter component, do some syntax checks, then escape and build filter_o
                // $matches[1] should be now something like "foo=bar"

                // detect multiple leaf components
                // [TODO] Maybe this will make problems with filters containing brackets inside the value
                if (stristr($matches[1], ')(')) {
                    return PEAR::raiseError("Filter parsing error: invalid filter syntax - multiple leaf components detected!");
                } else {
                    $filter_parts = Net_LDAP2_Util::split_attribute_string($matches[1], true, true);
                    if (count($filter_parts) != 3) {
                        return PEAR::raiseError("Filter parsing error: invalid filter syntax - unknown matching rule used");
                    } else {
                        $filter_o          = new Net_LDAP2_Filter();
                        // [TODO]: Do we need to escape at all? what about *-chars user provide and that should remain special?
                        //         I think, those prevent escaping! We need to check against PERL Net::LDAP!
                        // $value_arr         = Net_LDAP2_Util::escape_filter_value(array($filter_parts[2]));
                        // $value             = $value_arr[0];
                        $value             = $filter_parts[2];
                        $filter_o->_filter = '('.$filter_parts[0].$filter_parts[1].$value.')';
                        return $filter_o;
                    }
                }
            }
        } else {
               // ERROR: Filter components must be enclosed in round brackets
               return PEAR::raiseError("Filter parsing error: invalid filter syntax - filter components must be enclosed in round brackets");
        }
    }

    /**
    * Get the string representation of this filter
    *
    * This method runs through all filter objects and creates
    * the string representation of the filter. If this
    * filter object is a leaf filter, then it will return
    * the string representation of this filter.
    *
    * @return string|Net_LDAP2_Error
    */
    public function asString()
    {
        if ($this->isLeaf()) {
            $return = $this->_filter;
        } else {
            $return = '';
            foreach ($this->_subfilters as $filter) {
                $return = $return.$filter->asString();
            }
            $return = '(' . $this->_match . $return . ')';
        }
        return $return;
    }

    /**
    * Alias for perl interface as_string()
    *
    * @see asString()
    * @return string|Net_LDAP2_Error
    */
    public function as_string()
    {
        return $this->asString();
    }

    /**
    * Print the text representation of the filter to FH, or the currently selected output handle if FH is not given
    *
    * This method is only for compatibility to the perl interface.
    * However, the original method was called "print" but due to PHP language restrictions,
    * we can't have a print() method.
    *
    * @param resource $FH (optional) A filehandle resource
    *
    * @return true|Net_LDAP2_Error
    */
    public function printMe($FH = false)
    {
        if (!is_resource($FH)) {
            if (PEAR::isError($FH)) {
                return $FH;
            }
            $filter_str = $this->asString();
            if (PEAR::isError($filter_str)) {
                return $filter_str;
            } else {
                print($filter_str);
            }
        } else {
            $filter_str = $this->asString();
            if (PEAR::isError($filter_str)) {
                return $filter_str;
            } else {
                $res = @fwrite($FH, $this->asString());
                if ($res == false) {
                    return PEAR::raiseError("Unable to write filter string to filehandle \$FH!");
                }
            }
        }
        return true;
    }

    /**
    * This can be used to escape a string to provide a valid LDAP-Filter.
    *
    * LDAP will only recognise certain characters as the
    * character istself if they are properly escaped. This is
    * what this method does.
    * The method can be called statically, so you can use it outside
    * for your own purposes (eg for escaping only parts of strings)
    *
    * In fact, this is just a shorthand to {@link Net_LDAP2_Util::escape_filter_value()}.
    * For upward compatibiliy reasons you are strongly encouraged to use the escape
    * methods provided by the Net_LDAP2_Util class.
    *
    * @param string $value Any string who should be escaped
    *
    * @static
    * @return string         The string $string, but escaped
    * @deprecated  Do not use this method anymore, instead use Net_LDAP2_Util::escape_filter_value() directly
    */
    public static function escape($value)
    {
        $return = Net_LDAP2_Util::escape_filter_value(array($value));
        return $return[0];
    }

    /**
    * Is this a container or a leaf filter object?
    *
    * @access protected
    * @return boolean
    */
    protected function isLeaf()
    {
        if (count($this->_subfilters) > 0) {
            return false; // Container!
        } else {
            return true; // Leaf!
        }
    }

    /**
    * Filter entries using this filter or see if a filter matches
    *
    * @todo Currently slow and naive implementation with preg_match, could be optimized (esp. begins, ends filters etc)
    * @todo Currently only "="-based matches (equals, begins, ends, contains, any) implemented; Implement all the stuff!
    * @todo Implement expert code with schema checks in case $entry is connected to a directory
    * @param array|Net_LDAP2_Entry The entry (or array with entries) to check
    * @param array                 If given, the array will be appended with entries who matched the filter. Return value is true if any entry matched.
    * @return int|Net_LDAP2_Error Returns the number of matched entries or error
    */
    function matches(&$entries, &$results=array()) {
        $numOfMatches = 0;

        if (!is_array($entries)) {
            $all_entries = array(&$entries);
        } else {
            $all_entries = &$entries;
        }

        foreach ($all_entries as $entry) {
            // look at the current entry and see if filter matches

            $entry_matched = false;
            // if this is not a single component, do calculate all subfilters,
            // then assert the partial results with the given combination modifier
            if (!$this->isLeaf()) {
        
                // get partial results from subfilters
                $partial_results = array();
                foreach ($this->_subfilters as $filter) {
                    $partial_results[] = $filter->matches($entry);
                }
            
                // evaluate partial results using this filters combination rule
                switch ($this->_match) {
                    case '!':
                        // result is the neagtive result of the assertion
                        $entry_matched = !$partial_results[0];
                    break;

                    case '&':
                        // all partial results have to be boolean-true
                        $entry_matched = !in_array(false, $partial_results);
                    break;
                
                    case '|':
                        // at least one partial result has to be true
                        $entry_matched = in_array(true, $partial_results);
                    break;
                }
            
            } else {
                // Leaf filter: assert given entry
                // [TODO]: Could be optimized to avoid preg_match especially with "ends", "begins" etc
            
                // Translate the LDAP-match to some preg_match expression and evaluate it
                list($attribute, $match, $assertValue) = $this->getComponents();
                switch ($match) {
                    case '=':
                        $regexp = '/^'.str_replace('*', '.*', $assertValue).'$/i'; // not case sensitive unless specified by schema
                        $entry_matched = $entry->pregMatch($regexp, $attribute);
                    break;
                
                    // -------------------------------------
                    // [TODO]: implement <, >, <=, >= and =~
                    // -------------------------------------
                
                    default:
                        $err = PEAR::raiseError("Net_LDAP2_Filter match error: unsupported match rule '$match'!");
                        return $err;
                }
            
            }

            // process filter matching result
            if ($entry_matched) {
                $numOfMatches++;
                $results[] = $entry;
            }

        }

        return $numOfMatches;
    }


    /**
    * Retrieve this leaf-filters attribute, match and value component.
    *
    * For leaf filters, this returns array(attr, match, value).
    * Match is be the logical operator, not the text representation,
    * eg "=" instead of "equals". Note that some operators are really
    * a combination of operator+value with wildcard, like
    * "begins": That will return "=" with the value "value*"!
    *
    * For non-leaf filters this will drop an error.
    *
    * @todo $this->_match is not always available and thus not usable here; it would be great if it would set in the factory methods and constructor.
    * @return array|Net_LDAP2_Error
    */
    function getComponents() {
        if ($this->isLeaf()) {
            $raw_filter = preg_replace('/^\(|\)$/', '', $this->_filter);
            $parts = Net_LDAP2_Util::split_attribute_string($raw_filter, true, true);
            if (count($parts) != 3) {
                return PEAR::raiseError("Net_LDAP2_Filter getComponents() error: invalid filter syntax - unknown matching rule used");
            } else {
                return $parts;
            }
        } else {
            return PEAR::raiseError('Net_LDAP2_Filter getComponents() call is invalid for non-leaf filters!');
        }
    }


}
?>
PK|c�\�����"pear/net_ldap2/Net/LDAP2/Entry.phpnu�[���<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
/**
* File containing the Net_LDAP2_Entry interface class.
*
* PHP version 5
*
* @category  Net
* @package   Net_LDAP2
* @author    Jan Wagner <wagner@netsols.de>
* @author    Tarjej Huse <tarjei@bergfald.no>
* @author    Benedikt Hallinger <beni@php.net>
* @copyright 2009 Tarjej Huse, Jan Wagner, Benedikt Hallinger
* @license   http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
* @version   SVN: $Id$
* @link      http://pear.php.net/package/Net_LDAP2/
*/

/**
* Includes
*/
require_once 'PEAR.php';
require_once 'Net/LDAP2/Util.php';

/**
* Object representation of a directory entry
*
* This class represents a directory entry. You can add, delete, replace
* attributes and their values, rename the entry, delete the entry.
*
* @category Net
* @package  Net_LDAP2
* @author   Jan Wagner <wagner@netsols.de>
* @author   Tarjej Huse <tarjei@bergfald.no>
* @author   Benedikt Hallinger <beni@php.net>
* @license  http://www.gnu.org/copyleft/lesser.html LGPL
* @link     http://pear.php.net/package/Net_LDAP2/
*/
class Net_LDAP2_Entry extends PEAR
{
    /**
    * Entry ressource identifier
    *
    * @access protected
    * @var ressource
    */
    protected $_entry = null;

    /**
    * LDAP ressource identifier
    *
    * @access protected
    * @var ressource
    */
    protected $_link = null;

    /**
    * Net_LDAP2 object
    *
    * This object will be used for updating and schema checking
    *
    * @access protected
    * @var object Net_LDAP2
    */
    protected $_ldap = null;

    /**
    * Distinguished name of the entry
    *
    * @access protected
    * @var string
    */
    protected $_dn = null;

    /**
    * Attributes
    *
    * @access protected
    * @var array
    */
    protected $_attributes = array();

    /**
    * Original attributes before any modification
    *
    * @access protected
    * @var array
    */
    protected $_original = array();


    /**
    * Map of attribute names
    *
    * @access protected
    * @var array
    */
    protected $_map = array();


    /**
    * Is this a new entry?
    *
    * @access protected
    * @var boolean
    */
    protected $_new = true;

    /**
    * New distinguished name
    *
    * @access protected
    * @var string
    */
    protected $_newdn = null;

    /**
    * Shall the entry be deleted?
    *
    * @access protected
    * @var boolean
    */
    protected $_delete = false;

    /**
    * Map with changes to the entry
    *
    * @access protected
    * @var array
    */
    protected $_changes = array("add"     => array(),
                                "delete"  => array(),
                                "replace" => array()
                               );
    /**
    * Internal Constructor
    *
    * Constructor of the entry. Sets up the distinguished name and the entries
    * attributes.
    * You should not call this method manually! Use {@link Net_LDAP2_Entry::createFresh()}
    * or {@link Net_LDAP2_Entry::createConnected()} instead!
    *
    * @param Net_LDAP2|ressource|array $ldap Net_LDAP2 object, ldap-link ressource or array of attributes
    * @param string|ressource          $entry Either a DN or a LDAP-Entry ressource
    *
    * @access protected
    * @return none
    */
    public function __construct($ldap, $entry = null)
    {
        parent::__construct('Net_LDAP2_Error');

        // set up entry resource or DN
        if (is_resource($entry) || $entry instanceof \LDAP\ResultEntry) {
            $this->_entry = $entry;
        } else {
            $this->_dn = $entry;
        }

        // set up LDAP link
        if ($ldap instanceof Net_LDAP2) {
            $this->_ldap = $ldap;
            $this->_link = $ldap->getLink();
        } elseif (is_resource($ldap) || $ldap instanceof \LDAP\Connection) {
            $this->_link = $ldap;
        } elseif (is_array($ldap)) {
            // Special case: here $ldap is an array of attributes,
            // this means, we have no link. This is a "virtual" entry.
            // We just set up the attributes so one can work with the object
            // as expected, but an update() fails unless setLDAP() is called.
            $this->setAttributes($ldap);
        }

        // if this is an entry existing in the directory,
        // then set up as old and fetch attrs
        if ((is_resource($this->_entry) || $this->_entry instanceof \LDAP\ResultEntry)
            && (is_resource($this->_link) || $this->_link instanceof \LDAP\Connection)
        ) {
            $this->_new = false;
            $this->_dn  = @ldap_get_dn($this->_link, $this->_entry);
            $this->setAttributes();  // fetch attributes from server
        }
    }

    /**
    * Creates a fresh entry that may be added to the directory later on
    *
    * Use this method, if you want to initialize a fresh entry.
    *
    * The method should be called statically: $entry = Net_LDAP2_Entry::createFresh();
    * You should put a 'objectClass' attribute into the $attrs so the directory server
    * knows which object you want to create. However, you may omit this in case you
    * don't want to add this entry to a directory server.
    *
    * The attributes parameter is as following:
    * <code>
    * $attrs = array( 'attribute1' => array('value1', 'value2'),
    *                 'attribute2' => 'single value'
    *          );
    * </code>
    *
    * @param string $dn    DN of the Entry
    * @param array  $attrs Attributes of the entry
    *
    * @static
    * @return Net_LDAP2_Entry|Net_LDAP2_Error
    */
    public static function createFresh($dn, $attrs = array())
    {
        if (!is_array($attrs)) {
            return PEAR::raiseError("Unable to create fresh entry: Parameter \$attrs needs to be an array!");
        }

        $entry = new Net_LDAP2_Entry($attrs, $dn);
        return $entry;
    }

    /**
    * Creates a Net_LDAP2_Entry object out of an ldap entry resource
    *
    * Use this method, if you want to initialize an entry object that is
    * already present in some directory and that you have read manually.
    *
    * Please note, that if you want to create an entry object that represents
    * some already existing entry, you should use {@link createExisting()}.
    *
    * The method should be called statically: $entry = Net_LDAP2_Entry::createConnected();
    *
    * @param Net_LDAP2 $ldap  Net_LDA2 object
    * @param resource  $entry PHP LDAP entry resource
    *
    * @static
    * @return Net_LDAP2_Entry|Net_LDAP2_Error
    */
    public static function createConnected($ldap, $entry)
    {
        if (!$ldap instanceof Net_LDAP2) {
            return PEAR::raiseError("Unable to create connected entry: Parameter \$ldap needs to be a Net_LDAP2 object!");
        }
        if (!is_resource($entry) && !$entry instanceof \LDAP\ResultEntry) {
            return PEAR::raiseError("Unable to create connected entry: Parameter \$entry needs to be a ldap entry resource!");
        }

        $entry = new Net_LDAP2_Entry($ldap, $entry);
        return $entry;
    }

    /**
    * Creates an Net_LDAP2_Entry object that is considered already existing
    *
    * Use this method, if you want to modify an already existing entry
    * without fetching it first.
    * In most cases however, it is better to fetch the entry via Net_LDAP2->getEntry()!
    *
    * Please note that you should take care if you construct entries manually with this
    * because you may get weird synchronisation problems.
    * The attributes and values as well as the entry itself are considered existent
    * which may produce errors if you try to modify an entry which doesn't really exist
    * or if you try to overwrite some attribute with an value already present.
    *
    * This method is equal to calling createFresh() and after that markAsNew(FALSE).
    *
    * The method should be called statically: $entry = Net_LDAP2_Entry::createExisting();
    *
    * The attributes parameter is as following:
    * <code>
    * $attrs = array( 'attribute1' => array('value1', 'value2'),
    *                 'attribute2' => 'single value'
    *          );
    * </code>
    *
    * @param string $dn    DN of the Entry
    * @param array  $attrs Attributes of the entry
    *
    * @static
    * @return Net_LDAP2_Entry|Net_LDAP2_Error
    */
    public static function createExisting($dn, $attrs = array())
    {
        if (!is_array($attrs)) {
            return PEAR::raiseError("Unable to create entry object: Parameter \$attrs needs to be an array!");
        }

        $entry = Net_LDAP2_Entry::createFresh($dn, $attrs);
        if ($entry instanceof Net_LDAP2_Error) {
            return $entry;
        } else {
            $entry->markAsNew(false);
            return $entry;
        }
    }

    /**
    * Get or set the distinguished name of the entry
    *
    * If called without an argument the current (or the new DN if set) DN gets returned.
    * If you provide an DN, this entry is moved to the new location specified if a DN existed.
    * If the DN was not set, the DN gets initialized. Call {@link update()} to actually create
    * the new Entry in the directory.
    * To fetch the current active DN after setting a new DN but before an update(), you can use
    * {@link currentDN()} to retrieve the DN that is currently active.
    *
    * Please note that special characters (eg german umlauts) should be encoded using utf8_encode().
    * You may use {@link Net_LDAP2_Util::canonical_dn()} for properly encoding of the DN.
    *
    * @param string $dn New distinguished name
    *
    * @access public
    * @return string|true Distinguished name (or true if a new DN was provided)
    */
    public function dn($dn = null)
    {
        if (false == is_null($dn)) {
            if (is_null($this->_dn) ) {
                $this->_dn = $dn;
            } else {
                $this->_newdn = $dn;
            }
            return true;
        }
        return (isset($this->_newdn) ? $this->_newdn : $this->currentDN());
    }

    /**
    * Renames or moves the entry
    *
    * This is just a convinience alias to {@link dn()}
    * to make your code more meaningful.
    *
    * @param string $newdn The new DN
    *
    * @return true
    */
    public function move($newdn)
    {
        return $this->dn($newdn);
    }

    /**
    * Sets the internal attributes array
    *
    * This fetches the values for the attributes from the server.
    * The attribute Syntax will be checked so binary attributes will be returned
    * as binary values.
    *
    * Attributes may be passed directly via the $attributes parameter to setup this
    * entry manually. This overrides attribute fetching from the server.
    *
    * @param array $attributes Attributes to set for this entry
    *
    * @access protected
    * @return void
    */
    protected function setAttributes($attributes = null)
    {
        /*
        * fetch attributes from the server
        */
        if (is_null($attributes)
            && (is_resource($this->_entry) || $this->_entry instanceof \LDAP\ResultEntry)
            && (is_resource($this->_link) || $this->_link instanceof \LDAP\Connection)
        ) {
            // fetch schema
            if ($this->_ldap instanceof Net_LDAP2) {
                $schema = $this->_ldap->schema();
            }
            // fetch attributes
            $attributes = array();
            do {
                if (empty($attr)) {
                    $attr = @ldap_first_attribute($this->_link, $this->_entry);
                } else {
                    $attr = @ldap_next_attribute($this->_link, $this->_entry);
                }
                if ($attr) {
                    $func = 'ldap_get_values'; // standard function to fetch value

                    // Try to get binary values as binary data
                    if ($schema instanceof Net_LDAP2_Schema) {
                        if ($schema->isBinary($attr)) {
                             $func = 'ldap_get_values_len';
                        }
                    }
                    // fetch attribute value (needs error checking?)
                    $attributes[$attr] = $func($this->_link, $this->_entry, $attr);
                }
            } while ($attr);
        }

        /*
        * set attribute data directly, if passed
        */
        if (is_array($attributes) && count($attributes) > 0) {
            if (isset($attributes["count"]) && is_numeric($attributes["count"])) {
                unset($attributes["count"]);
            }
            foreach ($attributes as $k => $v) {
                // attribute names should not be numeric
                if (is_numeric($k)) {
                    continue;
                }
                // map generic attribute name to real one
                $this->_map[strtolower($k)] = $k;
                // attribute values should be in an array
                if (false == is_array($v)) {
                    $v = array($v);
                }
                // remove the value count (comes from ldap server)
                if (isset($v["count"])) {
                    unset($v["count"]);
                }
                $this->_attributes[$k] = $v;
            }
        }

        // save a copy for later use
        $this->_original = $this->_attributes;
    }

    /**
    * Get the values of all attributes in a hash
    *
    * The returned hash has the form
    * <code>array('attributename' => 'single value',
    *       'attributename' => array('value1', value2', value3'))</code>
    * Only attributes present at the entry will be returned.
    *
    * @access public
    * @return array Hash of all attributes with their values
    */
    public function getValues()
    {
        $attrs = array();
        foreach ($this->_attributes as $attr => $value) {
            $attrs[$attr] = $this->getValue($attr);
        }
        return $attrs;
    }

    /**
    * Get the value of a specific attribute
    *
    * The first parameter is the name of the attribute
    * The second parameter influences the way the value is returned:
    * 'single':  only the first value is returned as string
    * 'all':     all values are returned in an array
    * 'default': in all other cases an attribute value with a single value is
    *            returned as string, if it has multiple values it is returned
    *            as an array
    *
    * If the attribute is not set at this entry (no value or not defined in
    * schema), "false" is returned when $option is 'single', an empty string if
    * 'default', and an empty array when 'all'.
    *
    * You may use Net_LDAP2_Schema->checkAttribute() to see if the attribute
    * is defined for the objectClasses of this entry.
    *
    * @param string  $attr         Attribute name
    * @param string  $option       Option
    *
    * @access public
    * @return string|array
    */
    public function getValue($attr, $option = null)
    {
        $attr = $this->getAttrName($attr);

        // return depending on set $options
        if (!array_key_exists($attr, $this->_attributes)) {
            // attribute not set
            switch ($option) {
                case 'single':
                    $value = false;
                break;
                case 'all':
                    $value = array();
                break;
                default:
                    $value = '';
            }

        } else {
            // attribute present
            switch ($option) {
                case 'single':
                    $value = $this->_attributes[$attr][0];
                break;
                case 'all':
                    $value = $this->_attributes[$attr];
                break;
                default:
                    $value = $this->_attributes[$attr];
                    if (count($value) == 1) {
                        $value = array_shift($value);
                    }
            }

        }

        return $value;
    }

    /**
    * Alias function of getValue for perl-ldap interface
    *
    * @see getValue()
    * @return string|array|PEAR_Error
    */
    public function get_value()
    {
        $args = func_get_args();
        return call_user_func_array(array( $this, 'getValue' ), $args);
    }

    /**
    * Returns an array of attributes names
    *
    * @access public
    * @return array Array of attribute names
    */
    public function attributes()
    {
        return array_keys($this->_attributes);
    }

    /**
    * Returns whether an attribute exists or not
    *
    * @param string $attr Attribute name
    *
    * @access public
    * @return boolean
    */
    public function exists($attr)
    {
        $attr = $this->getAttrName($attr);
        return array_key_exists($attr, $this->_attributes);
    }

    /**
    * Adds a new attribute or a new value to an existing attribute
    *
    * The paramter has to be an array of the form:
    * array('attributename' => 'single value',
    *       'attributename' => array('value1', 'value2))
    * When the attribute already exists the values will be added, else the
    * attribute will be created. These changes are local to the entry and do
    * not affect the entry on the server until update() is called.
    *
    * Note, that you can add values of attributes that you haven't selected, but if
    * you do so, {@link getValue()} and {@link getValues()} will only return the
    * values you added, _NOT_ all values present on the server. To avoid this, just refetch
    * the entry after calling {@link update()} or select the attribute.
    *
    * @param array $attr Attributes to add
    *
    * @access public
    * @return true|Net_LDAP2_Error
    */
    public function add($attr = array())
    {
        if (false == is_array($attr)) {
            return PEAR::raiseError("Parameter must be an array");
        }
        if ($this->isNew()) {
            $this->setAttributes($attr);
        }
        foreach ($attr as $k => $v) {
            $k = $this->getAttrName($k);
            if (false == is_array($v)) {
                // Do not add empty values
                if ($v == null) {
                    continue;
                } else {
                    $v = array($v);
                }
            }
            // add new values to existing attribute or add new attribute
            if ($this->exists($k)) {
                $this->_attributes[$k] = array_unique(array_merge($this->_attributes[$k], $v));
            } else {
                $this->_map[strtolower($k)] = $k;
                $this->_attributes[$k]      = $v;
            }
            // save changes for update()
            if (!isset($this->_changes["add"][$k])) {
                $this->_changes["add"][$k] = array();
            }
            $this->_changes["add"][$k] = array_unique(array_merge($this->_changes["add"][$k], $v));
        }

        $return = true;
        return $return;
    }

    /**
    * Deletes an whole attribute or a value or the whole entry
    *
    * The parameter can be one of the following:
    *
    * "attributename" - The attribute as a whole will be deleted
    * array("attributename1", "attributename2) - All given attributes will be
    *                                            deleted
    * array("attributename" => "value") - The value will be deleted
    * array("attributename" => array("value1", "value2") - The given values
    *                                                      will be deleted
    * If $attr is null or omitted , then the whole Entry will be deleted!
    *
    * These changes are local to the entry and do
    * not affect the entry on the server until {@link update()} is called.
    *
    * Please note that you must select the attribute (at $ldap->search() for example)
    * to be able to delete values of it, Otherwise {@link update()} will silently fail
    * and remove nothing.
    *
    * @param string|array $attr Attributes to delete (NULL or missing to delete whole entry)
    *
    * @access public
    * @return true
    */
    public function delete($attr = null)
    {
        if (is_null($attr)) {
            $this->_delete = true;
            return true;
        }
        if (is_string($attr)) {
            $attr = array($attr);
        }
        // Make the assumption that attribute names cannot be numeric,
        // therefore this has to be a simple list of attribute names to delete
        if (is_numeric(key($attr))) {
            foreach ($attr as $name) {
                if (is_array($name)) {
                    // someone mixed modes (list mode but specific values given!)
                    $del_attr_name = array_search($name, $attr);
                    $this->delete(array($del_attr_name => $name));
                } else {
                    // mark for update() if this attr was not marked before
                    $name = $this->getAttrName($name);
                    if ($this->exists($name)) {
                        $this->_changes["delete"][$name] = null;
                        unset($this->_attributes[$name]);
                    }
                }
            }
        } else {
            // Here we have a hash with "attributename" => "value to delete"
            foreach ($attr as $name => $values) {
                if (is_int($name)) {
                    // someone mixed modes and gave us just an attribute name
                    $this->delete($values);
                } else {
                    // mark for update() if this attr was not marked before;
                    // this time it must consider the selected values also
                    $name = $this->getAttrName($name);
                    if ($this->exists($name)) {
                        if (false == is_array($values)) {
                            $values = array($values);
                        }
                        // save values to be deleted
                        if (empty($this->_changes["delete"][$name])) {
                            $this->_changes["delete"][$name] = array();
                        }
                        $this->_changes["delete"][$name] =
                            array_unique(array_merge($this->_changes["delete"][$name], $values));
                        foreach ($values as $value) {
                            // find the key for the value that should be deleted
                            $key = array_search($value, $this->_attributes[$name]);
                            if (false !== $key) {
                                // delete the value
                                unset($this->_attributes[$name][$key]);
                            }
                        }
                    }
                }
            }
        }
        $return = true;
        return $return;
    }

    /**
    * Replaces attributes or its values
    *
    * The parameter has to an array of the following form:
    * array("attributename" => "single value",
    *       "attribute2name" => array("value1", "value2"),
    *       "deleteme1" => null,
    *       "deleteme2" => "")
    * If the attribute does not yet exist it will be added instead (see also $force).
    * If the attribue value is null, the attribute will de deleted.
    *
    * These changes are local to the entry and do
    * not affect the entry on the server until {@link update()} is called.
    *
    * In some cases you are not allowed to read the attributes value (for
    * example the ActiveDirectory attribute unicodePwd) but are allowed to
    * replace the value. In this case replace() would assume that the attribute
    * is not in the directory yet and tries to add it which will result in an
    * LDAP_TYPE_OR_VALUE_EXISTS error.
    * To force replace mode instead of add, you can set $force to true.
    *
    * @param array $attr  Attributes to replace
    * @param bool  $force Force replacing mode in case we can't read the attr value but are allowed to replace it
    *
    * @access public
    * @return true|Net_LDAP2_Error
    */
    public function replace($attr = array(), $force = false)
    {
        if (false == is_array($attr)) {
            return PEAR::raiseError("Parameter must be an array");
        }
        foreach ($attr as $k => $v) {
            $k = $this->getAttrName($k);
            if (false == is_array($v)) {
                // delete attributes with empty values; treat ints as string
                if (is_int($v)) {
                    $v = "$v";
                }
                if ($v == null) {
                    $this->delete($k);
                    continue;
                } else {
                    $v = array($v);
                }
            }
            // existing attributes will get replaced
            if ($this->exists($k) || $force) {
                $this->_changes["replace"][$k] = $v;
                $this->_attributes[$k]         = $v;
            } else {
                // new ones just get added
                $this->add(array($k => $v));
            }
        }
        $return = true;
        return $return;
    }

    /**
    * Update the entry on the directory server
    *
    * This will evaluate all changes made so far and send them
    * to the directory server.
    * Please note, that if you make changes to objectclasses wich
    * have mandatory attributes set, update() will currently fail.
    * Remove the entry from the server and readd it as new in such cases.
    * This also will deal with problems with setting structural object classes.
    *
    * @param Net_LDAP2  $ldap If passed, a call to setLDAP() is issued prior update, thus switching the LDAP-server. This is for perl-ldap interface compliance
    * @param boolean    $deleteoldrdn (optional) if false the old RDN value(s) is retained as non-distinguishided values of the entry.
    *
    * @access public
    * @return true|Net_LDAP2_Error
    * @todo Entry rename with a DN containing special characters needs testing!
    */
    public function update($deleteoldrdn = true, $ldap = null)
    {
        if ($ldap) {
            $msg = $this->setLDAP($ldap);
            if (Net_LDAP2::isError($msg)) {
                return PEAR::raiseError('You passed an invalid $ldap variable to update()');
            }
        }

        // ensure we have a valid LDAP object
        $ldap = $this->getLDAP();
        if (!$ldap instanceof Net_LDAP2) {
            return PEAR::raiseError("The entries LDAP object is not valid");
        }

        // Get and check link
        $link = $ldap->getLink();
        if (!is_resource($link) && !$link instanceof \LDAP\Connection) {
            return PEAR::raiseError("Could not update entry: internal LDAP link is invalid");
        }

        /*
        * Delete the entry
        */
        if (true === $this->_delete) {
            return $ldap->delete($this);
        }

        /*
        * New entry
        */
        if (true === $this->_new) {
            $msg = $ldap->add($this);
            if (Net_LDAP2::isError($msg)) {
                return $msg;
            }
            $this->_new                = false;
            $this->_changes['add']     = array();
            $this->_changes['delete']  = array();
            $this->_changes['replace'] = array();
            $this->_original           = $this->_attributes;

            // In case the "new" entry was moved after creation, we must
            // adjust the internal DNs as the entry was already created
            // with the most current DN.
            if (false == is_null($this->_newdn)) {
                $this->_dn    = $this->_newdn;
                $this->_newdn = null;
            }

            $return = true;
            return $return;
        }

        /*
        * Rename/move entry
        */
        if (false == is_null($this->_newdn)) {
            if ($ldap->getLDAPVersion() !== 3) {
                return PEAR::raiseError("Renaming/Moving an entry is only supported in LDAPv3");
            }
            // make dn relative to parent (needed for ldap rename)
            $parent = Net_LDAP2_Util::ldap_explode_dn($this->_newdn, array('casefolding' => 'none', 'reverse' => false, 'onlyvalues' => false));
            if (Net_LDAP2::isError($parent)) {
                return $parent;
            }
            $child = array_shift($parent);
            // maybe the dn consist of a multivalued RDN, we must build the dn in this case
            // because the $child-RDN is an array!
            if (is_array($child)) {
                $child = Net_LDAP2_Util::canonical_dn($child);
            }
            $parent = Net_LDAP2_Util::canonical_dn($parent);

            // rename/move
            if (false == @ldap_rename($link, $this->_dn, $child, $parent, $deleteoldrdn)) {

                return PEAR::raiseError("Entry not renamed: " .
                                        @ldap_error($link), @ldap_errno($link));
            }
            // reflect changes to local copy
            $this->_dn    = $this->_newdn;
            $this->_newdn = null;
        }

        /*
        * Retrieve a entry that has all attributes we need so that the list of changes to build is created accurately
        */
        $fullEntry = $ldap->getEntry( $this->dn() );
        if ( Net_LDAP2::isError($fullEntry) ) {
            return PEAR::raiseError("Could not retrieve a full set of attributes to reconcile changes with");
        }
        $modifications = array();

        // ADD
        foreach ($this->_changes["add"] as $attr => $value) {
            // if attribute exists, we need to combine old and new values
            if ($fullEntry->exists($attr)) {
                $currentValue = $fullEntry->getValue($attr, "all");
                $value = array_merge( $currentValue, $value );
            }

            $modifications[$attr] = $value;
        }

        // DELETE
        foreach ($this->_changes["delete"] as $attr => $value) {
            // In LDAPv3 you need to specify the old values for deleting
            if (is_null($value) && $ldap->getLDAPVersion() === 3) {
                $value = $fullEntry->getValue($attr);
            }
            if (!is_array($value)) {
                $value = array($value);
            }

            // Find out what is missing from $value and exclude it
            $currentValue = isset($modifications[$attr]) ? $modifications[$attr] : $fullEntry->getValue($attr, "all");
            $modifications[$attr] = array_values( array_diff( $currentValue, $value ) );
        }

        // REPLACE
        foreach ($this->_changes["replace"] as $attr => $value) {
            $modifications[$attr] = $value;
        }

        // COMMIT
        if (false === @ldap_modify($link, $this->dn(), $modifications)) {
            return PEAR::raiseError("Could not modify the entry: " . @ldap_error($link), @ldap_errno($link));
        }

        // all went well, so _original (server) becomes _attributes (local copy), reset _changes too...
        $this->_changes['add']     = array();
        $this->_changes['delete']  = array();
        $this->_changes['replace'] = array();
        $this->_original           = $this->_attributes;

        $return = true;
        return $return;
    }

    /**
    * Returns the right attribute name
    *
    * @param string $attr Name of attribute
    *
    * @access protected
    * @return string The right name of the attribute
    */
    protected function getAttrName($attr)
    {
        $name = strtolower($attr);
        if (array_key_exists($name, $this->_map)) {
            $attr = $this->_map[$name];
        }
        return $attr;
    }

    /**
    * Returns a reference to the LDAP-Object of this entry
    *
    * @access public
    * @return Net_LDAP2|Net_LDAP2_Error   Reference to the Net_LDAP2 Object (the connection) or Net_LDAP2_Error
    */
    public function getLDAP()
    {
        if (!$this->_ldap instanceof Net_LDAP2) {
            $err = new PEAR_Error('LDAP is not a valid Net_LDAP2 object');
            return $err;
        } else {
            return $this->_ldap;
        }
    }

    /**
    * Sets a reference to the LDAP-Object of this entry
    *
    * After setting a Net_LDAP2 object, calling update() will use that object for
    * updating directory contents. Use this to dynamicly switch directorys.
    *
    * @param Net_LDAP2 $ldap Net_LDAP2 object that this entry should be connected to
    *
    * @access public
    * @return true|Net_LDAP2_Error
    */
    public function setLDAP($ldap)
    {
        if (!$ldap instanceof Net_LDAP2) {
            return PEAR::raiseError("LDAP is not a valid Net_LDAP2 object");
        } else {
            $this->_ldap = $ldap;
            return true;
        }
    }

    /**
    * Marks the entry as new/existing.
    *
    * If an Entry is marked as new, it will be added to the directory
    * when calling {@link update()}.
    * If the entry is marked as old ($mark = false), then the entry is
    * assumed to be present in the directory server wich results in
    * modification when calling {@link update()}.
    *
    * @param boolean $mark Value to set, defaults to "true"
    *
    * @return void
    */
    public function markAsNew($mark = true)
    {
        $this->_new = ($mark)? true : false;
    }

    /**
    * Applies a regular expression onto a single- or multivalued attribute (like preg_match())
    *
    * This method behaves like PHPs preg_match() but with some exceptions.
    * If you want to retrieve match information, then you MUST pass the
    * $matches parameter via reference! otherwise you will get no matches.
    * Since it is possible to have multi valued attributes the $matches
    * array will have a additionally numerical dimension (one for each value):
    * <code>
    * $matches = array(
    *         0 => array (usual preg_match() returnarray),
    *         1 => array (usual preg_match() returnarray)
    *     )
    * </code>
    * Please note, that $matches will be initialized to an empty array inside.
    *
    * Usage example:
    * <code>
    * $result = $entry->preg_match('/089(\d+)/', 'telephoneNumber', $matches);
    * if ( $result === true ){
    *     echo "First match: ".$matches[0][1];   // Match of value 1, content of first bracket
    * } else {
    *     if ( Net_LDAP2::isError($result) ) {
    *         echo "Error: ".$result->getMessage();
    *     } else {
    *         echo "No match found.";
    *     }
    * }
    * </code>
    *
    * Please note that it is important to test for an Net_LDAP2_Error, because objects are
    * evaluating to true by default, thus if an error occured, and you only check using "==" then
    * you get misleading results. Use the "identical" (===) operator to test for matches to
    * avoid this as shown above.
    *
    * @param string $regex     The regular expression
    * @param string $attr_name The attribute to search in
    * @param array  $matches   (optional, PASS BY REFERENCE!) Array to store matches in
    *
    * @return boolean|Net_LDAP2_Error  TRUE, if we had a match in one of the values, otherwise false. Net_LDAP2_Error in case something went wrong
    */
    public function pregMatch($regex, $attr_name, $matches = array())
    {
        $matches = array();

        // fetch attribute values
        $attr = $this->getValue($attr_name, 'all');

        // perform preg_match() on all values
        $match = false;
        foreach ($attr as $thisvalue) {
            $matches_int = array();
            if (preg_match($regex, $thisvalue, $matches_int)) {
                $match = true;
                array_push($matches, $matches_int); // store matches in reference
            }
        }
        return $match;
    }

    /**
    * Alias of {@link pregMatch()} for compatibility to Net_LDAP 1
    *
    * @see pregMatch()
    * @return boolean|Net_LDAP2_Error
    */
    public function preg_match()
    {
        $args = func_get_args();
        return call_user_func_array(array( $this, 'pregMatch' ), $args);
    }

    /**
    * Tells if the entry is consiedered as new (not present in the server)
    *
    * Please note, that this doesn't tell you if the entry is present on the server.
    * Use {@link Net_LDAP2::dnExists()} to see if an entry is already there.
    *
    * @return boolean
    */
    public function isNew()
    {
        return $this->_new;
    }


    /**
    * Is this entry going to be deleted once update() is called?
    *
    * @return boolean
    */
    public function willBeDeleted()
    {
        return $this->_delete;
    }

    /**
    * Is this entry going to be moved once update() is called?
    *
    * @return boolean
    */
    public function willBeMoved()
    {
        return ($this->dn() !== $this->currentDN());
    }

    /**
    * Returns always the original DN
    *
    * If an entry will be moved but {@link update()} was not called,
    * {@link dn()} will return the new DN. This method however, returns
    * always the current active DN.
    *
    * @return string
    */
    public function currentDN()
    {
        return $this->_dn;
    }

    /**
    * Returns the attribute changes to be carried out once update() is called
    *
    * @return array
    */
    public function getChanges()
    {
        return $this->_changes;
    }
}
?>
PK|c�\L��+��2pear/net_ldap2/Net/LDAP2/SchemaCache.interface.phpnu�[���<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
/**
* File containing the Net_LDAP2_SchemaCache interface class.
*
* PHP version 5
*
* @category  Net
* @package   Net_LDAP2
* @author    Benedikt Hallinger <beni@php.net>
* @copyright 2009 Benedikt Hallinger
* @license   http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
* @version   SVN: $Id$
* @link      http://pear.php.net/package/Net_LDAP2/
*/

/**
* Interface describing a custom schema cache object
*
* To implement a custom schema cache, one must implement this interface and
* pass the instanciated object to Net_LDAP2s registerSchemaCache() method.
*/
interface Net_LDAP2_SchemaCache
{
    /**
    * Return the schema object from the cache
    *
    * Net_LDAP2 will consider anything returned invalid, except
    * a valid Net_LDAP2_Schema object.
    * In case you return a Net_LDAP2_Error, this error will be routed
    * to the return of the $ldap->schema() call.
    * If you return something else, Net_LDAP2 will
    * fetch a fresh Schema object from the LDAP server.
    *
    * You may want to implement a cache aging mechanism here too.
    *
    * @return Net_LDAP2_Schema|Net_LDAP2_Error|false
    */
    public function loadSchema();

    /**
    * Store a schema object in the cache
    *
    * This method will be called, if Net_LDAP2 has fetched a fresh
    * schema object from LDAP and wants to init or refresh the cache.
    *
    * In case of errors you may return a Net_LDAP2_Error which will
    * be routet to the client.
    * Note that doing this prevents, that the schema object fetched from LDAP
    * will be given back to the client, so only return errors if storing
    * of the cache is something crucial (e.g. for doing something else with it).
    * Normaly you dont want to give back errors in which case Net_LDAP2 needs to
    * fetch the schema once per script run and instead use the error
    * returned from loadSchema().
    *
    * @return true|Net_LDAP2_Error
    */
    public function storeSchema($schema);
}
PK|c�\<Ά�a�a!pear/net_ldap2/Net/LDAP2/Util.phpnu�[���<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
/**
* File containing the Net_LDAP2_Util interface class.
*
* PHP version 5
*
* @category  Net
* @package   Net_LDAP2
* @author    Benedikt Hallinger <beni@php.net>
* @copyright 2009 Benedikt Hallinger
* @license   http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
* @version   SVN: $Id$
* @link      http://pear.php.net/package/Net_LDAP2/
*/

/**
* Includes
*/
require_once 'PEAR.php';

/**
* Utility Class for Net_LDAP2
*
* This class servers some functionality to the other classes of Net_LDAP2 but most of
* the methods can be used separately as well.
*
* @category Net
* @package  Net_LDAP2
* @author   Benedikt Hallinger <beni@php.net>
* @license  http://www.gnu.org/copyleft/lesser.html LGPL
* @link     http://pear.php.net/package/Net_LDAP22/
*/
class Net_LDAP2_Util extends PEAR
{
    /**
     * Constructor
     *
     * @access public
     */
    public function __construct()
    {
         // We do nothing here, since all methods can be called statically.
         // In Net_LDAP <= 0.7, we needed a instance of Util, because
         // it was possible to do utf8 encoding and decoding, but this
         // has been moved to the LDAP class. The constructor remains only
         // here to document the downward compatibility of creating an instance.
    }

    /**
    * Explodes the given DN into its elements
    *
    * {@link http://www.ietf.org/rfc/rfc2253.txt RFC 2253} says, a Distinguished Name is a sequence
    * of Relative Distinguished Names (RDNs), which themselves
    * are sets of Attributes. For each RDN a array is constructed where the RDN part is stored.
    *
    * For example, the DN 'OU=Sales+CN=J. Smith,DC=example,DC=net' is exploded to:
    * <kbd>array( [0] => array([0] => 'OU=Sales', [1] => 'CN=J. Smith'), [2] => 'DC=example', [3] => 'DC=net' )</kbd>
    *
    * [NOT IMPLEMENTED] DNs might also contain values, which are the bytes of the BER encoding of
    * the X.500 AttributeValue rather than some LDAP string syntax. These values are hex-encoded
    * and prefixed with a #. To distinguish such BER values, ldap_explode_dn uses references to
    * the actual values, e.g. '1.3.6.1.4.1.1466.0=#04024869,DC=example,DC=com' is exploded to:
    * [ { '1.3.6.1.4.1.1466.0' => "\004\002Hi" }, { 'DC' => 'example' }, { 'DC' => 'com' } ];
    * See {@link http://www.vijaymukhi.com/vmis/berldap.htm} for more information on BER.
    *
    *  It also performs the following operations on the given DN:
    *   - Unescape "\" followed by ",", "+", """, "\", "<", ">", ";", "#", "=", " ", or a hexpair
    *     and strings beginning with "#".
    *   - Removes the leading 'OID.' characters if the type is an OID instead of a name.
    *   - If an RDN contains multiple parts, the parts are re-ordered so that the attribute type names are in alphabetical order.
    *
    * OPTIONS is a list of name/value pairs, valid options are:
    *   casefold    Controls case folding of attribute types names.
    *               Attribute values are not affected by this option.
    *               The default is to uppercase. Valid values are:
    *               lower        Lowercase attribute types names.
    *               upper        Uppercase attribute type names. This is the default.
    *               none         Do not change attribute type names.
    *   reverse     If TRUE, the RDN sequence is reversed.
    *   onlyvalues  If TRUE, then only attributes values are returned ('foo' instead of 'cn=foo')
    *

    * @param string $dn      The DN that should be exploded
    * @param array  $options Options to use
    *
    * @static
    * @return array   Parts of the exploded DN
    * @todo implement BER
    */
    public static function ldap_explode_dn($dn, $options = array('casefold' => 'upper'))
    {
        if (!isset($options['onlyvalues'])) $options['onlyvalues']  = false;
        if (!isset($options['reverse']))    $options['reverse']     = false;
        if (!isset($options['casefold']))   $options['casefold']    = 'upper';

        // Escaping of DN and stripping of "OID."
        $dn = self::canonical_dn($dn, array('casefold' => $options['casefold']));

        // splitting the DN
        $dn_array = preg_split('/(?<=[^\\\\]),/', $dn);

        // clear wrong splitting (possibly we have split too much)
        // /!\ Not clear, if this is neccessary here
        //$dn_array = self::correct_dn_splitting($dn_array, ',');

        // construct subarrays for multivalued RDNs and unescape DN value
        // also convert to output format and apply casefolding
        foreach ($dn_array as $key => $value) {
            $value_u = self::unescape_dn_value($value);
            $rdns    = self::split_rdn_multival($value_u[0]);
            if (count($rdns) > 1) {
                // MV RDN!
                foreach ($rdns as $subrdn_k => $subrdn_v) {
                    // Casefolding
                    if ($options['casefold'] == 'upper') {
                        $subrdn_v = preg_replace_callback(
                            "/^\w+=/",
                            function ($matches) {
                                return strtoupper($matches[0]);
                            },
                            $subrdn_v
                        );
                    } else if ($options['casefold'] == 'lower') {
                        $subrdn_v = preg_replace_callback(
                            "/^\w+=/",
                            function ($matches) {
                                return strtolower($matches[0]);
                            },
                            $subrdn_v
                        );
                    }

                    if ($options['onlyvalues']) {
                        preg_match('/(.+?)(?<!\\\\)=(.+)/', $subrdn_v, $matches);
                        $rdn_ocl         = $matches[1];
                        $rdn_val         = $matches[2];
                        $unescaped       = self::unescape_dn_value($rdn_val);
                        $rdns[$subrdn_k] = $unescaped[0];
                    } else {
                        $unescaped = self::unescape_dn_value($subrdn_v);
                        $rdns[$subrdn_k] = $unescaped[0];
                    }
                }

                $dn_array[$key] = $rdns;
            } else {
                // normal RDN

                // Casefolding
                if ($options['casefold'] == 'upper') {
                    $value = preg_replace_callback(
                        "/^\w+=/",
                        function ($matches) {
                            return strtoupper($matches[0]);
                        },
                        $value
                    );
                } else if ($options['casefold'] == 'lower') {
                    $value = preg_replace_callback(
                        "/^\w+=/",
                        function ($matches) {
                            return strtolower($matches[0]);
                        },
                        $value
                    );
                }

                if ($options['onlyvalues']) {
                    preg_match('/(.+?)(?<!\\\\)=(.+)/', $value, $matches);
                    $dn_ocl         = $matches[1];
                    $dn_val         = $matches[2];
                    $unescaped      = self::unescape_dn_value($dn_val);
                    $dn_array[$key] = $unescaped[0];
                } else {
                    $unescaped = self::unescape_dn_value($value);
                    $dn_array[$key] = $unescaped[0];
                }
            }
        }

        if ($options['reverse']) {
            return array_reverse($dn_array);
        } else {
            return $dn_array;
        }
    }

    /**
    * Escapes a DN value according to RFC 2253
    *
    * Escapes the given VALUES according to RFC 2253 so that they can be safely used in LDAP DNs.
    * The characters ",", "+", """, "\", "<", ">", ";", "#", "=" with a special meaning in RFC 2252
    * are preceeded by ba backslash. Control characters with an ASCII code < 32 are represented as \hexpair.
    * Finally all leading and trailing spaces are converted to sequences of \20.
    *
    * @param array $values An array containing the DN values that should be escaped
    *
    * @static
    * @return array The array $values, but escaped
    */
    public static function escape_dn_value($values = array())
    {
        // Parameter validation
        if (!is_array($values)) {
            $values = array($values);
        }

        foreach ($values as $key => $val) {
            // Escaping of filter meta characters
            $val = str_replace('\\', '\\\\', $val);
            $val = str_replace(',',    '\,', $val);
            $val = str_replace('+',    '\+', $val);
            $val = str_replace('"',    '\"', $val);
            $val = str_replace('<',    '\<', $val);
            $val = str_replace('>',    '\>', $val);
            $val = str_replace(';',    '\;', $val);
            $val = str_replace('#',    '\#', $val);
            $val = str_replace('=',    '\=', $val);

            // ASCII < 32 escaping
            $val = self::asc2hex32($val);

            // Convert all leading and trailing spaces to sequences of \20.
            if (preg_match('/^(\s*)(.+?)(\s*)$/', $val, $matches)) {
                $val = $matches[2];
                for ($i = 0; $i < strlen($matches[1]); $i++) {
                    $val = '\20'.$val;
                }
                for ($i = 0; $i < strlen($matches[3]); $i++) {
                    $val = $val.'\20';
                }
            }

            if (null === $val) $val = '\0';  // apply escaped "null" if string is empty

            $values[$key] = $val;
        }

        return $values;
    }

    /**
    * Undoes the conversion done by escape_dn_value().
    *
    * Any escape sequence starting with a baskslash - hexpair or special character -
    * will be transformed back to the corresponding character.
    *
    * @param array $values Array of DN Values
    *
    * @return array Same as $values, but unescaped
    * @static
    */
    public static function unescape_dn_value($values = array())
    {
        // Parameter validation
        if (!is_array($values)) {
            $values = array($values);
        }

        foreach ($values as $key => $val) {
            // strip slashes from special chars
            $val = str_replace('\\\\', '\\', $val);
            $val = str_replace('\,',    ',', $val);
            $val = str_replace('\+',    '+', $val);
            $val = str_replace('\"',    '"', $val);
            $val = str_replace('\<',    '<', $val);
            $val = str_replace('\>',    '>', $val);
            $val = str_replace('\;',    ';', $val);
            $val = str_replace('\#',    '#', $val);
            $val = str_replace('\=',    '=', $val);

            // Translate hex code into ascii
            $values[$key] = self::hex2asc($val);
        }

        return $values;
    }

    /**
    * Returns the given DN in a canonical form
    *
    * Returns false if DN is not a valid Distinguished Name.
    * DN can either be a string or an array
    * as returned by ldap_explode_dn, which is useful when constructing a DN.
    * The DN array may have be indexed (each array value is a OCL=VALUE pair)
    * or associative (array key is OCL and value is VALUE).
    *
    * It performs the following operations on the given DN:
    *     - Removes the leading 'OID.' characters if the type is an OID instead of a name.
    *     - Escapes all RFC 2253 special characters (",", "+", """, "\", "<", ">", ";", "#", "="), slashes ("/"), and any other character where the ASCII code is < 32 as \hexpair.
    *     - Converts all leading and trailing spaces in values to be \20.
    *     - If an RDN contains multiple parts, the parts are re-ordered so that the attribute type names are in alphabetical order.
    *
    * OPTIONS is a list of name/value pairs, valid options are:
    *     casefold    Controls case folding of attribute type names.
    *                 Attribute values are not affected by this option. The default is to uppercase.
    *                 Valid values are:
    *                 lower        Lowercase attribute type names.
    *                 upper        Uppercase attribute type names. This is the default.
    *                 none         Do not change attribute type names.
    *     [NOT IMPLEMENTED] mbcescape   If TRUE, characters that are encoded as a multi-octet UTF-8 sequence will be escaped as \(hexpair){2,*}.
    *     reverse     If TRUE, the RDN sequence is reversed.
    *     separator   Separator to use between RDNs. Defaults to comma (',').
    *
    * Note: The empty string "" is a valid DN, so be sure not to do a "$can_dn == false" test,
    *       because an empty string evaluates to false. Use the "===" operator instead.
    *
    * @param array|string $dn      The DN
    * @param array        $options Options to use
    *
    * @static
    * @return false|string The canonical DN or FALSE
    * @todo implement option mbcescape
    */
    public static function canonical_dn($dn, $options = array('casefold' => 'upper', 'separator' => ','))
    {
        if ($dn === '') return $dn;  // empty DN is valid!

        // options check
        if (!isset($options['reverse'])) {
            $options['reverse'] = false;
        } else {
            $options['reverse'] = true;
        }
        if (!isset($options['casefold']))  $options['casefold'] = 'upper';
        if (!isset($options['separator'])) $options['separator'] = ',';


        if (!is_array($dn)) {
            // It is not clear to me if the perl implementation splits by the user defined
            // separator or if it just uses this separator to construct the new DN
            $dn = preg_split('/(?<=[^\\\\])'.$options['separator'].'/', $dn);

            // clear wrong splitting (possibly we have split too much)
            $dn = self::correct_dn_splitting($dn, $options['separator']);
        } else {
            // Is array, check, if the array is indexed or associative
            $assoc = false;
            foreach ($dn as $dn_key => $dn_part) {
                if (!is_int($dn_key)) {
                    $assoc = true;
                }
            }
            // convert to indexed, if associative array detected
            if ($assoc) {
                $newdn = array();
                foreach ($dn as $dn_key => $dn_part) {
                    if (is_array($dn_part)) {
                        ksort($dn_part, SORT_STRING); // we assume here, that the rdn parts are also associative
                        $newdn[] = $dn_part;  // copy array as-is, so we can resolve it later
                    } else {
                        $newdn[] = $dn_key.'='.$dn_part;
                    }
                }
                $dn =& $newdn;
            }
        }

        // Escaping and casefolding
        foreach ($dn as $pos => $dnval) {
            if (is_array($dnval)) {
                // subarray detected, this means very surely, that we had
                // a multivalued dn part, which must be resolved
                $dnval_new = '';
                foreach ($dnval as $subkey => $subval) {
                    // build RDN part
                    if (!is_int($subkey)) {
                        $subval = $subkey.'='.$subval;
                    }
                    $subval_processed = self::canonical_dn($subval);
                    if (false === $subval_processed) return false;
                    $dnval_new .= $subval_processed.'+';
                }
                $dn[$pos] = substr($dnval_new, 0, -1); // store RDN part, strip last plus
            } else {
                // try to split multivalued RDNS into array
                $rdns = self::split_rdn_multival($dnval);
                if (count($rdns) > 1) {
                    // Multivalued RDN was detected!
                    // The RDN value is expected to be correctly split by split_rdn_multival().
                    // It's time to sort the RDN and build the DN!
                    $rdn_string = '';
                    sort($rdns, SORT_STRING); // Sort RDN keys alphabetically
                    foreach ($rdns as $rdn) {
                        $subval_processed = self::canonical_dn($rdn);
                        if (false === $subval_processed) return false;
                        $rdn_string .= $subval_processed.'+';
                    }

                    $dn[$pos] = substr($rdn_string, 0, -1); // store RDN part, strip last plus

                } else {
                    // no multivalued RDN!
                    // split at first unescaped "="
                    $dn_comp = preg_split('/(?<=[^\\\\])=/', $rdns[0], 2);
                    $ocl     = ltrim($dn_comp[0]);  // trim left whitespaces 'cause of "cn=foo, l=bar" syntax (whitespace after comma)
                    $val     = $dn_comp[1];

                    // strip 'OID.', otherwise apply casefolding and escaping
                    if (substr(strtolower($ocl), 0, 4) == 'oid.') {
                        $ocl = substr($ocl, 4);
                    } else {
                        if ($options['casefold'] == 'upper') $ocl = strtoupper($ocl);
                        if ($options['casefold'] == 'lower') $ocl = strtolower($ocl);
                        $ocl = self::escape_dn_value(array($ocl));
                        $ocl = $ocl[0];
                    }

                    // escaping of dn-value
                    $val = self::escape_dn_value(array($val));
                    $val = str_replace('/', '\\2f', $val[0]);

                    $dn[$pos] = $ocl.'='.$val;
                }
            }
        }

        if ($options['reverse']) $dn = array_reverse($dn);
        return implode($options['separator'], $dn);
    }

    /**
    * Escapes the given VALUES according to RFC 2254 so that they can be safely used in LDAP filters.
    *
    * Any control characters with an ACII code < 32 as well as the characters with special meaning in
    * LDAP filters "*", "(", ")", and "\" (the backslash) are converted into the representation of a
    * backslash followed by two hex digits representing the hexadecimal value of the character.
    *
    * @param array $values Array of values to escape
    *
    * @static
    * @return array Array $values, but escaped
    */
    public static function escape_filter_value($values = array())
    {
        // Parameter validation
        if (!is_array($values)) {
            $values = array($values);
        }

        foreach ($values as $key => $val) {
            // Escaping of filter meta characters
            $val = str_replace('\\', '\5c', $val);
            $val = str_replace('*',  '\2a', $val);
            $val = str_replace('(',  '\28', $val);
            $val = str_replace(')',  '\29', $val);

            // ASCII < 32 escaping
            $val = self::asc2hex32($val);

            if (null === $val) $val = '\0';  // apply escaped "null" if string is empty

            $values[$key] = $val;
        }

        return $values;
    }

    /**
    * Undoes the conversion done by {@link escape_filter_value()}.
    *
    * Converts any sequences of a backslash followed by two hex digits into the corresponding character.
    *
    * @param array $values Array of values to escape
    *
    * @static
    * @return array Array $values, but unescaped
    */
    public static function unescape_filter_value($values = array())
    {
        // Parameter validation
        if (!is_array($values)) {
            $values = array($values);
        }

        foreach ($values as $key => $value) {
            // Translate hex code into ascii
            $values[$key] = self::hex2asc($value);
        }

        return $values;
    }

    /**
    * Converts all ASCII chars < 32 to "\HEX"
    *
    * @param string $string String to convert
    *
    * @static
    * @return string
    */
    public static function asc2hex32($string)
    {
        for ($i = 0; $i < strlen($string); $i++) {
            $char = substr($string, $i, 1);
            if (ord($char) < 32) {
                $hex = dechex(ord($char));
                if (strlen($hex) == 1) $hex = '0'.$hex;
                $string = str_replace($char, '\\'.$hex, $string);
            }
        }
        return $string;
    }

    /**
    * Converts all Hex expressions ("\HEX") to their original ASCII characters
    *
    * @param string $string String to convert
    *
    * @static
    * @author beni@php.net, heavily based on work from DavidSmith@byu.net
    * @return string
    */
    public static function hex2asc($string)
    {
        $string = preg_replace_callback(
            "/\\\[0-9A-Fa-f]{2}/",
            function ($matches) {
                return chr(hexdec($matches[0]));
            },
            $string
        );
        return $string;
    }

    /**
    * Split an multivalued RDN value into an Array
    *
    * A RDN can contain multiple values, spearated by a plus sign.
    * This function returns each separate ocl=value pair of the RDN part.
    *
    * If no multivalued RDN is detected, an array containing only
    * the original rdn part is returned.
    *
    * For example, the multivalued RDN 'OU=Sales+CN=J. Smith' is exploded to:
    * <kbd>array([0] => 'OU=Sales', [1] => 'CN=J. Smith')</kbd>
    *
    * The method trys to be smart if it encounters unescaped "+" characters, but may fail,
    * so ensure escaped "+"es in attr names and attr values.
    *
    * [BUG] If you have a multivalued RDN with unescaped plus characters
    *       and there is a unescaped plus sign at the end of an value followed by an
    *       attribute name containing an unescaped plus, then you will get wrong splitting:
    *         $rdn = 'OU=Sales+C+N=J. Smith';
    *       returns:
    *         array('OU=Sales+C', 'N=J. Smith');
    *       The "C+" is treaten as value of the first pair instead as attr name of the second pair.
    *       To prevent this, escape correctly.
    *
    * @param string $rdn Part of an (multivalued) escaped RDN (eg. ou=foo OR ou=foo+cn=bar)
    *
    * @static
    * @return array Array with the components of the multivalued RDN or Error
    */
    public static function split_rdn_multival($rdn)
    {
        $rdns = preg_split('/(?<!\\\\)\+/', $rdn);
        $rdns = self::correct_dn_splitting($rdns, '+');
        return array_values($rdns);
    }

    /**
    * Splits an attribute=value syntax into an array
    *
    * If escaped delimeters are used, they are returned escaped as well.
    * The split will occur at the first unescaped delimeter character.
    * In case an invalid delimeter is given, no split will be performed and an
    * one element array gets returned.
    * Optional also filter-assertion delimeters can be considered (>, <, >=, <=, ~=).
    *
    * @param string  $attr      Attribute and Value Syntax ("foo=bar")
    * @param boolean $extended  If set to true, also filter-assertion delimeter will be matched
    * @param boolean $withDelim If set to true, the return array contains the delimeter at index 1, putting the value to index 2
    *
    * @return array Indexed array: 0=attribute name, 1=attribute value OR ($withDelim=true): 0=attr, 1=delimeter, 2=value
    */
    public static function split_attribute_string($attr, $extended=false, $withDelim=false)
    {
	if ($withDelim) $withDelim = PREG_SPLIT_DELIM_CAPTURE;

        if (!$extended) {
            return preg_split('/(?<!\\\\)(=)/', $attr, 2, $withDelim);
        } else {
            return preg_split('/(?<!\\\\)(>=|<=|>|<|~=|=)/', $attr, 2, $withDelim);
        }
    }

    /**
    * Corrects splitting of dn parts
    *
    * @param array $dn        Raw DN array
    * @param array $separator Separator that was used when splitting
    *
    * @return array Corrected array
    * @access protected
    */
    protected static function correct_dn_splitting($dn = array(), $separator = ',')
    {
        foreach ($dn as $key => $dn_value) {
            $dn_value = $dn[$key]; // refresh value (foreach caches!)
            // if the dn_value is not in attr=value format, then we had an
            // unescaped separator character inside the attr name or the value.
            // We assume, that it was the attribute value.
            // [TODO] To solve this, we might ask the schema. Keep in mind, that UTIL class
            //        must remain independent from the other classes or connections.
            if (!preg_match('/.+(?<!\\\\)=.+/', $dn_value)) {
                unset($dn[$key]);
                if (array_key_exists($key-1, $dn)) {
                    $dn[$key-1] = $dn[$key-1].$separator.$dn_value; // append to previous attr value
                } else {
                    $dn[$key+1] = $dn_value.$separator.$dn[$key+1]; // first element: prepend to next attr name
                }
            }
        }
        return array_values($dn);
    }
}

?>
PK|c�\Z�����pear/net_smtp/composer.jsonnu�[���{
    "authors": [
        {
            "email": "jon@php.net",
            "name": "Jon Parise",
            "homepage": "https://www.indelible.org",
            "role": "Lead"
        },
        {
            "email": "chuck@horde.org",
            "name": "Chuck Hagenbuch",
            "role": "Lead"
        },
        {
            "email": "schengawegga@gmail.com",
            "name": "Armin Graefe",
            "role": "Lead"
        }
    ],
    "autoload": {
        "psr-0": {
            "Net": "./"
        }
    },
    "description": "An implementation of the SMTP protocol",
    "keywords": [
        "smtp",
        "mail",
        "email"
    ],
    "include-path": [
        "./"
    ],
    "license": "BSD-2-Clause",
    "name": "pear/net_smtp",
    "homepage": "https://pear.github.io/Net_SMTP/",
    "support": {
        "issues": "https://github.com/pear/Net_SMTP/issues",
        "source": "https://github.com/pear/Net_SMTP"
    },
    "type": "library",
    "require": {
        "php": ">=5.4.0",
        "pear/pear-core-minimal": "@stable",
        "pear/net_socket": "@stable"
    },
    "require-dev": {
        "phpunit/phpunit": "*"
    },
    "suggest": {
        "pear/auth_sasl": "Install optionally via your project's composer.json"
    }
}
PK|c�\����((pear/net_smtp/LICENSEnu�[���Copyright 2002-2017 Jon Parise and Chuck Hagenbuch.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution..

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
PK|c�\�SOܪ%�%pear/net_smtp/README.rstnu�[���======================
 The Net_SMTP Package
======================

--------------------
 User Documentation
--------------------

:Author:    Jon Parise
:Contact:   jon@php.net

.. contents:: Table of Contents
.. section-numbering::

Dependencies
============

The ``PEAR_Error`` Class
------------------------

The Net_SMTP package uses the `PEAR_Error`_ class for all of its `error
handling`_.

The ``Net_Socket`` Package
--------------------------

The Net_Socket_ package is used as the basis for all network communications.
Connection options can be specified via the `$socket_options` construction
parameter::

    $socket_options = array('ssl' => array('verify_peer_name' => false));
    $smtp = new Net_SMTP($host, null, null, false, 0, $socket_options);

**Note:** PHP 5.6 introduced `OpenSSL changes`_. Peer certificate verification
is now enabled by default. Although not recommended, `$socket_options` can be
used to disable peer verification (as shown above).

.. _OpenSSL changes: https://php.net/manual/en/migration56.openssl.php

The ``Auth_SASL`` Package
-------------------------

The `Auth_SASL`_ package is an optional dependency.  If it is available, the
Net_SMTP package will be able to support the DIGEST-MD5_ and CRAM-MD5_ SMTP
authentication methods.  Otherwise, only the LOGIN_ and PLAIN_ methods will
be available.

Error Handling
==============

All of the Net_SMTP class's public methods return a PEAR_Error_ object if an
error occurs.  The standard way to check for a PEAR_Error object is by using
`PEAR::isError()`_::

    if (PEAR::isError($error = $smtp->connect())) {
        die($error->getMessage());
    }

.. _PEAR::isError(): https://pear.php.net/manual/en/core.pear.pear.iserror.php

SMTP Authentication
===================

The Net_SMTP package supports the SMTP authentication standard (as defined
by RFC-2554_).  The Net_SMTP package supports the following authentication
methods, in order of preference:

.. _RFC-2554: https://www.ietf.org/rfc/rfc2554.txt

GSSAPI
------

The GSSAPI authentication method uses Kerberos 5 protocol (RFC-4120_).
Does not use user/password.
Requires Service Principal ``gssapi_principal`` parameter and
has an optional Credentials Cache ``gssapi_cname`` parameter.
Requires DNS and Key Distribution Center (KDC) setup.
It is considered the most secure method of SMTP authentication.

**Note:** The GSSAPI authentication method is only supported
if the krb5_ php extension is available.

.. _RFC-4120: https://tools.ietf.org/html/rfc4120
.. _krb5: https://pecl.php.net/package/krb5

DIGEST-MD5
----------

The DIGEST-MD5 authentication method uses `RSA Data Security Inc.`_'s MD5
Message Digest algorithm.  It is considered a more secure method of SMTP
authentication than PLAIN or LOGIN, while still vulnerable to MitM attacks
without TLS/SSL.

**Note:** The DIGEST-MD5 authentication method is only supported if the
AUTH_SASL_ package is available.

.. _RSA Data Security Inc.: https://www.rsasecurity.com/

CRAM-MD5
--------

The CRAM-MD5 authentication method has been superseded by the DIGEST-MD5_
method in terms of security.  It is provided here for compatibility with
older SMTP servers that may not support the newer DIGEST-MD5 algorithm.

**Note:** The CRAM-MD5 authentication method is only supported if the
AUTH_SASL_ package is available.

LOGIN
-----

The LOGIN authentication method encrypts the user's password using the
Base64_ encoding scheme.  Because decrypting a Base64-encoded string is
trivial, LOGIN is not considered a secure authentication method and should
be avoided.

.. _Base64: https://www.php.net/manual/en/function.base64-encode.php

PLAIN
-----

The PLAIN authentication method sends the user's password in plain text.
This method of authentication is not secure and should be avoided.

XOAUTH2
-------

The XOAUTH2 authentication method sends a username and an OAuth2 access token
as per `Gmail's SASL XOAUTH2 documentation`__.

.. __: https://developers.google.com/gmail/imap/xoauth2-protocol#smtp_protocol_exchange

Secure Connections
==================

If `secure socket transports`_ have been enabled in PHP, it is possible to
establish a secure connection to the remote SMTP server::

    $smtp = new Net_SMTP('ssl://mail.example.com', 465);

This example connects to ``mail.example.com`` on port 465 (a common SMTPS
port) using the ``ssl://`` transport.

TLS/SSL is enabled for authenticated connections by default (via the ``auth()``
method's ``$tls`` parameter), but the |STARTTLS|_ command can also be sent
manually using the ``starttls()`` method.

.. _secure socket transports: https://www.php.net/transports
.. |STARTTLS| replace:: ``STARTTLS``
.. _STARTTLS: https://tools.ietf.org/html/rfc3207

Sending Data
============

Message data is sent using the ``data()`` method.  The data can be supplied
as a single string or as an open file resource.

If a string is provided, it is passed through the `data quoting`_ system and
sent to the socket connection as a single block.  These operations are all
memory-based, so sending large messages may result in high memory usage.

If an open file resource is provided, the ``data()`` method will read the
message data from the file line-by-line.  Each chunk will be quoted and sent
to the socket connection individually, reducing the overall memory overhead of
this data sending operation.

Header data can be specified separately from message body data by passing it
as the optional second parameter to ``data()``.  This is especially useful
when an open file resource is being used to supply message data because it
allows header fields (like *Subject:*) to be built dynamically at runtime.

::

    $smtp->data($fp, "Subject: My Subject");

Data Quoting
============

By default, all outbound string data is quoted in accordance with SMTP
standards.  This means that all native Unix (``\n``) and Mac (``\r``) line
endings are converted to Internet-standard CRLF (``\r\n``) line endings.
Also, because the SMTP protocol uses a single leading period (``.``) to signal
an end to the message data, single leading periods in the original data
string are "doubled" (e.g. "``..``").

These string transformation can be expensive when large blocks of data are
involved.  For example, the Net_SMTP package is not aware of MIME parts (it
just sees the MIME message as one big string of characters), so it is not
able to skip non-text attachments when searching for characters that may
need to be quoted.

Because of this, it is possible to extend the Net_SMTP class in order to
implement your own custom quoting routine.  Just create a new class based on
the Net_SMTP class and reimplement the ``quotedata()`` method::

    require 'Net_SMTP.php';

    class Net_SMTP_custom extends Net_SMTP
    {
        function quotedata($data)
        {
            /* Perform custom data quoting */
        }
    }

Note that the ``$data`` parameter will be passed to the ``quotedata()``
function `by reference`_.  This means that you can operate directly on
``$data``.  It also the overhead of copying a large ``$data`` string to and
from the ``quotedata()`` method.

.. _by reference: https://www.php.net/manual/en/language.references.pass.php

Server Responses
================

The Net_SMTP package retains the server's last response for further
inspection.  The ``getResponse()`` method returns a 2-tuple (two element
array) containing the server's response code as an integer and the response's
arguments as a string.

Upon a successful connection, the server's greeting string is available via
the ``getGreeting()`` method.

Debugging
=========

The Net_SMTP package contains built-in debugging output routines (disabled by
default).  Debugging output must be explicitly enabled via the ``setDebug()``
method::

    $smtp->setDebug(true);

The debugging messages will be sent to the standard output stream by default.
If you need more control over the output, you can optionally install your own
debug handler.

::

    function debugHandler($smtp, $message)
    {
        echo "[$smtp->host] $message\n";
    }

    $smtp->setDebug(true, "debugHandler");


Examples
========

Basic Use
---------

The following script demonstrates how a simple email message can be sent
using the Net_SMTP package::

    require 'Net/SMTP.php';

    $host = 'mail.example.com';
    $from = 'user@example.com';
    $rcpt = array('recipient1@example.com', 'recipient2@example.com');
    $subj = "Subject: Test Message\n";
    $body = "Body Line 1\nBody Line 2";

    /* Create a new Net_SMTP object. */
    if (! ($smtp = new Net_SMTP($host))) {
        die("Unable to instantiate Net_SMTP object\n");
    }

    /* Connect to the SMTP server. */
    if (PEAR::isError($e = $smtp->connect())) {
        die($e->getMessage() . "\n");
    }

    /* Send the 'MAIL FROM:' SMTP command. */
    if (PEAR::isError($smtp->mailFrom($from))) {
        die("Unable to set sender to <$from>\n");
    }

    /* Address the message to each of the recipients. */
    foreach ($rcpt as $to) {
        if (PEAR::isError($res = $smtp->rcptTo($to))) {
            die("Unable to add recipient <$to>: " . $res->getMessage() . "\n");
        }
    }

    /* Set the body of the message. */
    if (PEAR::isError($smtp->data($subj . "\r\n" . $body))) {
        die("Unable to send data\n");
    }

    /* Disconnect from the SMTP server. */
    $smtp->disconnect();

.. _PEAR_Error: https://pear.php.net/manual/en/core.pear.pear-error.php
.. _Net_Socket: https://pear.php.net/package/Net_Socket
.. _Auth_SASL: https://pear.php.net/package/Auth_SASL

.. vim: tabstop=4 shiftwidth=4 softtabstop=4 expandtab textwidth=78 ft=rst:
PK|c�\	�(�{�{�pear/net_smtp/Net/SMTP.phpnu�[���<?php
/** vim: set expandtab softtabstop=4 tabstop=4 shiftwidth=4: */
// +----------------------------------------------------------------------+
// | PHP Version 5 and 7                                                  |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2021 Jon Parise and Chuck Hagenbuch               |
// | All rights reserved.                                                 |
// |                                                                      |
// | Redistribution and use in source and binary forms, with or without   |
// | modification, are permitted provided that the following conditions   |
// | are met:                                                             |
// |                                                                      |
// | 1. Redistributions of source code must retain the above copyright    |
// |    notice, this list of conditions and the following disclaimer.     |
// |                                                                      |
// | 2. Redistributions in binary form must reproduce the above copyright |
// |    notice, this list of conditions and the following disclaimer in   |
// |    the documentation and/or other materials provided with the        |
// |    distribution.                                                     |
// |                                                                      |
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  |
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT    |
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS    |
// | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE       |
// | COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
// | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;     |
// | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER     |
// | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT   |
// | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN    |
// | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE      |
// | POSSIBILITY OF SUCH DAMAGE.                                          |
// +----------------------------------------------------------------------+
// | Authors: Chuck Hagenbuch <chuck@horde.org>                           |
// |          Jon Parise <jon@php.net>                                    |
// |          Damian Alejandro Fernandez Sosa <damlists@cnba.uba.ar>      |
// +----------------------------------------------------------------------+

require_once 'PEAR.php';
require_once 'Net/Socket.php';

/**
 * Provides an implementation of the SMTP protocol using PEAR's
 * Net_Socket class.
 *
 * @package Net_SMTP
 * @author  Chuck Hagenbuch <chuck@horde.org>
 * @author  Jon Parise <jon@php.net>
 * @author  Damian Alejandro Fernandez Sosa <damlists@cnba.uba.ar>
 * @license http://opensource.org/licenses/bsd-license.php BSD-2-Clause
 *
 * @example basic.php A basic implementation of the Net_SMTP package.
 */
class Net_SMTP
{
    /**
     * The server to connect to.
     * @var string
     */
    public $host = 'localhost';

    /**
     * The port to connect to.
     * @var int
     */
    public $port = 25;

    /**
     * The value to give when sending EHLO or HELO.
     * @var string
     */
    public $localhost = 'localhost';

    /**
     * List of supported authentication methods, in preferential order.
     * @var array
     */
    public $auth_methods = array();

    /**
     * Use SMTP command pipelining (specified in RFC 2920) if the SMTP
     * server supports it.
     *
     * When pipeling is enabled, rcptTo(), mailFrom(), sendFrom(),
     * somlFrom() and samlFrom() do not wait for a response from the
     * SMTP server but return immediately.
     *
     * @var bool
     */
    public $pipelining = false;

    /**
     * Number of pipelined commands.
     * @var int
     */
    protected $pipelined_commands = 0;

    /**
     * Should debugging output be enabled?
     * @var boolean
     */
    protected $debug = false;

    /**
     * Debug output handler.
     * @var callback
     */
    protected $debug_handler = null;

    /**
     * The socket resource being used to connect to the SMTP server.
     * @var resource
     */
    protected $socket = null;

    /**
     * Array of socket options that will be passed to Net_Socket::connect().
     * @see stream_context_create()
     * @var array
     */
    protected $socket_options = null;

    /**
     * The socket I/O timeout value in seconds.
     * @var int
     */
    protected $timeout = 0;

    /**
     * The most recent server response code.
     * @var int
     */
    protected $code = -1;

    /**
     * The most recent server response arguments.
     * @var array
     */
    protected $arguments = array();

    /**
     * Stores the SMTP server's greeting string.
     * @var string
     */
    protected $greeting = null;

    /**
     * Stores detected features of the SMTP server.
     * @var array
     */
    protected $esmtp = array();

    /**
     * GSSAPI principal.
     * @var string
     */
    protected $gssapi_principal = null;

    /**
     * GSSAPI principal.
     * @var string
     */
    protected $gssapi_cname = null;

    /**
     * Instantiates a new Net_SMTP object, overriding any defaults
     * with parameters that are passed in.
     *
     * If you have SSL support in PHP, you can connect to a server
     * over SSL using an 'ssl://' prefix:
     *
     *   // 465 is a common smtps port.
     *   $smtp = new Net_SMTP('ssl://mail.host.com', 465);
     *   $smtp->connect();
     *
     * @param string  $host             The server to connect to.
     * @param integer $port             The port to connect to.
     * @param string  $localhost        The value to give when sending EHLO or HELO.
     * @param boolean $pipelining       Use SMTP command pipelining
     * @param integer $timeout          Socket I/O timeout in seconds.
     * @param array   $socket_options   Socket stream_context_create() options.
     * @param string  $gssapi_principal GSSAPI service principal name
     * @param string  $gssapi_cname     GSSAPI credentials cache
     *
     * @since 1.0
     */
    public function __construct($host = null, $port = null, $localhost = null,
        $pipelining = false, $timeout = 0, $socket_options = null,
        $gssapi_principal = null, $gssapi_cname = null
    ) {
        if (isset($host)) {
            $this->host = $host;
        }
        if (isset($port)) {
            $this->port = $port;
        }
        if (isset($localhost)) {
            $this->localhost = $localhost;
        }

        $this->pipelining       = $pipelining;
        $this->socket           = new Net_Socket();
        $this->socket_options   = $socket_options;
        $this->timeout          = $timeout;
        $this->gssapi_principal = $gssapi_principal;
        $this->gssapi_cname     = $gssapi_cname;

        /* If PHP krb5 extension is loaded, we enable GSSAPI method. */
        if (extension_loaded('krb5')) {
            $this->setAuthMethod('GSSAPI', array($this, 'authGSSAPI'));
        }

        /* Include the Auth_SASL package.  If the package is available, we
         * enable the authentication methods that depend upon it. */
        if (@include_once 'Auth/SASL.php') {
            $this->setAuthMethod('CRAM-MD5', array($this, 'authCramMD5'));
            $this->setAuthMethod('DIGEST-MD5', array($this, 'authDigestMD5'));
        }

        /* These standard authentication methods are always available. */
        $this->setAuthMethod('LOGIN', array($this, 'authLogin'), false);
        $this->setAuthMethod('PLAIN', array($this, 'authPlain'), false);
        $this->setAuthMethod('XOAUTH2', array($this, 'authXOAuth2'), false);
    }

    /**
     * Set the socket I/O timeout value in seconds plus microseconds.
     *
     * @param integer $seconds      Timeout value in seconds.
     * @param integer $microseconds Additional value in microseconds.
     *
     * @since 1.5.0
     */
    public function setTimeout($seconds, $microseconds = 0)
    {
        return $this->socket->setTimeout($seconds, $microseconds);
    }

    /**
     * Set the value of the debugging flag.
     *
     * @param boolean  $debug   New value for the debugging flag.
     * @param callback $handler Debug handler callback
     *
     * @since 1.1.0
     */
    public function setDebug($debug, $handler = null)
    {
        $this->debug         = $debug;
        $this->debug_handler = $handler;
    }

    /**
     * Write the given debug text to the current debug output handler.
     *
     * @param string $message Debug mesage text.
     *
     * @since 1.3.3
     */
    protected function debug($message)
    {
        if ($this->debug) {
            if ($this->debug_handler) {
                call_user_func_array(
                    $this->debug_handler, array(&$this, $message)
                );
            } else {
                echo "DEBUG: $message\n";
            }
        }
    }

    /**
     * Send the given string of data to the server.
     *
     * @param string $data The string of data to send.
     *
     * @return mixed The number of bytes that were actually written,
     *               or a PEAR_Error object on failure.
     *
     * @since 1.1.0
     */
    protected function send($data)
    {
        $this->debug("Send: $data");

        $result = $this->socket->write($data);
        if (!$result || PEAR::isError($result)) {
            $msg = $result ? $result->getMessage() : "unknown error";
            return PEAR::raiseError("Failed to write to socket: $msg");
        }

        return $result;
    }

    /**
     * Send a command to the server with an optional string of
     * arguments.  A carriage return / linefeed (CRLF) sequence will
     * be appended to each command string before it is sent to the
     * SMTP server - an error will be thrown if the command string
     * already contains any newline characters. Use send() for
     * commands that must contain newlines.
     *
     * @param string $command The SMTP command to send to the server.
     * @param string $args    A string of optional arguments to append
     *                        to the command.
     *
     * @return mixed The result of the send() call.
     *
     * @since 1.1.0
     */
    protected function put($command, $args = '')
    {
        if (!empty($args)) {
            $command .= ' ' . $args;
        }

        if (strcspn($command, "\r\n") !== strlen($command)) {
            return PEAR::raiseError('Commands cannot contain newlines');
        }

        return $this->send($command . "\r\n");
    }

    /**
     * Read a reply from the SMTP server.  The reply consists of a response
     * code and a response message.
     *
     * @param mixed $valid The set of valid response codes.  These
     *                     may be specified as an array of integer
     *                     values or as a single integer value.
     * @param bool  $later Do not parse the response now, but wait
     *                     until the last command in the pipelined
     *                     command group
     *
     * @return mixed True if the server returned a valid response code or
     *               a PEAR_Error object is an error condition is reached.
     *
     * @since 1.1.0
     *
     * @see getResponse
     */
    protected function parseResponse($valid, $later = false)
    {
        $this->code      = -1;
        $this->arguments = array();

        if ($later) {
            $this->pipelined_commands++;
            return true;
        }

        for ($i = 0; $i <= $this->pipelined_commands; $i++) {
            while ($line = $this->socket->readLine()) {
                $this->debug("Recv: $line");

                /* If we receive an empty line, the connection was closed. */
                if (empty($line)) {
                    $this->disconnect();
                    return PEAR::raiseError('Connection was closed');
                }

                /* Read the code and store the rest in the arguments array. */
                $code = substr($line, 0, 3);
                $this->arguments[] = trim(substr($line, 4));

                /* Check the syntax of the response code. */
                if (is_numeric($code)) {
                    $this->code = (int)$code;
                } else {
                    $this->code = -1;
                    break;
                }

                /* If this is not a multiline response, we're done. */
                if (substr($line, 3, 1) != '-') {
                    break;
                }
            }
        }

        $this->pipelined_commands = 0;

        /* Compare the server's response code with the valid code/codes. */
        if (is_int($valid) && ($this->code === $valid)) {
            return true;
        } elseif (is_array($valid) && in_array($this->code, $valid, true)) {
            return true;
        }

        return PEAR::raiseError('Invalid response code received from server', $this->code);
    }

    /**
     * Issue an SMTP command and verify its response.
     *
     * @param string $command The SMTP command string or data.
     * @param mixed  $valid   The set of valid response codes. These
     *                        may be specified as an array of integer
     *                        values or as a single integer value.
     *
     * @return mixed True on success or a PEAR_Error object on failure.
     *
     * @since 1.6.0
     */
    public function command($command, $valid)
    {
        if (PEAR::isError($error = $this->put($command))) {
            return $error;
        }
        if (PEAR::isError($error = $this->parseResponse($valid))) {
            return $error;
        }

        return true;
    }

    /**
     * Return a 2-tuple containing the last response from the SMTP server.
     *
     * @return array A two-element array: the first element contains the
     *               response code as an integer and the second element
     *               contains the response's arguments as a string.
     *
     * @since 1.1.0
     */
    public function getResponse()
    {
        return array($this->code, join("\n", $this->arguments));
    }

    /**
     * Return the SMTP server's greeting string.
     *
     * @return string A string containing the greeting string, or null if
     *                a greeting has not been received.
     *
     * @since 1.3.3
     */
    public function getGreeting()
    {
        return $this->greeting;
    }

    /**
     * Attempt to connect to the SMTP server.
     *
     * @param int  $timeout    The timeout value (in seconds) for the
     *                         socket connection attempt.
     * @param bool $persistent Should a persistent socket connection
     *                         be used?
     *
     * @return mixed Returns a PEAR_Error with an error message on any
     *               kind of failure, or true on success.
     * @since 1.0
     */
    public function connect($timeout = null, $persistent = false)
    {
        $this->greeting = null;

        $result = $this->socket->connect(
            $this->host, $this->port, $persistent, $timeout, $this->socket_options
        );

        if (PEAR::isError($result)) {
            return PEAR::raiseError(
                'Failed to connect socket: ' . $result->getMessage()
            );
        }

        /*
         * Now that we're connected, reset the socket's timeout value for
         * future I/O operations.  This allows us to have different socket
         * timeout values for the initial connection (our $timeout parameter)
         * and all other socket operations.
         */
        if ($this->timeout > 0) {
            if (PEAR::isError($error = $this->setTimeout($this->timeout))) {
                return $error;
            }
        }

        if (PEAR::isError($error = $this->parseResponse(220))) {
            return $error;
        }

        /* Extract and store a copy of the server's greeting string. */
        list(, $this->greeting) = $this->getResponse();

        if (PEAR::isError($error = $this->negotiate())) {
            return $error;
        }

        return true;
    }

    /**
     * Attempt to disconnect from the SMTP server.
     *
     * @param bool $force Forces a disconnection of the socket even if
     *                    the QUIT command fails
     *
     * @return mixed Returns a PEAR_Error with an error message on any
     *               kind of failure, or true on success.
     * @since 1.0
     */
    public function disconnect($force = false)
    {
        /* parseResponse is only needed if put QUIT is successful */
        if (!PEAR::isError($error = $this->put('QUIT'))) {
            $error = $this->parseResponse(221);
        }

        /* disconnecting socket if there is no error on the QUIT
         * command or force disconnecting is requested */
        if (!PEAR::isError($error) || $force) {
            if (PEAR::isError($error_socket = $this->socket->disconnect())) {
                return PEAR::raiseError(
                    'Failed to disconnect socket: ' . $error_socket->getMessage()
                );
            }
        }

        if (PEAR::isError($error)) {
            return $error;
        }

        return true;
    }

    /**
     * Attempt to send the EHLO command and obtain a list of ESMTP
     * extensions available, and failing that just send HELO.
     *
     * @return mixed Returns a PEAR_Error with an error message on any
     *               kind of failure, or true on success.
     *
     * @since 1.1.0
     */
    protected function negotiate()
    {
        if (PEAR::isError($error = $this->put('EHLO', $this->localhost))) {
            return $error;
        }

        if (PEAR::isError($this->parseResponse(250))) {
            /* If the EHLO failed, try the simpler HELO command. */
            if (PEAR::isError($error = $this->put('HELO', $this->localhost))) {
                return $error;
            }
            if (PEAR::isError($this->parseResponse(250))) {
                return PEAR::raiseError('HELO was not accepted', $this->code);
            }

            return true;
        }

        foreach ($this->arguments as $argument) {
            $verb      = strtok($argument, ' ');
            $len       = strlen($verb);
            $arguments = substr($argument, $len + 1, strlen($argument) - $len - 1);
            $this->esmtp[$verb] = $arguments;
        }

        if (!isset($this->esmtp['PIPELINING'])) {
            $this->pipelining = false;
        }

        return true;
    }

    /**
     * Returns the name of the best authentication method that the server
     * has advertised.
     *
     * @return mixed Returns a string containing the name of the best
     *               supported authentication method or a PEAR_Error object
     *               if a failure condition is encountered.
     * @since 1.1.0
     */
    protected function getBestAuthMethod()
    {
        $available_methods = explode(' ', $this->esmtp['AUTH']);

        foreach ($this->auth_methods as $method => $callback) {
            if (in_array($method, $available_methods)) {
                return $method;
            }
        }

        return PEAR::raiseError('No supported authentication methods');
    }
    
    /**
     * Establish STARTTLS Connection.
     *
     * @return mixed Returns a PEAR_Error with an error message on any
     *               kind of failure, true on success, or false if SSL/TLS
     *               isn't available.
     * @since 1.10.0
     */
    public function starttls()
    {
        /* We can only attempt a TLS connection if one has been requested,
         * we're running PHP 5.1.0 or later, have access to the OpenSSL
         * extension, are connected to an SMTP server which supports the
         * STARTTLS extension, and aren't already connected over a secure
         * (SSL) socket connection. */
        if (version_compare(PHP_VERSION, '5.1.0', '>=')
            && extension_loaded('openssl') && isset($this->esmtp['STARTTLS'])
            && strncasecmp($this->host, 'ssl://', 6) !== 0
            ) {
                /* Start the TLS connection attempt. */
                if (PEAR::isError($result = $this->put('STARTTLS'))) {
                    return $result;
                }
                if (PEAR::isError($result = $this->parseResponse(220))) {
                    return $result;
                }
                if (isset($this->socket_options['ssl']['crypto_method'])) {
                    $crypto_method = $this->socket_options['ssl']['crypto_method'];
                } else {
                    /* STREAM_CRYPTO_METHOD_TLS_ANY_CLIENT constant does not exist
                     * and STREAM_CRYPTO_METHOD_SSLv23_CLIENT constant is
                     * inconsistent across PHP versions. */
                    $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT
                    | @STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT
                    | @STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
                }
                if (PEAR::isError($result = $this->socket->enableCrypto(true, $crypto_method))) {
                    return $result;
                } elseif ($result !== true) {
                    return PEAR::raiseError('STARTTLS failed');
                }
                
                /* Send EHLO again to recieve the AUTH string from the
                 * SMTP server. */
                $this->negotiate();
            } else {
                return false;
            }
            
            return true;
    }
        
    /**
     * Attempt to do SMTP authentication.
     *
     * @param string $uid    The userid to authenticate as.
     * @param string $pwd    The password to authenticate with.
     * @param string $method The requested authentication method.  If none is
     *                       specified, the best supported method will be used.
     * @param bool   $tls    Flag indicating whether or not TLS should be attempted.
     * @param string $authz  An optional authorization identifier.  If specified, this
     *                       identifier will be used as the authorization proxy.
     *
     * @return mixed Returns a PEAR_Error with an error message on any
     *               kind of failure, or true on success.
     * @since 1.0
     */
    public function auth($uid, $pwd , $method = '', $tls = true, $authz = '')
    {
        /* We can only attempt a TLS connection if one has been requested,
         * we're running PHP 5.1.0 or later, have access to the OpenSSL
         * extension, are connected to an SMTP server which supports the
         * STARTTLS extension, and aren't already connected over a secure
         * (SSL) socket connection. */
        if ($tls) {
            /* Start the TLS connection attempt. */
            if (PEAR::isError($starttls = $this->starttls())) {
                return $starttls;
            }
        }

        if (empty($this->esmtp['AUTH'])) {
            return PEAR::raiseError('SMTP server does not support authentication');
        }

        /* If no method has been specified, get the name of the best
         * supported method advertised by the SMTP server. */
        if (empty($method)) {
            if (PEAR::isError($method = $this->getBestAuthMethod())) {
                /* Return the PEAR_Error object from _getBestAuthMethod(). */
                return $method;
            }
        } else {
            $method = strtoupper($method);
            if (!array_key_exists($method, $this->auth_methods)) {
                return PEAR::raiseError("$method is not a supported authentication method");
            }
        }

        if (!isset($this->auth_methods[$method])) {
            return PEAR::raiseError("$method is not a supported authentication method");
        }

        if (!is_callable($this->auth_methods[$method], false)) {
            return PEAR::raiseError("$method authentication method cannot be called");
        }

        if (is_array($this->auth_methods[$method])) {
            list($object, $method) = $this->auth_methods[$method];
            $result = $object->{$method}($uid, $pwd, $authz, $this);
        } else {
            $func   = $this->auth_methods[$method];
            $result = $func($uid, $pwd, $authz, $this);
        }

        /* If an error was encountered, return the PEAR_Error object. */
        if (PEAR::isError($result)) {
            return $result;
        }

        return true;
    }

    /**
     * Add a new authentication method.
     *
     * @param string $name     The authentication method name (e.g. 'PLAIN')
     * @param mixed  $callback The authentication callback (given as the name of a
     *                         function or as an (object, method name) array).
     * @param bool   $prepend  Should the new method be prepended to the list of
     *                         available methods?  This is the default behavior,
     *                         giving the new method the highest priority.
     *
     * @return mixed True on success or a PEAR_Error object on failure.
     *
     * @since 1.6.0
     */
    public function setAuthMethod($name, $callback, $prepend = true)
    {
        if (!is_string($name)) {
            return PEAR::raiseError('Method name is not a string');
        }

        if (!is_string($callback) && !is_array($callback)) {
            return PEAR::raiseError('Method callback must be string or array');
        }

        if (is_array($callback)) {
            if (!is_object($callback[0]) || !is_string($callback[1])) {
                return PEAR::raiseError('Bad mMethod callback array');
            }
        }

        if ($prepend) {
            $this->auth_methods = array_merge(
                array($name => $callback), $this->auth_methods
            );
        } else {
            $this->auth_methods[$name] = $callback;
        }

        return true;
    }

    /**
     * Authenticates the user using the DIGEST-MD5 method.
     *
     * @param string $uid   The userid to authenticate as.
     * @param string $pwd   The password to authenticate with.
     * @param string $authz The optional authorization proxy identifier.
     *
     * @return mixed Returns a PEAR_Error with an error message on any
     *               kind of failure, or true on success.
     * @since 1.1.0
     */
    protected function authDigestMD5($uid, $pwd, $authz = '')
    {
        if (PEAR::isError($error = $this->put('AUTH', 'DIGEST-MD5'))) {
            return $error;
        }
        /* 334: Continue authentication request */
        if (PEAR::isError($error = $this->parseResponse(334))) {
            /* 503: Error: already authenticated */
            if ($this->code === 503) {
                return true;
            }
            return $error;
        }

        $auth_sasl = new Auth_SASL;
        $digest    = $auth_sasl->factory('digest-md5');
        $challenge = base64_decode($this->arguments[0]);
        $auth_str  = base64_encode(
            $digest->getResponse($uid, $pwd, $challenge, $this->host, "smtp", $authz)
        );

        if (PEAR::isError($error = $this->put($auth_str))) {
            return $error;
        }
        /* 334: Continue authentication request */
        if (PEAR::isError($error = $this->parseResponse(334))) {
            return $error;
        }

        /* We don't use the protocol's third step because SMTP doesn't
         * allow subsequent authentication, so we just silently ignore
         * it. */
        if (PEAR::isError($error = $this->put(''))) {
            return $error;
        }
        /* 235: Authentication successful */
        if (PEAR::isError($error = $this->parseResponse(235))) {
            return $error;
        }
    }

    /**
     * Authenticates the user using the CRAM-MD5 method.
     *
     * @param string $uid   The userid to authenticate as.
     * @param string $pwd   The password to authenticate with.
     * @param string $authz The optional authorization proxy identifier.
     *
     * @return mixed Returns a PEAR_Error with an error message on any
     *               kind of failure, or true on success.
     * @since 1.1.0
     */
    protected function authCRAMMD5($uid, $pwd, $authz = '')
    {
        if (PEAR::isError($error = $this->put('AUTH', 'CRAM-MD5'))) {
            return $error;
        }
        /* 334: Continue authentication request */
        if (PEAR::isError($error = $this->parseResponse(334))) {
            /* 503: Error: already authenticated */
            if ($this->code === 503) {
                return true;
            }
            return $error;
        }

        $auth_sasl = new Auth_SASL;
        $challenge = base64_decode($this->arguments[0]);
        $cram      = $auth_sasl->factory('cram-md5');
        $auth_str  = base64_encode($cram->getResponse($uid, $pwd, $challenge));

        if (PEAR::isError($error = $this->put($auth_str))) {
            return $error;
        }

        /* 235: Authentication successful */
        if (PEAR::isError($error = $this->parseResponse(235))) {
            return $error;
        }
    }

    /**
     * Authenticates the user using the LOGIN method.
     *
     * @param string $uid   The userid to authenticate as.
     * @param string $pwd   The password to authenticate with.
     * @param string $authz The optional authorization proxy identifier.
     *
     * @return mixed Returns a PEAR_Error with an error message on any
     *               kind of failure, or true on success.
     * @since 1.1.0
     */
    protected function authLogin($uid, $pwd, $authz = '')
    {
        if (PEAR::isError($error = $this->put('AUTH', 'LOGIN'))) {
            return $error;
        }
        /* 334: Continue authentication request */
        if (PEAR::isError($error = $this->parseResponse(334))) {
            /* 503: Error: already authenticated */
            if ($this->code === 503) {
                return true;
            }
            return $error;
        }

        if (PEAR::isError($error = $this->put(base64_encode($uid)))) {
            return $error;
        }
        /* 334: Continue authentication request */
        if (PEAR::isError($error = $this->parseResponse(334))) {
            return $error;
        }

        if (PEAR::isError($error = $this->put(base64_encode($pwd)))) {
            return $error;
        }

        /* 235: Authentication successful */
        if (PEAR::isError($error = $this->parseResponse(235))) {
            return $error;
        }

        return true;
    }

    /**
     * Authenticates the user using the PLAIN method.
     *
     * @param string $uid   The userid to authenticate as.
     * @param string $pwd   The password to authenticate with.
     * @param string $authz The optional authorization proxy identifier.
     *
     * @return mixed Returns a PEAR_Error with an error message on any
     *               kind of failure, or true on success.
     * @since 1.1.0
     */
    protected function authPlain($uid, $pwd, $authz = '')
    {
        if (PEAR::isError($error = $this->put('AUTH', 'PLAIN'))) {
            return $error;
        }
        /* 334: Continue authentication request */
        if (PEAR::isError($error = $this->parseResponse(334))) {
            /* 503: Error: already authenticated */
            if ($this->code === 503) {
                return true;
            }
            return $error;
        }

        $auth_str = base64_encode($authz . chr(0) . $uid . chr(0) . $pwd);

        if (PEAR::isError($error = $this->put($auth_str))) {
            return $error;
        }

        /* 235: Authentication successful */
        if (PEAR::isError($error = $this->parseResponse(235))) {
            return $error;
        }

        return true;
    }

     /**
     * Authenticates the user using the GSSAPI method.
     *
     * PHP krb5 extension is required,
     * service principal and credentials cache must be set.
     *
     * @param string $uid   The userid to authenticate as.
     * @param string $pwd   The password to authenticate with.
     * @param string $authz The optional authorization proxy identifier.
     *
     * @return mixed Returns a PEAR_Error with an error message on any
     *               kind of failure, or true on success.
     */
    protected function authGSSAPI($uid, $pwd, $authz = '')
    {
        if (PEAR::isError($error = $this->put('AUTH', 'GSSAPI'))) {
            return $error;
        }
        /* 334: Continue authentication request */
        if (PEAR::isError($error = $this->parseResponse(334))) {
            /* 503: Error: already authenticated */
            if ($this->code === 503) {
                return true;
            }
            return $error;
        }

        if (!$this->gssapi_principal) {
            return PEAR::raiseError('No Kerberos service principal set', 2);
        }

        if (!empty($this->gssapi_cname)) {
            putenv('KRB5CCNAME=' . $this->gssapi_cname);
        }

        try {
            $ccache = new KRB5CCache();
            if (!empty($this->gssapi_cname)) {
                $ccache->open($this->gssapi_cname);
            }
            
            $gssapicontext = new GSSAPIContext();
            $gssapicontext->acquireCredentials($ccache);

            $token   = '';
            $success = $gssapicontext->initSecContext($this->gssapi_principal, null, null, null, $token);
            $token   = base64_encode($token);
        }
        catch (Exception $e) {
            return PEAR::raiseError('GSSAPI authentication failed: ' . $e->getMessage());
        }

        if (PEAR::isError($error = $this->put($token))) {
            return $error;
        }

        /* 334: Continue authentication request */
        if (PEAR::isError($error = $this->parseResponse(334))) {
            return $error;
        }

        $response = $this->arguments[0];

        try {
            $challenge = base64_decode($response);
            $gssapicontext->unwrap($challenge, $challenge);
            $gssapicontext->wrap($challenge, $challenge, true);
        }
        catch (Exception $e) {
            return PEAR::raiseError('GSSAPI authentication failed: ' . $e->getMessage());
        }

        if (PEAR::isError($error = $this->put(base64_encode($challenge)))) {
            return $error;
        }

        /* 235: Authentication successful */
        if (PEAR::isError($error = $this->parseResponse(235))) {
            return $error;
        }

        return true;
    }

    /**
     * Authenticates the user using the XOAUTH2 method.
     *
     * @param string $uid   The userid to authenticate as.
     * @param string $token The access token to authenticate with.
     * @param string $authz The optional authorization proxy identifier.
     *
     * @return mixed Returns a PEAR_Error with an error message on any
     *               kind of failure, or true on success.
     * @since 1.9.0
     */
    public function authXOAuth2($uid, $token, $authz, $conn)
    {
        $auth = base64_encode("user=$uid\1auth=$token\1\1");

        // Maximum length of the base64-encoded token to be sent in the initial response is 497 bytes, according to
        // RFC 4954 (https://datatracker.ietf.org/doc/html/rfc4954); for longer tokens an empty initial
        // response MUST be sent and the token must be sent separately
        // (497 bytes = 512 bytes /SMTP command length limit/ - 13 bytes /"AUTH XOAUTH2 "/ - 2 bytes /CRLF/)
        if (strlen($auth) <= 497) {
            if (PEAR::isError($error = $this->put('AUTH', 'XOAUTH2 ' . $auth))) {
                return $error;
            }
        } else {
            if (PEAR::isError($error = $this->put('AUTH', 'XOAUTH2'))) {
                return $error;
            }

            // server is expected to respond with 334
            if (PEAR::isError($error = $this->parseResponse(334))) {
                return $error;
            }

            // then follows the token
            if (PEAR::isError($error = $this->put($auth))) {
                return $error;
            }
        }

        /* 235: Authentication successful or 334: Continue authentication */
        if (PEAR::isError($error = $this->parseResponse([235, 334]))) {
            return $error;
        }

        /* 334: Continue authentication request */
        if ($this->code === 334) {
            /* Send an empty line as response to 334 */
            if (PEAR::isError($error = $this->put(''))) {
                return $error;
            }

            /* Expect 235: Authentication successful */
            if (PEAR::isError($error = $this->parseResponse(235))) {
                return $error;
            }
        }

        return true;
    }

    /**
     * Send the HELO command.
     *
     * @param string $domain The domain name to say we are.
     *
     * @return mixed Returns a PEAR_Error with an error message on any
     *               kind of failure, or true on success.
     * @since 1.0
     */
    public function helo($domain)
    {
        if (PEAR::isError($error = $this->put('HELO', $domain))) {
            return $error;
        }
        if (PEAR::isError($error = $this->parseResponse(250))) {
            return $error;
        }

        return true;
    }

    /**
     * Return the list of SMTP service extensions advertised by the server.
     *
     * @return array The list of SMTP service extensions.
     * @since 1.3
     */
    public function getServiceExtensions()
    {
        return $this->esmtp;
    }

    /**
     * Send the MAIL FROM: command.
     *
     * @param string $sender The sender (reverse path) to set.
     * @param string $params String containing additional MAIL parameters,
     *                       such as the NOTIFY flags defined by RFC 1891
     *                       or the VERP protocol.
     *
     *                       If $params is an array, only the 'verp' option
     *                       is supported.  If 'verp' is true, the XVERP
     *                       parameter is appended to the MAIL command.
     *                       If the 'verp' value is a string, the full
     *                       XVERP=value parameter is appended.
     *
     * @return mixed Returns a PEAR_Error with an error message on any
     *               kind of failure, or true on success.
     * @since 1.0
     */
    public function mailFrom($sender, $params = null)
    {
        $args = "FROM:<$sender>";

        /* Support the deprecated array form of $params. */
        if (is_array($params) && isset($params['verp'])) {
            if ($params['verp'] === true) {
                $args .= ' XVERP';
            } elseif (trim($params['verp'])) {
                $args .= ' XVERP=' . $params['verp'];
            }
        } elseif (is_string($params) && !empty($params)) {
            $args .= ' ' . $params;
        }

        if (PEAR::isError($error = $this->put('MAIL', $args))) {
            return $error;
        }
        if (PEAR::isError($error = $this->parseResponse(250, $this->pipelining))) {
            return $error;
        }

        return true;
    }

    /**
     * Send the RCPT TO: command.
     *
     * @param string $recipient The recipient (forward path) to add.
     * @param string $params    String containing additional RCPT parameters,
     *                          such as the NOTIFY flags defined by RFC 1891.
     *
     * @return mixed Returns a PEAR_Error with an error message on any
     *               kind of failure, or true on success.
     *
     * @since 1.0
     */
    public function rcptTo($recipient, $params = null)
    {
        $args = "TO:<$recipient>";
        if (is_string($params)) {
            $args .= ' ' . $params;
        }

        if (PEAR::isError($error = $this->put('RCPT', $args))) {
            return $error;
        }
        if (PEAR::isError($error = $this->parseResponse(array(250, 251), $this->pipelining))) {
            return $error;
        }

        return true;
    }

    /**
     * Quote the data so that it meets SMTP standards.
     *
     * This is provided as a separate public function to facilitate
     * easier overloading for the cases where it is desirable to
     * customize the quoting behavior.
     *
     * @param string &$data The message text to quote. The string must be passed
     *                      by reference, and the text will be modified in place.
     *
     * @since 1.2
     */
    public function quotedata(&$data)
    {
        /* Because a single leading period (.) signifies an end to the
         * data, legitimate leading periods need to be "doubled" ('..'). */
        $data = preg_replace('/^\./m', '..', $data);

        /* Change Unix (\n) and Mac (\r) linefeeds into CRLF's (\r\n). */
        $data = preg_replace('/(?:\r\n|\n|\r(?!\n))/', "\r\n", $data);
    }

    /**
     * Send the DATA command.
     *
     * @param mixed  $data    The message data, either as a string or an open
     *                        file resource.
     * @param string $headers The message headers.  If $headers is provided,
     *                        $data is assumed to contain only body data.
     *
     * @return mixed Returns a PEAR_Error with an error message on any
     *               kind of failure, or true on success.
     * @since 1.0
     */
    public function data($data, $headers = null)
    {
        /* Verify that $data is a supported type. */
        if (!is_string($data) && !is_resource($data)) {
            return PEAR::raiseError('Expected a string or file resource');
        }

        /* Start by considering the size of the optional headers string.  We
         * also account for the addition 4 character "\r\n\r\n" separator
         * sequence. */
        $size = $headers_size = (is_null($headers)) ? 0 : strlen($headers) + 4;

        if (is_resource($data)) {
            $stat = fstat($data);
            if ($stat === false) {
                return PEAR::raiseError('Failed to get file size');
            }
            $size += $stat['size'];
        } else {
            $size += strlen($data);
        }

        /* RFC 1870, section 3, subsection 3 states "a value of zero indicates
         * that no fixed maximum message size is in force".  Furthermore, it
         * says that if "the parameter is omitted no information is conveyed
         * about the server's fixed maximum message size". */
        $limit = (isset($this->esmtp['SIZE'])) ? $this->esmtp['SIZE'] : 0;
        if ($limit > 0 && $size >= $limit) {
            return PEAR::raiseError('Message size exceeds server limit');
        }

        /* Initiate the DATA command. */
        if (PEAR::isError($error = $this->put('DATA'))) {
            return $error;
        }
        if (PEAR::isError($error = $this->parseResponse(354))) {
            return $error;
        }

        /* If we have a separate headers string, send it first. */
        if (!is_null($headers)) {
            $this->quotedata($headers);
            if (PEAR::isError($result = $this->send($headers . "\r\n\r\n"))) {
                return $result;
            }

            /* Subtract the headers size now that they've been sent. */
            $size -= $headers_size;
        }

        /* Now we can send the message body data. */
        if (is_resource($data)) {
            /* Stream the contents of the file resource out over our socket
             * connection, line by line.  Each line must be run through the
             * quoting routine. */
            while (strlen($line = fread($data, 8192)) > 0) {
                /* If the last character is an newline, we need to grab the
                 * next character to check to see if it is a period. */
                while (!feof($data)) {
                    $char = fread($data, 1);
                    $line .= $char;
                    if ($char != "\n") {
                        break;
                    }
                }
                $this->quotedata($line);
                if (PEAR::isError($result = $this->send($line))) {
                    return $result;
                }
            }

             $last = $line;
        } else {
            /*
             * Break up the data by sending one chunk (up to 512k) at a time.
             * This approach reduces our peak memory usage.
             */
            for ($offset = 0; $offset < $size;) {
                $end = $offset + 512000;

                /*
                 * Ensure we don't read beyond our data size or span multiple
                 * lines.  quotedata() can't properly handle character data
                 * that's split across two line break boundaries.
                 */
                if ($end >= $size) {
                    $end = $size;
                } else {
                    for (; $end < $size; $end++) {
                        if ($data[$end] != "\n") {
                            break;
                        }
                    }
                }

                /* Extract our chunk and run it through the quoting routine. */
                $chunk = substr($data, $offset, $end - $offset);
                $this->quotedata($chunk);

                /* If we run into a problem along the way, abort. */
                if (PEAR::isError($result = $this->send($chunk))) {
                    return $result;
                }

                /* Advance the offset to the end of this chunk. */
                $offset = $end;
            }

            $last = $chunk;
        }

        /* Don't add another CRLF sequence if it's already in the data */
        $terminator = (substr($last, -2) == "\r\n" ? '' : "\r\n") . ".\r\n";

        /* Finally, send the DATA terminator sequence. */
        if (PEAR::isError($result = $this->send($terminator))) {
            return $result;
        }

        /* Verify that the data was successfully received by the server. */
        if (PEAR::isError($error = $this->parseResponse(250, $this->pipelining))) {
            return $error;
        }

        return true;
    }

    /**
     * Send the SEND FROM: command.
     *
     * @param string $path The reverse path to send.
     *
     * @return mixed Returns a PEAR_Error with an error message on any
     *               kind of failure, or true on success.
     * @since 1.2.6
     */
    public function sendFrom($path)
    {
        if (PEAR::isError($error = $this->put('SEND', "FROM:<$path>"))) {
            return $error;
        }
        if (PEAR::isError($error = $this->parseResponse(250, $this->pipelining))) {
            return $error;
        }

        return true;
    }

    /**
     * Send the SOML FROM: command.
     *
     * @param string $path The reverse path to send.
     *
     * @return mixed Returns a PEAR_Error with an error message on any
     *               kind of failure, or true on success.
     * @since 1.2.6
     */
    public function somlFrom($path)
    {
        if (PEAR::isError($error = $this->put('SOML', "FROM:<$path>"))) {
            return $error;
        }
        if (PEAR::isError($error = $this->parseResponse(250, $this->pipelining))) {
            return $error;
        }

        return true;
    }

    /**
     * Send the SAML FROM: command.
     *
     * @param string $path The reverse path to send.
     *
     * @return mixed Returns a PEAR_Error with an error message on any
     *               kind of failure, or true on success.
     * @since 1.2.6
     */
    public function samlFrom($path)
    {
        if (PEAR::isError($error = $this->put('SAML', "FROM:<$path>"))) {
            return $error;
        }
        if (PEAR::isError($error = $this->parseResponse(250, $this->pipelining))) {
            return $error;
        }

        return true;
    }

    /**
     * Send the RSET command.
     *
     * @return mixed Returns a PEAR_Error with an error message on any
     *               kind of failure, or true on success.
     * @since  1.0
     */
    public function rset()
    {
        if (PEAR::isError($error = $this->put('RSET'))) {
            return $error;
        }
        if (PEAR::isError($error = $this->parseResponse(250, $this->pipelining))) {
            return $error;
        }

        return true;
    }

    /**
     * Send the VRFY command.
     *
     * @param string $string The string to verify
     *
     * @return mixed Returns a PEAR_Error with an error message on any
     *               kind of failure, or true on success.
     * @since 1.0
     */
    public function vrfy($string)
    {
        /* Note: 251 is also a valid response code */
        if (PEAR::isError($error = $this->put('VRFY', $string))) {
            return $error;
        }
        if (PEAR::isError($error = $this->parseResponse(array(250, 252)))) {
            return $error;
        }

        return true;
    }

    /**
     * Send the NOOP command.
     *
     * @return mixed Returns a PEAR_Error with an error message on any
     *               kind of failure, or true on success.
     * @since 1.0
     */
    public function noop()
    {
        if (PEAR::isError($error = $this->put('NOOP'))) {
            return $error;
        }
        if (PEAR::isError($error = $this->parseResponse(250))) {
            return $error;
        }

        return true;
    }

    /**
     * Backwards-compatibility method.  identifySender()'s functionality is
     * now handled internally.
     *
     * @return boolean This method always return true.
     *
     * @since 1.0
     */
    public function identifySender()
    {
        return true;
    }
}
PK|c�\s�='��pear/net_socket/composer.jsonnu�[���{
    "authors": [
        {
            "email": "chuck@horde.org",
            "name": "Chuck Hagenbuch",
            "role": "Lead"
        },
        {
            "email": "stig@php.net",
            "name": "Stig Bakken",
            "role": "Lead"
        },
        {
            "email": "alec@php.net",
            "name": "Aleksander Machniak",
            "role": "Lead"
        }
    ],
    "autoload": {
        "psr-0": {
            "Net": "./"
        }
    },
    "description": "More info available on: http://pear.php.net/package/Net_Socket",
    "include-path": [
        "./"
    ],
    "license": "PHP License",
    "name": "pear/net_socket",
    "support": {
        "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Net_Socket",
        "source": "https://github.com/pear/Net_Socket"
    },
    "type": "library",
    "require": {
        "pear/pear_exception": "*"
    },
    "require-dev": {
        "phpunit/phpunit": "*"
    }
}PK|c�\�b��HHpear/net_socket/package.xmlnu�[���<?xml version="1.0" encoding="UTF-8"?>
<package packagerversion="1.9.1" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0 http://pear.php.net/dtd/tasks-1.0.xsd http://pear.php.net/dtd/package-2.0 http://pear.php.net/dtd/package-2.0.xsd">
 <name>Net_Socket</name>
 <channel>pear.php.net</channel>
 <summary>Network Socket Interface</summary>
 <description>Net_Socket is a class interface to TCP sockets.  It provides blocking
  and non-blocking operation, with different reading and writing modes
  (byte-wise, block-wise, line-wise and special formats like network
  byte-order ip addresses).</description>
 <lead>
  <name>Chuck Hagenbuch</name>
  <user>chagenbu</user>
  <email>chuck@horde.org</email>
  <active>yes</active>
 </lead>
 <lead>
  <name>Stig Bakken</name>
  <user>ssb</user>
  <email>stig@php.net</email>
  <active>no</active>
 </lead>
 <lead>
  <name>Aleksander Machniak</name>
  <user>alec</user>
  <email>alec@php.net</email>
  <active>yes</active>
 </lead>
 <date>2013-05-24</date>
 <time>20:00:00</time>
 <version>
  <release>1.0.14</release>
  <api>1.0.10</api>
 </version>
 <stability>
  <release>stable</release>
  <api>stable</api>
 </stability>
 <license uri="http://www.php.net/license/2_02.txt">PHP License</license>
 <notes>
- Fix connecting when host is specified with protocol prefix e.g. ssl://
 </notes>
 <contents>
  <dir baseinstalldir="/" name="/">
   <file baseinstalldir="/" md5sum="057d5c52b2dd9cfb2a458d532d95cfbb" name="Net/Socket.php" role="php" />
  </dir>
 </contents>
 <dependencies>
  <required>
   <php>
    <min>4.3.0</min>
   </php>
   <pearinstaller>
    <min>1.4.0b1</min>
   </pearinstaller>
  </required>
 </dependencies>
 <phprelease />
</package>
PK|c�\�W�OOpear/net_socket/Net/Socket.phpnu�[���<?php
/**
 * Net_Socket
 *
 * PHP Version 4
 *
 * Copyright (c) 1997-2013 The PHP Group
 *
 * This source file is subject to version 2.0 of the PHP license,
 * that is bundled with this package in the file LICENSE, and is
 * available at through the world-wide-web at
 * http://www.php.net/license/2_02.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 <ssb@php.net>
 *          Chuck Hagenbuch <chuck@horde.org>
 *
 * @category  Net
 * @package   Net_Socket
 * @author    Stig Bakken <ssb@php.net>
 * @author    Chuck Hagenbuch <chuck@horde.org>
 * @copyright 1997-2003 The PHP Group
 * @license   http://www.php.net/license/2_02.txt PHP 2.02
 * @link      http://pear.php.net/packages/Net_Socket
 */

require_once 'PEAR.php';

define('NET_SOCKET_READ', 1);
define('NET_SOCKET_WRITE', 2);
define('NET_SOCKET_ERROR', 4);

/**
 * Generalized Socket class.
 *
 * @category  Net
 * @package   Net_Socket
 * @author    Stig Bakken <ssb@php.net>
 * @author    Chuck Hagenbuch <chuck@horde.org>
 * @copyright 1997-2003 The PHP Group
 * @license   http://www.php.net/license/2_02.txt PHP 2.02
 * @link      http://pear.php.net/packages/Net_Socket
 */
class Net_Socket extends PEAR
{
    /**
     * Socket file pointer.
     * @var resource $fp
     */
    var $fp = null;

    /**
     * Whether the socket is blocking. Defaults to true.
     * @var boolean $blocking
     */
    var $blocking = true;

    /**
     * Whether the socket is persistent. Defaults to false.
     * @var boolean $persistent
     */
    var $persistent = false;

    /**
     * The IP address to connect to.
     * @var string $addr
     */
    var $addr = '';

    /**
     * The port number to connect to.
     * @var integer $port
     */
    var $port = 0;

    /**
     * Number of seconds to wait on socket operations before assuming
     * there's no more data. Defaults to no timeout.
     * @var integer|float $timeout
     */
    var $timeout = null;

    /**
     * Number of bytes to read at a time in readLine() and
     * readAll(). Defaults to 2048.
     * @var integer $lineLength
     */
    var $lineLength = 2048;

    /**
     * The string to use as a newline terminator. Usually "\r\n" or "\n".
     * @var string $newline
     */
    var $newline = "\r\n";

    /**
     * Connect to the specified port. If called when the socket is
     * already connected, it disconnects and connects again.
     *
     * @param string  $addr       IP address or host name (may be with protocol prefix).
     * @param integer $port       TCP port number.
     * @param boolean $persistent (optional) Whether the connection is
     *                            persistent (kept open between requests
     *                            by the web server).
     * @param integer $timeout    (optional) Connection socket timeout.
     * @param array   $options    See options for stream_context_create.
     *
     * @access public
     *
     * @return boolean|PEAR_Error  True on success or a PEAR_Error on failure.
     */
    function connect($addr, $port = 0, $persistent = null,
                     $timeout = null, $options = null)
    {
        if (is_resource($this->fp)) {
            @fclose($this->fp);
            $this->fp = null;
        }

        if (!$addr) {
            return $this->raiseError('$addr cannot be empty');
        } else if (strspn($addr, ':.0123456789') == strlen($addr)) {
            $this->addr = strpos($addr, ':') !== false ? '['.$addr.']' : $addr;
        } else {
            $this->addr = $addr;
        }

        $this->port = $port % 65536;

        if ($persistent !== null) {
            $this->persistent = $persistent;
        }

        $openfunc = $this->persistent ? 'pfsockopen' : 'fsockopen';
        $errno    = 0;
        $errstr   = '';

        $old_track_errors = @ini_set('track_errors', 1);

        if ($timeout <= 0) {
            $timeout = @ini_get('default_socket_timeout');
        }

        if ($options && function_exists('stream_context_create')) {
            $context = stream_context_create($options);

            // Since PHP 5 fsockopen doesn't allow context specification
            if (function_exists('stream_socket_client')) {
                $flags = STREAM_CLIENT_CONNECT;

                if ($this->persistent) {
                    $flags = STREAM_CLIENT_PERSISTENT;
                }

                $addr = $this->addr . ':' . $this->port;
                $fp   = stream_socket_client($addr, $errno, $errstr,
                                             $timeout, $flags, $context);
            } else {
                $fp = @$openfunc($this->addr, $this->port, $errno,
                                 $errstr, $timeout, $context);
            }
        } else {
            $fp = @$openfunc($this->addr, $this->port, $errno, $errstr, $timeout);
        }

        if (!$fp) {
            if ($errno == 0 && !strlen($errstr) && isset($php_errormsg)) {
                $errstr = $php_errormsg;
            }
            @ini_set('track_errors', $old_track_errors);
            return $this->raiseError($errstr, $errno);
        }

        @ini_set('track_errors', $old_track_errors);
        $this->fp = $fp;
        $this->setTimeout();
        return $this->setBlocking($this->blocking);
    }

    /**
     * Disconnects from the peer, closes the socket.
     *
     * @access public
     * @return mixed true on success or a PEAR_Error instance otherwise
     */
    function disconnect()
    {
        if (!is_resource($this->fp)) {
            return $this->raiseError('not connected');
        }

        @fclose($this->fp);
        $this->fp = null;
        return true;
    }

    /**
     * Set the newline character/sequence to use.
     *
     * @param string $newline  Newline character(s)
     * @return boolean True
     */
    function setNewline($newline)
    {
        $this->newline = $newline;
        return true;
    }

    /**
     * Find out if the socket is in blocking mode.
     *
     * @access public
     * @return boolean  The current blocking mode.
     */
    function isBlocking()
    {
        return $this->blocking;
    }

    /**
     * Sets whether the socket connection should be blocking or
     * not. A read call to a non-blocking socket will return immediately
     * if there is no data available, whereas it will block until there
     * is data for blocking sockets.
     *
     * @param boolean $mode True for blocking sockets, false for nonblocking.
     *
     * @access public
     * @return mixed true on success or a PEAR_Error instance otherwise
     */
    function setBlocking($mode)
    {
        if (!is_resource($this->fp)) {
            return $this->raiseError('not connected');
        }

        $this->blocking = $mode;
        stream_set_blocking($this->fp, (int)$this->blocking);
        return true;
    }

    /**
     * Sets the timeout value on socket descriptor,
     * expressed in the sum of seconds and microseconds
     *
     * @param integer $seconds      Seconds.
     * @param integer $microseconds Microseconds, optional.
     *
     * @access public
     * @return mixed True on success or false on failure or
     *               a PEAR_Error instance when not connected
     */
    function setTimeout($seconds = null, $microseconds = null)
    {
        if (!is_resource($this->fp)) {
            return $this->raiseError('not connected');
        }

        if ($seconds === null && $microseconds === null) {
            $seconds      = (int) $this->timeout;
            $microseconds = (int) (($this->timeout - $seconds) * 1000000);
        } else {
            $this->timeout = $seconds + $microseconds/1000000;
        }

        if ($this->timeout > 0) {
            return stream_set_timeout($this->fp, (int) $seconds, (int) $microseconds);
        }
        else {
            return false;
        }
    }

    /**
     * Sets the file buffering size on the stream.
     * See php's stream_set_write_buffer for more information.
     *
     * @param integer $size Write buffer size.
     *
     * @access public
     * @return mixed on success or an PEAR_Error object otherwise
     */
    function setWriteBuffer($size)
    {
        if (!is_resource($this->fp)) {
            return $this->raiseError('not connected');
        }

        $returned = stream_set_write_buffer($this->fp, $size);
        if ($returned == 0) {
            return true;
        }
        return $this->raiseError('Cannot set write buffer.');
    }

    /**
     * Returns information about an existing socket resource.
     * Currently returns four entries in the result array:
     *
     * <p>
     * timed_out (bool) - The socket timed out waiting for data<br>
     * blocked (bool) - The socket was blocked<br>
     * eof (bool) - Indicates EOF event<br>
     * unread_bytes (int) - Number of bytes left in the socket buffer<br>
     * </p>
     *
     * @access public
     * @return mixed Array containing information about existing socket
     *               resource or a PEAR_Error instance otherwise
     */
    function getStatus()
    {
        if (!is_resource($this->fp)) {
            return $this->raiseError('not connected');
        }

        return stream_get_meta_data($this->fp);
    }

    /**
     * Get a specified line of data
     *
     * @param int $size Reading ends when size - 1 bytes have been read,
     *                  or a newline or an EOF (whichever comes first).
     *                  If no size is specified, it will keep reading from
     *                  the stream until it reaches the end of the line.
     *
     * @access public
     * @return mixed $size bytes of data from the socket, or a PEAR_Error if
     *         not connected. If an error occurs, FALSE is returned.
     */
    function gets($size = null)
    {
        if (!is_resource($this->fp)) {
            return $this->raiseError('not connected');
        }

        if (is_null($size)) {
            return @fgets($this->fp);
        } else {
            return @fgets($this->fp, $size);
        }
    }

    /**
     * Read a specified amount of data. This is guaranteed to return,
     * and has the added benefit of getting everything in one fread()
     * chunk; if you know the size of the data you're getting
     * beforehand, this is definitely the way to go.
     *
     * @param integer $size The number of bytes to read from the socket.
     *
     * @access public
     * @return $size bytes of data from the socket, or a PEAR_Error if
     *         not connected.
     */
    function read($size)
    {
        if (!is_resource($this->fp)) {
            return $this->raiseError('not connected');
        }

        return @fread($this->fp, $size);
    }

    /**
     * Write a specified amount of data.
     *
     * @param string  $data      Data to write.
     * @param integer $blocksize Amount of data to write at once.
     *                           NULL means all at once.
     *
     * @access public
     * @return mixed If the socket is not connected, returns an instance of
     *               PEAR_Error.
     *               If the write succeeds, returns the number of bytes written.
     *               If the write fails, returns false.
     *               If the socket times out, returns an instance of PEAR_Error.
     */
    function write($data, $blocksize = null)
    {
        if (!is_resource($this->fp)) {
            return $this->raiseError('not connected');
        }

        if (is_null($blocksize) && !OS_WINDOWS) {
            $written = @fwrite($this->fp, $data);

            // Check for timeout or lost connection
            if ($written===false) {
                $meta_data = $this->getStatus();

                if (!is_array($meta_data)) {
                    return $meta_data; // PEAR_Error
                }

                if (!empty($meta_data['timed_out'])) {
                    return $this->raiseError('timed out');
                }
            }

            return $written;
        } else {
            if (is_null($blocksize)) {
                $blocksize = 1024;
            }

            $pos  = 0;
            $size = strlen($data);
            while ($pos < $size) {
                $written = @fwrite($this->fp, substr($data, $pos, $blocksize));

                // Check for timeout or lost connection
                if ($written===false) {
                    $meta_data = $this->getStatus();

                    if (!is_array($meta_data)) {
                        return $meta_data; // PEAR_Error
                    }

                    if (!empty($meta_data['timed_out'])) {
                        return $this->raiseError('timed out');
                    }

                    return $written;
                }

                $pos += $written;
            }

            return $pos;
        }
    }

    /**
     * Write a line of data to the socket, followed by a trailing newline.
     *
     * @param string $data Data to write
     *
     * @access public
     * @return mixed fwrite() result, or PEAR_Error when not connected
     */
    function writeLine($data)
    {
        if (!is_resource($this->fp)) {
            return $this->raiseError('not connected');
        }

        return fwrite($this->fp, $data . $this->newline);
    }

    /**
     * Tests for end-of-file on a socket descriptor.
     *
     * Also returns true if the socket is disconnected.
     *
     * @access public
     * @return bool
     */
    function eof()
    {
        return (!is_resource($this->fp) || feof($this->fp));
    }

    /**
     * Reads a byte of data
     *
     * @access public
     * @return 1 byte of data from the socket, or a PEAR_Error if
     *         not connected.
     */
    function readByte()
    {
        if (!is_resource($this->fp)) {
            return $this->raiseError('not connected');
        }

        return ord(@fread($this->fp, 1));
    }

    /**
     * Reads a word of data
     *
     * @access public
     * @return 1 word of data from the socket, or a PEAR_Error if
     *         not connected.
     */
    function readWord()
    {
        if (!is_resource($this->fp)) {
            return $this->raiseError('not connected');
        }

        $buf = @fread($this->fp, 2);
        return (ord($buf[0]) + (ord($buf[1]) << 8));
    }

    /**
     * Reads an int of data
     *
     * @access public
     * @return integer  1 int of data from the socket, or a PEAR_Error if
     *                  not connected.
     */
    function readInt()
    {
        if (!is_resource($this->fp)) {
            return $this->raiseError('not connected');
        }

        $buf = @fread($this->fp, 4);
        return (ord($buf[0]) + (ord($buf[1]) << 8) +
                (ord($buf[2]) << 16) + (ord($buf[3]) << 24));
    }

    /**
     * Reads a zero-terminated string of data
     *
     * @access public
     * @return string, or a PEAR_Error if
     *         not connected.
     */
    function readString()
    {
        if (!is_resource($this->fp)) {
            return $this->raiseError('not connected');
        }

        $string = '';
        while (($char = @fread($this->fp, 1)) != "\x00") {
            $string .= $char;
        }
        return $string;
    }

    /**
     * Reads an IP Address and returns it in a dot formatted string
     *
     * @access public
     * @return Dot formatted string, or a PEAR_Error if
     *         not connected.
     */
    function readIPAddress()
    {
        if (!is_resource($this->fp)) {
            return $this->raiseError('not connected');
        }

        $buf = @fread($this->fp, 4);
        return sprintf('%d.%d.%d.%d', ord($buf[0]), ord($buf[1]),
                       ord($buf[2]), ord($buf[3]));
    }

    /**
     * Read until either the end of the socket or a newline, whichever
     * comes first. Strips the trailing newline from the returned data.
     *
     * @access public
     * @return All available data up to a newline, without that
     *         newline, or until the end of the socket, or a PEAR_Error if
     *         not connected.
     */
    function readLine()
    {
        if (!is_resource($this->fp)) {
            return $this->raiseError('not connected');
        }

        $line = '';

        $timeout = time() + $this->timeout;

        while (!feof($this->fp) && (!$this->timeout || time() < $timeout)) {
            $line .= @fgets($this->fp, $this->lineLength);
            if (substr($line, -1) == "\n") {
                return rtrim($line, $this->newline);
            }
        }
        return $line;
    }

    /**
     * Read until the socket closes, or until there is no more data in
     * the inner PHP buffer. If the inner buffer is empty, in blocking
     * mode we wait for at least 1 byte of data. Therefore, in
     * blocking mode, if there is no data at all to be read, this
     * function will never exit (unless the socket is closed on the
     * remote end).
     *
     * @access public
     *
     * @return string  All data until the socket closes, or a PEAR_Error if
     *                 not connected.
     */
    function readAll()
    {
        if (!is_resource($this->fp)) {
            return $this->raiseError('not connected');
        }

        $data = '';
        while (!feof($this->fp)) {
            $data .= @fread($this->fp, $this->lineLength);
        }
        return $data;
    }

    /**
     * Runs the equivalent of the select() system call on the socket
     * with a timeout specified by tv_sec and tv_usec.
     *
     * @param integer $state   Which of read/write/error to check for.
     * @param integer $tv_sec  Number of seconds for timeout.
     * @param integer $tv_usec Number of microseconds for timeout.
     *
     * @access public
     * @return False if select fails, integer describing which of read/write/error
     *         are ready, or PEAR_Error if not connected.
     */
    function select($state, $tv_sec, $tv_usec = 0)
    {
        if (!is_resource($this->fp)) {
            return $this->raiseError('not connected');
        }

        $read   = null;
        $write  = null;
        $except = null;
        if ($state & NET_SOCKET_READ) {
            $read[] = $this->fp;
        }
        if ($state & NET_SOCKET_WRITE) {
            $write[] = $this->fp;
        }
        if ($state & NET_SOCKET_ERROR) {
            $except[] = $this->fp;
        }
        if (false === ($sr = stream_select($read, $write, $except,
                                          $tv_sec, $tv_usec))) {
            return false;
        }

        $result = 0;
        if (count($read)) {
            $result |= NET_SOCKET_READ;
        }
        if (count($write)) {
            $result |= NET_SOCKET_WRITE;
        }
        if (count($except)) {
            $result |= NET_SOCKET_ERROR;
        }
        return $result;
    }

    /**
     * Turns encryption on/off on a connected socket.
     *
     * @param bool    $enabled Set this parameter to true to enable encryption
     *                         and false to disable encryption.
     * @param integer $type    Type of encryption. See stream_socket_enable_crypto()
     *                         for values.
     *
     * @see    http://se.php.net/manual/en/function.stream-socket-enable-crypto.php
     * @access public
     * @return false on error, true on success and 0 if there isn't enough data
     *         and the user should try again (non-blocking sockets only).
     *         A PEAR_Error object is returned if the socket is not
     *         connected
     */
    function enableCrypto($enabled, $type)
    {
        if (version_compare(phpversion(), "5.1.0", ">=")) {
            if (!is_resource($this->fp)) {
                return $this->raiseError('not connected');
            }
            return @stream_socket_enable_crypto($this->fp, $enabled, $type);
        } else {
            $msg = 'Net_Socket::enableCrypto() requires php version >= 5.1.0';
            return $this->raiseError($msg);
        }
    }

}
PK|c�\��0AC5C5&pear/console_getopt/Console/Getopt.phpnu�[���<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
/**
 * PHP Version 5
 *
 * Copyright (c) 2001-2015, The PEAR developers
 *
 * This source file is subject to the BSD-2-Clause license,
 * that is bundled with this package in the file LICENSE, and is
 * available through the world-wide-web at the following url:
 * http://opensource.org/licenses/bsd-license.php.
 *
 * @category Console
 * @package  Console_Getopt
 * @author   Andrei Zmievski <andrei@php.net>
 * @license  http://opensource.org/licenses/bsd-license.php BSD-2-Clause
 * @version  CVS: $Id$
 * @link     http://pear.php.net/package/Console_Getopt
 */

require_once 'PEAR.php';

/**
 * Command-line options parsing class.
 *
 * @category Console
 * @package  Console_Getopt
 * @author   Andrei Zmievski <andrei@php.net>
 * @license  http://opensource.org/licenses/bsd-license.php BSD-2-Clause
 * @link     http://pear.php.net/package/Console_Getopt
 */
class Console_Getopt
{

    /**
     * Parses the command-line options.
     *
     * The first parameter to this function should be the list of command-line
     * arguments without the leading reference to the running program.
     *
     * The second parameter is a string of allowed short options. Each of the
     * option letters can be followed by a colon ':' to specify that the option
     * requires an argument, or a double colon '::' to specify that the option
     * takes an optional argument.
     *
     * The third argument is an optional array of allowed long options. The
     * leading '--' should not be included in the option name. Options that
     * require an argument should be followed by '=', and options that take an
     * option argument should be followed by '=='.
     *
     * The return value is an array of two elements: the list of parsed
     * options and the list of non-option command-line arguments. Each entry in
     * the list of parsed options is a pair of elements - the first one
     * specifies the option, and the second one specifies the option argument,
     * if there was one.
     *
     * Long and short options can be mixed.
     *
     * Most of the semantics of this function are based on GNU getopt_long().
     *
     * @param array  $args          an array of command-line arguments
     * @param string $short_options specifies the list of allowed short options
     * @param array  $long_options  specifies the list of allowed long options
     * @param boolean $skip_unknown suppresses Console_Getopt: unrecognized option
     *
     * @return array two-element array containing the list of parsed options and
     * the non-option arguments
     */
    public static function getopt2($args, $short_options, $long_options = null, $skip_unknown = false)
    {
        return Console_Getopt::doGetopt(2, $args, $short_options, $long_options, $skip_unknown);
    }

    /**
     * This function expects $args to start with the script name (POSIX-style).
     * Preserved for backwards compatibility.
     *
     * @param array  $args          an array of command-line arguments
     * @param string $short_options specifies the list of allowed short options
     * @param array  $long_options  specifies the list of allowed long options
     *
     * @see getopt2()
     * @return array two-element array containing the list of parsed options and
     * the non-option arguments
     */
    public static function getopt($args, $short_options, $long_options = null, $skip_unknown = false)
    {
        return Console_Getopt::doGetopt(1, $args, $short_options, $long_options, $skip_unknown);
    }

    /**
     * The actual implementation of the argument parsing code.
     *
     * @param int    $version       Version to use
     * @param array  $args          an array of command-line arguments
     * @param string $short_options specifies the list of allowed short options
     * @param array  $long_options  specifies the list of allowed long options
     * @param boolean $skip_unknown suppresses Console_Getopt: unrecognized option
     *
     * @return array
     */
    public static function doGetopt($version, $args, $short_options, $long_options = null, $skip_unknown = false)
    {
        // in case you pass directly readPHPArgv() as the first arg
        if (PEAR::isError($args)) {
            return $args;
        }

        if (empty($args)) {
            return array(array(), array());
        }

        $non_opts = $opts = array();

        settype($args, 'array');

        if ($long_options) {
            sort($long_options);
        }

        /*
         * Preserve backwards compatibility with callers that relied on
         * erroneous POSIX fix.
         */
        if ($version < 2) {
            if (isset($args[0][0]) && $args[0][0] != '-') {
                array_shift($args);
            }
        }

        for ($i = 0; $i < count($args); $i++) {
            $arg = $args[$i];
            /* The special element '--' means explicit end of
               options. Treat the rest of the arguments as non-options
               and end the loop. */
            if ($arg == '--') {
                $non_opts = array_merge($non_opts, array_slice($args, $i + 1));
                break;
            }

            if ($arg[0] != '-' || (strlen($arg) > 1 && $arg[1] == '-' && !$long_options)) {
                $non_opts = array_merge($non_opts, array_slice($args, $i));
                break;
            } elseif (strlen($arg) > 1 && $arg[1] == '-') {
                $error = Console_Getopt::_parseLongOption(substr($arg, 2),
                                                          $long_options,
                                                          $opts,
                                                          $i,
                                                          $args,
                                                          $skip_unknown);
                if (PEAR::isError($error)) {
                    return $error;
                }
            } elseif ($arg == '-') {
                // - is stdin
                $non_opts = array_merge($non_opts, array_slice($args, $i));
                break;
            } else {
                $error = Console_Getopt::_parseShortOption(substr($arg, 1),
                                                           $short_options,
                                                           $opts,
                                                           $i,
                                                           $args,
                                                           $skip_unknown);
                if (PEAR::isError($error)) {
                    return $error;
                }
            }
        }

        return array($opts, $non_opts);
    }

    /**
     * Parse short option
     *
     * @param string     $arg           Argument
     * @param string[]   $short_options Available short options
     * @param string[][] &$opts
     * @param int        &$argIdx
     * @param string[]   $args
     * @param boolean    $skip_unknown suppresses Console_Getopt: unrecognized option
     *
     * @return void
     */
    protected static function _parseShortOption($arg, $short_options, &$opts, &$argIdx, $args, $skip_unknown)
    {
        for ($i = 0; $i < strlen($arg); $i++) {
            $opt     = $arg[$i];
            $opt_arg = null;

            /* Try to find the short option in the specifier string. */
            if (($spec = strstr($short_options, $opt)) === false || $arg[$i] == ':') {
                if ($skip_unknown === true) {
                    break;
                }

                $msg = "Console_Getopt: unrecognized option -- $opt";
                return PEAR::raiseError($msg);
            }

            if (strlen($spec) > 1 && $spec[1] == ':') {
                if (strlen($spec) > 2 && $spec[2] == ':') {
                    if ($i + 1 < strlen($arg)) {
                        /* Option takes an optional argument. Use the remainder of
                           the arg string if there is anything left. */
                        $opts[] = array($opt, substr($arg, $i + 1));
                        break;
                    }
                } else {
                    /* Option requires an argument. Use the remainder of the arg
                       string if there is anything left. */
                    if ($i + 1 < strlen($arg)) {
                        $opts[] = array($opt,  substr($arg, $i + 1));
                        break;
                    } else if (isset($args[++$argIdx])) {
                        $opt_arg = $args[$argIdx];
                        /* Else use the next argument. */;
                        if (Console_Getopt::_isShortOpt($opt_arg)
                            || Console_Getopt::_isLongOpt($opt_arg)) {
                            $msg = "option requires an argument --$opt";
                            return PEAR::raiseError("Console_Getopt: " . $msg);
                        }
                    } else {
                        $msg = "option requires an argument --$opt";
                        return PEAR::raiseError("Console_Getopt: " . $msg);
                    }
                }
            }

            $opts[] = array($opt, $opt_arg);
        }
    }

    /**
     * Checks if an argument is a short option
     *
     * @param string $arg Argument to check
     *
     * @return bool
     */
    protected static function _isShortOpt($arg)
    {
        return strlen($arg) == 2 && $arg[0] == '-'
               && preg_match('/[a-zA-Z]/', $arg[1]);
    }

    /**
     * Checks if an argument is a long option
     *
     * @param string $arg Argument to check
     *
     * @return bool
     */
    protected static function _isLongOpt($arg)
    {
        return strlen($arg) > 2 && $arg[0] == '-' && $arg[1] == '-' &&
               preg_match('/[a-zA-Z]+$/', substr($arg, 2));
    }

    /**
     * Parse long option
     *
     * @param string     $arg          Argument
     * @param string[]   $long_options Available long options
     * @param string[][] &$opts
     * @param int        &$argIdx
     * @param string[]   $args
     *
     * @return void|PEAR_Error
     */
    protected static function _parseLongOption($arg, $long_options, &$opts, &$argIdx, $args, $skip_unknown)
    {
        @list($opt, $opt_arg) = explode('=', $arg, 2);

        $opt_len = strlen($opt);

        for ($i = 0; $i < count($long_options); $i++) {
            $long_opt  = $long_options[$i];
            $opt_start = substr($long_opt, 0, $opt_len);

            $long_opt_name = str_replace('=', '', $long_opt);

            /* Option doesn't match. Go on to the next one. */
            if ($long_opt_name != $opt) {
                continue;
            }

            $opt_rest = substr($long_opt, $opt_len);

            /* Check that the options uniquely matches one of the allowed
               options. */
            if ($i + 1 < count($long_options)) {
                $next_option_rest = substr($long_options[$i + 1], $opt_len);
            } else {
                $next_option_rest = '';
            }

            if ($opt_rest != '' && $opt[0] != '=' &&
                $i + 1 < count($long_options) &&
                $opt == substr($long_options[$i+1], 0, $opt_len) &&
                $next_option_rest != '' &&
                $next_option_rest[0] != '=') {

                $msg = "Console_Getopt: option --$opt is ambiguous";
                return PEAR::raiseError($msg);
            }

            if (substr($long_opt, -1) == '=') {
                if (substr($long_opt, -2) != '==') {
                    /* Long option requires an argument.
                       Take the next argument if one wasn't specified. */;
                    if (!strlen($opt_arg)) {
                        if (!isset($args[++$argIdx])) {
                            $msg = "Console_Getopt: option requires an argument --$opt";
                            return PEAR::raiseError($msg);
                        }
                        $opt_arg = $args[$argIdx];
                    }

                    if (Console_Getopt::_isShortOpt($opt_arg)
                        || Console_Getopt::_isLongOpt($opt_arg)) {
                        $msg = "Console_Getopt: option requires an argument --$opt";
                        return PEAR::raiseError($msg);
                    }
                }
            } else if ($opt_arg) {
                $msg = "Console_Getopt: option --$opt doesn't allow an argument";
                return PEAR::raiseError($msg);
            }

            $opts[] = array('--' . $opt, $opt_arg);
            return;
        }

        if ($skip_unknown === true) {
            return;
        }

        return PEAR::raiseError("Console_Getopt: unrecognized option --$opt");
    }

    /**
     * Safely read the $argv PHP array across different PHP configurations.
     * Will take care on register_globals and register_argc_argv ini directives
     *
     * @return mixed the $argv PHP array or PEAR error if not registered
     */
    public static function readPHPArgv()
    {
        global $argv;
        if (!is_array($argv)) {
            if (!@is_array($_SERVER['argv'])) {
                if (!@is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) {
                    $msg = "Could not read cmd args (register_argc_argv=Off?)";
                    return PEAR::raiseError("Console_Getopt: " . $msg);
                }
                return $GLOBALS['HTTP_SERVER_VARS']['argv'];
            }
            return $_SERVER['argv'];
        }
        return $argv;
    }

}
PK|c�\+L�uu!pear/console_getopt/composer.jsonnu�[���{
    "authors": [
        {
            "email": "andrei@php.net",
            "name": "Andrei Zmievski",
            "role": "Lead"
        },
        {
            "email": "stig@php.net",
            "name": "Stig Bakken",
            "role": "Developer"
        },
        {
            "email": "cellog@php.net",
            "name": "Greg Beaver",
            "role": "Helper"
        }
    ],
    "autoload": {
        "psr-0": {
            "Console": "./"
        }
    },
    "description": "More info available on: http://pear.php.net/package/Console_Getopt",
    "include-path": [
        "./"
    ],
    "license": "BSD-2-Clause",
    "name": "pear/console_getopt",
    "support": {
        "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Console_Getopt",
        "source": "https://github.com/pear/Console_Getopt"
    },
    "type": "library"
}
PK|c�\K��Kpear/console_getopt/LICENSEnu�[���Copyright (c) 2001-2015, The PEAR developers
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
PK|c�\ׁ���pear/console_getopt/README.rstnu�[���*******************************************
Console_Getopt - Command-line option parser
*******************************************

This is a PHP implementation of "getopt" supporting both short and long options.
It helps parsing command line options in your PHP script.

Homepage: http://pear.php.net/package/Console_Getopt

.. image:: https://travis-ci.org/pear/Console_Getopt.svg?branch=master
    :target: https://travis-ci.org/pear/Console_Getopt


Alternatives
============

* Console_CommandLine__ (recommended)
* Console_GetoptPlus__

__ http://pear.php.net/package/Console_CommandLine
__ http://pear.php.net/package/Console_GetoptPlus


License
=======
BSD-2-Clause
PK|c�\���S^^pear/console_getopt/package.xmlnu�[���<?xml version="1.0" encoding="UTF-8"?>
<package packagerversion="1.9.2" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0 http://pear.php.net/dtd/tasks-1.0.xsd http://pear.php.net/dtd/package-2.0 http://pear.php.net/dtd/package-2.0.xsd">
 <name>Console_Getopt</name>
 <channel>pear.php.net</channel>
 <summary>Command-line option parser</summary>
 <description>This is a PHP implementation of &quot;getopt&quot; supporting both
short and long options.</description>
 <lead>
  <name>Andrei Zmievski</name>
  <user>andrei</user>
  <email>andrei@php.net</email>
  <active>no</active>
 </lead>
 <developer>
  <name>Stig Bakken</name>
  <user>ssb</user>
  <email>stig@php.net</email>
  <active>no</active>
 </developer>
 <helper>
  <name>Greg Beaver</name>
  <user>cellog</user>
  <email>cellog@php.net</email>
  <active>no</active>
 </helper>

 <date>2019-11-20</date>
 <version>
  <release>1.4.3</release>
  <api>1.4.0</api>
 </version>
 <stability>
  <release>stable</release>
  <api>stable</api>
 </stability>
 <license uri="http://opensource.org/licenses/bsd-license.php">BSD-2-Clause</license>

 <notes>
* PR #4:  Fix PHP 7.4 deprecation: array/string curly braces access
* PR #5:  fix phplint warnings
 </notes>

 <contents>
  <dir name="/">
   <dir name="Console">
    <file name="Getopt.php" role="php" />
   </dir>
   <dir name="tests">
    <file role="test" name="001-getopt.phpt" />
    <file role="test" name="bug10557.phpt" />
    <file role="test" name="bug11068.phpt" />
    <file role="test" name="bug13140.phpt" />
   </dir>
  </dir>
 </contents>

 <compatible>
  <name>PEAR</name>
  <channel>pear.php.net</channel>
  <min>1.4.0</min>
  <max>1.999.999</max>
 </compatible>

 <dependencies>
  <required>
   <php>
    <min>5.4.0</min>
   </php>
   <pearinstaller>
    <min>1.8.0</min>
   </pearinstaller>
  </required>
 </dependencies>

 <phprelease />

 <changelog>

  <release>
    <date>2019-11-20</date>
    <version>
     <release>1.4.3</release>
     <api>1.4.0</api>
    </version>
    <stability>
     <release>stable</release>
     <api>stable</api>
    </stability>
    <license uri="http://opensource.org/licenses/bsd-license.php">BSD-2-Clause</license>
    <notes>
    * PR #4:  Fix PHP 7.4 deprecation: array/string curly braces access
    * PR #5:  fix phplint warnings
    </notes>
  </release>

  <release>
    <date>2019-02-06</date>
    <version>
     <release>1.4.2</release>
     <api>1.4.0</api>
    </version>
    <stability>
     <release>stable</release>
     <api>stable</api>
    </stability>
    <license uri="http://opensource.org/licenses/bsd-license.php">BSD-2-Clause</license>
    <notes>
    * Remove use of each(), which is removed in PHP 8
    </notes>
  </release>

  <release>
    <date>2015-07-20</date>
    <version>
     <release>1.4.1</release>
     <api>1.4.0</api>
    </version>
    <stability>
     <release>stable</release>
     <api>stable</api>
    </stability>
    <license uri="http://opensource.org/licenses/bsd-license.php">BSD-2-Clause</license>
    <notes>
     * Fix unit test on PHP 7 [cweiske]
    </notes>
  </release>

  <release>
    <date>2015-02-22</date>
    <version>
     <release>1.4.0</release>
     <api>1.4.0</api>
    </version>
    <stability>
     <release>stable</release>
     <api>stable</api>
    </stability>
    <license uri="http://opensource.org/licenses/bsd-license.php">BSD-2-Clause</license>
    <notes>
     * Change license to BSD-2-Clause
     * Set minimum PHP version to 5.4.0
     * Mark static methods with "static" keyword
    </notes>
  </release>

  <release>
    <date>2011-03-07</date>
    <version>
     <release>1.3.1</release>
     <api>1.3.0</api>
    </version>
    <stability>
     <release>stable</release>
     <api>stable</api>
    </stability>
    <license uri="http://www.php.net/license">PHP License</license>
    <notes>
     * Change the minimum PEAR installer dep to be lower
    </notes>
  </release>

  <release>
    <date>2010-12-11</date>
    <time>20:20:13</time>
    <version>
     <release>1.3.0</release>
     <api>1.3.0</api>
    </version>
    <stability>
     <release>stable</release>
     <api>stable</api>
    </stability>
    <license uri="http://www.php.net/license">PHP License</license>
    <notes>
   * Implement Request #13140: [PATCH] to skip unknown parameters. [patch by rquadling, improved on by dufuz]
    </notes>
  </release>

  <release>
   <date>2007-06-12</date>
   <version>
    <release>1.2.3</release>
    <api>1.2.1</api>
   </version>
   <stability>
    <release>stable</release>
    <api>stable</api>
   </stability>
   <license uri="http://www.php.net/license">PHP License</license>
   <notes>
* fix Bug #11068: No way to read plain &quot;-&quot; option [cardoe]
   </notes>
  </release>
  <release>
   <version>
    <release>1.2.2</release>
    <api>1.2.1</api>
   </version>
   <stability>
    <release>stable</release>
    <api>stable</api>
   </stability>
   <date>2007-02-17</date>
   <license uri="http://www.php.net/license">PHP License</license>
   <notes>
* fix Bug #4475: An ambiguous error occurred when specifying similar longoption name.
* fix Bug #10055: Not failing properly on short options missing required values
   </notes>
  </release>
  <release>
   <version>
    <release>1.2.1</release>
    <api>1.2.1</api>
   </version>
   <stability>
    <release>stable</release>
    <api>stable</api>
   </stability>
   <date>2006-12-08</date>
   <license uri="http://www.php.net/license">PHP License</license>
   <notes>
Fixed bugs #4448 (Long parameter values truncated with longoption parameter) and #7444 (Trailing spaces after php closing tag)
   </notes>
  </release>
  <release>
   <version>
    <release>1.2</release>
    <api>1.2</api>
   </version>
   <stability>
    <release>stable</release>
    <api>stable</api>
   </stability>
   <date>2003-12-11</date>
   <license uri="http://www.php.net/license">PHP License</license>
   <notes>
Fix to preserve BC with 1.0 and allow correct behaviour for new users
   </notes>
  </release>
  <release>
   <version>
    <release>1.0</release>
    <api>1.0</api>
   </version>
   <stability>
    <release>stable</release>
    <api>stable</api>
   </stability>
   <date>2002-09-13</date>
   <license uri="http://www.php.net/license">PHP License</license>
   <notes>
Stable release
   </notes>
  </release>
  <release>
   <version>
    <release>0.11</release>
    <api>0.11</api>
   </version>
   <stability>
    <release>beta</release>
    <api>beta</api>
   </stability>
   <date>2002-05-26</date>
   <license uri="http://www.php.net/license">PHP License</license>
   <notes>
POSIX getopt compatibility fix: treat first element of args
        array as command name
   </notes>
  </release>
  <release>
   <version>
    <release>0.10</release>
    <api>0.10</api>
   </version>
   <stability>
    <release>beta</release>
    <api>beta</api>
   </stability>
   <date>2002-05-12</date>
   <license uri="http://www.php.net/license">PHP License</license>
   <notes>
Packaging fix
   </notes>
  </release>
  <release>
   <version>
    <release>0.9</release>
    <api>0.9</api>
   </version>
   <stability>
    <release>beta</release>
    <api>beta</api>
   </stability>
   <date>2002-05-12</date>
   <license uri="http://www.php.net/license">PHP License</license>
   <notes>
Initial release
   </notes>
  </release>
 </changelog>
</package>
PK|c�\DoPnnpear/mail_mime/composer.jsonnu�[���{
    "authors": [
        {
            "email": "cipri@php.net",
            "name": "Cipriano Groenendal",
            "role": "Lead"
        },
        {
            "email": "alec@php.net",
            "name": "Aleksander Machniak",
            "role": "Lead"
        }
    ],
    "autoload": {
        "psr-0": {
            "Mail": "./"
        }
    },
    "description": "Mail_Mime provides classes to create MIME messages",
    "homepage": "http://pear.php.net/package/Mail_Mime",
    "include-path": [
        "./"
    ],
    "license": "BSD-3-Clause",
    "name": "pear/mail_mime",
    "support": {
        "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Mail_Mime",
        "source": "https://github.com/pear/Mail_Mime"
    },
    "type": "library",
    "require": {
        "php": ">=5.2.0",
        "pear/pear-core-minimal": "*"
    }
}
PK|c�\��Zjpear/mail_mime/READMEnu�[���This package is http://pear.php.net/package/Mail_Mime and has been migrated from http://svn.php.net/repository/pear/packages/Mail_Mime

Please report all new issues via the PEAR bug tracker.

If this package is marked as unmaintained and you have fixes, please submit your pull requests and start discussion on the pear-qa mailing list.

To test, run either
$ phpunit tests/
  or
$ pear run-tests -r

To build, simply
$ pear package

To install from scratch
$ pear install package.xml

To upgrade
$ pear upgrade -f package.xml
PK|c�\Z�8�g�g�pear/mail_mime/Mail/mime.phpnu�[���<?php

/**
 * The Mail_Mime class is used to create MIME E-mail messages
 *
 * The Mail_Mime class provides an OO interface to create MIME
 * enabled email messages. This way you can create emails that
 * contain plain-text bodies, HTML bodies, attachments, inline
 * images and specific headers.
 *
 * Compatible with PHP version 5, 7 and 8
 *
 * LICENSE: This LICENSE is in the BSD license style.
 * Copyright (c) 2002-2003, Richard Heyes <richard@phpguru.org>
 * Copyright (c) 2003-2006, PEAR <pear-group@php.net>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * - Neither the name of the authors, nor the names of its contributors 
 *   may be used to endorse or promote products derived from this 
 *   software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @category  Mail
 * @package   Mail_Mime
 * @author    Richard Heyes  <richard@phpguru.org>
 * @author    Tomas V.V. Cox <cox@idecnet.com>
 * @author    Cipriano Groenendal <cipri@php.net>
 * @author    Sean Coates <sean@php.net>
 * @author    Aleksander Machniak <alec@php.net>
 * @copyright 2003-2006 PEAR <pear-group@php.net>
 * @license   http://www.opensource.org/licenses/bsd-license.php BSD License
 * @version   Release: @package_version@
 * @link      http://pear.php.net/package/Mail_mime
 *
 *            This class is based on HTML Mime Mail class from
 *            Richard Heyes <richard@phpguru.org> which was based also
 *            in the mime_mail.class by Tobias Ratschiller <tobias@dnet.it>
 *            and Sascha Schumann <sascha@schumann.cx>
 */


require_once 'PEAR.php';
require_once 'Mail/mimePart.php';


/**
 * The Mail_Mime class provides an OO interface to create MIME
 * enabled email messages. This way you can create emails that
 * contain plain-text bodies, HTML bodies, attachments, inline
 * images and specific headers.
 *
 * @category  Mail
 * @package   Mail_Mime
 * @author    Richard Heyes  <richard@phpguru.org>
 * @author    Tomas V.V. Cox <cox@idecnet.com>
 * @author    Cipriano Groenendal <cipri@php.net>
 * @author    Sean Coates <sean@php.net>
 * @copyright 2003-2006 PEAR <pear-group@php.net>
 * @license   http://www.opensource.org/licenses/bsd-license.php BSD License
 * @version   Release: @package_version@
 * @link      http://pear.php.net/package/Mail_mime
 */
class Mail_mime
{
    /**
     * Contains the plain text part of the email
     *
     * @var string
     */
    protected $txtbody = '';

    /**
     * Contains the html part of the email
     *
     * @var string
     */
    protected $htmlbody = '';

    /**
     * Contains the text/calendar part of the email
     *
     * @var string
     */
    protected $calbody = '';

    /**
     * List of the attached images
     *
     * @var array
     */
    protected $html_images = array();

    /**
     * List of the attachements
     *
     * @var array
     */
    protected $parts = array();

    /**
     * Headers for the mail
     *
     * @var array
     */
    protected $headers = array();

    /**
     * Build parameters
     *
     * @var array
     */
    protected $build_params = array(
        // What encoding to use for the headers
        // Options: quoted-printable or base64
        'head_encoding' => 'quoted-printable',
        // What encoding to use for plain text
        // Options: 7bit, 8bit, base64, or quoted-printable
        'text_encoding' => 'quoted-printable',
        // What encoding to use for html
        // Options: 7bit, 8bit, base64, or quoted-printable
        'html_encoding' => 'quoted-printable',
        // What encoding to use for calendar part
        // Options: 7bit, 8bit, base64, or quoted-printable
        'calendar_encoding' => 'quoted-printable',
        // The character set to use for html
        'html_charset'  => 'ISO-8859-1',
        // The character set to use for text
        'text_charset'  => 'ISO-8859-1',
        // The character set to use for calendar part
        'calendar_charset'  => 'UTF-8',
        // The character set to use for headers
        'head_charset'  => 'ISO-8859-1',
        // End-of-line sequence
        'eol'           => "\r\n",
        // Delay attachment files IO until building the message
        'delay_file_io' => false,
        // Default calendar method
        'calendar_method' => 'request',
        // multipart part preamble (RFC2046 5.1.1)
        'preamble' => '',
    );


    /**
     * Constructor function
     *
     * @param mixed $params Build parameters that change the way the email
     *                      is built. Should be an associative array.
     *                      See $_build_params.
     *
     * @return void
     */
    public function __construct($params = array())
    {
        // Backward-compatible EOL setting
        if (is_string($params)) {
            $this->build_params['eol'] = $params;
        } else if (defined('MAIL_MIME_CRLF') && !isset($params['eol'])) {
            $this->build_params['eol'] = MAIL_MIME_CRLF;
        }

        // Update build parameters
        if (!empty($params) && is_array($params)) {
            $this->build_params = array_merge($this->build_params, $params);
        }
    }

    /**
     * Set build parameter value
     *
     * @param string $name  Parameter name
     * @param string $value Parameter value
     *
     * @return void
     * @since  1.6.0
     */
    public function setParam($name, $value)
    {
        $this->build_params[$name] = $value;
    }

    /**
     * Get build parameter value
     *
     * @param string $name Parameter name
     *
     * @return mixed Parameter value
     * @since  1.6.0
     */
    public function getParam($name)
    {
        return isset($this->build_params[$name]) ? $this->build_params[$name] : null;
    }

    /**
     * Accessor function to set the body text. Body text is used if
     * it's not an html mail being sent or else is used to fill the
     * text/plain part that emails clients who don't support
     * html should show.
     *
     * @param string $data   Either a string or the file name with the contents
     * @param bool   $isfile If true the first param should be treated
     *                       as a file name, else as a string (default)
     * @param bool   $append If true the text or file is appended to
     *                       the existing body, else the old body is
     *                       overwritten
     *
     * @return mixed True on success or PEAR_Error object
     */
    public function setTXTBody($data, $isfile = false, $append = false)
    {
        return $this->setBody('txtbody', $data, $isfile, $append);
    }

    /**
     * Get message text body
     *
     * @return string Text body
     * @since  1.6.0
     */
    public function getTXTBody()
    {
        return $this->txtbody;
    }

    /**
     * Adds a html part to the mail.
     *
     * @param string $data   Either a string or the file name with the contents
     * @param bool   $isfile A flag that determines whether $data is a
     *                       filename, or a string(false, default)
     *
     * @return bool True on success or PEAR_Error object
     */
    public function setHTMLBody($data, $isfile = false)
    {
        return $this->setBody('htmlbody', $data, $isfile);
    }

    /**
     * Get message HTML body
     *
     * @return string HTML body
     * @since  1.6.0
     */
    public function getHTMLBody()
    {
        return $this->htmlbody;
    }

    /**
     * Function to set a body of text/calendar part (not attachment)
     *
     * @param string $data     Either a string or the file name with the contents
     * @param bool   $isfile   If true the first param should be treated
     *                         as a file name, else as a string (default)
     * @param bool   $append   If true the text or file is appended to
     *                         the existing body, else the old body is
     *                         overwritten
     * @param string $method   iCalendar object method
     * @param string $charset  iCalendar character set
     * @param string $encoding Transfer encoding
     *
     * @return mixed True on success or PEAR_Error object
     * @since  1.9.0
     */
    public function setCalendarBody($data, $isfile = false, $append = false,
        $method = 'request', $charset = 'UTF-8', $encoding = 'quoted-printable'
    ) {
        $result = $this->setBody('calbody', $data, $isfile, $append);

        if ($result === true) {
            $this->build_params['calendar_method']  = $method;
            $this->build_params['calendar_charset'] = $charset;
            $this->build_params['calendar_encoding'] = $encoding;
        }
    }

    /**
     * Get body of calendar part
     *
     * @return string Calendar part body
     * @since  1.9.0
     */
    public function getCalendarBody()
    {
        return $this->calbody;
    }

    /**
     * Adds an image to the list of embedded images.
     * Images added this way will be added as related parts of the HTML message.
     *
     * To correctly match the HTML image with the related attachment
     * HTML should refer to it by a filename (specified in $file or $name
     * arguments) or by cid:<content-id> (specified in $content_id arg).
     *
     * @param string $file       The image file name OR image data itself
     * @param string $c_type     The content type
     * @param string $name       The filename of the image. Used to find
     *                           the image in HTML content.
     * @param bool   $isfile     Whether $file is a filename or not.
     *                           Defaults to true
     * @param string $content_id Desired Content-ID of MIME part
     *                           Defaults to generated unique ID
     *
     * @return bool True on success
     */
    public function addHTMLImage($file,
        $c_type = 'application/octet-stream',
        $name = '',
        $isfile = true,
        $content_id = null
    ) {
        $bodyfile = null;

        if ($isfile) {
            // Don't load file into memory
            if ($this->build_params['delay_file_io']) {
                $filedata = null;
                $bodyfile = $file;
            } else {
                if (self::isError($filedata = $this->file2str($file))) {
                    return $filedata;
                }
            }

            $filename = $name ? $name : $file;
        } else {
            $filedata = $file;
            $filename = $name;
        }

        if (!$content_id) {
            $content_id = preg_replace('/[^0-9a-zA-Z]/', '', uniqid(time(), true));
        }

        $this->html_images[] = array(
            'body'      => $filedata,
            'body_file' => $bodyfile,
            'name'      => $filename,
            'c_type'    => $c_type,
            'cid'       => $content_id
        );

        return true;
    }

    /**
     * Adds a file to the list of attachments.
     *
     * @param mixed  $file        The file name or the file contents itself,
     *                            it can be also Mail_mimePart object
     * @param string $c_type      The content type
     * @param string $name        The filename of the attachment
     *                            Only use if $file is the contents
     * @param bool   $isfile      Whether $file is a filename or not. Defaults to true
     * @param string $encoding    The type of encoding to use. Defaults to base64.
     *                            Possible values: 7bit, 8bit, base64 or quoted-printable.
     * @param string $disposition The content-disposition of this file
     *                            Defaults to attachment.
     *                            Possible values: attachment, inline.
     * @param string $charset     The character set of attachment's content.
     * @param string $language    The language of the attachment
     * @param string $location    The RFC 2557.4 location of the attachment
     * @param string $n_encoding  Encoding of the attachment's name in Content-Type
     *                            By default filenames are encoded using RFC2231 method
     *                            Here you can set RFC2047 encoding (quoted-printable
     *                            or base64) instead
     * @param string $f_encoding  Encoding of the attachment's filename
     *                            in Content-Disposition header.
     * @param string $description Content-Description header
     * @param string $h_charset   The character set of the headers e.g. filename
     *                            If not specified, $charset will be used
     * @param array  $add_headers Additional part headers. Array keys can be in form
     *                            of <header_name>:<parameter_name>
     *
     * @return mixed True on success or PEAR_Error object
     */
    public function addAttachment($file,
        $c_type      = 'application/octet-stream',
        $name        = '',
        $isfile      = true,
        $encoding    = 'base64',
        $disposition = 'attachment',
        $charset     = '',
        $language    = '',
        $location    = '',
        $n_encoding  = null,
        $f_encoding  = null,
        $description = '',
        $h_charset   = null,
        $add_headers = array()
    ) {
        if ($file instanceof Mail_mimePart) {
            $this->parts[] = $file;
            return true;
        }

        $bodyfile = null;

        if ($isfile) {
            // Don't load file into memory
            if ($this->build_params['delay_file_io']) {
                $filedata = null;
                $bodyfile = $file;
            } else {
                if (self::isError($filedata = $this->file2str($file))) {
                    return $filedata;
                }
            }
            // Force the name the user supplied, otherwise use $file
            $filename = ($name ? $name : $this->basename($file));
        } else {
            $filedata = $file;
            $filename = $name;
        }

        if (!strlen($filename)) {
            $msg = "The supplied filename for the attachment can't be empty";
            return self::raiseError($msg);
        }

        $this->parts[] = array(
            'body'        => $filedata,
            'body_file'   => $bodyfile,
            'name'        => $filename,
            'c_type'      => $c_type,
            'charset'     => $charset,
            'encoding'    => $encoding,
            'language'    => $language,
            'location'    => $location,
            'disposition' => $disposition,
            'description' => $description,
            'add_headers' => $add_headers,
            'name_encoding'     => $n_encoding,
            'filename_encoding' => $f_encoding,
            'headers_charset'   => $h_charset,
        );

        return true;
    }

    /**
     * Checks if the current message has many parts
     *
     * @return bool True if the message has many parts, False otherwise.
     * @since  1.9.0
     */
    public function isMultipart()
    {
        return count($this->parts) > 0 || count($this->html_images) > 0
            || (strlen($this->htmlbody) > 0 && strlen($this->txtbody) > 0);
    }

    /**
     * Get the contents of the given file name as string
     *
     * @param string $file_name Path of file to process
     *
     * @return string Contents of $file_name
     */
    protected function file2str($file_name)
    {
        // Check state of file and raise an error properly
        if (!is_string($file_name)) {
            return self::raiseError('Invalid or empty file name');
        }
        if (!file_exists($file_name)) {
            return self::raiseError('File not found: ' . $file_name);
        }
        if (!is_file($file_name)) {
            return self::raiseError('Not a regular file: ' . $file_name);
        }
        if (!is_readable($file_name)) {
            return self::raiseError('File is not readable: ' . $file_name);
        }

        // Temporarily reset magic_quotes_runtime and read file contents
        if (version_compare(PHP_VERSION, '5.4.0', '<')) {
            $magic_quotes = @ini_set('magic_quotes_runtime', 0);
        }

        $cont = file_get_contents($file_name);

        if (isset($magic_quotes)) {
            @ini_set('magic_quotes_runtime', $magic_quotes);
        }

        return $cont;
    }

    /**
     * Adds a text subpart to the mimePart object and
     * returns it during the build process.
     *
     * @param mixed $obj The object to add the part to, or
     *                   anything else if a new object is to be created.
     *
     * @return Mail_mimePart The text mimePart object
     */
    protected function addTextPart($obj = null)
    {
        return $this->addBodyPart($obj, $this->txtbody, 'text/plain', 'text');
    }

    /**
     * Adds a html subpart to the mimePart object and
     * returns it during the build process.
     *
     * @param mixed $obj The object to add the part to, or
     *                   anything else if a new object is to be created.
     *
     * @return Mail_mimePart The html mimePart object
     */
    protected function addHtmlPart($obj = null)
    {
        return $this->addBodyPart($obj, $this->htmlbody, 'text/html', 'html');
    }

    /**
     * Adds a calendar subpart to the mimePart object and
     * returns it during the build process.
     *
     * @param mixed $obj The object to add the part to, or
     *                   anything else if a new object is to be created.
     *
     * @return Mail_mimePart The text mimePart object
     */
    protected function addCalendarPart($obj = null)
    {
        $ctype = 'text/calendar; method='. $this->build_params['calendar_method'];

        return $this->addBodyPart($obj, $this->calbody, $ctype, 'calendar');
    }

    /**
     * Creates a new mimePart object, using multipart/mixed as
     * the initial content-type and returns it during the
     * build process.
     *
     * @param array $params Additional part parameters
     *
     * @return Mail_mimePart The multipart/mixed mimePart object
     */
    protected function addMixedPart($params = array())
    {
        $params['content_type'] = 'multipart/mixed';
        $params['eol']          = $this->build_params['eol'];

        // Create empty multipart/mixed Mail_mimePart object to return
        return new Mail_mimePart('', $params);
    }

    /**
     * Adds a multipart/alternative part to a mimePart
     * object (or creates one), and returns it during
     * the build process.
     *
     * @param mixed $obj The object to add the part to, or
     *                   anything else if a new object is to be created.
     *
     * @return Mail_mimePart The multipart/mixed mimePart object
     */
    protected function addAlternativePart($obj = null)
    {
        $params['content_type'] = 'multipart/alternative';
        $params['eol']          = $this->build_params['eol'];

        if (is_object($obj)) {
            $ret = $obj->addSubpart('', $params);
        } else {
            $ret = new Mail_mimePart('', $params);
        }

        return $ret;
    }

    /**
     * Adds a multipart/related part to a mimePart
     * object (or creates one), and returns it during
     * the build process.
     *
     * @param mixed $obj The object to add the part to, or
     *                   anything else if a new object is to be created
     *
     * @return Mail_mimePart The multipart/mixed mimePart object
     */
    protected function addRelatedPart($obj = null)
    {
        $params['content_type'] = 'multipart/related';
        $params['eol']          = $this->build_params['eol'];

        if (is_object($obj)) {
            $ret = $obj->addSubpart('', $params);
        } else {
            $ret = new Mail_mimePart('', $params);
        }

        return $ret;
    }

    /**
     * Adds an html image subpart to a mimePart object
     * and returns it during the build process.
     *
     * @param object $obj   The mimePart to add the image to
     * @param array  $value The image information
     *
     * @return Mail_mimePart The image mimePart object
     */
    protected function addHtmlImagePart($obj, $value)
    {
        $params['content_type'] = $value['c_type'];
        $params['encoding']     = 'base64';
        $params['disposition']  = 'inline';
        $params['filename']     = $value['name'];
        $params['cid']          = $value['cid'];
        $params['body_file']    = $value['body_file'];
        $params['eol']          = $this->build_params['eol'];

        if (!empty($value['name_encoding'])) {
            $params['name_encoding'] = $value['name_encoding'];
        }
        if (!empty($value['filename_encoding'])) {
            $params['filename_encoding'] = $value['filename_encoding'];
        }

        return $obj->addSubpart($value['body'], $params);
    }

    /**
     * Adds an attachment subpart to a mimePart object
     * and returns it during the build process.
     *
     * @param object $obj   The mimePart to add the image to
     * @param mixed  $value The attachment information array or Mail_mimePart object
     *
     * @return Mail_mimePart The image mimePart object
     */
    protected function addAttachmentPart($obj, $value)
    {
        if ($value instanceof Mail_mimePart) {
            return $obj->addSubpart($value);
        }

        $params['eol']          = $this->build_params['eol'];
        $params['filename']     = $value['name'];
        $params['encoding']     = $value['encoding'];
        $params['content_type'] = $value['c_type'];
        $params['body_file']    = $value['body_file'];
        $params['disposition']  = isset($value['disposition']) ?
                                  $value['disposition'] : 'attachment';

        // content charset
        if (!empty($value['charset'])) {
            $params['charset'] = $value['charset'];
        }
        // headers charset (filename, description)
        if (!empty($value['headers_charset'])) {
            $params['headers_charset'] = $value['headers_charset'];
        }
        if (!empty($value['language'])) {
            $params['language'] = $value['language'];
        }
        if (!empty($value['location'])) {
            $params['location'] = $value['location'];
        }
        if (!empty($value['name_encoding'])) {
            $params['name_encoding'] = $value['name_encoding'];
        }
        if (!empty($value['filename_encoding'])) {
            $params['filename_encoding'] = $value['filename_encoding'];
        }
        if (!empty($value['description'])) {
            $params['description'] = $value['description'];
        }
        if (is_array($value['add_headers'])) {
            $params['headers'] = $value['add_headers'];
        }

        return $obj->addSubpart($value['body'], $params);
    }

    /**
     * Returns the complete e-mail, ready to send using an alternative
     * mail delivery method. Note that only the mailpart that is made
     * with Mail_Mime is created. This means that,
     * YOU WILL HAVE NO TO: HEADERS UNLESS YOU SET IT YOURSELF
     * using the $headers parameter!
     *
     * @param string $separation The separation between these two parts.
     * @param array  $params     The Build parameters passed to the
     *                           get() function. See get() for more info.
     * @param array  $headers    The extra headers that should be passed
     *                           to the headers() method.
     *                           See that function for more info.
     * @param bool   $overwrite  Overwrite the existing headers with new.
     *
     * @return mixed The complete e-mail or PEAR error object
     */
    public function getMessage($separation = null, $params = null, $headers = null,
        $overwrite = false
    ) {
        if ($separation === null) {
            $separation = $this->build_params['eol'];
        }

        $body = $this->get($params);

        if (self::isError($body)) {
            return $body;
        }

        return $this->txtHeaders($headers, $overwrite) . $separation . $body;
    }

    /**
     * Returns the complete e-mail body, ready to send using an alternative
     * mail delivery method.
     *
     * @param array $params The Build parameters passed to the
     *                      get() method. See get() for more info.
     *
     * @return mixed The e-mail body or PEAR error object
     * @since  1.6.0
     */
    public function getMessageBody($params = null)
    {
        return $this->get($params, null, true);
    }

    /**
     * Writes (appends) the complete e-mail into file.
     *
     * @param string $filename  Output file location
     * @param array  $params    The Build parameters passed to the
     *                          get() method. See get() for more info.
     * @param array  $headers   The extra headers that should be passed
     *                          to the headers() function.
     *                          See that function for more info.
     * @param bool   $overwrite Overwrite the existing headers with new.
     *
     * @return mixed True or PEAR error object
     * @since  1.6.0
     */
    public function saveMessage($filename, $params = null, $headers = null, $overwrite = false)
    {
        // Check state of file and raise an error properly
        if (file_exists($filename) && !is_writable($filename)) {
            return self::raiseError('File is not writable: ' . $filename);
        }

        // Temporarily reset magic_quotes_runtime and read file contents
        if (version_compare(PHP_VERSION, '5.4.0', '<')) {
            $magic_quotes = @ini_set('magic_quotes_runtime', 0);
        }

        if (!($fh = fopen($filename, 'ab'))) {
            return self::raiseError('Unable to open file: ' . $filename);
        }

        // Write message headers into file (skipping Content-* headers)
        $head = $this->txtHeaders($headers, $overwrite, true);
        if (fwrite($fh, $head) === false) {
            return self::raiseError('Error writing to file: ' . $filename);
        }

        fclose($fh);

        if (isset($magic_quotes)) {
            @ini_set('magic_quotes_runtime', $magic_quotes);
        }

        // Write the rest of the message into file
        $res = $this->get($params, $filename);

        return $res ? $res : true;
    }

    /**
     * Writes (appends) the complete e-mail body into file or stream.
     *
     * @param mixed $filename Output filename or file pointer where to save
     *                        the message instead of returning it
     * @param array $params   The Build parameters passed to the
     *                        get() method. See get() for more info.
     *
     * @return mixed True or PEAR error object
     * @since  1.6.0
     */
    public function saveMessageBody($filename, $params = null)
    {
        if (!is_resource($filename)) {
            // Check state of file and raise an error properly
            if (!file_exists($filename) || !is_writable($filename)) {
                return self::raiseError('File is not writable: ' . $filename);
            }

            if (!($fh = fopen($filename, 'ab'))) {
                return self::raiseError('Unable to open file: ' . $filename);
            }
        } else {
            $fh = $filename;
        }

        // Temporarily reset magic_quotes_runtime and read file contents
        if (version_compare(PHP_VERSION, '5.4.0', '<')) {
            $magic_quotes = @ini_set('magic_quotes_runtime', 0);
        }

        // Write the rest of the message into file
        $res = $this->get($params, $fh, true);

        if (!is_resource($filename)) {
            fclose($fh);
        }

        if (isset($magic_quotes)) {
            @ini_set('magic_quotes_runtime', $magic_quotes);
        }

        return $res ? $res : true;
    }

    /**
     * Builds the multipart message from the list ($this->parts) and
     * returns the mime content.
     *
     * @param array $params    Build parameters that change the way the email
     *                         is built. Should be associative. See $_build_params.
     * @param mixed $filename  Output filename or file pointer where to save
     *                         the message instead of returning it
     * @param bool  $skip_head True if you want to return/save only the message
     *                         without headers
     *
     * @return mixed The MIME message content string, null or PEAR error object
     */
    public function get($params = null, $filename = null, $skip_head = false)
    {
        if (!empty($params) && is_array($params)) {
            $this->build_params = array_merge($this->build_params, $params);
        }

        if (isset($this->headers['From'])) {
            // Bug #11381: Illegal characters in domain ID
            if (preg_match('#(@[0-9a-zA-Z\-\.]+)#', $this->headers['From'], $matches)) {
                $domainID = $matches[1];
            } else {
                $domainID = '@localhost';
            }

            foreach ($this->html_images as $i => $img) {
                $cid = $this->html_images[$i]['cid'];
                if (!preg_match('#'.preg_quote($domainID).'$#', $cid)) {
                    $this->html_images[$i]['cid'] = $cid . $domainID;
                }
            }
        }

        if (count($this->html_images) && strlen($this->htmlbody) > 0) {
            foreach ($this->html_images as $key => $value) {
                $rval  = preg_quote($value['name'], '#');
                $regex = array(
                    '#(\s)((?i)src|background|href(?-i))\s*=\s*(["\']?)' . $rval . '\3#',
                    '#(?i)url(?-i)\(\s*(["\']?)' . $rval . '\1\s*\)#',
                );

                $rep = array(
                    '\1\2=\3cid:' . $value['cid'] .'\3',
                    'url(\1cid:' . $value['cid'] . '\1)',
                );

                $this->htmlbody = preg_replace($regex, $rep, $this->htmlbody);
                $this->html_images[$key]['name']
                    = $this->basename($this->html_images[$key]['name']);
            }
        }

        $this->checkParams();

        $message = $this->buildBodyPart();

        if (!isset($message)) {
            return null;
        }

        // Use saved boundary
        if (!empty($this->build_params['boundary'])) {
            $boundary = $this->build_params['boundary'];
        } else {
            $boundary = null;
        }

        // Write output to file
        if ($filename) {
            // Append mimePart message headers and body into file
            $headers = $message->encodeToFile($filename, $boundary, $skip_head);
            if (self::isError($headers)) {
                return $headers;
            }
            $this->headers = array_merge($this->headers, $headers);
        } else {
            $output = $message->encode($boundary, $skip_head);
            if (self::isError($output)) {
                return $output;
            }
            $this->headers = array_merge($this->headers, $output['headers']);
        }

        // remember the boundary used, in case we'd handle headers() call later
        if (empty($boundary) && !empty($this->headers['Content-Type'])) {
            if (preg_match('/boundary="([^"]+)/', $this->headers['Content-Type'], $m)) {
                $this->build_params['boundary'] = $m[1];
            }
        }

        return $filename ? null : $output['body'];
    }

    /**
     * Builds the main body MIME part for the email body. It will add a mixed part
     * if attachments are found.  If no attachments are found  it will return an
     * alternative part if several body texts are found (text, html, calendar),
     * or a single part if only one body text is found.
     *
     * @return Mail_mimePart|null The corresponding part for the body or null.
     *
     * @see buildAlternativeParts
     * @see buildHtmlParts
     */
    protected function buildBodyPart()
    {
        $parts_count  = count($this->parts);
        $mixed_params = array('preamble' => $this->build_params['preamble']);
        $message      = null;

        if ($parts_count > 0) {
            $message = $this->addMixedPart($mixed_params);
            $this->buildAlternativeParts($message, null);
            for ($i = 0; $i < $parts_count; $i++) {
                $this->addAttachmentPart($message, $this->parts[$i]);
            }
        } else {
            $message = $this->buildAlternativeParts(null, $mixed_params);
        }

        return $message;
    }

    /**
     * Builds a single text, html, or calendar part only if one of them is found.
     * If two or more parts are found, then an alternative part containing them is built.
     *
     * @param Mail_mimePart|null $parent_part  The parent mime part to add
     *                                         the part or null
     * @param array              $mixed_params The needed params to create the
     *                                         part when no parent_part is
     *                                         received.
     *
     * @return null|Mail_mimePart The main part built inside the method. It will be an
     *                            alternative part or text, html, or calendar part.
     *                            Null if no body texts are found.
     */
    protected function buildAlternativeParts($parent_part, $mixed_params = null)
    {
        $html               = strlen($this->htmlbody) > 0;
        $calendar           = strlen($this->calbody) > 0;
        $has_text           = strlen($this->txtbody) > 0;
        $alternatives_count = $html + $calendar + $has_text;

        if ($alternatives_count > 1) {
            $alt_part = $this->addAlternativePart($parent_part ? $parent_part : $mixed_params);
        } else {
            $alt_part = null;
        }

        $dest_part = $alt_part ? $alt_part : $parent_part;
        $part = null;

        if ($has_text) {
            $part = $this->addTextPart($dest_part);
        }

        if ($html) {
            $part = $this->buildHtmlParts($dest_part);
        }

        if ($calendar) {
            $part = $this->addCalendarPart($dest_part);
        }

        return $dest_part ? $dest_part : $part;
    }

    /**
     * Builds html part as a single part or inside a related part with the html
     * images thar were found.
     *
     * @param Mail_mimePart|null $parent_part The object to add the part to,
     *                                        or anything else if a new object
     *                                        is to be created.
     *
     * @return Mail_mimePart|null The created part or null if no htmlbody found.
     */
    protected function buildHtmlParts($parent_part)
    {
        if (!strlen($this->htmlbody)) {
            return null;
        }

        $count_html_images = count($this->html_images);

        if ($count_html_images > 0) {
            $part = $this->addRelatedPart($parent_part);
            $this->addHtmlPart($part);
        } else {
            $part = $this->addHtmlPart($parent_part);
        }

        for ($i = 0; $i < $count_html_images; $i++) {
            $this->addHtmlImagePart($part, $this->html_images[$i]);
        }

        return $part;
    }

    /**
     * Returns an array with the headers needed to prepend to the email
     * (MIME-Version and Content-Type). Format of argument is:
     * $array['header-name'] = 'header-value';
     *
     * @param array $xtra_headers Assoc array with any extra headers (optional)
     *                            (Don't set Content-Type for multipart messages here!)
     * @param bool  $overwrite    Overwrite already existing headers.
     * @param bool  $skip_content Don't return content headers: Content-Type,
     *                            Content-Disposition and Content-Transfer-Encoding
     *
     * @return array Assoc array with the mime headers
     */
    public function headers($xtra_headers = null, $overwrite = false, $skip_content = false)
    {
        // Add mime version header
        $headers['MIME-Version'] = '1.0';

        if (!empty($xtra_headers)) {
            $headers = array_merge($headers, $xtra_headers);
        }

        if ($overwrite) {
            $this->headers = array_merge($this->headers, $headers);
        } else {
            $this->headers = array_merge($headers, $this->headers);
        }

        // Always reset Content-Type/Content-Transfer-Encoding headers
        // In case the message structure changed in meantime
        unset($this->headers['Content-Type']);
        unset($this->headers['Content-Transfer-Encoding']);
        unset($this->headers['Content-Disposition']);

        $this->headers = array_merge($this->headers, $this->contentHeaders());

        $headers = $this->headers;

        if ($skip_content) {
            unset($headers['Content-Type']);
            unset($headers['Content-Transfer-Encoding']);
            unset($headers['Content-Disposition']);
        } else if (!empty($this->build_params['ctype'])) {
            $headers['Content-Type'] = $this->build_params['ctype'];
        }

        return $this->encodeHeaders($headers);
    }

    /**
     * Get the text version of the headers
     * (usefull if you want to use the PHP mail() function)
     *
     * @param array $xtra_headers Assoc array with any extra headers (optional)
     *                            (Don't set Content-Type for multipart messages here!)
     * @param bool  $overwrite    Overwrite the existing headers with new.
     * @param bool  $skip_content Don't return content headers: Content-Type,
     *                            Content-Disposition and Content-Transfer-Encoding
     *
     * @return string Plain text headers
     */
    public function txtHeaders($xtra_headers = null, $overwrite = false, $skip_content = false)
    {
        $headers = $this->headers($xtra_headers, $overwrite, $skip_content);

        // Place Received: headers at the beginning of the message
        // Spam detectors often flag messages with it after the Subject: as spam
        if (isset($headers['Received'])) {
            $received = $headers['Received'];
            unset($headers['Received']);
            $headers = array('Received' => $received) + $headers;
        }

        $ret = '';
        $eol = $this->build_params['eol'];

        foreach ($headers as $key => $val) {
            if (is_array($val)) {
                foreach ($val as $value) {
                    $ret .= "$key: $value" . $eol;
                }
            } else {
                $ret .= "$key: $val" . $eol;
            }
        }

        return $ret;
    }

    /**
     * Sets message Content-Type header.
     * Use it to build messages with various content-types e.g. miltipart/raport
     * not supported by contentHeaders() function.
     *
     * @param string $type   Type name
     * @param array  $params Hash array of header parameters
     *
     * @return void
     * @since  1.7.0
     */
    public function setContentType($type, $params = array())
    {
        $header = $type;

        $eol = !empty($this->build_params['eol']) ? $this->build_params['eol'] : "\r\n";

        // add parameters
        $token_regexp = '#([^\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E])#';

        if (is_array($params)) {
            foreach ($params as $name => $value) {
                if ($name == 'boundary') {
                    $this->build_params['boundary'] = $value;
                } else if (!preg_match($token_regexp, $value)) {
                    $header .= ";$eol $name=$value";
                } else {
                    $value = addcslashes($value, '\\"');
                    $header .= ";$eol $name=\"$value\"";
                }
            }
        }

        // add required boundary parameter if not defined
        if (stripos($type, 'multipart/') === 0) {
            if (empty($this->build_params['boundary'])) {
                $this->build_params['boundary'] = '=_' . md5(rand() . microtime());
            }

            $header .= ";$eol boundary=\"".$this->build_params['boundary']."\"";
        }

        $this->build_params['ctype'] = $header;
    }

    /**
     * Sets the Subject header
     *
     * @param string $subject String to set the subject to.
     *
     * @return void
     */
    public function setSubject($subject)
    {
        $this->headers['Subject'] = $subject;
    }

    /**
     * Set an email to the From (the sender) header
     *
     * @param string $email The email address to use
     *
     * @return void
     */
    public function setFrom($email)
    {
        $this->headers['From'] = $email;
    }

    /**
     * Add an email to the To header
     * (multiple calls to this method are allowed)
     *
     * @param string $email The email direction to add
     *
     * @return void
     */
    public function addTo($email)
    {
        if (isset($this->headers['To'])) {
            $this->headers['To'] .= ", $email";
        } else {
            $this->headers['To'] = $email;
        }
    }

    /**
     * Add an email to the Cc (carbon copy) header
     * (multiple calls to this method are allowed)
     *
     * @param string $email The email direction to add
     *
     * @return void
     */
    public function addCc($email)
    {
        if (isset($this->headers['Cc'])) {
            $this->headers['Cc'] .= ", $email";
        } else {
            $this->headers['Cc'] = $email;
        }
    }

    /**
     * Add an email to the Bcc (blank carbon copy) header
     * (multiple calls to this method are allowed)
     *
     * @param string $email The email direction to add
     *
     * @return void
     */
    public function addBcc($email)
    {
        if (isset($this->headers['Bcc'])) {
            $this->headers['Bcc'] .= ", $email";
        } else {
            $this->headers['Bcc'] = $email;
        }
    }

    /**
     * Since the PHP send function requires you to specify
     * recipients (To: header) separately from the other
     * headers, the To: header is not properly encoded.
     * To fix this, you can use this public method to encode
     * your recipients before sending to the send function.
     *
     * @param string $recipients A comma-delimited list of recipients
     *
     * @return string Encoded data
     */
    public function encodeRecipients($recipients)
    {
        $input  = array('To' => $recipients);
        $retval = $this->encodeHeaders($input);

        return $retval['To'] ;
    }

    /**
     * Encodes headers as per RFC2047
     *
     * @param array $input  The header data to encode
     * @param array $params Extra build parameters
     *
     * @return array Encoded data
     */
    protected function encodeHeaders($input, $params = array())
    {
        $build_params = $this->build_params;

        if (!empty($params)) {
            $build_params = array_merge($build_params, $params);
        }

        foreach ($input as $hdr_name => $hdr_value) {
            if (is_array($hdr_value)) {
                foreach ($hdr_value as $idx => $value) {
                    $input[$hdr_name][$idx] = $this->encodeHeader(
                        $hdr_name, $value,
                        $build_params['head_charset'], $build_params['head_encoding']
                    );
                }
            } else if ($hdr_value !== null) {
                $input[$hdr_name] = $this->encodeHeader(
                    $hdr_name, $hdr_value,
                    $build_params['head_charset'], $build_params['head_encoding']
                );
            } else {
                unset($input[$hdr_name]);
            }
        }

        return $input;
    }

    /**
     * Encodes a header as per RFC2047
     *
     * @param string $name     The header name
     * @param string $value    The header data to encode
     * @param string $charset  Character set name
     * @param string $encoding Encoding name (base64 or quoted-printable)
     *
     * @return string Encoded header data (without a name)
     * @since  1.5.3
     */
    public function encodeHeader($name, $value, $charset, $encoding)
    {
        return Mail_mimePart::encodeHeader(
            $name, $value, $charset, $encoding, $this->build_params['eol']
        );
    }

    /**
     * Get file's basename (locale independent)
     *
     * @param string $filename Filename
     *
     * @return string Basename
     */
    protected function basename($filename)
    {
        // basename() is not unicode safe and locale dependent
        if (stristr(PHP_OS, 'win') || stristr(PHP_OS, 'netware')) {
            return preg_replace('/^.*[\\\\\\/]/', '', $filename);
        } else {
            return preg_replace('/^.*[\/]/', '', $filename);
        }
    }

    /**
     * Get Content-Type and Content-Transfer-Encoding headers of the message
     *
     * @return array Headers array
     */
    protected function contentHeaders()
    {
        $attachments      = count($this->parts) > 0;
        $html_images      = count($this->html_images) > 0;
        $html             = strlen($this->htmlbody) > 0;
        $calendar         = strlen($this->calbody) > 0;
        $has_text         = strlen($this->txtbody) > 0;
        $has_alternatives = ($html + $calendar + $has_text) > 1;
        $headers          = array();

        // See get()
        switch (true) {
        case $has_text && !$attachments && !$has_alternatives:
            $headers['Content-Type'] = 'text/plain';
            break;

        case $html && !$html_images && !$attachments && !$has_alternatives:
            $headers['Content-Type'] = 'text/html';
            break;

        case $html && $html_images && !$attachments && !$has_alternatives:
            $headers['Content-Type'] = 'multipart/related';
            break;

        case $calendar && !$attachments && !$has_alternatives:
            $headers['Content-Type'] = 'text/calendar';
            break;

        case $has_alternatives && !$attachments:
            $headers['Content-Type'] = 'multipart/alternative';
            break;

        case $attachments:
            $headers['Content-Type'] = 'multipart/mixed';
            break;
        }

        // Note: This is outside of the above switch construct to workaround
        // opcache bug: https://bugzilla.opensuse.org/show_bug.cgi?id=1166235
        if (empty($headers)) {
            return $headers;
        }

        $this->checkParams();

        $eol = !empty($this->build_params['eol'])
            ? $this->build_params['eol'] : "\r\n";

        if ($headers['Content-Type'] == 'text/plain') {
            // single-part message: add charset and encoding
            if ($this->build_params['text_charset']) {
                $charset = 'charset=' . $this->build_params['text_charset'];
                // place charset parameter in the same line, if possible
                // 26 = strlen("Content-Type: text/plain; ")
                $headers['Content-Type']
                    .= (strlen($charset) + 26 <= 76) ? "; $charset" : ";$eol $charset";
            }

            $headers['Content-Transfer-Encoding']
                = $this->build_params['text_encoding'];
        } else if ($headers['Content-Type'] == 'text/html') {
            // single-part message: add charset and encoding
            if ($this->build_params['html_charset']) {
                $charset = 'charset=' . $this->build_params['html_charset'];
                // place charset parameter in the same line, if possible
                $headers['Content-Type']
                    .= (strlen($charset) + 25 <= 76) ? "; $charset" : ";$eol $charset";
            }
            $headers['Content-Transfer-Encoding']
                = $this->build_params['html_encoding'];
        } else if ($headers['Content-Type'] == 'text/calendar') {
            // single-part message: add charset and encoding
            if ($this->build_params['calendar_charset']) {
                $charset = 'charset=' . $this->build_params['calendar_charset'];
                $headers['Content-Type'] .= "; $charset";
            }

            if ($this->build_params['calendar_method']) {
                $method = 'method=' . $this->build_params['calendar_method'];
                $headers['Content-Type'] .= "; $method";
            }

            $headers['Content-Transfer-Encoding']
                = $this->build_params['calendar_encoding'];
        } else {
            // multipart message: and boundary
            if (!empty($this->build_params['boundary'])) {
                $boundary = $this->build_params['boundary'];
            } else if (!empty($this->headers['Content-Type'])
                && preg_match('/boundary="([^"]+)"/', $this->headers['Content-Type'], $m)
            ) {
                $boundary = $m[1];
            } else {
                $boundary = '=_' . md5(rand() . microtime());
            }

            $this->build_params['boundary'] = $boundary;
            $headers['Content-Type'] .= ";$eol boundary=\"$boundary\"";
        }

        return $headers;
    }

    /**
     * Validate and set build parameters
     *
     * @return void
     */
    protected function checkParams()
    {
        $encodings = array('7bit', '8bit', 'base64', 'quoted-printable');

        $this->build_params['text_encoding']
            = strtolower($this->build_params['text_encoding']);
        $this->build_params['html_encoding']
            = strtolower($this->build_params['html_encoding']);
        $this->build_params['calendar_encoding']
            = strtolower($this->build_params['calendar_encoding']);

        if (!in_array($this->build_params['text_encoding'], $encodings)) {
            $this->build_params['text_encoding'] = '7bit';
        }
        if (!in_array($this->build_params['html_encoding'], $encodings)) {
            $this->build_params['html_encoding'] = '7bit';
        }
        if (!in_array($this->build_params['calendar_encoding'], $encodings)) {
            $this->build_params['calendar_encoding'] = '7bit';
        }

        // text body
        if ($this->build_params['text_encoding'] == '7bit'
            && !preg_match('/ascii/i', $this->build_params['text_charset'])
            && preg_match('/[^\x00-\x7F]/', $this->txtbody)
        ) {
            $this->build_params['text_encoding'] = 'quoted-printable';
        }
        // html body
        if ($this->build_params['html_encoding'] == '7bit'
            && !preg_match('/ascii/i', $this->build_params['html_charset'])
            && preg_match('/[^\x00-\x7F]/', $this->htmlbody)
        ) {
            $this->build_params['html_encoding'] = 'quoted-printable';
        }
        // calendar body
        if ($this->build_params['calendar_encoding'] == '7bit'
            && !preg_match('/ascii/i', $this->build_params['calendar_charset'])
            && preg_match('/[^\x00-\x7F]/', $this->calbody)
        ) {
            $this->build_params['calendar_encoding'] = 'quoted-printable';
        }
    }

    /**
     * Set body of specified message part
     *
     * @param string $type   One of: txtbody, calbody, htmlbody
     * @param string $data   Either a string or the file name with the contents
     * @param bool   $isfile If true the first param should be treated
     *                       as a file name, else as a string (default)
     * @param bool   $append If true the text or file is appended to
     *                       the existing body, else the old body is
     *                       overwritten
     *
     * @return mixed True on success or PEAR_Error object
     */
    protected function setBody($type, $data, $isfile = false, $append = false)
    {
        if ($isfile) {
            $data = $this->file2str($data);
            if (self::isError($data)) {
                return $data;
            }
        }

        if (!$append) {
            $this->{$type} = $data;
        } else {
            $this->{$type} .= $data;
        }

        return true;
    }

    /**
     * Adds a subpart to the mimePart object and
     * returns it during the build process.
     *
     * @param mixed  $obj   The object to add the part to, or
     *                      anything else if a new object is to be created.
     * @param string $body  Part body
     * @param string $ctype Part content type
     * @param string $type  Internal part type
     *
     * @return Mail_mimePart The mimePart object
     */
    protected function addBodyPart($obj, $body, $ctype, $type)
    {
        $params['content_type'] = $ctype;
        $params['encoding']     = $this->build_params[$type . '_encoding'];
        $params['charset']      = $this->build_params[$type . '_charset'];
        $params['eol']          = $this->build_params['eol'];

        if (is_object($obj)) {
            $ret = $obj->addSubpart($body, $params);
        } else {
            $ret = new Mail_mimePart($body, $params);
        }

        return $ret;
    }

    /**
     * PEAR::isError implementation
     *
     * @param mixed $data Object
     *
     * @return bool True if object is an instance of PEAR_Error
     */
    public static function isError($data)
    {
        // PEAR::isError() is not PHP 5.4 compatible (see Bug #19473)
        if (is_a($data, 'PEAR_Error')) {
            return true;
        }

        return false;
    }

    /**
     * PEAR::raiseError implementation
     *
     * @param string $message A text error message
     *
     * @return PEAR_Error Instance of PEAR_Error
     */
    public static function raiseError($message)
    {
        // PEAR::raiseError() is not PHP 5.4 compatible
        return new PEAR_Error($message);
    }
}
PK|c�\|n��W�W� pear/mail_mime/Mail/mimePart.phpnu�[���<?php
/**
 * The Mail_mimePart class is used to create MIME E-mail messages
 *
 * This class enables you to manipulate and build a mime email
 * from the ground up. The Mail_Mime class is a userfriendly api
 * to this class for people who aren't interested in the internals
 * of mime mail.
 * This class however allows full control over the email.
 *
 * Compatible with PHP version 5, 7 and 8
 *
 * LICENSE: This LICENSE is in the BSD license style.
 * Copyright (c) 2002-2003, Richard Heyes <richard@phpguru.org>
 * Copyright (c) 2003-2006, PEAR <pear-group@php.net>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * - Neither the name of the authors, nor the names of its contributors
 *   may be used to endorse or promote products derived from this
 *   software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @category  Mail
 * @package   Mail_Mime
 * @author    Richard Heyes  <richard@phpguru.org>
 * @author    Cipriano Groenendal <cipri@php.net>
 * @author    Sean Coates <sean@php.net>
 * @author    Aleksander Machniak <alec@php.net>
 * @copyright 2003-2006 PEAR <pear-group@php.net>
 * @license   http://www.opensource.org/licenses/bsd-license.php BSD License
 * @version   Release: @package_version@
 * @link      http://pear.php.net/package/Mail_mime
 */

/**
 * Require PEAR
 *
 * This package depends on PEAR to raise errors.
 */
require_once 'PEAR.php';

/**
 * The Mail_mimePart class is used to create MIME E-mail messages
 *
 * This class enables you to manipulate and build a mime email
 * from the ground up. The Mail_Mime class is a userfriendly api
 * to this class for people who aren't interested in the internals
 * of mime mail.
 * This class however allows full control over the email.
 *
 * @category  Mail
 * @package   Mail_Mime
 * @author    Richard Heyes  <richard@phpguru.org>
 * @author    Cipriano Groenendal <cipri@php.net>
 * @author    Sean Coates <sean@php.net>
 * @author    Aleksander Machniak <alec@php.net>
 * @copyright 2003-2006 PEAR <pear-group@php.net>
 * @license   http://www.opensource.org/licenses/bsd-license.php BSD License
 * @version   Release: @package_version@
 * @link      http://pear.php.net/package/Mail_mime
 */
class Mail_mimePart
{
    /**
     * The encoding type of this part
     *
     * @var string
     */
    protected $encoding;

    /**
     * An array of subparts
     *
     * @var array
     */
    protected $subparts = array();

    /**
     * The output of this part after being built
     *
     * @var string
     */
    protected $encoded;

    /**
     * Headers for this part
     *
     * @var array
     */
    protected $headers = array();

    /**
     * The body of this part (not encoded)
     *
     * @var string
     */
    protected $body;

    /**
     * The location of file with body of this part (not encoded)
     *
     * @var string
     */
    protected $body_file;

    /**
     * The short text of multipart part preamble (RFC2046 5.1.1)
     *
     * @var string
     */
    protected $preamble;

    /**
     * The end-of-line sequence
     *
     * @var string
     */
    protected $eol = "\r\n";


    /**
     * Constructor.
     *
     * Sets up the object.
     *
     * @param string $body   The body of the mime part if any.
     * @param array  $params An associative array of optional parameters:
     *                       - content_type: The content type for this part eg multipart/mixed
     *                       - encoding:  The encoding to use, 7bit, 8bit, base64, or quoted-printable
     *                       - charset: Content character set
     *                       - cid: Content ID to apply
     *                       - disposition: Content disposition, inline or attachment
     *                       - filename: Filename parameter for content disposition
     *                       - description: Content description
     *                       - name_encoding: Encoding of the attachment name (Content-Type)
     *                       By default filenames are encoded using RFC2231
     *                       Here you can set RFC2047 encoding (quoted-printable
     *                       or base64) instead
     *                       - filename_encoding: Encoding of the attachment filename (Content-Disposition)
     *                       See 'name_encoding'
     *                       - headers_charset: Charset of the headers e.g. filename, description.
     *                       If not set, 'charset' will be used
     *                       - eol: End of line sequence. Default: "\r\n"
     *                       - headers: Hash array with additional part headers. Array keys
     *                       can be in form of <header_name>:<parameter_name>
     *                       - body_file: Location of file with part's body (instead of $body)
     *                       - preamble: short text of multipart part preamble (RFC2046 5.1.1)
     */
    public function __construct($body = '', $params = array())
    {
        if (!empty($params['eol'])) {
            $this->eol = $params['eol'];
        } else if (defined('MAIL_MIMEPART_CRLF')) { // backward-copat.
            $this->eol = MAIL_MIMEPART_CRLF;
        }

        // Additional part headers
        if (!empty($params['headers']) && is_array($params['headers'])) {
            $headers = $params['headers'];
        }

        foreach ($params as $key => $value) {
            switch ($key) {
            case 'encoding':
                $this->encoding = $value;
                $headers['Content-Transfer-Encoding'] = $value;
                break;

            case 'cid':
                $headers['Content-ID'] = '<' . $value . '>';
                break;

            case 'location':
                $headers['Content-Location'] = $value;
                break;

            case 'body_file':
                $this->body_file = $value;
                break;

            case 'preamble':
                $this->preamble = $value;
                break;

            // for backward compatibility
            case 'dfilename':
                $params['filename'] = $value;
                break;
            }
        }

        // Default content-type
        if (empty($params['content_type'])) {
            $params['content_type'] = 'text/plain';
        }

        // Content-Type
        $headers['Content-Type'] = $params['content_type'];
        if (!empty($params['charset'])) {
            $charset = "charset={$params['charset']}";
            // place charset parameter in the same line, if possible
            if ((strlen($headers['Content-Type']) + strlen($charset) + 16) <= 76) {
                $headers['Content-Type'] .= '; ';
            } else {
                $headers['Content-Type'] .= ';' . $this->eol . ' ';
            }
            $headers['Content-Type'] .= $charset;

            // Default headers charset
            if (!isset($params['headers_charset'])) {
                $params['headers_charset'] = $params['charset'];
            }
        }

        // header values encoding parameters
        $h_charset  = !empty($params['headers_charset']) ? $params['headers_charset'] : 'US-ASCII';
        $h_language = !empty($params['language']) ? $params['language'] : null;
        $h_encoding = !empty($params['name_encoding']) ? $params['name_encoding'] : null;

        if (!empty($params['filename'])) {
            $headers['Content-Type'] .= ';' . $this->eol;
            $headers['Content-Type'] .= $this->buildHeaderParam(
                'name', $params['filename'], $h_charset, $h_language, $h_encoding
            );
        }

        // Content-Disposition
        if (!empty($params['disposition'])) {
            $headers['Content-Disposition'] = $params['disposition'];
            if (!empty($params['filename'])) {
                $headers['Content-Disposition'] .= ';' . $this->eol;
                $headers['Content-Disposition'] .= $this->buildHeaderParam(
                    'filename', $params['filename'], $h_charset, $h_language,
                    !empty($params['filename_encoding']) ? $params['filename_encoding'] : null
                );
            }

            // add attachment size
            $size = $this->body_file ? filesize($this->body_file) : strlen($body);
            if ($size) {
                $headers['Content-Disposition'] .= ';' . $this->eol . ' size=' . $size;
            }
        }

        if (!empty($params['description'])) {
            $headers['Content-Description'] = $this->encodeHeader(
                'Content-Description', $params['description'], $h_charset, $h_encoding,
                $this->eol
            );
        }

        // Search and add existing headers' parameters
        foreach ($headers as $key => $value) {
            $items = explode(':', $key);
            if (count($items) == 2) {
                $header = $items[0];
                $param  = $items[1];
                if (isset($headers[$header])) {
                    $headers[$header] .= ';' . $this->eol;
                }
                $headers[$header] .= $this->buildHeaderParam(
                    $param, $value, $h_charset, $h_language, $h_encoding
                );
                unset($headers[$key]);
            }
        }

        // Default encoding
        if (!isset($this->encoding)) {
            $this->encoding = '7bit';
        }

        // Assign stuff to member variables
        $this->encoded  = array();
        $this->headers  = $headers;
        $this->body     = $body;
    }

    /**
     * Encodes and returns the email. Also stores
     * it in the encoded member variable
     *
     * @param string $boundary Pre-defined boundary string
     *
     * @return array An associative array containing two elements,
     *               body and headers. The headers element is itself
     *               an indexed array. On error returns PEAR error object.
     */
    public function encode($boundary = null)
    {
        $encoded =& $this->encoded;

        if (count($this->subparts)) {
            $boundary = $boundary ? $boundary : '=_' . md5(rand() . microtime());
            $eol = $this->eol;

            $this->headers['Content-Type'] .= ";$eol boundary=\"$boundary\"";

            $encoded['body'] = '';

            if ($this->preamble) {
                $encoded['body'] .= $this->preamble . $eol . $eol;
            }

            for ($i = 0; $i < count($this->subparts); $i++) {
                $encoded['body'] .= '--' . $boundary . $eol;
                $tmp = $this->subparts[$i]->encode();
                if (is_a($tmp, 'PEAR_Error')) {
                    return $tmp;
                }
                foreach ($tmp['headers'] as $key => $value) {
                    $encoded['body'] .= $key . ': ' . $value . $eol;
                }
                $encoded['body'] .= $eol . $tmp['body'] . $eol;
            }

            $encoded['body'] .= '--' . $boundary . '--' . $eol;
        } else if ($this->body) {
            $encoded['body'] = $this->getEncodedData($this->body, $this->encoding);
        } else if ($this->body_file) {
            // Temporarily reset magic_quotes_runtime for file reads and writes
            if (version_compare(PHP_VERSION, '5.4.0', '<')) {
                $magic_quotes = @ini_set('magic_quotes_runtime', 0);
            }
            $body = $this->getEncodedDataFromFile($this->body_file, $this->encoding);
            if (isset($magic_quotes)) {
                @ini_set('magic_quotes_runtime', $magic_quotes);
            }

            if (is_a($body, 'PEAR_Error')) {
                return $body;
            }
            $encoded['body'] = $body;
        } else {
            $encoded['body'] = '';
        }

        // Add headers to $encoded
        $encoded['headers'] =& $this->headers;

        return $encoded;
    }

    /**
     * Encodes and saves the email into file or stream.
     * Data will be appended to the file/stream.
     *
     * @param mixed  $filename  Existing file location
     *                          or file pointer resource
     * @param string $boundary  Pre-defined boundary string
     * @param bool   $skip_head True if you don't want to save headers
     *
     * @return array An associative array containing message headers
     *               or PEAR error object
     * @since  1.6.0
     */
    public function encodeToFile($filename, $boundary = null, $skip_head = false)
    {
        if (!is_resource($filename)) {
            if (file_exists($filename) && !is_writable($filename)) {
                $err = self::raiseError('File is not writeable: ' . $filename);
                return $err;
            }

            if (!($fh = fopen($filename, 'ab'))) {
                $err = self::raiseError('Unable to open file: ' . $filename);
                return $err;
            }
        } else {
            $fh = $filename;
        }

        // Temporarily reset magic_quotes_runtime for file reads and writes
        if (version_compare(PHP_VERSION, '5.4.0', '<')) {
            $magic_quotes = @ini_set('magic_quotes_runtime', 0);
        }

        $res = $this->encodePartToFile($fh, $boundary, $skip_head);

        if (!is_resource($filename)) {
            fclose($fh);
        }

        if (isset($magic_quotes)) {
            @ini_set('magic_quotes_runtime', $magic_quotes);
        }

        return is_a($res, 'PEAR_Error') ? $res : $this->headers;
    }

    /**
     * Encodes given email part into file
     *
     * @param string $fh        Output file handle
     * @param string $boundary  Pre-defined boundary string
     * @param bool   $skip_head True if you don't want to save headers
     *
     * @return array True on sucess or PEAR error object
     */
    protected function encodePartToFile($fh, $boundary = null, $skip_head = false)
    {
        $eol = $this->eol;

        if (count($this->subparts)) {
            $boundary = $boundary ? $boundary : '=_' . md5(rand() . microtime());
            $this->headers['Content-Type'] .= ";$eol boundary=\"$boundary\"";
        }

        if (!$skip_head) {
            foreach ($this->headers as $key => $value) {
                fwrite($fh, $key . ': ' . $value . $eol);
            }
            $f_eol = $eol;
        } else {
            $f_eol = '';
        }

        if (count($this->subparts)) {
            if ($this->preamble) {
                fwrite($fh, $f_eol . $this->preamble . $eol);
                $f_eol = $eol;
            }

            for ($i = 0; $i < count($this->subparts); $i++) {
                fwrite($fh, $f_eol . '--' . $boundary . $eol);
                $res = $this->subparts[$i]->encodePartToFile($fh);
                if (is_a($res, 'PEAR_Error')) {
                    return $res;
                }
                $f_eol = $eol;
            }

            fwrite($fh, $eol . '--' . $boundary . '--' . $eol);
        } else if ($this->body) {
            fwrite($fh, $f_eol);
            fwrite($fh, $this->getEncodedData($this->body, $this->encoding));
        } else if ($this->body_file) {
            fwrite($fh, $f_eol);
            $res = $this->getEncodedDataFromFile(
                $this->body_file, $this->encoding, $fh
            );
            if (is_a($res, 'PEAR_Error')) {
                return $res;
            }
        }

        return true;
    }

    /**
     * Adds a subpart to current mime part and returns
     * a reference to it
     *
     * @param mixed $body   The body of the subpart or Mail_mimePart object
     * @param array $params The parameters for the subpart, same
     *                      as the $params argument for constructor
     *
     * @return Mail_mimePart A reference to the part you just added.
     */
    public function addSubpart($body, $params = null)
    {
        if ($body instanceof Mail_mimePart) {
            $part = $body;
        } else {
            $part = new Mail_mimePart($body, $params);
        }

        $this->subparts[] = $part;

        return $part;
    }

    /**
     * Returns encoded data based upon encoding passed to it
     *
     * @param string $data     The data to encode.
     * @param string $encoding The encoding type to use, 7bit, base64,
     *                         or quoted-printable.
     *
     * @return string Encoded data string
     */
    protected function getEncodedData($data, $encoding)
    {
        switch ($encoding) {
        case 'quoted-printable':
            return self::quotedPrintableEncode($data, 76, $this->eol);
            break;

        case 'base64':
            return rtrim(chunk_split(base64_encode($data), 76, $this->eol));
            break;

        case '8bit':
        case '7bit':
        default:
            return $data;
        }
    }

    /**
     * Returns encoded data based upon encoding passed to it
     *
     * @param string   $filename Data file location
     * @param string   $encoding The encoding type to use, 7bit, base64,
     *                           or quoted-printable.
     * @param resource $fh       Output file handle. If set, data will be
     *                           stored into it instead of returning it
     *
     * @return string Encoded data or PEAR error object
     */
    protected function getEncodedDataFromFile($filename, $encoding, $fh = null)
    {
        if (!is_readable($filename)) {
            $err = self::raiseError('Unable to read file: ' . $filename);
            return $err;
        }

        if (!($fd = fopen($filename, 'rb'))) {
            $err = self::raiseError('Could not open file: ' . $filename);
            return $err;
        }

        $data = '';

        switch ($encoding) {
        case 'quoted-printable':
            while (!feof($fd)) {
                $buffer = self::quotedPrintableEncode(fgets($fd), 76, $this->eol);
                if ($fh) {
                    fwrite($fh, $buffer);
                } else {
                    $data .= $buffer;
                }
            }
            break;

        case 'base64':
            while (!feof($fd)) {
                // Should read in a multiple of 57 bytes so that
                // the output is 76 bytes per line. Don't use big chunks
                // because base64 encoding is memory expensive
                $buffer = fread($fd, 57 * 9198); // ca. 0.5 MB
                $buffer = base64_encode($buffer);
                $buffer = chunk_split($buffer, 76, $this->eol);
                if (feof($fd)) {
                    $buffer = rtrim($buffer);
                }

                if ($fh) {
                    fwrite($fh, $buffer);
                } else {
                    $data .= $buffer;
                }
            }
            break;

        case '8bit':
        case '7bit':
        default:
            while (!feof($fd)) {
                $buffer = fread($fd, 1048576); // 1 MB
                if ($fh) {
                    fwrite($fh, $buffer);
                } else {
                    $data .= $buffer;
                }
            }
        }

        fclose($fd);

        if (!$fh) {
            return $data;
        }
    }

    /**
     * Encodes data to quoted-printable standard.
     *
     * @param string $input    The data to encode
     * @param int    $line_max Optional max line length. Should
     *                         not be more than 76 chars
     * @param string $eol      End-of-line sequence. Default: "\r\n"
     *
     * @return string Encoded data
     */
    public static function quotedPrintableEncode($input , $line_max = 76, $eol = "\r\n")
    {
        /*
        // imap_8bit() is extremely fast, but doesn't handle properly some characters
        if (function_exists('imap_8bit') && $line_max == 76) {
            $input = preg_replace('/\r?\n/', "\r\n", $input);
            $input = imap_8bit($input);
            if ($eol != "\r\n") {
                $input = str_replace("\r\n", $eol, $input);
            }
            return $input;
        }
        */
        $lines  = preg_split("/\r?\n/", $input);
        $escape = '=';
        $output = '';

        foreach ($lines as $idx => $line) {
            $newline = '';
            $i = 0;

            while (isset($line[$i])) {
                $char = $line[$i];
                $dec  = ord($char);
                $i++;

                if (($dec == 32) && (!isset($line[$i]))) {
                    // convert space at eol only
                    $char = '=20';
                } elseif ($dec == 9 && isset($line[$i])) {
                    ; // Do nothing if a TAB is not on eol
                } elseif (($dec == 61) || ($dec < 32) || ($dec > 126)) {
                    // Escape unprintable chars
                    $char = $escape . sprintf('%02X', $dec);
                } elseif (($dec == 46) && (($newline == '')
                    || ((strlen($newline) + strlen(".=")) > $line_max
                    && isset($line[$i])))
                ) {
                    // Bug #9722: convert full-stop at bol,
                    // some Windows servers need this, won't break anything (cipri)
                    // Bug #11731: full-stop at bol also needs to be encoded
                    // if this line would push us over the line_max limit.
                    $char = '=2E';
                }

                // EOL is not counted
                if ((strlen($newline) + strlen($char) == $line_max)
                    && !isset($line[$i])
                ) {
                    ; // no soft break is needed if we're the last char
                } elseif ((strlen($newline) + strlen($char)) >= $line_max) {
                    // soft line break; " =\r\n" is okay
                    $output  .= $newline . $escape . $eol;
                    $newline  = '';
                }

                $newline .= $char;
            } // end of for

            $output .= $newline . $eol;
            unset($lines[$idx]);
        }

        // Don't want last crlf
        $output = substr($output, 0, -1 * strlen($eol));

        return $output;
    }

    /**
     * Encodes the parameter of a header.
     *
     * @param string $name      The name of the header-parameter
     * @param string $value     The value of the paramter
     * @param string $charset   The characterset of $value
     * @param string $language  The language used in $value
     * @param string $encoding  Parameter encoding. If not set, parameter value
     *                          is encoded according to RFC2231
     * @param int    $maxLength The maximum length of a line. Defauls to 75
     *
     * @return string
     */
    protected function buildHeaderParam($name, $value, $charset = null,
        $language = null, $encoding = null, $maxLength = 75
    ) {
        // RFC 2045:
        // value needs encoding if contains non-ASCII chars or is longer than 78 chars
        if (!preg_match('#[^\x20-\x7E]#', $value)) {
            $token_regexp = '#([^\x21\x23-\x27\x2A\x2B\x2D'
                . '\x2E\x30-\x39\x41-\x5A\x5E-\x7E])#';
            if (!preg_match($token_regexp, $value)) {
                // token
                if (strlen($name) + strlen($value) + 3 <= $maxLength) {
                    return " {$name}={$value}";
                }
            } else {
                // quoted-string
                $quoted = addcslashes($value, '\\"');
                if (strlen($name) + strlen($quoted) + 5 <= $maxLength) {
                    return " {$name}=\"{$quoted}\"";
                }
            }
        }

        // RFC2047: use quoted-printable/base64 encoding
        if ($encoding == 'quoted-printable' || $encoding == 'base64') {
            return $this->buildRFC2047Param($name, $value, $charset, $encoding);
        }

        // RFC2231:
        $encValue = preg_replace_callback(
            '/([^\x21\x23\x24\x26\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E])/',
            array($this, 'encodeReplaceCallback'), $value
        );
        $value = "$charset'$language'$encValue";

        $header = " {$name}*={$value}";
        if (strlen($header) <= $maxLength) {
            return $header;
        }

        $preLength = strlen(" {$name}*0*=");
        $maxLength = max(16, $maxLength - $preLength - 3);
        $maxLengthReg = "|(.{0,$maxLength}[^\%][^\%])|";

        $headers = array();
        $headCount = 0;
        while ($value) {
            $matches = array();
            $found = preg_match($maxLengthReg, $value, $matches);
            if ($found) {
                $headers[] = " {$name}*{$headCount}*={$matches[0]}";
                $value = substr($value, strlen($matches[0]));
            } else {
                $headers[] = " {$name}*{$headCount}*={$value}";
                $value = '';
            }
            $headCount++;
        }

        $headers = implode(';' . $this->eol, $headers);
        return $headers;
    }

    /**
     * Encodes header parameter as per RFC2047 if needed
     *
     * @param string $name      The parameter name
     * @param string $value     The parameter value
     * @param string $charset   The parameter charset
     * @param string $encoding  Encoding type (quoted-printable or base64)
     * @param int    $maxLength Encoded parameter max length. Default: 76
     *
     * @return string Parameter line
     */
    protected function buildRFC2047Param($name, $value, $charset,
        $encoding = 'quoted-printable', $maxLength = 76
    ) {
        // WARNING: RFC 2047 says: "An 'encoded-word' MUST NOT be used in
        // parameter of a MIME Content-Type or Content-Disposition field",
        // but... it's supported by many clients/servers
        $quoted = '';

        if ($encoding == 'base64') {
            $value = base64_encode($value);
            $prefix = '=?' . $charset . '?B?';
            $suffix = '?=';

            // 2 x SPACE, 2 x '"', '=', ';'
            $add_len = strlen($prefix . $suffix) + strlen($name) + 6;
            $len = $add_len + strlen($value);

            while ($len > $maxLength) { 
                // We can cut base64-encoded string every 4 characters
                $real_len = floor(($maxLength - $add_len) / 4) * 4;
                $_quote = substr($value, 0, $real_len);
                $value = substr($value, $real_len);

                $quoted .= $prefix . $_quote . $suffix . $this->eol . ' ';
                $add_len = strlen($prefix . $suffix) + 4; // 2 x SPACE, '"', ';'
                $len = strlen($value) + $add_len;
            }
            $quoted .= $prefix . $value . $suffix;

        } else {
            // quoted-printable
            $value = $this->encodeQP($value);
            $prefix = '=?' . $charset . '?Q?';
            $suffix = '?=';

            // 2 x SPACE, 2 x '"', '=', ';'
            $add_len = strlen($prefix . $suffix) + strlen($name) + 6;
            $len = $add_len + strlen($value);

            while ($len > $maxLength) {
                $length = $maxLength - $add_len;
                // don't break any encoded letters
                if (preg_match("/^(.{0,$length}[^\=][^\=])/", $value, $matches)) {
                    $_quote = $matches[1];
                }

                $quoted .= $prefix . $_quote . $suffix . $this->eol . ' ';
                $value = substr($value, strlen($_quote));
                $add_len = strlen($prefix . $suffix) + 4; // 2 x SPACE, '"', ';'
                $len = strlen($value) + $add_len;
            }

            $quoted .= $prefix . $value . $suffix;
        }

        return " {$name}=\"{$quoted}\"";
    }

    /**
     * Return charset for mbstring functions.
     * Replace ISO-2022-JP with ISO-2022-JP-MS to convert Windows dependent
     * characters.
     *
     * @param string $charset A original charset
     *
     * @return string A charset for mbstring
     * @since  1.10.8
     */
    protected static function mbstringCharset($charset)
    {
        $mb_charset = $charset;

        if ($charset == 'ISO-2022-JP') {
            $mb_charset = 'ISO-2022-JP-MS';
        }

        return $mb_charset;
    }

    /**
     * Encodes a header as per RFC2047
     *
     * @param string $name     The header name
     * @param string $value    The header data to encode
     * @param string $charset  Character set name
     * @param string $encoding Encoding name (base64 or quoted-printable)
     * @param string $eol      End-of-line sequence. Default: "\r\n"
     *
     * @return string Encoded header data (without a name)
     * @since  1.6.1
     */
    public static function encodeHeader($name, $value, $charset = 'ISO-8859-1',
        $encoding = 'quoted-printable', $eol = "\r\n"
    ) {
        // Structured headers
        $comma_headers = array(
            'from', 'to', 'cc', 'bcc', 'sender', 'reply-to',
            'resent-from', 'resent-to', 'resent-cc', 'resent-bcc',
            'resent-sender', 'resent-reply-to',
            'mail-reply-to', 'mail-followup-to',
            'return-receipt-to', 'disposition-notification-to',
        );
        $other_headers = array(
            'references', 'in-reply-to', 'message-id', 'resent-message-id',
        );

        $name = strtolower($name);

        if (in_array($name, $comma_headers)) {
            $separator = ',';
        } else if (in_array($name, $other_headers)) {
            $separator = ' ';
        }

        if (!$charset) {
            $charset = 'ISO-8859-1';
        }

        // exploding quoted strings as well as some regexes below do not
        // work properly with some charset e.g. ISO-2022-JP, we'll use UTF-8
        $mb = $charset != 'UTF-8' && function_exists('mb_convert_encoding');
        $mb_charset = Mail_mimePart::mbstringCharset($charset);

        // Structured header (make sure addr-spec inside is not encoded)
        if (!empty($separator)) {
            // Simple e-mail address regexp
            $email_regexp = '([^\s<]+|("[^\r\n"]+"))@[^\s"]+';

            if ($mb) {
                $value = mb_convert_encoding($value, 'UTF-8', $mb_charset);
            }

            $parts = Mail_mimePart::explodeQuotedString("[\t$separator]", $value);
            $value = '';

            foreach ($parts as $part) {
                $part = preg_replace('/\r?\n[\s\t]*/', $eol . ' ', $part);
                $part = trim($part);

                if (!$part) {
                    continue;
                }
                if ($value) {
                    $value .= $separator == ',' ? $separator . ' ' : ' ';
                } else {
                    $value = $name . ': ';
                }

                // let's find phrase (name) and/or addr-spec
                if (preg_match('/^<' . $email_regexp . '>$/', $part)) {
                    $value .= $part;
                } else if (preg_match('/^' . $email_regexp . '$/', $part)) {
                    // address without brackets and without name
                    $value .= $part;
                } else if (preg_match('/<*' . $email_regexp . '>*$/', $part, $matches)) {
                    // address with name (handle name)
                    $address = $matches[0];
                    $word    = str_replace($address, '', $part);
                    $word    = trim($word);

                    // check if phrase requires quoting
                    if ($word) {
                        // non-ASCII: require encoding
                        if (preg_match('#([^\s\x21-\x7E]){1}#', $word)) {
                            if ($word[0] == '"' && $word[strlen($word)-1] == '"') {
                                // de-quote quoted-string, encoding changes
                                // string to atom
                                $word = substr($word, 1, -1);
                                $word = preg_replace('/\\\\([\\\\"])/', '$1', $word);
                            }
                            if ($mb) {
                                $word = mb_convert_encoding($word, $mb_charset, 'UTF-8');
                            }

                            // find length of last line
                            if (($pos = strrpos($value, $eol)) !== false) {
                                $last_len = strlen($value) - $pos;
                            } else {
                                $last_len = strlen($value);
                            }

                            $word = Mail_mimePart::encodeHeaderValue(
                                $word, $charset, $encoding, $last_len, $eol
                            );
                        } else if (($word[0] != '"' || $word[strlen($word)-1] != '"')
                            && preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $word)
                        ) {
                            // ASCII: quote string if needed
                            $word = '"'.addcslashes($word, '\\"').'"';
                        }
                    }

                    $value .= $word.' '.$address;
                } else {
                    if ($mb) {
                        $part = mb_convert_encoding($part, $mb_charset, 'UTF-8');
                    }
                    // addr-spec not found, don't encode (?)
                    $value .= $part;
                }

                // RFC2822 recommends 78 characters limit, use 76 from RFC2047
                $value = wordwrap($value, 76, $eol . ' ');
            }

            // remove header name prefix (there could be EOL too)
            $value = preg_replace(
                '/^'.$name.':('.preg_quote($eol, '/').')* /', '', $value
            );
        } else {
            // Unstructured header
            // non-ASCII: require encoding
            if (preg_match('#([^\s\x21-\x7E]){1}#', $value)) {
                if ($value[0] == '"' && $value[strlen($value)-1] == '"') {
                    if ($mb) {
                        $value = mb_convert_encoding($value, 'UTF-8', $mb_charset);
                    }
                    // de-quote quoted-string, encoding changes
                    // string to atom
                    $value = substr($value, 1, -1);
                    $value = preg_replace('/\\\\([\\\\"])/', '$1', $value);
                    if ($mb) {
                        $value = mb_convert_encoding($value, $mb_charset, 'UTF-8');
                    }
                }

                $value = Mail_mimePart::encodeHeaderValue(
                    $value, $charset, $encoding, strlen($name) + 2, $eol
                );
            } else if (strlen($name.': '.$value) > 78) {
                // ASCII: check if header line isn't too long and use folding
                $value = preg_replace('/\r?\n[\s\t]*/', $eol . ' ', $value);
                $tmp   = wordwrap($name . ': ' . $value, 78, $eol . ' ');
                $value = preg_replace('/^' . $name . ':\s*/', '', $tmp);
                // hard limit 998 (RFC2822)
                $value = wordwrap($value, 998, $eol . ' ', true);
            }
        }

        return $value;
    }

    /**
     * Explode quoted string
     *
     * @param string $delimiter Delimiter expression string for preg_match()
     * @param string $string    Input string
     *
     * @return array String tokens array
     */
    protected static function explodeQuotedString($delimiter, $string)
    {
        $result = array();
        $strlen = strlen($string);
        $quoted_string = '"(?:[^"\\\\]|\\\\.)*"';

        for ($p=$i=0; $i < $strlen; $i++) {
            if ($string[$i] === '"') {
                $r = preg_match("/$quoted_string/", $string, $matches, 0, $i);
                if (!$r || empty($matches[0])) {
                    break;
                }
                $i += strlen($matches[0]) - 1;
            } else if (preg_match("/$delimiter/", $string[$i])) {
                $result[] = substr($string, $p, $i - $p);
                $p = $i + 1;
            }
        }
        $result[] = substr($string, $p);
        return $result;
    }

    /**
     * Encodes a header value as per RFC2047
     *
     * @param string $value      The header data to encode
     * @param string $charset    Character set name
     * @param string $encoding   Encoding name (base64 or quoted-printable)
     * @param int    $prefix_len Prefix length. Default: 0
     * @param string $eol        End-of-line sequence. Default: "\r\n"
     *
     * @return string Encoded header data
     * @since  1.6.1
     */
    public static function encodeHeaderValue($value, $charset, $encoding, $prefix_len = 0, $eol = "\r\n")
    {
        // #17311: Use multibyte aware method (requires mbstring extension)
        if ($result = Mail_mimePart::encodeMB($value, $charset, $encoding, $prefix_len, $eol)) {
            return $result;
        }

        // Generate the header using the specified params and dynamicly
        // determine the maximum length of such strings.
        // 75 is the value specified in the RFC.
        $encoding = $encoding == 'base64' ? 'B' : 'Q';
        $prefix = '=?' . $charset . '?' . $encoding .'?';
        $suffix = '?=';
        $maxLength = 75 - strlen($prefix . $suffix);
        $maxLength1stLine = $maxLength - $prefix_len;

        if ($encoding == 'B') {
            // Base64 encode the entire string
            $value = base64_encode($value);

            // We can cut base64 every 4 characters, so the real max
            // we can get must be rounded down.
            $maxLength = $maxLength - ($maxLength % 4);
            $maxLength1stLine = $maxLength1stLine - ($maxLength1stLine % 4);

            $cutpoint = $maxLength1stLine;
            $output = '';

            while ($value) {
                // Split translated string at every $maxLength
                $part = substr($value, 0, $cutpoint);
                $value = substr($value, $cutpoint);
                $cutpoint = $maxLength;
                // RFC 2047 specifies that any split header should
                // be separated by a CRLF SPACE.
                if ($output) {
                    $output .= $eol . ' ';
                }
                $output .= $prefix . $part . $suffix;
            }
            $value = $output;
        } else {
            // quoted-printable encoding has been selected
            $value = Mail_mimePart::encodeQP($value);

            // This regexp will break QP-encoded text at every $maxLength
            // but will not break any encoded letters.
            $reg1st = "|(.{0,$maxLength1stLine}[^\=][^\=])|";
            $reg2nd = "|(.{0,$maxLength}[^\=][^\=])|";

            if (strlen($value) > $maxLength1stLine) {
                // Begin with the regexp for the first line.
                $reg = $reg1st;
                $output = '';
                while ($value) {
                    // Split translated string at every $maxLength
                    // But make sure not to break any translated chars.
                    $found = preg_match($reg, $value, $matches);

                    // After this first line, we need to use a different
                    // regexp for the first line.
                    $reg = $reg2nd;

                    // Save the found part and encapsulate it in the
                    // prefix & suffix. Then remove the part from the
                    // $value_out variable.
                    if ($found) {
                        $part = $matches[0];
                        $len = strlen($matches[0]);
                        $value = substr($value, $len);
                    } else {
                        $part = $value;
                        $value = '';
                    }

                    // RFC 2047 specifies that any split header should
                    // be separated by a CRLF SPACE
                    if ($output) {
                        $output .= $eol . ' ';
                    }
                    $output .= $prefix . $part . $suffix;
                }
                $value = $output;
            } else {
                $value = $prefix . $value . $suffix;
            }
        }

        return $value;
    }

    /**
     * Encodes the given string using quoted-printable
     *
     * @param string $str String to encode
     *
     * @return string Encoded string
     * @since  1.6.0
     */
    public static function encodeQP($str)
    {
        // Bug #17226 RFC 2047 restricts some characters
        // if the word is inside a phrase, permitted chars are only:
        // ASCII letters, decimal digits, "!", "*", "+", "-", "/", "=", and "_"

        // "=",  "_",  "?" must be encoded
        $regexp = '/([\x22-\x29\x2C\x2E\x3A-\x40\x5B-\x60\x7B-\x7E\x80-\xFF])/';
        $str = preg_replace_callback(
            $regexp, array('Mail_mimePart', 'qpReplaceCallback'), $str
        );

        return str_replace(' ', '_', $str);
    }

    /**
     * Encodes the given string using base64 or quoted-printable.
     * This method makes sure that encoded-word represents an integral
     * number of characters as per RFC2047.
     *
     * @param string $str        String to encode
     * @param string $charset    Character set name
     * @param string $encoding   Encoding name (base64 or quoted-printable)
     * @param int    $prefix_len Prefix length. Default: 0
     * @param string $eol        End-of-line sequence. Default: "\r\n"
     *
     * @return string Encoded string
     * @since  1.8.0
     */
    public static function encodeMB($str, $charset, $encoding, $prefix_len=0, $eol="\r\n")
    {
        if (!function_exists('mb_substr') || !function_exists('mb_strlen')) {
            return;
        }

        $encoding = $encoding == 'base64' ? 'B' : 'Q';
        // 75 is the value specified in the RFC
        $prefix = '=?' . $charset . '?'.$encoding.'?';
        $suffix = '?=';
        $maxLength = 75 - strlen($prefix . $suffix);
        $mb_charset = Mail_mimePart::mbstringCharset($charset);

        // A multi-octet character may not be split across adjacent encoded-words
        // So, we'll loop over each character
        // mb_stlen() with wrong charset will generate a warning here and return null
        $length      = mb_strlen($str, $mb_charset);
        $result      = '';
        $line_length = $prefix_len;

        if ($encoding == 'B') {
            // base64
            $start = 0;
            $prev  = '';

            for ($i=1; $i<=$length; $i++) {
                // See #17311
                $chunk = mb_substr($str, $start, $i-$start, $mb_charset);
                $chunk = base64_encode($chunk);
                $chunk_len = strlen($chunk);

                if ($line_length + $chunk_len == $maxLength || $i == $length) {
                    if ($result) {
                        $result .= "\n";
                    }
                    $result .= $chunk;
                    $line_length = 0;
                    $start = $i;
                } else if ($line_length + $chunk_len > $maxLength) {
                    if ($result) {
                        $result .= "\n";
                    }
                    if ($prev) {
                        $result .= $prev;
                    }
                    $line_length = 0;
                    $start = $i - 1;
                } else {
                    $prev = $chunk;
                }
            }
        } else {
            // quoted-printable
            // see encodeQP()
            $regexp = '/([\x22-\x29\x2C\x2E\x3A-\x40\x5B-\x60\x7B-\x7E\x80-\xFF])/';

            for ($i=0; $i<=$length; $i++) {
                $char = mb_substr($str, $i, 1, $mb_charset);
                // RFC recommends underline (instead of =20) in place of the space
                // that's one of the reasons why we're not using iconv_mime_encode()
                if ($char == ' ') {
                    $char = '_';
                    $char_len = 1;
                } else {
                    $char = preg_replace_callback(
                        $regexp, array('Mail_mimePart', 'qpReplaceCallback'), $char
                    );
                    $char_len = strlen($char);
                }

                if ($line_length + $char_len > $maxLength) {
                    if ($result) {
                        $result .= "\n";
                    }
                    $line_length = 0;
                }

                $result      .= $char;
                $line_length += $char_len;
            }
        }

        if ($result) {
            $result = $prefix
                .str_replace("\n", $suffix.$eol.' '.$prefix, $result).$suffix;
        }

        return $result;
    }

    /**
     * Callback function to replace extended characters (\x80-xFF) with their
     * ASCII values (RFC2047: quoted-printable)
     *
     * @param array $matches Preg_replace's matches array
     *
     * @return string Encoded character string
     */
    protected static function qpReplaceCallback($matches)
    {
        return sprintf('=%02X', ord($matches[1]));
    }

    /**
     * Callback function to replace extended characters (\x80-xFF) with their
     * ASCII values (RFC2231)
     *
     * @param array $matches Preg_replace's matches array
     *
     * @return string Encoded character string
     */
    protected static function encodeReplaceCallback($matches)
    {
        return sprintf('%%%02X', ord($matches[1]));
    }

    /**
     * PEAR::raiseError implementation
     *
     * @param string $message A text error message
     *
     * @return PEAR_Error Instance of PEAR_Error
     */
    public static function raiseError($message)
    {
        // PEAR::raiseError() is not PHP 5.4 compatible
        return new PEAR_Error($message);
    }
}
PK|c�\����pear/net_sieve/composer.jsonnu�[���{
    "authors": [
        {
            "email": "alec@alec.pl",
            "name": "Aleksander Machniak",
            "role": "Lead"
        },
        {
            "email": "jan@horde.org",
            "name": "Jan Schneider",
            "role": "Lead"
        },
        {
            "email": "richard@php.net",
            "name": "Richard Heyes",
            "role": "Lead"
        },
        {
            "email": "damlists@cnba.uba.ar",
            "name": "Damian Fernandez Sosa",
            "role": "Lead"
        },
        {
            "email": "amistry@am-productions.biz",
            "name": "Anish Mistry",
            "role": "Lead"
        }
    ],
    "autoload": {
        "classmap": [
            "./"
        ]
    },
    "description": "More info available on: https://pear.php.net/package/Net_Sieve",
    "license": "BSD-2-Clause",
    "name": "pear/net_sieve",
    "support": {
        "issues": "https://pear.php.net/bugs/search.php?cmd=display&package_name[]=Net_Sieve",
        "source": "https://github.com/pear/Net_Sieve"
    },
    "type": "library",
    "require": {
        "php": ">=5.6.0",
        "pear/pear-core-minimal": "~1.10",
        "pear/net_socket": "~1.2"
    },
    "require-dev": {
        "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^9"
    },
    "suggest": {
        "pear/auth_sasl": "Install optionally via your project's composer.json"
    }
}
PK|c�\_�m���pear/net_sieve/Sieve.phpnu�[���<?php
/**
 * This file contains the Net_Sieve class.
 *
 * +-----------------------------------------------------------------------+
 * | All rights reserved.                                                  |
 * |                                                                       |
 * | Redistribution and use in source and binary forms, with or without    |
 * | modification, are permitted provided that the following conditions    |
 * | are met:                                                              |
 * |                                                                       |
 * | o Redistributions of source code must retain the above copyright      |
 * |   notice, this list of conditions and the following disclaimer.       |
 * | o Redistributions in binary form must reproduce the above copyright   |
 * |   notice, this list of conditions and the following disclaimer in the |
 * |   documentation and/or other materials provided with the distribution.|
 * |                                                                       |
 * | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   |
 * | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     |
 * | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
 * | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  |
 * | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
 * | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      |
 * | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
 * | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
 * | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   |
 * | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
 * | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  |
 * +-----------------------------------------------------------------------+
 *
 * @category  Networking
 * @package   Net_Sieve
 * @author    Richard Heyes <richard@phpguru.org>
 * @author    Damian Fernandez Sosa <damlists@cnba.uba.ar>
 * @author    Anish Mistry <amistry@am-productions.biz>
 * @author    Jan Schneider <jan@horde.org>
 * @copyright 2002-2003 Richard Heyes
 * @copyright 2006-2008 Anish Mistry
 * @license   https://www.opensource.org/licenses/bsd-license.php BSD
 * @link      https://pear.php.net/package/Net_Sieve
 */

require_once 'PEAR.php';
require_once 'Net/Socket.php';

/**
 * Disconnected state
 *
 * @const NET_SIEVE_STATE_DISCONNECTED
 */
define('NET_SIEVE_STATE_DISCONNECTED', 1);

/**
 * Authorisation state
 *
 * @const NET_SIEVE_STATE_AUTHORISATION
 */
define('NET_SIEVE_STATE_AUTHORISATION', 2);

/**
 * Transaction state
 *
 * @const NET_SIEVE_STATE_TRANSACTION
 */
define('NET_SIEVE_STATE_TRANSACTION', 3);


/**
 * A class for talking to the timsieved server which comes with Cyrus IMAP.
 *
 * @category  Networking
 * @package   Net_Sieve
 * @author    Richard Heyes <richard@phpguru.org>
 * @author    Damian Fernandez Sosa <damlists@cnba.uba.ar>
 * @author    Anish Mistry <amistry@am-productions.biz>
 * @author    Jan Schneider <jan@horde.org>
 * @author    Neil Munday <neil@mundayweb.com>
 * @copyright 2002-2003 Richard Heyes
 * @copyright 2006-2008 Anish Mistry
 * @license   http://www.opensource.org/licenses/bsd-license.php BSD
 * @version   Release: @package_version@
 * @link      http://pear.php.net/package/Net_Sieve
 * @link      http://tools.ietf.org/html/rfc5228 RFC 5228 (Sieve: An Email
 *            Filtering Language)
 * @link      http://tools.ietf.org/html/rfc5804 RFC 5804 A Protocol for
 *            Remotely Managing Sieve Scripts
 */
class Net_Sieve
{
    /**
     * The authentication methods this class supports.
     *
     * Can be overwritten if having problems with certain methods.
     *
     * @var array
     */
    var $supportedAuthMethods = [
        'DIGEST-MD5',
        'CRAM-MD5',
        'EXTERNAL',
        'PLAIN' ,
        'LOGIN',
        'GSSAPI',
        'XOAUTH2',
        'OAUTHBEARER'
    ];

    /**
     * SASL authentication methods that require Auth_SASL.
     *
     * @var array
     */
    var $supportedSASLAuthMethods = ['DIGEST-MD5', 'CRAM-MD5'];

    /**
     * The socket handle.
     *
     * @var resource
     */
    var $_sock;

    /**
     * Parameters and connection information.
     *
     * @var array
     */
    var $_data;

    /**
     * Server capabilities.
     *
     * @var array
     */
    var $_capability = [];

    /**
     * Current state of the connection.
     *
     * One of the NET_SIEVE_STATE_* constants.
     *
     * @var int
     */
    var $_state;

    /**
     * PEAR object to avoid strict warnings.
     *
     * @var PEAR_Error
     */
    var $_pear;

    /**
     * Constructor error.
     *
     * @var PEAR_Error
     */
    var $_error;

    /**
     * Whether to enable debugging.
     *
     * @var bool
     */
    var $_debug = false;

    /**
     * Debug output handler.
     *
     * This has to be a valid callback.
     *
     * @var string|array
     */
    var $_debug_handler = null;

    /**
     * Whether to pick up an already established connection.
     *
     * @var bool
     */
    var $_bypassAuth = false;

    /**
     * Whether to use TLS if available.
     *
     * @var bool
     */
    var $_useTLS = true;

    /**
     * Additional options for stream_context_create().
     *
     * @var array
     */
    var $_options = null;

    /**
     * Maximum number of referral loops
     *
     * @var array
     */
    var $_maxReferralCount = 15;

    /**
     * Kerberos service principal to use for GSSAPI authentication.
     *
     * @var string
     */
    var $_gssapiPrincipal = null;

    /**
     * Kerberos service cname to use for GSSAPI authentication.
     *
     * @var string
     */
    var $_gssapiCN = null;

    /**
     * Constructor.
     *
     * Sets up the object, connects to the server and logs in. Stores any
     * generated error in $this->_error, which can be retrieved using the
     * getError() method.
     *
     * @param string  $user       Login username.
     * @param string  $pass       Login password.
     * @param string  $host       Hostname of server.
     * @param string  $port       Port of server.
     * @param string  $logintype  Type of login to perform (see
     *                            $supportedAuthMethods), use `OAUTH` and lib
     *                            will choose between OAUTHBEARER or XOAUTH2
     *                            according the server's capabilities.
     * @param string  $euser      Effective user. If authenticating as an
     *                            administrator, login as this user.
     * @param bool    $debug      Whether to enable debugging (@see setDebug()).
     * @param string  $bypassAuth Skip the authentication phase. Useful if the
     *                            socket is already open.
     * @param bool    $useTLS     Use TLS if available.
     * @param array   $options    Additional options for
     *                            stream_context_create().
     * @param mixed   $handler    A callback handler for the debug output.
     * @param string  $principal  Kerberos service principal to use
     *                            with GSSAPI authentication.
     * @param string  $cname      Kerberos service cname to use
     *                            with GSSAPI authentication.
     */
    function __construct($user = null, $pass  = null, $host = 'localhost',
        $port = 2000, $logintype = '', $euser = '',
        $debug = false, $bypassAuth = false, $useTLS = true,
        $options = null, $handler = null, $principal = null, $cname = null
    ) {
        $this->_pear = new PEAR();
        $this->_state             = NET_SIEVE_STATE_DISCONNECTED;
        $this->_data['user']      = $user;
        $this->_data['pass']      = $pass;
        $this->_data['host']      = $host;
        $this->_data['port']      = $port;
        $this->_data['logintype'] = $logintype;
        $this->_data['euser']     = $euser;
        $this->_sock              = new Net_Socket();
        $this->_bypassAuth        = $bypassAuth;
        $this->_useTLS            = $useTLS;
        $this->_options           = (array) $options;
        $this->_gssapiPrincipal   = $principal;
        $this->_gssapiCN          = $cname;

        $this->setDebug($debug, $handler);

        /* Try to include the Auth_SASL package.  If the package is not
         * available, we disable the authentication methods that depend upon
         * it. */
        if ((@include_once 'Auth/SASL.php') === false) {
            $this->_debug('Auth_SASL not present');
            $this->supportedAuthMethods = array_diff(
                $this->supportedAuthMethods,
                $this->supportedSASLAuthMethods
            );
        }

        if (is_string($user) && strlen($user) && strlen($pass)) {
            $this->_error = $this->_handleConnectAndLogin();
        }
    }

    /**
     * Returns any error that may have been generated in the constructor.
     *
     * @return bool|PEAR_Error False if no error, PEAR_Error otherwise.
     */
    function getError()
    {
        return is_a($this->_error, 'PEAR_Error') ? $this->_error : false;
    }

    /**
     * Sets the debug state and handler function.
     *
     * @param bool   $debug   Whether to enable debugging.
     * @param string $handler A custom debug handler. Must be a valid callback.
     *
     * @return void
     */
    function setDebug($debug = true, $handler = null)
    {
        $this->_debug = $debug;
        $this->_debug_handler = $handler;
    }

    /**
     * Sets the Kerberos service principal for use with GSSAPI
     * authentication.
     *
     * @param string $principal The Kerberos service principal
     *
     * @return void
     */
    function setServicePrincipal($principal)
    {
        $this->_gssapiPrincipal = $principal;
    }

    /**
     * Sets the Kerberos service CName for use with GSSAPI
     * authentication.
     *
     * @param string $cname The Kerberos service principal
     *
     * @return void
     */
    function setServiceCN($cname)
    {
        $this->_gssapiCN = $cname;
    }

    /**
     * Connects to the server and logs in.
     *
     * @return bool|PEAR_Error True on success, PEAR_Error on failure.
     */
    function _handleConnectAndLogin()
    {
        $res = $this->connect($this->_data['host'], $this->_data['port'], $this->_options, $this->_useTLS);
        if (is_a($res, 'PEAR_Error')) {
            return $res;
        }

        if ($this->_bypassAuth === false) {
            $res = $this->login($this->_data['user'], $this->_data['pass'], $this->_data['logintype'], $this->_data['euser'], $this->_bypassAuth);
            if (is_a($res, 'PEAR_Error')) {
                return $res;
            }
        }

        return true;
    }

    /**
     * Handles connecting to the server and checks the response validity.
     *
     * @param string  $host    Hostname of server.
     * @param string  $port    Port of server.
     * @param array   $options List of options to pass to
     *                         stream_context_create().
     * @param bool    $useTLS  Use TLS if available.
     *
     * @return bool|PEAR_Error True on success, PEAR_Error otherwise.
     */
    function connect($host, $port, $options = null, $useTLS = true)
    {
        $this->_data['host'] = $host;
        $this->_data['port'] = $port;
        $this->_useTLS       = $useTLS;

        if (is_array($options)) {
            $this->_options = array_merge($this->_options, $options);
        }

        if (NET_SIEVE_STATE_DISCONNECTED != $this->_state) {
            return $this->_pear->raiseError('Not currently in DISCONNECTED state', 1);
        }

        $res = $this->_sock->connect($host, $port, false, 5, $options);
        if (is_a($res, 'PEAR_Error')) {
            return $res;
        }

        if ($this->_bypassAuth) {
            $this->_state = NET_SIEVE_STATE_TRANSACTION;

            // Reset capabilities
            $this->_parseCapability('');
        } else {
            $this->_state = NET_SIEVE_STATE_AUTHORISATION;

            $res = $this->_doCmd();
            if (is_a($res, 'PEAR_Error')) {
                return $res;
            }

            // Reset capabilities (use unattended capabilities)
            $this->_parseCapability($res);
        }

        // Explicitly ask for the capabilities if needed
        if (empty($this->_capability['implementation'])) {
            $res = $this->_cmdCapability();
            if (is_a($res, 'PEAR_Error')) {
                return $this->_pear->raiseError(
                    'Failed to connect, server said: ' . $res->getMessage(), 2
                );
            }
        }

        // Check if we can enable TLS via STARTTLS.
        if ($useTLS && !empty($this->_capability['starttls'])
            && function_exists('stream_socket_enable_crypto')
        ) {
            $res = $this->_startTLS();
            if (is_a($res, 'PEAR_Error')) {
                return $res;
            }
        }

        return true;
    }

    /**
     * Disconnect from the Sieve server.
     *
     * @param bool $sendLogoutCMD Whether to send LOGOUT command before
     *                            disconnecting.
     *
     * @return bool|PEAR_Error True on success, PEAR_Error otherwise.
     */
    function disconnect($sendLogoutCMD = true)
    {
        return $this->_cmdLogout($sendLogoutCMD);
    }

    /**
     * Logs into server.
     *
     * @param string  $user       Login username.
     * @param string  $pass       Login password.
     * @param string  $logintype  Type of login method to use.
     * @param string  $euser      Effective UID (perform on behalf of $euser).
     * @param bool    $bypassAuth Do not perform authentication.
     *
     * @return bool|PEAR_Error True on success, PEAR_Error otherwise.
     */
    function login($user, $pass, $logintype = null, $euser = '', $bypassAuth = false)
    {
        $this->_data['user']      = $user;
        $this->_data['pass']      = $pass;
        $this->_data['logintype'] = $logintype;
        $this->_data['euser']     = $euser;
        $this->_bypassAuth        = $bypassAuth;

        if (NET_SIEVE_STATE_AUTHORISATION != $this->_state) {
            return $this->_pear->raiseError('Not currently in AUTHORISATION state', 1);
        }

        if (!$bypassAuth) {
            $res = $this->_cmdAuthenticate($user, $pass, $logintype, $euser);
            if (is_a($res, 'PEAR_Error')) {
                return $res;
            }
        }

        $this->_state = NET_SIEVE_STATE_TRANSACTION;

        return true;
    }

    /**
     * Returns an indexed array of scripts currently on the server.
     *
     * @param string $active Will be set to the name of the active script
     *
     * @return array|PEAR_Error Indexed array of scriptnames, PEAR_Error on failure
     */
    function listScripts(&$active = null)
    {
        if (is_array($scripts = $this->_cmdListScripts())) {
            if (isset($scripts[1])) {
                $active = $scripts[1];
            }

            return $scripts[0];
        }

        return $scripts;
    }

    /**
     * Returns the active script.
     *
     * @return string|null The active scriptname.
     */
    function getActive()
    {
        if (is_array($scripts = $this->_cmdListScripts())) {
            return $scripts[1];
        }
    }

    /**
     * Sets the active script.
     *
     * @param string $scriptname The name of the script to be set as active.
     *
     * @return bool|PEAR_Error True on success, PEAR_Error on failure.
     */
    function setActive($scriptname)
    {
        return $this->_cmdSetActive($scriptname);
    }

    /**
     * Retrieves a script.
     *
     * @param string $scriptname The name of the script to be retrieved.
     *
     * @return string|PEAR_Error The script on success, PEAR_Error on failure.
     */
    function getScript($scriptname)
    {
        return $this->_cmdGetScript($scriptname);
    }

    /**
     * Adds a script to the server.
     *
     * @param string  $scriptname Name of the script.
     * @param string  $script     The script content.
     * @param bool    $makeactive Whether to make this the active script.
     *
     * @return bool|PEAR_Error True on success, PEAR_Error on failure.
     */
    function installScript($scriptname, $script, $makeactive = false)
    {
        $res = $this->_cmdPutScript($scriptname, $script);
        if (is_a($res, 'PEAR_Error')) {
            return $res;
        }

        if ($makeactive) {
            return $this->_cmdSetActive($scriptname);
        }

        return true;
    }

    /**
     * Removes a script from the server.
     *
     * @param string $scriptname Name of the script.
     *
     * @return bool|PEAR_Error True on success, PEAR_Error on failure.
     */
    function removeScript($scriptname)
    {
        return $this->_cmdDeleteScript($scriptname);
    }

    /**
     * Checks if the server has space to store the script by the server.
     *
     * @param string $scriptname The name of the script to mark as active.
     * @param int    $size       The size of the script.
     *
     * @return bool|PEAR_Error True if there is space, PEAR_Error otherwise.
     *
     * @todo Rename to hasSpace()
     */
    function haveSpace($scriptname, $size)
    {
        if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
            return $this->_pear->raiseError('Not currently in TRANSACTION state', 1);
        }

        $res = $this->_doCmd(sprintf('HAVESPACE %s %d', $this->_escape($scriptname), $size));
        if (is_a($res, 'PEAR_Error')) {
            return $res;
        }

        return true;
    }

    /**
     * Returns the list of extensions the server supports.
     *
     * @return array|PEAR_Error List of extensions or PEAR_Error on failure.
     */
    function getExtensions()
    {
        if (NET_SIEVE_STATE_DISCONNECTED == $this->_state) {
            return $this->_pear->raiseError('Not currently connected', 7);
        }

        return $this->_capability['extensions'];
    }

    /**
     * Returns whether the server supports an extension.
     *
     * @param string $extension The extension to check.
     *
     * @return bool|PEAR_Error Whether the extension is supported or PEAR_Error on failure.
     */
    function hasExtension($extension)
    {
        if (NET_SIEVE_STATE_DISCONNECTED == $this->_state) {
            return $this->_pear->raiseError('Not currently connected', 7);
        }

        $extension = trim($this->_toUpper($extension));
        if (is_array($this->_capability['extensions'])) {
            foreach ($this->_capability['extensions'] as $ext) {
                if ($ext == $extension) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Returns the list of authentication methods the server supports.
     *
     * @return array|PEAR_Error List of authentication methods or PEAR_Error on failure.
     */
    function getAuthMechs()
    {
        if (NET_SIEVE_STATE_DISCONNECTED == $this->_state) {
            return $this->_pear->raiseError('Not currently connected', 7);
        }

        return $this->_capability['sasl'];
    }

    /**
     * Returns whether the server supports an authentication method.
     *
     * @param string $method The method to check.
     *
     * @return bool|PEAR_Error Whether the method is supported or PEAR_Error on failure.
     */
    function hasAuthMech($method)
    {
        if (NET_SIEVE_STATE_DISCONNECTED == $this->_state) {
            return $this->_pear->raiseError('Not currently connected', 7);
        }

        $method = trim($this->_toUpper($method));

        if (is_array($this->_capability['sasl'])) {
            foreach ($this->_capability['sasl'] as $sasl) {
                if ($sasl == $method) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Handles the authentication using any known method.
     *
     * @param string $uid        The userid to authenticate as.
     * @param string $pwd        The password to authenticate with.
     * @param string $userMethod The method to use. If empty, the class chooses
     *                           the best (strongest) available method.
     * @param string $euser      The effective uid to authenticate as.
     *
     * @return void
     */
    function _cmdAuthenticate($uid, $pwd, $userMethod = null, $euser = '')
    {
        $method = $this->_getBestAuthMethod($userMethod);
        if (is_a($method, 'PEAR_Error')) {
            return $method;
        }

        switch ($method) {
        case 'DIGEST-MD5':
            return $this->_authDigestMD5($uid, $pwd, $euser);
        case 'CRAM-MD5':
            $result = $this->_authCRAMMD5($uid, $pwd, $euser);
            break;
        case 'LOGIN':
            $result = $this->_authLOGIN($uid, $pwd, $euser);
            break;
        case 'PLAIN':
            $result = $this->_authPLAIN($uid, $pwd, $euser);
            break;
        case 'EXTERNAL':
            $result = $this->_authEXTERNAL($uid, $pwd, $euser);
            break;
        case 'GSSAPI':
            $result = $this->_authGSSAPI($pwd);
            break;
        case 'XOAUTH2':
            $result = $this->_authXOAUTH2($uid, $pwd, $euser);
            break;
        case 'OAUTHBEARER':
            $result = $this->_authOAUTHBEARER($uid, $pwd, $euser);
            break;
        default :
            $result = $this->_pear->raiseError(
                $method . ' is not a supported authentication method'
            );
            break;
        }

        $res = $this->_doCmd();
        if (is_a($res, 'PEAR_Error')) {
            return $res;
        }

        if ($this->_pear->isError($res = $this->_cmdCapability())) {
            return $this->_pear->raiseError(
                'Failed to connect, server said: ' . $res->getMessage(), 2
            );
        }

        return $result;
    }

    /**
     * Authenticates the user using the PLAIN method.
     *
     * @param string $user  The userid to authenticate as.
     * @param string $pass  The password to authenticate with.
     * @param string $euser The effective uid to authenticate as.
     *
     * @return void
     */
    function _authPLAIN($user, $pass, $euser)
    {
        return $this->_sendCmd(
            sprintf(
                'AUTHENTICATE "PLAIN" "%s"',
                base64_encode($euser . chr(0) . $user . chr(0) . $pass)
            )
        );
    }

    /**
     * Authenticates the user using the GSSAPI method.
     *
     * @note the PHP krb5 extension is required and the service principal and cname
     *       must have been set.
     * @see  setServicePrincipal()
     *
     * @return void
     */
    function _authGSSAPI()
    {
        if (!extension_loaded('krb5')) {
            return $this->_pear->raiseError('The krb5 extension is required for GSSAPI authentication', 2);
        }

        if (!$this->_gssapiPrincipal) {
            return $this->_pear->raiseError('No Kerberos service principal set', 2);
        }

        if (!empty($this->_gssapiCN)) {
            putenv('KRB5CCNAME=' . $this->_gssapiCN);
        }

        try {
            $ccache = new KRB5CCache();
            if (!empty($this->_gssapiCN)) {
                $ccache->open($this->_gssapiCN);
            }

            $gssapicontext = new GSSAPIContext();
            $gssapicontext->acquireCredentials($ccache);

            $token   = '';
            $success = $gssapicontext->initSecContext($this->_gssapiPrincipal, null, null, null, $token);
            $token   = base64_encode($token);
        }
        catch (Exception $e) {
            return $this->_pear->raiseError('GSSAPI authentication failed: ' . $e->getMessage());
        }

        $this->_sendCmd("AUTHENTICATE \"GSSAPI\" {" . strlen($token) . "+}");

        $response = $this->_doCmd($token, true);

        try {
            $challenge = base64_decode(substr($response, 1, -1));
            $gssapicontext->unwrap($challenge, $challenge);
            $gssapicontext->wrap($challenge, $challenge, true);
        }
        catch (Exception $e) {
            return $this->_pear->raiseError('GSSAPI authentication failed: ' . $e->getMessage());
        }

        $response = base64_encode($challenge);

        $this->_sendCmd("{" . strlen($response) . "+}");

        return $this->_sendCmd($response);
    }

    /**
     * Authenticates the user using the LOGIN method.
     *
     * @param string $user  The userid to authenticate as.
     * @param string $pass  The password to authenticate with.
     * @param string $euser The effective uid to authenticate as. Not used.
     *
     * @return void
     */
    function _authLOGIN($user, $pass, $euser)
    {
        $result = $this->_sendCmd('AUTHENTICATE "LOGIN"');
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        $result = $this->_doCmd('"' . base64_encode($user) . '"', true);
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        return $this->_doCmd('"' . base64_encode($pass) . '"', true);
    }

    /**
     * Authenticates the user using the CRAM-MD5 method.
     *
     * @param string $user  The userid to authenticate as.
     * @param string $pass  The password to authenticate with.
     * @param string $euser The effective uid to authenticate as. Not used.
     *
     * @return void
     */
    function _authCRAMMD5($user, $pass, $euser)
    {
        $challenge = $this->_doCmd('AUTHENTICATE "CRAM-MD5"', true);
        if (is_a($challenge, 'PEAR_Error')) {
            return $challenge;
        }

        $auth_sasl = new Auth_SASL;
        $cram      = $auth_sasl->factory('crammd5');
        $challenge = base64_decode(trim($challenge));
        $response  = $cram->getResponse($user, $pass, $challenge);

        if (is_a($response, 'PEAR_Error')) {
            return $response;
        }

        return $this->_sendStringResponse(base64_encode($response));
    }

    /**
     * Authenticates the user using the DIGEST-MD5 method.
     *
     * @param string $user  The userid to authenticate as.
     * @param string $pass  The password to authenticate with.
     * @param string $euser The effective uid to authenticate as.
     *
     * @return void
     */
    function _authDigestMD5($user, $pass, $euser)
    {
        $challenge = $this->_doCmd('AUTHENTICATE "DIGEST-MD5"', true);
        if (is_a($challenge, 'PEAR_Error')) {
            return $challenge;
        }

        $auth_sasl = new Auth_SASL;
        $digest    = $auth_sasl->factory('digestmd5');
        $challenge = base64_decode(trim($challenge));

        // @todo Really 'localhost'?
        $response = $digest->getResponse($user, $pass, $challenge, 'localhost', 'sieve', $euser);
        if (is_a($response, 'PEAR_Error')) {
            return $response;
        }

        $result = $this->_sendStringResponse(base64_encode($response));
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        $result = $this->_doCmd('', true);
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        if ($this->_toUpper(substr($result, 0, 2)) == 'OK') {
            return;
        }

        /* We don't use the protocol's third step because SIEVE doesn't allow
         * subsequent authentication, so we just silently ignore it. */
        $result = $this->_sendStringResponse('');
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        return $this->_doCmd();
    }

    /**
     * Authenticates the user using the EXTERNAL method.
     *
     * @param string $user  The userid to authenticate as.
     * @param string $pass  The password to authenticate with.
     * @param string $euser The effective uid to authenticate as.
     *
     * @return void
     *
     * @since 1.1.7
     */
    function _authEXTERNAL($user, $pass, $euser)
    {
        $cmd = sprintf(
            'AUTHENTICATE "EXTERNAL" "%s"',
            base64_encode(strlen($euser) ? $euser : $user)
        );

        return $this->_sendCmd($cmd);
    }

    /**
     * Authenticates the user using the XOAUTH2 method.
     *
     * @param string $user  The userid to authenticate as.
     * @param string $token The access token prefixed by it's type
     *                      example: "Bearer $access_token".
     * @param string $euser The effective uid to authenticate as.
     *
     * @return void
     */
    function _authXOAUTH2($user, $token, $euser)
    {
        // default to $user if $euser is not set
        if (! $euser) {
            $euser = $user;
        }

        $auth = base64_encode("user=$euser\001auth=$token\001\001");
        return $this->_sendCmd("AUTHENTICATE \"XOAUTH2\" \"$auth\"");
    }

    /**
     * Authenticates the user using the OAUTHBEARER method.
     *
     * @param string $user  The userid to authenticate as.
     * @param string $token The access token prefixed by it's type
     *                      example: "Bearer $access_token".
     * @param string $euser The effective uid to authenticate as.
     *
     * @return void
     *
     * @see https://www.rfc-editor.org/rfc/rfc7628.html
     * @since 1.4.7
     */
    function _authOAUTHBEARER($user, $token, $euser)
    {
        // default to $user if $euser is not set
        if (! $euser) {
            $euser = $user;
        }

        $auth = base64_encode("n,a=$euser\001auth=$token\001\001");
        return $this->_sendCmd("AUTHENTICATE \"OAUTHBEARER\" \"$auth\"");
    }

    /**
     * Removes a script from the server.
     *
     * @param string $scriptname Name of the script to delete.
     *
     * @return bool|PEAR_Error True on success, PEAR_Error otherwise.
     */
    function _cmdDeleteScript($scriptname)
    {
        if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
            return $this->_pear->raiseError('Not currently in AUTHORISATION state', 1);
        }

        $res = $this->_doCmd(sprintf('DELETESCRIPT %s', $this->_escape($scriptname)));
        if (is_a($res, 'PEAR_Error')) {
            return $res;
        }

        return true;
    }

    /**
     * Retrieves the contents of the named script.
     *
     * @param string $scriptname Name of the script to retrieve.
     *
     * @return string|PEAR_Error The script if successful, PEAR_Error otherwise.
     */
    function _cmdGetScript($scriptname)
    {
        if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
            return $this->_pear->raiseError('Not currently in AUTHORISATION state', 1);
        }

        $res = $this->_doCmd(sprintf('GETSCRIPT %s', $this->_escape($scriptname)));
        if (is_a($res, 'PEAR_Error')) {
            return $res;
        }

        return preg_replace('/^{[0-9]+}\r\n/', '', $res);
    }

    /**
     * Sets the active script, i.e. the one that gets run on new mail by the
     * server.
     *
     * @param string $scriptname The name of the script to mark as active.
     *
     * @return bool|PEAR_Error True on success, PEAR_Error otherwise.
     */
    function _cmdSetActive($scriptname)
    {
        if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
            return $this->_pear->raiseError('Not currently in AUTHORISATION state', 1);
        }

        $res = $this->_doCmd(sprintf('SETACTIVE %s', $this->_escape($scriptname)));
        if (is_a($res, 'PEAR_Error')) {
            return $res;
        }

        return true;
    }

    /**
     * Returns the list of scripts on the server.
     *
     * @return array  An array with the list of scripts in the first element
     *                and the active script in the second element on success,
     *                PEAR_Error otherwise.
     */
    function _cmdListScripts()
    {
        if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
            return $this->_pear->raiseError('Not currently in AUTHORISATION state', 1);
        }

        $res = $this->_doCmd('LISTSCRIPTS');
        if (is_a($res, 'PEAR_Error')) {
            return $res;
        }

        $scripts = [];
        $activescript = null;
        $res = explode("\r\n", $res);
        foreach ($res as $value) {
            if (preg_match('/^"(.*)"( ACTIVE)?$/i', $value, $matches)) {
                $script_name = stripslashes($matches[1]);
                $scripts[] = $script_name;
                if (!empty($matches[2])) {
                    $activescript = $script_name;
                }
            }
        }

        return [$scripts, $activescript];
    }

    /**
     * Adds a script to the server.
     *
     * @param string $scriptname Name of the new script.
     * @param string $scriptdata The new script.
     *
     * @return bool|PEAR_Error True on success, PEAR_Error otherwise.
     */
    function _cmdPutScript($scriptname, $scriptdata)
    {
        if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
            return $this->_pear->raiseError('Not currently in AUTHORISATION state', 1);
        }

        $stringLength = $this->_getLineLength($scriptdata);
        $command      = sprintf(
            "PUTSCRIPT %s {%d+}\r\n%s",
            $this->_escape($scriptname),
            $stringLength,
            $scriptdata
        );

        $res = $this->_doCmd($command);
        if (is_a($res, 'PEAR_Error')) {
            return $res;
        }

        return true;
    }

    /**
     * Logs out of the server and terminates the connection.
     *
     * @param bool $sendLogoutCMD Whether to send LOGOUT command before
     *                            disconnecting.
     *
     * @return bool|PEAR_Error True on success, PEAR_Error otherwise.
     */
    function _cmdLogout($sendLogoutCMD = true)
    {
        if (NET_SIEVE_STATE_DISCONNECTED == $this->_state) {
            return $this->_pear->raiseError('Not currently connected', 1);
        }

        if ($sendLogoutCMD) {
            $res = $this->_doCmd('LOGOUT');
            if (is_a($res, 'PEAR_Error')) {
                return $res;
            }
        }

        $this->_sock->disconnect();
        $this->_state = NET_SIEVE_STATE_DISCONNECTED;

        return true;
    }

    /**
     * Sends the CAPABILITY command
     *
     * @return bool|PEAR_Error True on success, PEAR_Error otherwise.
     */
    function _cmdCapability()
    {
        if (NET_SIEVE_STATE_DISCONNECTED == $this->_state) {
            return $this->_pear->raiseError('Not currently connected', 1);
        }
        $res = $this->_doCmd('CAPABILITY');
        if (is_a($res, 'PEAR_Error')) {
            return $res;
        }
        $this->_parseCapability($res);
        return true;
    }

    /**
     * Parses the response from the CAPABILITY command and stores the result
     * in $_capability.
     *
     * @param string $data The response from the capability command.
     *
     * @return void
     */
    function _parseCapability($data)
    {
        // Clear the cached capabilities.
        $this->_capability = ['sasl' => [], 'extensions' => []];

        $data = preg_split('/\r?\n/', $this->_toUpper($data), -1, PREG_SPLIT_NO_EMPTY);

        for ($i = 0; $i < count($data); $i++) {
            if (!preg_match('/^"([A-Z]+)"( "(.*)")?$/', $data[$i], $matches)) {
                continue;
            }
            switch ($matches[1]) {
            case 'IMPLEMENTATION':
                $this->_capability['implementation'] = $matches[3];
                break;

            case 'SASL':
                if (!empty($matches[3])) {
                    $this->_capability['sasl'] = preg_split('/\s+/', $matches[3]);
                }
                break;

            case 'SIEVE':
                if (!empty($matches[3])) {
                    $this->_capability['extensions'] = preg_split('/\s+/', $matches[3]);
                }
                break;

            case 'STARTTLS':
                $this->_capability['starttls'] = true;
                break;
            }
        }
    }

    /**
     * Sends a command to the server
     *
     * @param string $cmd The command to send.
     *
     * @return void
     */
    function _sendCmd($cmd)
    {
        $status = $this->_sock->getStatus();
        if (is_a($status, 'PEAR_Error') || $status['eof']) {
            return $this->_pear->raiseError('Failed to write to socket: connection lost');
        }
        $error = $this->_sock->write($cmd . "\r\n");
        if (is_a($error, 'PEAR_Error')) {
            return $this->_pear->raiseError(
                'Failed to write to socket: ' . $error->getMessage()
            );
        }
        $this->_debug("C: $cmd");
    }

    /**
     * Sends a string response to the server.
     *
     * @param string $str The string to send.
     *
     * @return void
     */
    function _sendStringResponse($str)
    {
        return $this->_sendCmd('{' . $this->_getLineLength($str) . "+}\r\n" . $str);
    }

    /**
     * Receives a single line from the server.
     *
     * @return string  The server response line.
     */
    function _recvLn()
    {
        $lastline = $this->_sock->gets(8192);
        if (is_a($lastline, 'PEAR_Error')) {
            return $this->_pear->raiseError(
                'Failed to read from socket: ' . $lastline->getMessage()
            );
        }

        $lastline = rtrim($lastline);
        $this->_debug("S: $lastline");

        if ($lastline === '') {
            return $this->_pear->raiseError('Failed to read from socket');
        }

        return $lastline;
    }

    /**
     * Receives a number of bytes from the server.
     *
     * @param int $length Number of bytes to read.
     *
     * @return string The server response.
     */
    function _recvBytes($length)
    {
        $response = '';
        $response_length = 0;
        while ($response_length < $length) {
            $response .= $this->_sock->read($length - $response_length);
            $response_length = $this->_getLineLength($response);
        }
        $this->_debug('S: ' . rtrim($response));
        return $response;
    }

    /**
     * Send a command and retrieves a response from the server.
     *
     * @param string $cmd  The command to send.
     * @param bool   $auth Whether this is an authentication command.
     *
     * @return string|PEAR_Error Reponse string if an OK response, PEAR_Error
     *                           if a NO response.
     */
    function _doCmd($cmd = '', $auth = false)
    {
        $referralCount = 0;
        while ($referralCount < $this->_maxReferralCount) {
            if (strlen($cmd)) {
                $error = $this->_sendCmd($cmd);
                if (is_a($error, 'PEAR_Error')) {
                    return $error;
                }
            }

            $response = '';
            while (true) {
                $line = $this->_recvLn();
                if (is_a($line, 'PEAR_Error')) {
                    return $line;
                }

                if (preg_match('/^(OK|NO)/i', $line, $tag)) {
                    // Check for string literal message.
                    if (preg_match('/{([0-9]+)}$/', $line, $matches)) {
                        $line = substr($line, 0, -(strlen($matches[1]) + 2))
                            . str_replace(
                                "\r\n", ' ', $this->_recvBytes($matches[1] + 2)
                            );
                    }

                    if ('OK' == $this->_toUpper($tag[1])) {
                        $response .= $line;
                        return rtrim($response);
                    }

                    return $this->_pear->raiseError(trim($response . substr($line, 2)), 3);
                }

                if (preg_match('/^BYE/i', $line)) {
                    $error = $this->disconnect(false);
                    if (is_a($error, 'PEAR_Error')) {
                        return $this->_pear->raiseError(
                            'Cannot handle BYE, the error was: '
                            . $error->getMessage(),
                            4
                        );
                    }
                    // Check for referral, then follow it.  Otherwise, carp an
                    // error.
                    if (preg_match('/^bye \(referral "(sieve:\/\/)?([^"]+)/i', $line, $matches)) {
                        // Replace the old host with the referral host
                        // preserving any protocol prefix.
                        $this->_data['host'] = preg_replace(
                            '/\w+(?!(\w|\:\/\/)).*/', $matches[2],
                            $this->_data['host']
                        );
                        $error = $this->_handleConnectAndLogin();
                        if (is_a($error, 'PEAR_Error')) {
                            return $this->_pear->raiseError(
                                'Cannot follow referral to '
                                . $this->_data['host'] . ', the error was: '
                                . $error->getMessage(),
                                5
                            );
                        }
                        break;
                    }
                    return $this->_pear->raiseError(trim($response . $line), 6);
                }

                if (preg_match('/^{([0-9]+)}/', $line, $matches)) {
                    // Matches literal string responses.
                    $line = $this->_recvBytes($matches[1] + 2);
                    if (!$auth) {
                        // Receive the pending OK only if we aren't
                        // authenticating since string responses during
                        // authentication don't need an OK.
                        $this->_recvLn();
                    }
                    return $line;
                }

                if ($auth) {
                    // String responses during authentication don't need an
                    // OK.
                    $response .= $line;
                    return rtrim($response);
                }

                $response .= $line . "\r\n";
                $referralCount++;
            }
        }

        return $this->_pear->raiseError('Max referral count (' . $referralCount . ') reached. Cyrus murder loop error?', 7);
    }

    /**
     * Returns the name of the best authentication method that the server
     * has advertised.
     *
     * @param string $userMethod Only consider this method as available.
     *
     * @return string The name of the best supported authentication method or
     *                a PEAR_Error object on failure.
     */
    function _getBestAuthMethod($userMethod = null)
    {
        if (!isset($this->_capability['sasl'])) {
            return $this->_pear->raiseError('This server doesn\'t support any authentication methods. SASL problem?');
        }

        if (!$this->_capability['sasl']) {
            return $this->_pear->raiseError('This server doesn\'t support any authentication methods.');
        }

        if ($userMethod) {
            // special case of OAUTH, use the supported method
            if ($userMethod === 'OAUTH') {
                foreach (['OAUTHBEARER', 'XOAUTH2'] as $method) {
                    if (in_array($method, $this->_capability['sasl'])) {
                        return $method;
                    }
                }
            } elseif (in_array($userMethod, $this->_capability['sasl'])) {
                return $userMethod;
            }

            $msg = 'No supported authentication method found. The server supports these methods: %s, but we want to use: %s';
            return $this->_pear->raiseError(
                sprintf($msg, implode(', ', $this->_capability['sasl']), $userMethod)
            );
        }

        foreach ($this->supportedAuthMethods as $method) {
            if (in_array($method, $this->_capability['sasl'])) {
                return $method;
            }
        }

        $msg = 'No supported authentication method found. The server supports these methods: %s, but we only support: %s';
        return $this->_pear->raiseError(
            sprintf($msg, implode(', ', $this->_capability['sasl']), implode(', ', $this->supportedAuthMethods))
        );
    }

    /**
     * Starts a TLS connection.
     *
     * @return bool|PEAR_Error True on success, PEAR_Error on failure.
     */
    function _startTLS()
    {
        $res = $this->_doCmd('STARTTLS');
        if (is_a($res, 'PEAR_Error')) {
            return $res;
        }

        if (isset($this->_options['ssl']['crypto_method'])) {
            $crypto_method = $this->_options['ssl']['crypto_method'];
        } else {
            // There is no flag to enable all TLS methods. Net_SMTP
            // handles enabling TLS similarly.
            $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT
                | @STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT
                | @STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
        }

        if (!stream_socket_enable_crypto($this->_sock->fp, true, $crypto_method)) {
            return $this->_pear->raiseError('Failed to establish TLS connection', 2);
        }

        $this->_debug('STARTTLS negotiation successful');

        // The server should be sending a CAPABILITY response after
        // negotiating TLS. Read it, and ignore if it doesn't.
        // Unfortunately old Cyrus versions are broken and don't send a
        // CAPABILITY response, thus we would wait here forever. Parse the
        // Cyrus version and work around this broken behavior.
        if (empty($this->_capability['implementation'])
            || !preg_match('/^CYRUS TIMSIEVED V([0-9.]+)/', $this->_capability['implementation'], $matches)
            || version_compare($matches[1], '2.3.10', '>=')
        ) {
            $res = $this->_doCmd();
        }

        // Reset capabilities (use unattended capabilities)
        $this->_parseCapability(is_string($res) ? $res : '');

        // Query the server capabilities again now that we are under encryption.
        if (empty($this->_capability['implementation'])) {
            $res = $this->_cmdCapability();
            if (is_a($res, 'PEAR_Error')) {
                return $this->_pear->raiseError(
                    'Failed to connect, server said: ' . $res->getMessage(), 2
                );
            }
        }

        return true;
    }

    /**
     * Returns the length of a string.
     *
     * @param string $string A string.
     *
     * @return int The length of the string.
     */
    function _getLineLength($string)
    {
        if (extension_loaded('mbstring')) {
            return mb_strlen($string, '8bit');
        } else {
            return strlen($string);
        }
    }

    /**
     * Locale independant strtoupper() implementation.
     *
     * @param string $string The string to convert to lowercase.
     *
     * @return string The lowercased string, based on ASCII encoding.
     */
    function _toUpper($string)
    {
        $language = setlocale(LC_CTYPE, 0);
        setlocale(LC_CTYPE, 'C');
        $string = strtoupper($string);
        setlocale(LC_CTYPE, $language);
        return $string;
    }

    /**
     * Converts strings into RFC's quoted-string or literal-c2s form.
     *
     * @param string $string The string to convert.
     *
     * @return string Result string.
     */
    function _escape($string)
    {
        // Some implementations don't allow UTF-8 characters in quoted-string,
        // use literal-c2s.
        if (preg_match('/[^\x01-\x09\x0B-\x0C\x0E-\x7F]/', $string)) {
            return sprintf("{%d+}\r\n%s", $this->_getLineLength($string), $string);
        }

        return '"' . addcslashes($string, '\\"') . '"';
    }

    /**
     * Write debug text to the current debug output handler.
     *
     * @param string $message Debug message text.
     *
     * @return void
     */
    function _debug($message)
    {
        if ($this->_debug) {
            if ($this->_debug_handler) {
                call_user_func_array($this->_debug_handler, [$this, $message]);
            } else {
                echo "$message\n";
            }
        }
    }
}
PK|c�\�]<--'pear/pear-core-minimal/src/OS/Guess.phpnu�[���<?php
/**
 * The OS_Guess class
 *
 * PHP versions 4 and 5
 *
 * @category  pear
 * @package   PEAR
 * @author    Stig Bakken <ssb@php.net>
 * @author    Gregory Beaver <cellog@php.net>
 * @copyright 1997-2009 The Authors
 * @license   http://opensource.org/licenses/bsd-license.php New BSD License
 * @link      http://pear.php.net/package/PEAR
 * @since     File available since PEAR 0.1
 */

// {{{ uname examples

// php_uname() without args returns the same as 'uname -a', or a PHP-custom
// string for Windows.
// PHP versions prior to 4.3 return the uname of the host where PHP was built,
// as of 4.3 it returns the uname of the host running the PHP code.
//
// PC RedHat Linux 7.1:
// Linux host.example.com 2.4.2-2 #1 Sun Apr 8 20:41:30 EDT 2001 i686 unknown
//
// PC Debian Potato:
// Linux host 2.4.17 #2 SMP Tue Feb 12 15:10:04 CET 2002 i686 unknown
//
// PC FreeBSD 3.3:
// FreeBSD host.example.com 3.3-STABLE FreeBSD 3.3-STABLE #0: Mon Feb 21 00:42:31 CET 2000     root@example.com:/usr/src/sys/compile/CONFIG  i386
//
// PC FreeBSD 4.3:
// FreeBSD host.example.com 4.3-RELEASE FreeBSD 4.3-RELEASE #1: Mon Jun 25 11:19:43 EDT 2001     root@example.com:/usr/src/sys/compile/CONFIG  i386
//
// PC FreeBSD 4.5:
// FreeBSD host.example.com 4.5-STABLE FreeBSD 4.5-STABLE #0: Wed Feb  6 23:59:23 CET 2002     root@example.com:/usr/src/sys/compile/CONFIG  i386
//
// PC FreeBSD 4.5 w/uname from GNU shellutils:
// FreeBSD host.example.com 4.5-STABLE FreeBSD 4.5-STABLE #0: Wed Feb  i386 unknown
//
// HP 9000/712 HP-UX 10:
// HP-UX iq B.10.10 A 9000/712 2008429113 two-user license
//
// HP 9000/712 HP-UX 10 w/uname from GNU shellutils:
// HP-UX host B.10.10 A 9000/712 unknown
//
// IBM RS6000/550 AIX 4.3:
// AIX host 3 4 000003531C00
//
// AIX 4.3 w/uname from GNU shellutils:
// AIX host 3 4 000003531C00 unknown
//
// SGI Onyx IRIX 6.5 w/uname from GNU shellutils:
// IRIX64 host 6.5 01091820 IP19 mips
//
// SGI Onyx IRIX 6.5:
// IRIX64 host 6.5 01091820 IP19
//
// SparcStation 20 Solaris 8 w/uname from GNU shellutils:
// SunOS host.example.com 5.8 Generic_108528-12 sun4m sparc
//
// SparcStation 20 Solaris 8:
// SunOS host.example.com 5.8 Generic_108528-12 sun4m sparc SUNW,SPARCstation-20
//
// Mac OS X (Darwin)
// Darwin home-eden.local 7.5.0 Darwin Kernel Version 7.5.0: Thu Aug  5 19:26:16 PDT 2004; root:xnu/xnu-517.7.21.obj~3/RELEASE_PPC  Power Macintosh
//
// Mac OS X early versions
//

// }}}

/* TODO:
 * - define endianness, to allow matchSignature("bigend") etc.
 */

/**
 * Retrieves information about the current operating system
 *
 * This class uses php_uname() to grok information about the current OS
 *
 * @category  pear
 * @package   PEAR
 * @author    Stig Bakken <ssb@php.net>
 * @author    Gregory Beaver <cellog@php.net>
 * @copyright 1997-2020 The Authors
 * @license   http://opensource.org/licenses/bsd-license.php New BSD License
 * @version   Release: @package_version@
 * @link      http://pear.php.net/package/PEAR
 * @since     Class available since Release 0.1
 */
class OS_Guess
{
    var $sysname;
    var $nodename;
    var $cpu;
    var $release;
    var $extra;

    function __construct($uname = null)
    {
        list($this->sysname,
             $this->release,
             $this->cpu,
             $this->extra,
             $this->nodename) = $this->parseSignature($uname);
    }

    function parseSignature($uname = null)
    {
        static $sysmap = array(
            'HP-UX' => 'hpux',
            'IRIX64' => 'irix',
        );
        static $cpumap = array(
            'i586' => 'i386',
            'i686' => 'i386',
            'ppc' => 'powerpc',
        );
        if ($uname === null) {
            $uname = php_uname();
        }
        $parts = preg_split('/\s+/', trim($uname));
        $n = count($parts);

        $release  = $machine = $cpu = '';
        $sysname  = $parts[0];
        $nodename = $parts[1];
        $cpu      = $parts[$n-1];
        $extra = '';
        if ($cpu == 'unknown') {
            $cpu = $parts[$n - 2];
        }

        switch ($sysname) {
            case 'AIX' :
                $release = "$parts[3].$parts[2]";
                break;
            case 'Windows' :
                $release = $parts[1];
                if ($release == '95/98') {
                    $release = '9x';
                }
                $cpu = 'i386';
                break;
            case 'Linux' :
                $extra = $this->_detectGlibcVersion();
                // use only the first two digits from the kernel version
                $release = preg_replace('/^([0-9]+\.[0-9]+).*/', '\1', $parts[2]);
                break;
            case 'Mac' :
                $sysname = 'darwin';
                $nodename = $parts[2];
                $release = $parts[3];
                $cpu = $this->_determineIfPowerpc($cpu, $parts);
                break;
            case 'Darwin' :
                $cpu = $this->_determineIfPowerpc($cpu, $parts);
                $release = preg_replace('/^([0-9]+\.[0-9]+).*/', '\1', $parts[2]);
                break;
            default:
                $release = preg_replace('/-.*/', '', $parts[2]);
                break;
        }

        if (isset($sysmap[$sysname])) {
            $sysname = $sysmap[$sysname];
        } else {
            $sysname = strtolower($sysname);
        }
        if (isset($cpumap[$cpu])) {
            $cpu = $cpumap[$cpu];
        }
        return array($sysname, $release, $cpu, $extra, $nodename);
    }

    function _determineIfPowerpc($cpu, $parts)
    {
        $n = count($parts);
        if ($cpu == 'Macintosh' && $parts[$n - 2] == 'Power') {
            $cpu = 'powerpc';
        }
        return $cpu;
    }

    function _detectGlibcVersion()
    {
        static $glibc = false;
        if ($glibc !== false) {
            return $glibc; // no need to run this multiple times
        }
        $major = $minor = 0;
        include_once "System.php";

        // Let's try reading possible libc.so.6 symlinks
        $libcs = array(
            '/lib64/libc.so.6',
            '/lib/libc.so.6',
            '/lib/i386-linux-gnu/libc.so.6'
        );
        $versions = array();
        foreach ($libcs as $file) {
            $versions = $this->_readGlibCVersionFromSymlink($file);
            if ($versions != []) {
                list($major, $minor) = $versions;
                break;
            }
        }

        // Use glibc's <features.h> header file to
        // get major and minor version number:
        if (!($major && $minor)) {
            $versions = $this->_readGlibCVersionFromFeaturesHeaderFile();
        }
        if (is_array($versions) && $versions != []) {
            list($major, $minor) = $versions;
        }

        if (!($major && $minor)) {
            return $glibc = '';
        }

        return $glibc = "glibc{$major}.{$minor}";
    }

    function _readGlibCVersionFromSymlink($file)
    {
        $versions = array();
        if (@is_link($file)
            && (preg_match('/^libc-(.*)\.so$/', basename(readlink($file)), $matches))
        ) {
            $versions = explode('.', $matches[1]);
        }
        return $versions;
    }


    function _readGlibCVersionFromFeaturesHeaderFile()
    {
        $features_header_file = '/usr/include/features.h';
        if (!(@file_exists($features_header_file)
            && @is_readable($features_header_file))
        ) {
            return array();
        }
        if (!@file_exists('/usr/bin/cpp') || !@is_executable('/usr/bin/cpp')) {
            return $this->_parseFeaturesHeaderFile($features_header_file);
        } // no cpp

        return $this->_fromGlibCTest();
    }

    function _parseFeaturesHeaderFile($features_header_file)
    {
        $features_file = fopen($features_header_file, 'rb');
        while (!feof($features_file)) {
            $line = fgets($features_file, 8192);
            if (!$this->_IsADefinition($line)) {
                continue;
            }
            if (strpos($line, '__GLIBC__')) {
                // major version number #define __GLIBC__ version
                $line = preg_split('/\s+/', $line);
                $glibc_major = trim($line[2]);
                if (isset($glibc_minor)) {
                    break;
                }
                continue;
            }

            if (strpos($line, '__GLIBC_MINOR__')) {
                // got the minor version number
                // #define __GLIBC_MINOR__ version
                $line = preg_split('/\s+/', $line);
                $glibc_minor = trim($line[2]);
                if (isset($glibc_major)) {
                    break;
                }
            }
        }
        fclose($features_file);
        if (!isset($glibc_major) || !isset($glibc_minor)) {
            return array();
        }
        return array(trim($glibc_major), trim($glibc_minor));
    }

    function _IsADefinition($line)
    {
        if ($line === false) {
            return false;
        }
        return strpos(trim($line), '#define') !== false;
    }

    function _fromGlibCTest()
    {
        $major = null;
        $minor = null;

        $tmpfile = System::mktemp("glibctest");
        $fp = fopen($tmpfile, "w");
        fwrite($fp, "#include <features.h>\n__GLIBC__ __GLIBC_MINOR__\n");
        fclose($fp);
        $cpp = popen("/usr/bin/cpp $tmpfile", "r");
        while ($line = fgets($cpp, 1024)) {
            if ($line[0] == '#' || trim($line) == '') {
                continue;
            }

            if (list($major, $minor) = explode(' ', trim($line))) {
                break;
            }
        }
        pclose($cpp);
        unlink($tmpfile);
        if ($major !== null && $minor !== null) {
            return [$major, $minor];
        }
    }

    function getSignature()
    {
        if (empty($this->extra)) {
            return "{$this->sysname}-{$this->release}-{$this->cpu}";
        }
        return "{$this->sysname}-{$this->release}-{$this->cpu}-{$this->extra}";
    }

    function getSysname()
    {
        return $this->sysname;
    }

    function getNodename()
    {
        return $this->nodename;
    }

    function getCpu()
    {
        return $this->cpu;
    }

    function getRelease()
    {
        return $this->release;
    }

    function getExtra()
    {
        return $this->extra;
    }

    function matchSignature($match)
    {
        $fragments = is_array($match) ? $match : explode('-', $match);
        $n = count($fragments);
        $matches = 0;
        if ($n > 0) {
            $matches += $this->_matchFragment($fragments[0], $this->sysname);
        }
        if ($n > 1) {
            $matches += $this->_matchFragment($fragments[1], $this->release);
        }
        if ($n > 2) {
            $matches += $this->_matchFragment($fragments[2], $this->cpu);
        }
        if ($n > 3) {
            $matches += $this->_matchFragment($fragments[3], $this->extra);
        }
        return ($matches == $n);
    }

    function _matchFragment($fragment, $value)
    {
        if (strcspn($fragment, '*?') < strlen($fragment)) {
            $expression = str_replace(
                array('*', '?', '/'),
                array('.*', '.', '\\/'),
                $fragment
            );
            $reg = '/^' . $expression . '\\z/';
            return preg_match($reg, $value);
        }
        return ($fragment == '*' || !strcasecmp($fragment, $value));
    }
}
/*
 * Local Variables:
 * indent-tabs-mode: nil
 * c-basic-offset: 4
 * End:
 */
PK|c�\7�?�����#pear/pear-core-minimal/src/PEAR.phpnu�[���<?php
/**
 * PEAR, the PHP Extension and Application Repository
 *
 * PEAR class and PEAR_Error class
 *
 * PHP versions 4 and 5
 *
 * @category   pear
 * @package    PEAR
 * @author     Sterling Hughes <sterling@php.net>
 * @author     Stig Bakken <ssb@php.net>
 * @author     Tomas V.V.Cox <cox@idecnet.com>
 * @author     Greg Beaver <cellog@php.net>
 * @copyright  1997-2010 The Authors
 * @license    http://opensource.org/licenses/bsd-license.php New BSD License
 * @link       http://pear.php.net/package/PEAR
 * @since      File available since Release 0.1
 */

/**#@+
 * ERROR constants
 */
define('PEAR_ERROR_RETURN',     1);
define('PEAR_ERROR_PRINT',      2);
define('PEAR_ERROR_TRIGGER',    4);
define('PEAR_ERROR_DIE',        8);
define('PEAR_ERROR_CALLBACK',  16);
/**
 * WARNING: obsolete
 * @deprecated
 */
define('PEAR_ERROR_EXCEPTION', 32);
/**#@-*/

if (substr(PHP_OS, 0, 3) == 'WIN') {
    define('OS_WINDOWS', true);
    define('OS_UNIX',    false);
    define('PEAR_OS',    'Windows');
} else {
    define('OS_WINDOWS', false);
    define('OS_UNIX',    true);
    define('PEAR_OS',    'Unix'); // blatant assumption
}

$GLOBALS['_PEAR_default_error_mode']     = PEAR_ERROR_RETURN;
$GLOBALS['_PEAR_default_error_options']  = E_USER_NOTICE;
$GLOBALS['_PEAR_destructor_object_list'] = array();
$GLOBALS['_PEAR_shutdown_funcs']         = array();
$GLOBALS['_PEAR_error_handler_stack']    = array();

@ini_set('track_errors', true);

/**
 * Base class for other PEAR classes.  Provides rudimentary
 * emulation of destructors.
 *
 * If you want a destructor in your class, inherit PEAR and make a
 * destructor method called _yourclassname (same name as the
 * constructor, but with a "_" prefix).  Also, in your constructor you
 * have to call the PEAR constructor: $this->PEAR();.
 * The destructor method will be called without parameters.  Note that
 * at in some SAPI implementations (such as Apache), any output during
 * the request shutdown (in which destructors are called) seems to be
 * discarded.  If you need to get any debug information from your
 * destructor, use error_log(), syslog() or something similar.
 *
 * IMPORTANT! To use the emulated destructors you need to create the
 * objects by reference: $obj =& new PEAR_child;
 *
 * @category   pear
 * @package    PEAR
 * @author     Stig Bakken <ssb@php.net>
 * @author     Tomas V.V. Cox <cox@idecnet.com>
 * @author     Greg Beaver <cellog@php.net>
 * @copyright  1997-2006 The PHP Group
 * @license    http://opensource.org/licenses/bsd-license.php New BSD License
 * @version    Release: @package_version@
 * @link       http://pear.php.net/package/PEAR
 * @see        PEAR_Error
 * @since      Class available since PHP 4.0.2
 * @link        http://pear.php.net/manual/en/core.pear.php#core.pear.pear
 */
class PEAR
{
    /**
     * Whether to enable internal debug messages.
     *
     * @var     bool
     * @access  private
     */
    var $_debug = false;

    /**
     * Default error mode for this object.
     *
     * @var     int
     * @access  private
     */
    var $_default_error_mode = null;

    /**
     * Default error options used for this object when error mode
     * is PEAR_ERROR_TRIGGER.
     *
     * @var     int
     * @access  private
     */
    var $_default_error_options = null;

    /**
     * Default error handler (callback) for this object, if error mode is
     * PEAR_ERROR_CALLBACK.
     *
     * @var     string
     * @access  private
     */
    var $_default_error_handler = '';

    /**
     * Which class to use for error objects.
     *
     * @var     string
     * @access  private
     */
    var $_error_class = 'PEAR_Error';

    /**
     * An array of expected errors.
     *
     * @var     array
     * @access  private
     */
    var $_expected_errors = array();

    /**
     * List of methods that can be called both statically and non-statically.
     * @var array
     */
    protected static $bivalentMethods = array(
        'setErrorHandling' => true,
        'raiseError' => true,
        'throwError' => true,
        'pushErrorHandling' => true,
        'popErrorHandling' => true,
    );

    /**
     * Constructor.  Registers this object in
     * $_PEAR_destructor_object_list for destructor emulation if a
     * destructor object exists.
     *
     * @param string $error_class  (optional) which class to use for
     *        error objects, defaults to PEAR_Error.
     * @access public
     * @return void
     */
    function __construct($error_class = null)
    {
        $classname = strtolower(get_class($this));
        if ($this->_debug) {
            print "PEAR constructor called, class=$classname\n";
        }

        if ($error_class !== null) {
            $this->_error_class = $error_class;
        }

        while ($classname && strcasecmp($classname, "pear")) {
            $destructor = "_$classname";
            if (method_exists($this, $destructor)) {
                global $_PEAR_destructor_object_list;
                $_PEAR_destructor_object_list[] = $this;
                if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) {
                    register_shutdown_function("_PEAR_call_destructors");
                    $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true;
                }
                break;
            } else {
                $classname = get_parent_class($classname);
            }
        }
    }

    /**
     * Only here for backwards compatibility.
     * E.g. Archive_Tar calls $this->PEAR() in its constructor.
     *
     * @param string $error_class Which class to use for error objects,
     *                            defaults to PEAR_Error.
     */
    public function PEAR($error_class = null)
    {
        self::__construct($error_class);
    }

    /**
     * Destructor (the emulated type of...).  Does nothing right now,
     * but is included for forward compatibility, so subclass
     * destructors should always call it.
     *
     * See the note in the class desciption about output from
     * destructors.
     *
     * @access public
     * @return void
     */
    function _PEAR() {
        if ($this->_debug) {
            printf("PEAR destructor called, class=%s\n", strtolower(get_class($this)));
        }
    }

    public function __call($method, $arguments)
    {
        if (!isset(self::$bivalentMethods[$method])) {
            trigger_error(
                'Call to undefined method PEAR::' . $method . '()', E_USER_ERROR
            );
        }
        return call_user_func_array(
            array(__CLASS__, '_' . $method),
            array_merge(array($this), $arguments)
        );
    }

    public static function __callStatic($method, $arguments)
    {
        if (!isset(self::$bivalentMethods[$method])) {
            trigger_error(
                'Call to undefined method PEAR::' . $method . '()', E_USER_ERROR
            );
        }
        return call_user_func_array(
            array(__CLASS__, '_' . $method),
            array_merge(array(null), $arguments)
        );
    }

    /**
    * If you have a class that's mostly/entirely static, and you need static
    * properties, you can use this method to simulate them. Eg. in your method(s)
    * do this: $myVar = &PEAR::getStaticProperty('myclass', 'myVar');
    * You MUST use a reference, or they will not persist!
    *
    * @param  string $class  The calling classname, to prevent clashes
    * @param  string $var    The variable to retrieve.
    * @return mixed   A reference to the variable. If not set it will be
    *                 auto initialised to NULL.
    */
    public static function &getStaticProperty($class, $var)
    {
        static $properties;
        if (!isset($properties[$class])) {
            $properties[$class] = array();
        }

        if (!array_key_exists($var, $properties[$class])) {
            $properties[$class][$var] = null;
        }

        return $properties[$class][$var];
    }

    /**
    * Use this function to register a shutdown method for static
    * classes.
    *
    * @param  mixed $func  The function name (or array of class/method) to call
    * @param  mixed $args  The arguments to pass to the function
    *
    * @return void
    */
    public static function registerShutdownFunc($func, $args = array())
    {
        // if we are called statically, there is a potential
        // that no shutdown func is registered.  Bug #6445
        if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) {
            register_shutdown_function("_PEAR_call_destructors");
            $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true;
        }
        $GLOBALS['_PEAR_shutdown_funcs'][] = array($func, $args);
    }

    /**
     * Tell whether a value is a PEAR error.
     *
     * @param   mixed $data   the value to test
     * @param   int   $code   if $data is an error object, return true
     *                        only if $code is a string and
     *                        $obj->getMessage() == $code or
     *                        $code is an integer and $obj->getCode() == $code
     *
     * @return  bool    true if parameter is an error
     */
    public static function isError($data, $code = null)
    {
        if (!is_a($data, 'PEAR_Error')) {
            return false;
        }

        if (is_null($code)) {
            return true;
        } elseif (is_string($code)) {
            return $data->getMessage() == $code;
        }

        return $data->getCode() == $code;
    }

    /**
     * Sets how errors generated by this object should be handled.
     * Can be invoked both in objects and statically.  If called
     * statically, setErrorHandling sets the default behaviour for all
     * PEAR objects.  If called in an object, setErrorHandling sets
     * the default behaviour for that object.
     *
     * @param object $object
     *        Object the method was called on (non-static mode)
     *
     * @param int $mode
     *        One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT,
     *        PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE,
     *        PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION.
     *
     * @param mixed $options
     *        When $mode is PEAR_ERROR_TRIGGER, this is the error level (one
     *        of E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR).
     *
     *        When $mode is PEAR_ERROR_CALLBACK, this parameter is expected
     *        to be the callback function or method.  A callback
     *        function is a string with the name of the function, a
     *        callback method is an array of two elements: the element
     *        at index 0 is the object, and the element at index 1 is
     *        the name of the method to call in the object.
     *
     *        When $mode is PEAR_ERROR_PRINT or PEAR_ERROR_DIE, this is
     *        a printf format string used when printing the error
     *        message.
     *
     * @access public
     * @return void
     * @see PEAR_ERROR_RETURN
     * @see PEAR_ERROR_PRINT
     * @see PEAR_ERROR_TRIGGER
     * @see PEAR_ERROR_DIE
     * @see PEAR_ERROR_CALLBACK
     * @see PEAR_ERROR_EXCEPTION
     *
     * @since PHP 4.0.5
     */
    protected static function _setErrorHandling(
        $object, $mode = null, $options = null
    ) {
        if ($object !== null) {
            $setmode     = &$object->_default_error_mode;
            $setoptions  = &$object->_default_error_options;
        } else {
            $setmode     = &$GLOBALS['_PEAR_default_error_mode'];
            $setoptions  = &$GLOBALS['_PEAR_default_error_options'];
        }

        switch ($mode) {
            case PEAR_ERROR_EXCEPTION:
            case PEAR_ERROR_RETURN:
            case PEAR_ERROR_PRINT:
            case PEAR_ERROR_TRIGGER:
            case PEAR_ERROR_DIE:
            case null:
                $setmode = $mode;
                $setoptions = $options;
                break;

            case PEAR_ERROR_CALLBACK:
                $setmode = $mode;
                // class/object method callback
                if (is_callable($options)) {
                    $setoptions = $options;
                } else {
                    trigger_error("invalid error callback", E_USER_WARNING);
                }
                break;

            default:
                trigger_error("invalid error mode", E_USER_WARNING);
                break;
        }
    }

    /**
     * This method is used to tell which errors you expect to get.
     * Expected errors are always returned with error mode
     * PEAR_ERROR_RETURN.  Expected error codes are stored in a stack,
     * and this method pushes a new element onto it.  The list of
     * expected errors are in effect until they are popped off the
     * stack with the popExpect() method.
     *
     * Note that this method can not be called statically
     *
     * @param mixed $code a single error code or an array of error codes to expect
     *
     * @return int     the new depth of the "expected errors" stack
     * @access public
     */
    function expectError($code = '*')
    {
        if (is_array($code)) {
            array_push($this->_expected_errors, $code);
        } else {
            array_push($this->_expected_errors, array($code));
        }
        return count($this->_expected_errors);
    }

    /**
     * This method pops one element off the expected error codes
     * stack.
     *
     * @return array   the list of error codes that were popped
     */
    function popExpect()
    {
        return array_pop($this->_expected_errors);
    }

    /**
     * This method checks unsets an error code if available
     *
     * @param mixed error code
     * @return bool true if the error code was unset, false otherwise
     * @access private
     * @since PHP 4.3.0
     */
    function _checkDelExpect($error_code)
    {
        $deleted = false;
        foreach ($this->_expected_errors as $key => $error_array) {
            if (in_array($error_code, $error_array)) {
                unset($this->_expected_errors[$key][array_search($error_code, $error_array)]);
                $deleted = true;
            }

            // clean up empty arrays
            if (0 == count($this->_expected_errors[$key])) {
                unset($this->_expected_errors[$key]);
            }
        }

        return $deleted;
    }

    /**
     * This method deletes all occurrences of the specified element from
     * the expected error codes stack.
     *
     * @param  mixed $error_code error code that should be deleted
     * @return mixed list of error codes that were deleted or error
     * @access public
     * @since PHP 4.3.0
     */
    function delExpect($error_code)
    {
        $deleted = false;
        if ((is_array($error_code) && (0 != count($error_code)))) {
            // $error_code is a non-empty array here; we walk through it trying
            // to unset all values
            foreach ($error_code as $key => $error) {
                $deleted =  $this->_checkDelExpect($error) ? true : false;
            }

            return $deleted ? true : PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME
        } elseif (!empty($error_code)) {
            // $error_code comes alone, trying to unset it
            if ($this->_checkDelExpect($error_code)) {
                return true;
            }

            return PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME
        }

        // $error_code is empty
        return PEAR::raiseError("The expected error you submitted is empty"); // IMPROVE ME
    }

    /**
     * This method is a wrapper that returns an instance of the
     * configured error class with this object's default error
     * handling applied.  If the $mode and $options parameters are not
     * specified, the object's defaults are used.
     *
     * @param mixed $message a text error message or a PEAR error object
     *
     * @param int $code      a numeric error code (it is up to your class
     *                  to define these if you want to use codes)
     *
     * @param int $mode      One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT,
     *                  PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE,
     *                  PEAR_ERROR_CALLBACK, PEAR_ERROR_EXCEPTION.
     *
     * @param mixed $options If $mode is PEAR_ERROR_TRIGGER, this parameter
     *                  specifies the PHP-internal error level (one of
     *                  E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR).
     *                  If $mode is PEAR_ERROR_CALLBACK, this
     *                  parameter specifies the callback function or
     *                  method.  In other error modes this parameter
     *                  is ignored.
     *
     * @param string $userinfo If you need to pass along for example debug
     *                  information, this parameter is meant for that.
     *
     * @param string $error_class The returned error object will be
     *                  instantiated from this class, if specified.
     *
     * @param bool $skipmsg If true, raiseError will only pass error codes,
     *                  the error message parameter will be dropped.
     *
     * @return object   a PEAR error object
     * @see PEAR::setErrorHandling
     * @since PHP 4.0.5
     */
    protected static function _raiseError($object,
                         $message = null,
                         $code = null,
                         $mode = null,
                         $options = null,
                         $userinfo = null,
                         $error_class = null,
                         $skipmsg = false)
    {
        // The error is yet a PEAR error object
        if (is_object($message)) {
            $code        = $message->getCode();
            $userinfo    = $message->getUserInfo();
            $error_class = $message->getType();
            $message->error_message_prefix = '';
            $message     = $message->getMessage();
        }

        if (
            $object !== null &&
            isset($object->_expected_errors) &&
            count($object->_expected_errors) > 0 &&
            count($exp = end($object->_expected_errors))
        ) {
            if ($exp[0] === "*" ||
                (is_int(reset($exp)) && in_array($code, $exp)) ||
                (is_string(reset($exp)) && in_array($message, $exp))
            ) {
                $mode = PEAR_ERROR_RETURN;
            }
        }

        // No mode given, try global ones
        if ($mode === null) {
            // Class error handler
            if ($object !== null && isset($object->_default_error_mode)) {
                $mode    = $object->_default_error_mode;
                $options = $object->_default_error_options;
            // Global error handler
            } elseif (isset($GLOBALS['_PEAR_default_error_mode'])) {
                $mode    = $GLOBALS['_PEAR_default_error_mode'];
                $options = $GLOBALS['_PEAR_default_error_options'];
            }
        }

        if ($error_class !== null) {
            $ec = $error_class;
        } elseif ($object !== null && isset($object->_error_class)) {
            $ec = $object->_error_class;
        } else {
            $ec = 'PEAR_Error';
        }

        if ($skipmsg) {
            $a = new $ec($code, $mode, $options, $userinfo);
        } else {
            $a = new $ec($message, $code, $mode, $options, $userinfo);
        }

        return $a;
    }

    /**
     * Simpler form of raiseError with fewer options.  In most cases
     * message, code and userinfo are enough.
     *
     * @param mixed $message a text error message or a PEAR error object
     *
     * @param int $code      a numeric error code (it is up to your class
     *                  to define these if you want to use codes)
     *
     * @param string $userinfo If you need to pass along for example debug
     *                  information, this parameter is meant for that.
     *
     * @return object   a PEAR error object
     * @see PEAR::raiseError
     */
    protected static function _throwError($object, $message = null, $code = null, $userinfo = null)
    {
        if ($object !== null) {
            $a = $object->raiseError($message, $code, null, null, $userinfo);
            return $a;
        }

        $a = PEAR::raiseError($message, $code, null, null, $userinfo);
        return $a;
    }

    public static function staticPushErrorHandling($mode, $options = null)
    {
        $stack       = &$GLOBALS['_PEAR_error_handler_stack'];
        $def_mode    = &$GLOBALS['_PEAR_default_error_mode'];
        $def_options = &$GLOBALS['_PEAR_default_error_options'];
        $stack[] = array($def_mode, $def_options);
        switch ($mode) {
            case PEAR_ERROR_EXCEPTION:
            case PEAR_ERROR_RETURN:
            case PEAR_ERROR_PRINT:
            case PEAR_ERROR_TRIGGER:
            case PEAR_ERROR_DIE:
            case null:
                $def_mode = $mode;
                $def_options = $options;
                break;

            case PEAR_ERROR_CALLBACK:
                $def_mode = $mode;
                // class/object method callback
                if (is_callable($options)) {
                    $def_options = $options;
                } else {
                    trigger_error("invalid error callback", E_USER_WARNING);
                }
                break;

            default:
                trigger_error("invalid error mode", E_USER_WARNING);
                break;
        }
        $stack[] = array($mode, $options);
        return true;
    }

    public static function staticPopErrorHandling()
    {
        $stack = &$GLOBALS['_PEAR_error_handler_stack'];
        $setmode     = &$GLOBALS['_PEAR_default_error_mode'];
        $setoptions  = &$GLOBALS['_PEAR_default_error_options'];
        array_pop($stack);
        list($mode, $options) = $stack[sizeof($stack) - 1];
        array_pop($stack);
        switch ($mode) {
            case PEAR_ERROR_EXCEPTION:
            case PEAR_ERROR_RETURN:
            case PEAR_ERROR_PRINT:
            case PEAR_ERROR_TRIGGER:
            case PEAR_ERROR_DIE:
            case null:
                $setmode = $mode;
                $setoptions = $options;
                break;

            case PEAR_ERROR_CALLBACK:
                $setmode = $mode;
                // class/object method callback
                if (is_callable($options)) {
                    $setoptions = $options;
                } else {
                    trigger_error("invalid error callback", E_USER_WARNING);
                }
                break;

            default:
                trigger_error("invalid error mode", E_USER_WARNING);
                break;
        }
        return true;
    }

    /**
     * Push a new error handler on top of the error handler options stack. With this
     * you can easily override the actual error handler for some code and restore
     * it later with popErrorHandling.
     *
     * @param mixed $mode (same as setErrorHandling)
     * @param mixed $options (same as setErrorHandling)
     *
     * @return bool Always true
     *
     * @see PEAR::setErrorHandling
     */
    protected static function _pushErrorHandling($object, $mode, $options = null)
    {
        $stack = &$GLOBALS['_PEAR_error_handler_stack'];
        if ($object !== null) {
            $def_mode    = &$object->_default_error_mode;
            $def_options = &$object->_default_error_options;
        } else {
            $def_mode    = &$GLOBALS['_PEAR_default_error_mode'];
            $def_options = &$GLOBALS['_PEAR_default_error_options'];
        }
        $stack[] = array($def_mode, $def_options);

        if ($object !== null) {
            $object->setErrorHandling($mode, $options);
        } else {
            PEAR::setErrorHandling($mode, $options);
        }
        $stack[] = array($mode, $options);
        return true;
    }

    /**
    * Pop the last error handler used
    *
    * @return bool Always true
    *
    * @see PEAR::pushErrorHandling
    */
    protected static function _popErrorHandling($object)
    {
        $stack = &$GLOBALS['_PEAR_error_handler_stack'];
        array_pop($stack);
        list($mode, $options) = $stack[sizeof($stack) - 1];
        array_pop($stack);
        if ($object !== null) {
            $object->setErrorHandling($mode, $options);
        } else {
            PEAR::setErrorHandling($mode, $options);
        }
        return true;
    }

    /**
    * OS independent PHP extension load. Remember to take care
    * on the correct extension name for case sensitive OSes.
    *
    * @param string $ext The extension name
    * @return bool Success or not on the dl() call
    */
    public static function loadExtension($ext)
    {
        if (extension_loaded($ext)) {
            return true;
        }

        // if either returns true dl() will produce a FATAL error, stop that
        if (
            function_exists('dl') === false ||
            ini_get('enable_dl') != 1
        ) {
            return false;
        }

        if (OS_WINDOWS) {
            $suffix = '.dll';
        } elseif (PHP_OS == 'HP-UX') {
            $suffix = '.sl';
        } elseif (PHP_OS == 'AIX') {
            $suffix = '.a';
        } elseif (PHP_OS == 'OSX') {
            $suffix = '.bundle';
        } else {
            $suffix = '.so';
        }

        return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix);
    }

    /**
     * Get SOURCE_DATE_EPOCH environment variable
     * See https://reproducible-builds.org/specs/source-date-epoch/
     *
     * @return int
     * @access public
     */
    static function getSourceDateEpoch()
    {
        if ($source_date_epoch = getenv('SOURCE_DATE_EPOCH')) {
            if (preg_match('/^\d+$/', $source_date_epoch)) {
                return (int) $source_date_epoch;
            } else {
            //  "If the value is malformed, the build process SHOULD exit with a non-zero error code."
            self::raiseError("Invalid SOURCE_DATE_EPOCH: $source_date_epoch");
            exit(1);
            }
        } else {
            return time();
        }
    }
}

function _PEAR_call_destructors()
{
    global $_PEAR_destructor_object_list;
    if (is_array($_PEAR_destructor_object_list) &&
        sizeof($_PEAR_destructor_object_list))
    {
        reset($_PEAR_destructor_object_list);

        $destructLifoExists = PEAR::getStaticProperty('PEAR', 'destructlifo');

        if ($destructLifoExists) {
            $_PEAR_destructor_object_list = array_reverse($_PEAR_destructor_object_list);
        }

        foreach ($_PEAR_destructor_object_list as $k => $objref) {
            $classname = get_class($objref);
            while ($classname) {
                $destructor = "_$classname";
                if (method_exists($objref, $destructor)) {
                    $objref->$destructor();
                    break;
                } else {
                    $classname = get_parent_class($classname);
                }
            }
        }
        // Empty the object list to ensure that destructors are
        // not called more than once.
        $_PEAR_destructor_object_list = array();
    }

    // Now call the shutdown functions
    if (
        isset($GLOBALS['_PEAR_shutdown_funcs']) &&
        is_array($GLOBALS['_PEAR_shutdown_funcs']) &&
        !empty($GLOBALS['_PEAR_shutdown_funcs'])
    ) {
        foreach ($GLOBALS['_PEAR_shutdown_funcs'] as $value) {
            call_user_func_array($value[0], $value[1]);
        }
    }
}

/**
 * Standard PEAR error class for PHP 4
 *
 * This class is supserseded by {@link PEAR_Exception} in PHP 5
 *
 * @category   pear
 * @package    PEAR
 * @author     Stig Bakken <ssb@php.net>
 * @author     Tomas V.V. Cox <cox@idecnet.com>
 * @author     Gregory Beaver <cellog@php.net>
 * @copyright  1997-2006 The PHP Group
 * @license    http://opensource.org/licenses/bsd-license.php New BSD License
 * @version    Release: @package_version@
 * @link       http://pear.php.net/manual/en/core.pear.pear-error.php
 * @see        PEAR::raiseError(), PEAR::throwError()
 * @since      Class available since PHP 4.0.2
 */
class PEAR_Error
{
    var $error_message_prefix = '';
    var $mode                 = PEAR_ERROR_RETURN;
    var $level                = E_USER_NOTICE;
    var $code                 = -1;
    var $message              = '';
    var $userinfo             = '';
    var $backtrace            = null;
    var $callback             = null;

    /**
     * PEAR_Error constructor
     *
     * @param string $message  message
     *
     * @param int $code     (optional) error code
     *
     * @param int $mode     (optional) error mode, one of: PEAR_ERROR_RETURN,
     * PEAR_ERROR_PRINT, PEAR_ERROR_DIE, PEAR_ERROR_TRIGGER,
     * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION
     *
     * @param mixed $options   (optional) error level, _OR_ in the case of
     * PEAR_ERROR_CALLBACK, the callback function or object/method
     * tuple.
     *
     * @param string $userinfo (optional) additional user/debug info
     *
     * @access public
     *
     */
    function __construct($message = 'unknown error', $code = null,
                        $mode = null, $options = null, $userinfo = null)
    {
        if ($mode === null) {
            $mode = PEAR_ERROR_RETURN;
        }
        $this->message   = $message;
        $this->code      = $code;
        $this->mode      = $mode;
        $this->userinfo  = $userinfo;

        $skiptrace = PEAR::getStaticProperty('PEAR_Error', 'skiptrace');

        if (!$skiptrace) {
            $this->backtrace = debug_backtrace();
            if (isset($this->backtrace[0]) && isset($this->backtrace[0]['object'])) {
                unset($this->backtrace[0]['object']);
            }
        }

        if ($mode & PEAR_ERROR_CALLBACK) {
            $this->level = E_USER_NOTICE;
            $this->callback = $options;
        } else {
            if ($options === null) {
                $options = E_USER_NOTICE;
            }

            $this->level = $options;
            $this->callback = null;
        }

        if ($this->mode & PEAR_ERROR_PRINT) {
            if (is_null($options) || is_int($options)) {
                $format = "%s";
            } else {
                $format = $options;
            }

            printf($format, $this->getMessage());
        }

        if ($this->mode & PEAR_ERROR_TRIGGER) {
            trigger_error($this->getMessage(), $this->level);
        }

        if ($this->mode & PEAR_ERROR_DIE) {
            $msg = $this->getMessage();
            if (is_null($options) || is_int($options)) {
                $format = "%s";
                if (substr($msg, -1) != "\n") {
                    $msg .= "\n";
                }
            } else {
                $format = $options;
            }
            printf($format, $msg);
            exit($code);
        }

        if ($this->mode & PEAR_ERROR_CALLBACK && is_callable($this->callback)) {
            call_user_func($this->callback, $this);
        }

        if ($this->mode & PEAR_ERROR_EXCEPTION) {
            trigger_error("PEAR_ERROR_EXCEPTION is obsolete, use class PEAR_Exception for exceptions", E_USER_WARNING);
            eval('$e = new Exception($this->message, $this->code);throw($e);');
        }
    }

    /**
     * Only here for backwards compatibility.
     *
     * Class "Cache_Error" still uses it, among others.
     *
     * @param string $message  Message
     * @param int    $code     Error code
     * @param int    $mode     Error mode
     * @param mixed  $options  See __construct()
     * @param string $userinfo Additional user/debug info
     */
    public function PEAR_Error(
        $message = 'unknown error', $code = null, $mode = null,
        $options = null, $userinfo = null
    ) {
        self::__construct($message, $code, $mode, $options, $userinfo);
    }

    /**
     * Get the error mode from an error object.
     *
     * @return int error mode
     * @access public
     */
    function getMode()
    {
        return $this->mode;
    }

    /**
     * Get the callback function/method from an error object.
     *
     * @return mixed callback function or object/method array
     * @access public
     */
    function getCallback()
    {
        return $this->callback;
    }

    /**
     * Get the error message from an error object.
     *
     * @return  string  full error message
     * @access public
     */
    function getMessage()
    {
        return ($this->error_message_prefix . $this->message);
    }

    /**
     * Get error code from an error object
     *
     * @return int error code
     * @access public
     */
     function getCode()
     {
        return $this->code;
     }

    /**
     * Get the name of this error/exception.
     *
     * @return string error/exception name (type)
     * @access public
     */
    function getType()
    {
        return get_class($this);
    }

    /**
     * Get additional user-supplied information.
     *
     * @return string user-supplied information
     * @access public
     */
    function getUserInfo()
    {
        return $this->userinfo;
    }

    /**
     * Get additional debug information supplied by the application.
     *
     * @return string debug information
     * @access public
     */
    function getDebugInfo()
    {
        return $this->getUserInfo();
    }

    /**
     * Get the call backtrace from where the error was generated.
     * Supported with PHP 4.3.0 or newer.
     *
     * @param int $frame (optional) what frame to fetch
     * @return array Backtrace, or NULL if not available.
     * @access public
     */
    function getBacktrace($frame = null)
    {
        if (defined('PEAR_IGNORE_BACKTRACE')) {
            return null;
        }
        if ($frame === null) {
            return $this->backtrace;
        }
        return $this->backtrace[$frame];
    }

    function addUserInfo($info)
    {
        if (empty($this->userinfo)) {
            $this->userinfo = $info;
        } else {
            $this->userinfo .= " ** $info";
        }
    }

    function __toString()
    {
        return $this->getMessage();
    }

    /**
     * Make a string representation of this object.
     *
     * @return string a string with an object summary
     * @access public
     */
    function toString()
    {
        $modes = array();
        $levels = array(E_USER_NOTICE  => 'notice',
                        E_USER_WARNING => 'warning',
                        E_USER_ERROR   => 'error');
        if ($this->mode & PEAR_ERROR_CALLBACK) {
            if (is_array($this->callback)) {
                $callback = (is_object($this->callback[0]) ?
                    strtolower(get_class($this->callback[0])) :
                    $this->callback[0]) . '::' .
                    $this->callback[1];
            } else {
                $callback = $this->callback;
            }
            return sprintf('[%s: message="%s" code=%d mode=callback '.
                           'callback=%s prefix="%s" info="%s"]',
                           strtolower(get_class($this)), $this->message, $this->code,
                           $callback, $this->error_message_prefix,
                           $this->userinfo);
        }
        if ($this->mode & PEAR_ERROR_PRINT) {
            $modes[] = 'print';
        }
        if ($this->mode & PEAR_ERROR_TRIGGER) {
            $modes[] = 'trigger';
        }
        if ($this->mode & PEAR_ERROR_DIE) {
            $modes[] = 'die';
        }
        if ($this->mode & PEAR_ERROR_RETURN) {
            $modes[] = 'return';
        }
        return sprintf('[%s: message="%s" code=%d mode=%s level=%s '.
                       'prefix="%s" info="%s"]',
                       strtolower(get_class($this)), $this->message, $this->code,
                       implode("|", $modes), $levels[$this->level],
                       $this->error_message_prefix,
                       $this->userinfo);
    }
}

/*
 * Local Variables:
 * mode: php
 * tab-width: 4
 * c-basic-offset: 4
 * End:
 */
PK|c�\V���.pear/pear-core-minimal/src/PEAR/ErrorStack.phpnu�[���<?php
/**
 * Error Stack Implementation
 * 
 * This is an incredibly simple implementation of a very complex error handling
 * facility.  It contains the ability
 * to track multiple errors from multiple packages simultaneously.  In addition,
 * it can track errors of many levels, save data along with the error, context
 * information such as the exact file, line number, class and function that
 * generated the error, and if necessary, it can raise a traditional PEAR_Error.
 * It has built-in support for PEAR::Log, to log errors as they occur
 * 
 * Since version 0.2alpha, it is also possible to selectively ignore errors,
 * through the use of an error callback, see {@link pushCallback()}
 * 
 * Since version 0.3alpha, it is possible to specify the exception class
 * returned from {@link push()}
 *
 * Since version PEAR1.3.2, ErrorStack no longer instantiates an exception class.  This can
 * still be done quite handily in an error callback or by manipulating the returned array
 * @category   Debugging
 * @package    PEAR_ErrorStack
 * @author     Greg Beaver <cellog@php.net>
 * @copyright  2004-2008 Greg Beaver
 * @license    http://opensource.org/licenses/bsd-license.php New BSD License
 * @link       http://pear.php.net/package/PEAR_ErrorStack
 */

/**
 * Singleton storage
 * 
 * Format:
 * <pre>
 * array(
 *  'package1' => PEAR_ErrorStack object,
 *  'package2' => PEAR_ErrorStack object,
 *  ...
 * )
 * </pre>
 * @access private
 * @global array $GLOBALS['_PEAR_ERRORSTACK_SINGLETON']
 */
$GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] = array();

/**
 * Global error callback (default)
 * 
 * This is only used if set to non-false.  * is the default callback for
 * all packages, whereas specific packages may set a default callback
 * for all instances, regardless of whether they are a singleton or not.
 *
 * To exclude non-singletons, only set the local callback for the singleton
 * @see PEAR_ErrorStack::setDefaultCallback()
 * @access private
 * @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK']
 */
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'] = array(
    '*' => false,
);

/**
 * Global Log object (default)
 * 
 * This is only used if set to non-false.  Use to set a default log object for
 * all stacks, regardless of instantiation order or location
 * @see PEAR_ErrorStack::setDefaultLogger()
 * @access private
 * @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']
 */
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = false;

/**
 * Global Overriding Callback
 * 
 * This callback will override any error callbacks that specific loggers have set.
 * Use with EXTREME caution
 * @see PEAR_ErrorStack::staticPushCallback()
 * @access private
 * @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']
 */
$GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = array();

/**#@+
 * One of four possible return values from the error Callback
 * @see PEAR_ErrorStack::_errorCallback()
 */
/**
 * If this is returned, then the error will be both pushed onto the stack
 * and logged.
 */
define('PEAR_ERRORSTACK_PUSHANDLOG', 1);
/**
 * If this is returned, then the error will only be pushed onto the stack,
 * and not logged.
 */
define('PEAR_ERRORSTACK_PUSH', 2);
/**
 * If this is returned, then the error will only be logged, but not pushed
 * onto the error stack.
 */
define('PEAR_ERRORSTACK_LOG', 3);
/**
 * If this is returned, then the error is completely ignored.
 */
define('PEAR_ERRORSTACK_IGNORE', 4);
/**
 * If this is returned, then the error is logged and die() is called.
 */
define('PEAR_ERRORSTACK_DIE', 5);
/**#@-*/

/**
 * Error code for an attempt to instantiate a non-class as a PEAR_ErrorStack in
 * the singleton method.
 */
define('PEAR_ERRORSTACK_ERR_NONCLASS', 1);

/**
 * Error code for an attempt to pass an object into {@link PEAR_ErrorStack::getMessage()}
 * that has no __toString() method
 */
define('PEAR_ERRORSTACK_ERR_OBJTOSTRING', 2);
/**
 * Error Stack Implementation
 *
 * Usage:
 * <code>
 * // global error stack
 * $global_stack = &PEAR_ErrorStack::singleton('MyPackage');
 * // local error stack
 * $local_stack = new PEAR_ErrorStack('MyPackage');
 * </code>
 * @author     Greg Beaver <cellog@php.net>
 * @version    @package_version@
 * @package    PEAR_ErrorStack
 * @category   Debugging
 * @copyright  2004-2008 Greg Beaver
 * @license    http://opensource.org/licenses/bsd-license.php New BSD License
 * @link       http://pear.php.net/package/PEAR_ErrorStack
 */
class PEAR_ErrorStack {
    /**
     * Errors are stored in the order that they are pushed on the stack.
     * @since 0.4alpha Errors are no longer organized by error level.
     * This renders pop() nearly unusable, and levels could be more easily
     * handled in a callback anyway
     * @var array
     * @access private
     */
    var $_errors = array();

    /**
     * Storage of errors by level.
     *
     * Allows easy retrieval and deletion of only errors from a particular level
     * @since PEAR 1.4.0dev
     * @var array
     * @access private
     */
    var $_errorsByLevel = array();

    /**
     * Package name this error stack represents
     * @var string
     * @access protected
     */
    var $_package;
    
    /**
     * Determines whether a PEAR_Error is thrown upon every error addition
     * @var boolean
     * @access private
     */
    var $_compat = false;
    
    /**
     * If set to a valid callback, this will be used to generate the error
     * message from the error code, otherwise the message passed in will be
     * used
     * @var false|string|array
     * @access private
     */
    var $_msgCallback = false;
    
    /**
     * If set to a valid callback, this will be used to generate the error
     * context for an error.  For PHP-related errors, this will be a file
     * and line number as retrieved from debug_backtrace(), but can be
     * customized for other purposes.  The error might actually be in a separate
     * configuration file, or in a database query.
     * @var false|string|array
     * @access protected
     */
    var $_contextCallback = false;

    /**
     * If set to a valid callback, this will be called every time an error
     * is pushed onto the stack.  The return value will be used to determine
     * whether to allow an error to be pushed or logged.
     *
     * The return value must be one an PEAR_ERRORSTACK_* constant
     * @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG
     * @var false|string|array
     * @access protected
     */
    var $_errorCallback = array();

    /**
     * PEAR::Log object for logging errors
     * @var false|Log
     * @access protected
     */
    var $_logger = false;

    /**
     * Error messages - designed to be overridden
     * @var array
     * @abstract
     */
    var $_errorMsgs = array();

    /**
     * Set up a new error stack
     *
     * @param string   $package name of the package this error stack represents
     * @param callback $msgCallback callback used for error message generation
     * @param callback $contextCallback callback used for context generation,
     *                 defaults to {@link getFileLine()}
     * @param boolean  $throwPEAR_Error
     */
    function __construct($package, $msgCallback = false, $contextCallback = false,
                         $throwPEAR_Error = false)
    {
        $this->_package = $package;
        $this->setMessageCallback($msgCallback);
        $this->setContextCallback($contextCallback);
        $this->_compat = $throwPEAR_Error;
    }
    
    /**
     * Return a single error stack for this package.
     * 
     * Note that all parameters are ignored if the stack for package $package
     * has already been instantiated
     * @param string   $package name of the package this error stack represents
     * @param callback $msgCallback callback used for error message generation
     * @param callback $contextCallback callback used for context generation,
     *                 defaults to {@link getFileLine()}
     * @param boolean  $throwPEAR_Error
     * @param string   $stackClass class to instantiate
     *
     * @return PEAR_ErrorStack
     */
    public static function &singleton(
        $package, $msgCallback = false, $contextCallback = false,
        $throwPEAR_Error = false, $stackClass = 'PEAR_ErrorStack'
    ) {
        if (isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) {
            return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package];
        }
        if (!class_exists($stackClass)) {
            if (function_exists('debug_backtrace')) {
                $trace = debug_backtrace();
            }
            PEAR_ErrorStack::staticPush('PEAR_ErrorStack', PEAR_ERRORSTACK_ERR_NONCLASS,
                'exception', array('stackclass' => $stackClass),
                'stack class "%stackclass%" is not a valid class name (should be like PEAR_ErrorStack)',
                false, $trace);
        }
        $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package] =
            new $stackClass($package, $msgCallback, $contextCallback, $throwPEAR_Error);

        return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package];
    }

    /**
     * Internal error handler for PEAR_ErrorStack class
     * 
     * Dies if the error is an exception (and would have died anyway)
     * @access private
     */
    function _handleError($err)
    {
        if ($err['level'] == 'exception') {
            $message = $err['message'];
            if (isset($_SERVER['REQUEST_URI'])) {
                echo '<br />';
            } else {
                echo "\n";
            }
            var_dump($err['context']);
            die($message);
        }
    }
    
    /**
     * Set up a PEAR::Log object for all error stacks that don't have one
     * @param Log $log 
     */
    public static function setDefaultLogger(&$log)
    {
        if (is_object($log) && method_exists($log, 'log') ) {
            $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = &$log;
        } elseif (is_callable($log)) {
            $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = &$log;
        }
    }
    
    /**
     * Set up a PEAR::Log object for this error stack
     * @param Log $log 
     */
    function setLogger(&$log)
    {
        if (is_object($log) && method_exists($log, 'log') ) {
            $this->_logger = &$log;
        } elseif (is_callable($log)) {
            $this->_logger = &$log;
        }
    }
    
    /**
     * Set an error code => error message mapping callback
     * 
     * This method sets the callback that can be used to generate error
     * messages for any instance
     * @param array|string Callback function/method
     */
    function setMessageCallback($msgCallback)
    {
        if (!$msgCallback) {
            $this->_msgCallback = array(&$this, 'getErrorMessage');
        } else {
            if (is_callable($msgCallback)) {
                $this->_msgCallback = $msgCallback;
            }
        }
    }
    
    /**
     * Get an error code => error message mapping callback
     * 
     * This method returns the current callback that can be used to generate error
     * messages
     * @return array|string|false Callback function/method or false if none
     */
    function getMessageCallback()
    {
        return $this->_msgCallback;
    }
    
    /**
     * Sets a default callback to be used by all error stacks
     * 
     * This method sets the callback that can be used to generate error
     * messages for a singleton
     * @param array|string Callback function/method
     * @param string Package name, or false for all packages
     */
    public static function setDefaultCallback($callback = false, $package = false)
    {
        if (!is_callable($callback)) {
            $callback = false;
        }
        $package = $package ? $package : '*';
        $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$package] = $callback;
    }
    
    /**
     * Set a callback that generates context information (location of error) for an error stack
     * 
     * This method sets the callback that can be used to generate context
     * information for an error.  Passing in NULL will disable context generation
     * and remove the expensive call to debug_backtrace()
     * @param array|string|null Callback function/method
     */
    function setContextCallback($contextCallback)
    {
        if ($contextCallback === null) {
            return $this->_contextCallback = false;
        }
        if (!$contextCallback) {
            $this->_contextCallback = array(&$this, 'getFileLine');
        } else {
            if (is_callable($contextCallback)) {
                $this->_contextCallback = $contextCallback;
            }
        }
    }
    
    /**
     * Set an error Callback
     * If set to a valid callback, this will be called every time an error
     * is pushed onto the stack.  The return value will be used to determine
     * whether to allow an error to be pushed or logged.
     * 
     * The return value must be one of the ERRORSTACK_* constants.
     * 
     * This functionality can be used to emulate PEAR's pushErrorHandling, and
     * the PEAR_ERROR_CALLBACK mode, without affecting the integrity of
     * the error stack or logging
     * @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG
     * @see popCallback()
     * @param string|array $cb
     */
    function pushCallback($cb)
    {
        array_push($this->_errorCallback, $cb);
    }
    
    /**
     * Remove a callback from the error callback stack
     * @see pushCallback()
     * @return array|string|false
     */
    function popCallback()
    {
        if (!count($this->_errorCallback)) {
            return false;
        }
        return array_pop($this->_errorCallback);
    }
    
    /**
     * Set a temporary overriding error callback for every package error stack
     *
     * Use this to temporarily disable all existing callbacks (can be used
     * to emulate the @ operator, for instance)
     * @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG
     * @see staticPopCallback(), pushCallback()
     * @param string|array $cb
     */
    public static function staticPushCallback($cb)
    {
        array_push($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'], $cb);
    }
    
    /**
     * Remove a temporary overriding error callback
     * @see staticPushCallback()
     * @return array|string|false
     */
    public static function staticPopCallback()
    {
        $ret = array_pop($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK']);
        if (!is_array($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'])) {
            $GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = array();
        }
        return $ret;
    }
    
    /**
     * Add an error to the stack
     * 
     * If the message generator exists, it is called with 2 parameters.
     *  - the current Error Stack object
     *  - an array that is in the same format as an error.  Available indices
     *    are 'code', 'package', 'time', 'params', 'level', and 'context'
     * 
     * Next, if the error should contain context information, this is
     * handled by the context grabbing method.
     * Finally, the error is pushed onto the proper error stack
     * @param int    $code      Package-specific error code
     * @param string $level     Error level.  This is NOT spell-checked
     * @param array  $params    associative array of error parameters
     * @param string $msg       Error message, or a portion of it if the message
     *                          is to be generated
     * @param array  $repackage If this error re-packages an error pushed by
     *                          another package, place the array returned from
     *                          {@link pop()} in this parameter
     * @param array  $backtrace Protected parameter: use this to pass in the
     *                          {@link debug_backtrace()} that should be used
     *                          to find error context
     * @return PEAR_Error|array if compatibility mode is on, a PEAR_Error is also
     * thrown.  If a PEAR_Error is returned, the userinfo
     * property is set to the following array:
     * 
     * <code>
     * array(
     *    'code' => $code,
     *    'params' => $params,
     *    'package' => $this->_package,
     *    'level' => $level,
     *    'time' => time(),
     *    'context' => $context,
     *    'message' => $msg,
     * //['repackage' => $err] repackaged error array/Exception class
     * );
     * </code>
     * 
     * Normally, the previous array is returned.
     */
    function push($code, $level = 'error', $params = array(), $msg = false,
                  $repackage = false, $backtrace = false)
    {
        $context = false;
        // grab error context
        if ($this->_contextCallback) {
            if (!$backtrace) {
                $backtrace = debug_backtrace();
            }
            $context = call_user_func($this->_contextCallback, $code, $params, $backtrace);
        }
        
        // save error
        $time = explode(' ', microtime());
        $time = $time[1] + $time[0];
        $err = array(
                'code' => $code,
                'params' => $params,
                'package' => $this->_package,
                'level' => $level,
                'time' => $time,
                'context' => $context,
                'message' => $msg,
               );

        if ($repackage) {
            $err['repackage'] = $repackage;
        }

        // set up the error message, if necessary
        if ($this->_msgCallback) {
            $msg = call_user_func_array($this->_msgCallback,
                                        array(&$this, $err));
            $err['message'] = $msg;
        }        
        $push = $log = true;
        $die = false;
        // try the overriding callback first
        $callback = $this->staticPopCallback();
        if ($callback) {
            $this->staticPushCallback($callback);
        }
        if (!is_callable($callback)) {
            // try the local callback next
            $callback = $this->popCallback();
            if (is_callable($callback)) {
                $this->pushCallback($callback);
            } else {
                // try the default callback
                $callback = isset($GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$this->_package]) ?
                    $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$this->_package] :
                    $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK']['*'];
            }
        }
        if (is_callable($callback)) {
            switch(call_user_func($callback, $err)){
            	case PEAR_ERRORSTACK_IGNORE: 
            		return $err;
        		break;
            	case PEAR_ERRORSTACK_PUSH: 
            		$log = false;
        		break;
            	case PEAR_ERRORSTACK_LOG: 
            		$push = false;
        		break;
            	case PEAR_ERRORSTACK_DIE: 
            		$die = true;
        		break;
                // anything else returned has the same effect as pushandlog
            }
        }
        if ($push) {
            array_unshift($this->_errors, $err);
            if (!isset($this->_errorsByLevel[$err['level']])) {
                $this->_errorsByLevel[$err['level']] = array();
            }
            $this->_errorsByLevel[$err['level']][] = &$this->_errors[0];
        }
        if ($log) {
            if ($this->_logger || $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']) {
                $this->_log($err);
            }
        }
        if ($die) {
            die();
        }
        if ($this->_compat && $push) {
            return $this->raiseError($msg, $code, null, null, $err);
        }
        return $err;
    }
    
    /**
     * Static version of {@link push()}
     * 
     * @param string $package   Package name this error belongs to
     * @param int    $code      Package-specific error code
     * @param string $level     Error level.  This is NOT spell-checked
     * @param array  $params    associative array of error parameters
     * @param string $msg       Error message, or a portion of it if the message
     *                          is to be generated
     * @param array  $repackage If this error re-packages an error pushed by
     *                          another package, place the array returned from
     *                          {@link pop()} in this parameter
     * @param array  $backtrace Protected parameter: use this to pass in the
     *                          {@link debug_backtrace()} that should be used
     *                          to find error context
     * @return PEAR_Error|array if compatibility mode is on, a PEAR_Error is also
     *                          thrown.  see docs for {@link push()}
     */
    public static function staticPush(
        $package, $code, $level = 'error', $params = array(),
        $msg = false, $repackage = false, $backtrace = false
    ) {
        $s = &PEAR_ErrorStack::singleton($package);
        if ($s->_contextCallback) {
            if (!$backtrace) {
                if (function_exists('debug_backtrace')) {
                    $backtrace = debug_backtrace();
                }
            }
        }
        return $s->push($code, $level, $params, $msg, $repackage, $backtrace);
    }
    
    /**
     * Log an error using PEAR::Log
     * @param array $err Error array
     * @param array $levels Error level => Log constant map
     * @access protected
     */
    function _log($err)
    {
        if ($this->_logger) {
            $logger = &$this->_logger;
        } else {
            $logger = &$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'];
        }
        if (is_a($logger, 'Log')) {
            $levels = array(
                'exception' => PEAR_LOG_CRIT,
                'alert' => PEAR_LOG_ALERT,
                'critical' => PEAR_LOG_CRIT,
                'error' => PEAR_LOG_ERR,
                'warning' => PEAR_LOG_WARNING,
                'notice' => PEAR_LOG_NOTICE,
                'info' => PEAR_LOG_INFO,
                'debug' => PEAR_LOG_DEBUG);
            if (isset($levels[$err['level']])) {
                $level = $levels[$err['level']];
            } else {
                $level = PEAR_LOG_INFO;
            }
            $logger->log($err['message'], $level, $err);
        } else { // support non-standard logs
            call_user_func($logger, $err);
        }
    }

    
    /**
     * Pop an error off of the error stack
     * 
     * @return false|array
     * @since 0.4alpha it is no longer possible to specify a specific error
     * level to return - the last error pushed will be returned, instead
     */
    function pop()
    {
        $err = @array_shift($this->_errors);
        if (!is_null($err)) {
            @array_pop($this->_errorsByLevel[$err['level']]);
            if (!count($this->_errorsByLevel[$err['level']])) {
                unset($this->_errorsByLevel[$err['level']]);
            }
        }
        return $err;
    }

    /**
     * Pop an error off of the error stack, static method
     *
     * @param string package name
     * @return boolean
     * @since PEAR1.5.0a1
     */
    static function staticPop($package)
    {
        if ($package) {
            if (!isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) {
                return false;
            }
            return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->pop();
        }
    }

    /**
     * Determine whether there are any errors on the stack
     * @param string|array Level name.  Use to determine if any errors
     * of level (string), or levels (array) have been pushed
     * @return boolean
     */
    function hasErrors($level = false)
    {
        if ($level) {
            return isset($this->_errorsByLevel[$level]);
        }
        return count($this->_errors);
    }
    
    /**
     * Retrieve all errors since last purge
     * 
     * @param boolean set in order to empty the error stack
     * @param string level name, to return only errors of a particular severity
     * @return array
     */
    function getErrors($purge = false, $level = false)
    {
        if (!$purge) {
            if ($level) {
                if (!isset($this->_errorsByLevel[$level])) {
                    return array();
                } else {
                    return $this->_errorsByLevel[$level];
                }
            } else {
                return $this->_errors;
            }
        }
        if ($level) {
            $ret = $this->_errorsByLevel[$level];
            foreach ($this->_errorsByLevel[$level] as $i => $unused) {
                // entries are references to the $_errors array
                $this->_errorsByLevel[$level][$i] = false;
            }
            // array_filter removes all entries === false
            $this->_errors = array_filter($this->_errors);
            unset($this->_errorsByLevel[$level]);
            return $ret;
        }
        $ret = $this->_errors;
        $this->_errors = array();
        $this->_errorsByLevel = array();
        return $ret;
    }
    
    /**
     * Determine whether there are any errors on a single error stack, or on any error stack
     *
     * The optional parameter can be used to test the existence of any errors without the need of
     * singleton instantiation
     * @param string|false Package name to check for errors
     * @param string Level name to check for a particular severity
     * @return boolean
     */
    public static function staticHasErrors($package = false, $level = false)
    {
        if ($package) {
            if (!isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) {
                return false;
            }
            return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->hasErrors($level);
        }
        foreach ($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] as $package => $obj) {
            if ($obj->hasErrors($level)) {
                return true;
            }
        }
        return false;
    }
    
    /**
     * Get a list of all errors since last purge, organized by package
     * @since PEAR 1.4.0dev BC break! $level is now in the place $merge used to be
     * @param boolean $purge Set to purge the error stack of existing errors
     * @param string  $level Set to a level name in order to retrieve only errors of a particular level
     * @param boolean $merge Set to return a flat array, not organized by package
     * @param array   $sortfunc Function used to sort a merged array - default
     *        sorts by time, and should be good for most cases
     *
     * @return array 
     */
    public static function staticGetErrors(
        $purge = false, $level = false, $merge = false,
        $sortfunc = array('PEAR_ErrorStack', '_sortErrors')
    ) {
        $ret = array();
        if (!is_callable($sortfunc)) {
            $sortfunc = array('PEAR_ErrorStack', '_sortErrors');
        }
        foreach ($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] as $package => $obj) {
            $test = $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->getErrors($purge, $level);
            if ($test) {
                if ($merge) {
                    $ret = array_merge($ret, $test);
                } else {
                    $ret[$package] = $test;
                }
            }
        }
        if ($merge) {
            usort($ret, $sortfunc);
        }
        return $ret;
    }
    
    /**
     * Error sorting function, sorts by time
     * @access private
     */
    public static function _sortErrors($a, $b)
    {
        if ($a['time'] == $b['time']) {
            return 0;
        }
        if ($a['time'] < $b['time']) {
            return 1;
        }
        return -1;
    }

    /**
     * Standard file/line number/function/class context callback
     *
     * This function uses a backtrace generated from {@link debug_backtrace()}
     * and so will not work at all in PHP < 4.3.0.  The frame should
     * reference the frame that contains the source of the error.
     * @return array|false either array('file' => file, 'line' => line,
     *         'function' => function name, 'class' => class name) or
     *         if this doesn't work, then false
     * @param unused
     * @param integer backtrace frame.
     * @param array Results of debug_backtrace()
     */
    public static function getFileLine($code, $params, $backtrace = null)
    {
        if ($backtrace === null) {
            return false;
        }
        $frame = 0;
        $functionframe = 1;
        if (!isset($backtrace[1])) {
            $functionframe = 0;
        } else {
            while (isset($backtrace[$functionframe]['function']) &&
                  $backtrace[$functionframe]['function'] == 'eval' &&
                  isset($backtrace[$functionframe + 1])) {
                $functionframe++;
            }
        }
        if (isset($backtrace[$frame])) {
            if (!isset($backtrace[$frame]['file'])) {
                $frame++;
            }
            $funcbacktrace = $backtrace[$functionframe];
            $filebacktrace = $backtrace[$frame];
            $ret = array('file' => $filebacktrace['file'],
                         'line' => $filebacktrace['line']);
            // rearrange for eval'd code or create function errors
            if (strpos($filebacktrace['file'], '(') && 
            	  preg_match(';^(.*?)\((\d+)\) : (.*?)\\z;', $filebacktrace['file'],
                  $matches)) {
                $ret['file'] = $matches[1];
                $ret['line'] = $matches[2] + 0;
            }
            if (isset($funcbacktrace['function']) && isset($backtrace[1])) {
                if ($funcbacktrace['function'] != 'eval') {
                    if ($funcbacktrace['function'] == '__lambda_func') {
                        $ret['function'] = 'create_function() code';
                    } else {
                        $ret['function'] = $funcbacktrace['function'];
                    }
                }
            }
            if (isset($funcbacktrace['class']) && isset($backtrace[1])) {
                $ret['class'] = $funcbacktrace['class'];
            }
            return $ret;
        }
        return false;
    }
    
    /**
     * Standard error message generation callback
     * 
     * This method may also be called by a custom error message generator
     * to fill in template values from the params array, simply
     * set the third parameter to the error message template string to use
     * 
     * The special variable %__msg% is reserved: use it only to specify
     * where a message passed in by the user should be placed in the template,
     * like so:
     * 
     * Error message: %msg% - internal error
     * 
     * If the message passed like so:
     * 
     * <code>
     * $stack->push(ERROR_CODE, 'error', array(), 'server error 500');
     * </code>
     * 
     * The returned error message will be "Error message: server error 500 -
     * internal error"
     * @param PEAR_ErrorStack
     * @param array
     * @param string|false Pre-generated error message template
     *
     * @return string
     */
    public static function getErrorMessage(&$stack, $err, $template = false)
    {
        if ($template) {
            $mainmsg = $template;
        } else {
            $mainmsg = $stack->getErrorMessageTemplate($err['code']);
        }
        $mainmsg = str_replace('%__msg%', $err['message'], $mainmsg);
        if (is_array($err['params']) && count($err['params'])) {
            foreach ($err['params'] as $name => $val) {
                if (is_array($val)) {
                    // @ is needed in case $val is a multi-dimensional array
                    $val = @implode(', ', $val);
                }
                if (is_object($val)) {
                    if (method_exists($val, '__toString')) {
                        $val = $val->__toString();
                    } else {
                        PEAR_ErrorStack::staticPush('PEAR_ErrorStack', PEAR_ERRORSTACK_ERR_OBJTOSTRING,
                            'warning', array('obj' => get_class($val)),
                            'object %obj% passed into getErrorMessage, but has no __toString() method');
                        $val = 'Object';
                    }
                }
                $mainmsg = str_replace('%' . $name . '%', $val, $mainmsg);
            }
        }
        return $mainmsg;
    }
    
    /**
     * Standard Error Message Template generator from code
     * @return string
     */
    function getErrorMessageTemplate($code)
    {
        if (!isset($this->_errorMsgs[$code])) {
            return '%__msg%';
        }
        return $this->_errorMsgs[$code];
    }
    
    /**
     * Set the Error Message Template array
     * 
     * The array format must be:
     * <pre>
     * array(error code => 'message template',...)
     * </pre>
     * 
     * Error message parameters passed into {@link push()} will be used as input
     * for the error message.  If the template is 'message %foo% was %bar%', and the
     * parameters are array('foo' => 'one', 'bar' => 'six'), the error message returned will
     * be 'message one was six'
     * @return string
     */
    function setErrorMessageTemplate($template)
    {
        $this->_errorMsgs = $template;
    }
    
    
    /**
     * emulate PEAR::raiseError()
     * 
     * @return PEAR_Error
     */
    function raiseError()
    {
        require_once 'PEAR.php';
        $args = func_get_args();
        return call_user_func_array(array('PEAR', 'raiseError'), $args);
    }
}
$stack = &PEAR_ErrorStack::singleton('PEAR_ErrorStack');
$stack->pushCallback(array('PEAR_ErrorStack', '_handleError'));
?>
PK|c�\�;_�CC)pear/pear-core-minimal/src/PEAR/Error.phpnu�[���<?php
/**
 * Dummy file to make autoloaders work
 *
 * PHP version 5
 *
 * @category PEAR
 * @package  PEAR
 * @author   Christian Weiske <cweiske@php.net>
 * @license  http://opensource.org/licenses/bsd-license.php New BSD License
 * @link     http://pear.php.net/package/PEAR
 */
require_once __DIR__ . '/../PEAR.php';
?>PK|c�\m��*�P�P%pear/pear-core-minimal/src/System.phpnu�[���<?php
/**
 * File/Directory manipulation
 *
 * PHP versions 4 and 5
 *
 * @category   pear
 * @package    System
 * @author     Tomas V.V.Cox <cox@idecnet.com>
 * @copyright  1997-2009 The Authors
 * @license    http://opensource.org/licenses/bsd-license.php New BSD License
 * @link       http://pear.php.net/package/PEAR
 * @since      File available since Release 0.1
 */

/**
 * base class
 */
require_once 'PEAR.php';
require_once 'Console/Getopt.php';

$GLOBALS['_System_temp_files'] = array();

/**
* System offers cross platform compatible system functions
*
* Static functions for different operations. Should work under
* Unix and Windows. The names and usage has been taken from its respectively
* GNU commands. The functions will return (bool) false on error and will
* trigger the error with the PHP trigger_error() function (you can silence
* the error by prefixing a '@' sign after the function call, but this
* is not recommended practice.  Instead use an error handler with
* {@link set_error_handler()}).
*
* Documentation on this class you can find in:
* http://pear.php.net/manual/
*
* Example usage:
* if (!@System::rm('-r file1 dir1')) {
*    print "could not delete file1 or dir1";
* }
*
* In case you need to to pass file names with spaces,
* pass the params as an array:
*
* System::rm(array('-r', $file1, $dir1));
*
* @category   pear
* @package    System
* @author     Tomas V.V. Cox <cox@idecnet.com>
* @copyright  1997-2006 The PHP Group
* @license    http://opensource.org/licenses/bsd-license.php New BSD License
* @version    Release: @package_version@
* @link       http://pear.php.net/package/PEAR
* @since      Class available since Release 0.1
* @static
*/
class System
{
    /**
     * returns the commandline arguments of a function
     *
     * @param    string  $argv           the commandline
     * @param    string  $short_options  the allowed option short-tags
     * @param    string  $long_options   the allowed option long-tags
     * @return   array   the given options and there values
     */
    public static function _parseArgs($argv, $short_options, $long_options = null)
    {
        if (!is_array($argv) && $argv !== null) {
            /*
            // Quote all items that are a short option
            $av = preg_split('/(\A| )--?[a-z0-9]+[ =]?((?<!\\\\)((,\s*)|((?<!,)\s+))?)/i', $argv, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE);
            $offset = 0;
            foreach ($av as $a) {
                $b = trim($a[0]);
                if ($b[0] == '"' || $b[0] == "'") {
                    continue;
                }

                $escape = escapeshellarg($b);
                $pos = $a[1] + $offset;
                $argv = substr_replace($argv, $escape, $pos, strlen($b));
                $offset += 2;
            }
            */

            // Find all items, quoted or otherwise
            preg_match_all("/(?:[\"'])(.*?)(?:['\"])|([^\s]+)/", $argv, $av);
            $argv = $av[1];
            foreach ($av[2] as $k => $a) {
                if (empty($a)) {
                    continue;
                }
                $argv[$k] = trim($a) ;
            }
        }

        return Console_Getopt::getopt2($argv, $short_options, $long_options);
    }

    /**
     * Output errors with PHP trigger_error(). You can silence the errors
     * with prefixing a "@" sign to the function call: @System::mkdir(..);
     *
     * @param mixed $error a PEAR error or a string with the error message
     * @return bool false
     */
    protected static function raiseError($error)
    {
        if (PEAR::isError($error)) {
            $error = $error->getMessage();
        }
        trigger_error($error, E_USER_WARNING);
        return false;
    }

    /**
     * Creates a nested array representing the structure of a directory
     *
     * System::_dirToStruct('dir1', 0) =>
     *   Array
     *    (
     *    [dirs] => Array
     *        (
     *            [0] => dir1
     *        )
     *
     *    [files] => Array
     *        (
     *            [0] => dir1/file2
     *            [1] => dir1/file3
     *        )
     *    )
     * @param    string  $sPath      Name of the directory
     * @param    integer $maxinst    max. deep of the lookup
     * @param    integer $aktinst    starting deep of the lookup
     * @param    bool    $silent     if true, do not emit errors.
     * @return   array   the structure of the dir
     */
    protected static function _dirToStruct($sPath, $maxinst, $aktinst = 0, $silent = false)
    {
        $struct = array('dirs' => array(), 'files' => array());
        if (($dir = @opendir($sPath)) === false) {
            if (!$silent) {
                System::raiseError("Could not open dir $sPath");
            }
            return $struct; // XXX could not open error
        }

        $struct['dirs'][] = $sPath = realpath($sPath); // XXX don't add if '.' or '..' ?
        $list = array();
        while (false !== ($file = readdir($dir))) {
            if ($file != '.' && $file != '..') {
                $list[] = $file;
            }
        }

        closedir($dir);
        natsort($list);
        if ($aktinst < $maxinst || $maxinst == 0) {
            foreach ($list as $val) {
                $path = $sPath . DIRECTORY_SEPARATOR . $val;
                if (is_dir($path) && !is_link($path)) {
                    $tmp    = System::_dirToStruct($path, $maxinst, $aktinst+1, $silent);
                    $struct = array_merge_recursive($struct, $tmp);
                } else {
                    $struct['files'][] = $path;
                }
            }
        }

        return $struct;
    }

    /**
     * Creates a nested array representing the structure of a directory and files
     *
     * @param    array $files Array listing files and dirs
     * @return   array
     * @static
     * @see System::_dirToStruct()
     */
    protected static function _multipleToStruct($files)
    {
        $struct = array('dirs' => array(), 'files' => array());
        settype($files, 'array');
        foreach ($files as $file) {
            if (is_dir($file) && !is_link($file)) {
                $tmp    = System::_dirToStruct($file, 0);
                $struct = array_merge_recursive($tmp, $struct);
            } else {
                if (!in_array($file, $struct['files'])) {
                    $struct['files'][] = $file;
                }
            }
        }
        return $struct;
    }

    /**
     * The rm command for removing files.
     * Supports multiple files and dirs and also recursive deletes
     *
     * @param    string  $args   the arguments for rm
     * @return   mixed   PEAR_Error or true for success
     * @static
     * @access   public
     */
    public static function rm($args)
    {
        $opts = System::_parseArgs($args, 'rf'); // "f" does nothing but I like it :-)
        if (PEAR::isError($opts)) {
            return System::raiseError($opts);
        }
        foreach ($opts[0] as $opt) {
            if ($opt[0] == 'r') {
                $do_recursive = true;
            }
        }
        $ret = true;
        if (isset($do_recursive)) {
            $struct = System::_multipleToStruct($opts[1]);
            foreach ($struct['files'] as $file) {
                if (!@unlink($file)) {
                    $ret = false;
                }
            }

            rsort($struct['dirs']);
            foreach ($struct['dirs'] as $dir) {
                if (!@rmdir($dir)) {
                    $ret = false;
                }
            }
        } else {
            foreach ($opts[1] as $file) {
                $delete = (is_dir($file)) ? 'rmdir' : 'unlink';
                if (!@$delete($file)) {
                    $ret = false;
                }
            }
        }
        return $ret;
    }

    /**
     * Make directories.
     *
     * The -p option will create parent directories
     * @param    string  $args    the name of the director(y|ies) to create
     * @return   bool    True for success
     */
    public static function mkDir($args)
    {
        $opts = System::_parseArgs($args, 'pm:');
        if (PEAR::isError($opts)) {
            return System::raiseError($opts);
        }

        $mode = 0777; // default mode
        foreach ($opts[0] as $opt) {
            if ($opt[0] == 'p') {
                $create_parents = true;
            } elseif ($opt[0] == 'm') {
                // if the mode is clearly an octal number (starts with 0)
                // convert it to decimal
                if (strlen($opt[1]) && $opt[1][0] == '0') {
                    $opt[1] = octdec($opt[1]);
                } else {
                    // convert to int
                    $opt[1] += 0;
                }
                $mode = $opt[1];
            }
        }

        $ret = true;
        if (isset($create_parents)) {
            foreach ($opts[1] as $dir) {
                $dirstack = array();
                while ((!file_exists($dir) || !is_dir($dir)) &&
                        $dir != DIRECTORY_SEPARATOR) {
                    array_unshift($dirstack, $dir);
                    $dir = dirname($dir);
                }

                while ($newdir = array_shift($dirstack)) {
                    if (!is_writeable(dirname($newdir))) {
                        $ret = false;
                        break;
                    }

                    if (!mkdir($newdir, $mode)) {
                        $ret = false;
                    }
                }
            }
        } else {
            foreach($opts[1] as $dir) {
                if ((@file_exists($dir) || !is_dir($dir)) && !mkdir($dir, $mode)) {
                    $ret = false;
                }
            }
        }

        return $ret;
    }

    /**
     * Concatenate files
     *
     * Usage:
     * 1) $var = System::cat('sample.txt test.txt');
     * 2) System::cat('sample.txt test.txt > final.txt');
     * 3) System::cat('sample.txt test.txt >> final.txt');
     *
     * Note: as the class use fopen, urls should work also
     *
     * @param    string  $args   the arguments
     * @return   boolean true on success
     */
    public static function &cat($args)
    {
        $ret = null;
        $files = array();
        if (!is_array($args)) {
            $args = preg_split('/\s+/', $args, -1, PREG_SPLIT_NO_EMPTY);
        }

        $count_args = count($args);
        for ($i = 0; $i < $count_args; $i++) {
            if ($args[$i] == '>') {
                $mode = 'wb';
                $outputfile = $args[$i+1];
                break;
            } elseif ($args[$i] == '>>') {
                $mode = 'ab+';
                $outputfile = $args[$i+1];
                break;
            } else {
                $files[] = $args[$i];
            }
        }
        $outputfd = false;
        if (isset($mode)) {
            if (!$outputfd = fopen($outputfile, $mode)) {
                $err = System::raiseError("Could not open $outputfile");
                return $err;
            }
            $ret = true;
        }
        foreach ($files as $file) {
            if (!$fd = fopen($file, 'r')) {
                System::raiseError("Could not open $file");
                continue;
            }
            while ($cont = fread($fd, 2048)) {
                if (is_resource($outputfd)) {
                    fwrite($outputfd, $cont);
                } else {
                    $ret .= $cont;
                }
            }
            fclose($fd);
        }
        if (is_resource($outputfd)) {
            fclose($outputfd);
        }
        return $ret;
    }

    /**
     * Creates temporary files or directories. This function will remove
     * the created files when the scripts finish its execution.
     *
     * Usage:
     *   1) $tempfile = System::mktemp("prefix");
     *   2) $tempdir  = System::mktemp("-d prefix");
     *   3) $tempfile = System::mktemp();
     *   4) $tempfile = System::mktemp("-t /var/tmp prefix");
     *
     * prefix -> The string that will be prepended to the temp name
     *           (defaults to "tmp").
     * -d     -> A temporary dir will be created instead of a file.
     * -t     -> The target dir where the temporary (file|dir) will be created. If
     *           this param is missing by default the env vars TMP on Windows or
     *           TMPDIR in Unix will be used. If these vars are also missing
     *           c:\windows\temp or /tmp will be used.
     *
     * @param   string  $args  The arguments
     * @return  mixed   the full path of the created (file|dir) or false
     * @see System::tmpdir()
     */
    public static function mktemp($args = null)
    {
        static $first_time = true;
        $opts = System::_parseArgs($args, 't:d');
        if (PEAR::isError($opts)) {
            return System::raiseError($opts);
        }

        foreach ($opts[0] as $opt) {
            if ($opt[0] == 'd') {
                $tmp_is_dir = true;
            } elseif ($opt[0] == 't') {
                $tmpdir = $opt[1];
            }
        }

        $prefix = (isset($opts[1][0])) ? $opts[1][0] : 'tmp';
        if (!isset($tmpdir)) {
            $tmpdir = System::tmpdir();
        }

        if (!System::mkDir(array('-p', $tmpdir))) {
            return false;
        }

        $tmp = tempnam($tmpdir, $prefix);
        if (isset($tmp_is_dir)) {
            unlink($tmp); // be careful possible race condition here
            if (!mkdir($tmp, 0700)) {
                return System::raiseError("Unable to create temporary directory $tmpdir");
            }
        }

        $GLOBALS['_System_temp_files'][] = $tmp;
        if (isset($tmp_is_dir)) {
            //$GLOBALS['_System_temp_files'][] = dirname($tmp);
        }

        if ($first_time) {
            PEAR::registerShutdownFunc(array('System', '_removeTmpFiles'));
            $first_time = false;
        }

        return $tmp;
    }

    /**
     * Remove temporary files created my mkTemp. This function is executed
     * at script shutdown time
     */
    public static function _removeTmpFiles()
    {
        if (count($GLOBALS['_System_temp_files'])) {
            $delete = $GLOBALS['_System_temp_files'];
            array_unshift($delete, '-r');
            System::rm($delete);
            $GLOBALS['_System_temp_files'] = array();
        }
    }

    /**
     * Get the path of the temporal directory set in the system
     * by looking in its environments variables.
     * Note: php.ini-recommended removes the "E" from the variables_order setting,
     * making unavaible the $_ENV array, that s why we do tests with _ENV
     *
     * @return string The temporary directory on the system
     */
    public static function tmpdir()
    {
        if (OS_WINDOWS) {
            if ($var = isset($_ENV['TMP']) ? $_ENV['TMP'] : getenv('TMP')) {
                return $var;
            }
            if ($var = isset($_ENV['TEMP']) ? $_ENV['TEMP'] : getenv('TEMP')) {
                return $var;
            }
            if ($var = isset($_ENV['USERPROFILE']) ? $_ENV['USERPROFILE'] : getenv('USERPROFILE')) {
                return $var;
            }
            if ($var = isset($_ENV['windir']) ? $_ENV['windir'] : getenv('windir')) {
                return $var;
            }
            return getenv('SystemRoot') . '\temp';
        }
        if ($var = isset($_ENV['TMPDIR']) ? $_ENV['TMPDIR'] : getenv('TMPDIR')) {
            return $var;
        }
        return realpath(function_exists('sys_get_temp_dir') ? sys_get_temp_dir() : '/tmp');
    }

    /**
     * The "which" command (show the full path of a command)
     *
     * @param string $program The command to search for
     * @param mixed  $fallback Value to return if $program is not found
     *
     * @return mixed A string with the full path or false if not found
     * @author Stig Bakken <ssb@php.net>
     */
    public static function which($program, $fallback = false)
    {
        // enforce API
        if (!is_string($program) || '' == $program) {
            return $fallback;
        }

        // full path given
        if (basename($program) != $program) {
            $path_elements[] = dirname($program);
            $program = basename($program);
        } else {
            $path = getenv('PATH');
            if (!$path) {
                $path = getenv('Path'); // some OSes are just stupid enough to do this
            }

            $path_elements = explode(PATH_SEPARATOR, $path);
        }

        if (OS_WINDOWS) {
            $exe_suffixes = getenv('PATHEXT')
                                ? explode(PATH_SEPARATOR, getenv('PATHEXT'))
                                : array('.exe','.bat','.cmd','.com');
            // allow passing a command.exe param
            if (strpos($program, '.') !== false) {
                array_unshift($exe_suffixes, '');
            }
        } else {
            $exe_suffixes = array('');
        }

        foreach ($exe_suffixes as $suff) {
            foreach ($path_elements as $dir) {
                $file = $dir . DIRECTORY_SEPARATOR . $program . $suff;
                // It's possible to run a .bat on Windows that is_executable
                // would return false for. The is_executable check is meaningless...
                if (OS_WINDOWS) {
                    if (file_exists($file)) {
                        return $file;
                    }
                } else {
                    if (is_executable($file)) {
                        return $file;
                    }
                }
            }
        }
        return $fallback;
    }

    /**
     * The "find" command
     *
     * Usage:
     *
     * System::find($dir);
     * System::find("$dir -type d");
     * System::find("$dir -type f");
     * System::find("$dir -name *.php");
     * System::find("$dir -name *.php -name *.htm*");
     * System::find("$dir -maxdepth 1");
     *
     * Params implemented:
     * $dir            -> Start the search at this directory
     * -type d         -> return only directories
     * -type f         -> return only files
     * -maxdepth <n>   -> max depth of recursion
     * -name <pattern> -> search pattern (bash style). Multiple -name param allowed
     *
     * @param  mixed Either array or string with the command line
     * @return array Array of found files
     */
    public static function find($args)
    {
        if (!is_array($args)) {
            $args = preg_split('/\s+/', $args, -1, PREG_SPLIT_NO_EMPTY);
        }
        $dir = realpath(array_shift($args));
        if (!$dir) {
            return array();
        }
        $patterns = array();
        $depth = 0;
        $do_files = $do_dirs = true;
        $args_count = count($args);
        for ($i = 0; $i < $args_count; $i++) {
            switch ($args[$i]) {
                case '-type':
                    if (in_array($args[$i+1], array('d', 'f'))) {
                        if ($args[$i+1] == 'd') {
                            $do_files = false;
                        } else {
                            $do_dirs = false;
                        }
                    }
                    $i++;
                    break;
                case '-name':
                    $name = preg_quote($args[$i+1], '#');
                    // our magic characters ? and * have just been escaped,
                    // so now we change the escaped versions to PCRE operators
                    $name = strtr($name, array('\?' => '.', '\*' => '.*'));
                    $patterns[] = '('.$name.')';
                    $i++;
                    break;
                case '-maxdepth':
                    $depth = $args[$i+1];
                    break;
            }
        }
        $path = System::_dirToStruct($dir, $depth, 0, true);
        if ($do_files && $do_dirs) {
            $files = array_merge($path['files'], $path['dirs']);
        } elseif ($do_dirs) {
            $files = $path['dirs'];
        } else {
            $files = $path['files'];
        }
        if (count($patterns)) {
            $dsq = preg_quote(DIRECTORY_SEPARATOR, '#');
            $pattern = '#(^|'.$dsq.')'.implode('|', $patterns).'($|'.$dsq.')#';
            $ret = array();
            $files_count = count($files);
            for ($i = 0; $i < $files_count; $i++) {
                // only search in the part of the file below the current directory
                $filepart = basename($files[$i]);
                if (preg_match($pattern, $filepart)) {
                    $ret[] = $files[$i];
                }
            }
            return $ret;
        }
        return $files;
    }
}
PK|c�\-�y66$pear/pear-core-minimal/composer.jsonnu�[���{
    "name": "pear/pear-core-minimal",
    "description": "Minimal set of PEAR core files to be used as composer dependency",
    "license": "BSD-3-Clause",
    "authors": [
        {
            "email": "cweiske@php.net",
            "name": "Christian Weiske",
            "role": "Lead"
        }
    ],
    "autoload": {
        "classmap": [
            "src/"
        ]
    },
    "include-path": [
        "src/"
    ],
    "support": {
        "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=PEAR",
        "source": "https://github.com/pear/pear-core-minimal"
    },
    "type": "library",
    "require": {
        "php": ">=5.4",
        "pear/console_getopt": "~1.4",
        "pear/pear_exception": "~1.0"
    },
    "replace": {
        "rsky/pear-core-min": "self.version"
    }
}
PK|c�\B����!pear/pear-core-minimal/README.rstnu�[���******************************
Minimal set of PEAR core files
******************************

This repository provides a set of files from ``pear-core``
that are often used in PEAR packages.

It follows the `pear-core`__ repository and gets updated whenever a new
PEAR version is released.

It's meant to be used as dependency for composer packages.

__ https://github.com/pear/pear-core

==============
Included files
==============
- ``OS/Guess.php``
- ``PEAR.php``
- ``PEAR/ErrorStack.php``
- ``System.php``
PK|c�\�3����!pear/pear_exception/composer.jsonnu�[���{
    "name": "pear/pear_exception",
    "description": "The PEAR Exception base class.",
    "type": "class",
    "keywords": [
        "exception"
    ],
    "homepage": "https://github.com/pear/PEAR_Exception",
    "license": "BSD-2-Clause",
    "authors": [
        {
            "name": "Helgi Thormar",
            "email": "dufuz@php.net"
        },
        {
            "name": "Greg Beaver",
            "email": "cellog@php.net"
        }
    ],
    "require": {
        "php": ">=5.2.0"
    },
    "autoload": {
        "classmap": ["PEAR/"]
    },
    "extra": {
        "branch-alias": {
            "dev-master": "1.0.x-dev"
        }
    },
    "include-path": [
        "."
    ],
    "support": {
        "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=PEAR_Exception",
        "source": "https://github.com/pear/PEAR_Exception"
    },
    "require-dev": {
        "phpunit/phpunit": "<9"
    }
}
PK|c�\�u����pear/pear_exception/LICENSEnu�[���Copyright (c) 1997-2009,
 Stig Bakken <ssb@php.net>,
 Gregory Beaver <cellog@php.net>,
 Helgi Þormar Þorbjörnsson <helgi@php.net>,
 Tomas V.V.Cox <cox@idecnet.com>,
 Martin Jansen <mj@php.net>.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
PK|c�\��_�)<)<&pear/pear_exception/PEAR/Exception.phpnu�[���<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
/**
 * PEAR_Exception
 *
 * PHP version 5
 *
 * @category  PEAR
 * @package   PEAR_Exception
 * @author    Tomas V. V. Cox <cox@idecnet.com>
 * @author    Hans Lellelid <hans@velum.net>
 * @author    Bertrand Mansion <bmansion@mamasam.com>
 * @author    Greg Beaver <cellog@php.net>
 * @copyright 1997-2009 The Authors
 * @license   http://opensource.org/licenses/bsd-license.php New BSD License
 * @link      http://pear.php.net/package/PEAR_Exception
 * @since     File available since Release 1.0.0
 */


/**
 * Base PEAR_Exception Class
 *
 * 1) Features:
 *
 * - Nestable exceptions (throw new PEAR_Exception($msg, $prev_exception))
 * - Definable triggers, shot when exceptions occur
 * - Pretty and informative error messages
 * - Added more context info available (like class, method or cause)
 * - cause can be a PEAR_Exception or an array of mixed
 *   PEAR_Exceptions/PEAR_ErrorStack warnings
 * - callbacks for specific exception classes and their children
 *
 * 2) Ideas:
 *
 * - Maybe a way to define a 'template' for the output
 *
 * 3) Inherited properties from PHP Exception Class:
 *
 * protected $message
 * protected $code
 * protected $line
 * protected $file
 * private   $trace
 *
 * 4) Inherited methods from PHP Exception Class:
 *
 * __clone
 * __construct
 * getMessage
 * getCode
 * getFile
 * getLine
 * getTraceSafe
 * getTraceSafeAsString
 * __toString
 *
 * 5) Usage example
 *
 * <code>
 *  require_once 'PEAR/Exception.php';
 *
 *  class Test {
 *     function foo() {
 *         throw new PEAR_Exception('Error Message', ERROR_CODE);
 *     }
 *  }
 *
 *  function myLogger($pear_exception) {
 *     echo $pear_exception->getMessage();
 *  }
 *  // each time a exception is thrown the 'myLogger' will be called
 *  // (its use is completely optional)
 *  PEAR_Exception::addObserver('myLogger');
 *  $test = new Test;
 *  try {
 *     $test->foo();
 *  } catch (PEAR_Exception $e) {
 *     print $e;
 *  }
 * </code>
 *
 * @category  PEAR
 * @package   PEAR_Exception
 * @author    Tomas V.V.Cox <cox@idecnet.com>
 * @author    Hans Lellelid <hans@velum.net>
 * @author    Bertrand Mansion <bmansion@mamasam.com>
 * @author    Greg Beaver <cellog@php.net>
 * @copyright 1997-2009 The Authors
 * @license   http://opensource.org/licenses/bsd-license.php New BSD License
 * @version   Release: @package_version@
 * @link      http://pear.php.net/package/PEAR_Exception
 * @since     Class available since Release 1.0.0
 */
class PEAR_Exception extends Exception
{
    const OBSERVER_PRINT = -2;
    const OBSERVER_TRIGGER = -4;
    const OBSERVER_DIE = -8;
    protected $cause;
    private static $_observers = array();
    private static $_uniqueid = 0;
    private $_trace;

    /**
     * Supported signatures:
     *  - PEAR_Exception(string $message);
     *  - PEAR_Exception(string $message, int $code);
     *  - PEAR_Exception(string $message, Exception $cause);
     *  - PEAR_Exception(string $message, Exception $cause, int $code);
     *  - PEAR_Exception(string $message, PEAR_Error $cause);
     *  - PEAR_Exception(string $message, PEAR_Error $cause, int $code);
     *  - PEAR_Exception(string $message, array $causes);
     *  - PEAR_Exception(string $message, array $causes, int $code);
     *
     * @param string                              $message exception message
     * @param int|Exception|PEAR_Error|array|null $p2      exception cause
     * @param int|null                            $p3      exception code or null
     */
    public function __construct($message, $p2 = null, $p3 = null)
    {
        if (is_int($p2)) {
            $code = $p2;
            $this->cause = null;
        } elseif (is_object($p2) || is_array($p2)) {
            // using is_object allows both Exception and PEAR_Error
            if (is_object($p2) && !($p2 instanceof Exception)) {
                if (!class_exists('PEAR_Error') || !($p2 instanceof PEAR_Error)) {
                    throw new PEAR_Exception(
                        'exception cause must be Exception, ' .
                        'array, or PEAR_Error'
                    );
                }
            }
            $code = $p3;
            if (is_array($p2) && isset($p2['message'])) {
                // fix potential problem of passing in a single warning
                $p2 = array($p2);
            }
            $this->cause = $p2;
        } else {
            $code = null;
            $this->cause = null;
        }
        parent::__construct($message, (int) $code);
        $this->signal();
    }

    /**
     * Add an exception observer
     *
     * @param mixed  $callback - A valid php callback, see php func is_callable()
     *                         - A PEAR_Exception::OBSERVER_* constant
     *                         - An array(const PEAR_Exception::OBSERVER_*,
     *                           mixed $options)
     * @param string $label    The name of the observer. Use this if you want
     *                         to remove it later with removeObserver()
     *
     * @return void
     */
    public static function addObserver($callback, $label = 'default')
    {
        self::$_observers[$label] = $callback;
    }

    /**
     * Remove an exception observer
     *
     * @param string $label Name of the observer
     *
     * @return void
     */
    public static function removeObserver($label = 'default')
    {
        unset(self::$_observers[$label]);
    }

    /**
     * Generate a unique ID for an observer
     *
     * @return int unique identifier for an observer
     */
    public static function getUniqueId()
    {
        return self::$_uniqueid++;
    }

    /**
     * Send a signal to all observers
     *
     * @return void
     */
    protected function signal()
    {
        foreach (self::$_observers as $func) {
            if (is_callable($func)) {
                call_user_func($func, $this);
                continue;
            }
            settype($func, 'array');
            switch ($func[0]) {
            case self::OBSERVER_PRINT :
                $f = (isset($func[1])) ? $func[1] : '%s';
                printf($f, $this->getMessage());
                break;
            case self::OBSERVER_TRIGGER :
                $f = (isset($func[1])) ? $func[1] : E_USER_NOTICE;
                trigger_error($this->getMessage(), $f);
                break;
            case self::OBSERVER_DIE :
                $f = (isset($func[1])) ? $func[1] : '%s';
                die(printf($f, $this->getMessage()));
                break;
            default:
                trigger_error('invalid observer type', E_USER_WARNING);
            }
        }
    }

    /**
     * Return specific error information that can be used for more detailed
     * error messages or translation.
     *
     * This method may be overridden in child exception classes in order
     * to add functionality not present in PEAR_Exception and is a placeholder
     * to define API
     *
     * The returned array must be an associative array of parameter => value like so:
     * <pre>
     * array('name' => $name, 'context' => array(...))
     * </pre>
     *
     * @return array
     */
    public function getErrorData()
    {
        return array();
    }

    /**
     * Returns the exception that caused this exception to be thrown
     *
     * @return Exception|array The context of the exception
     */
    public function getCause()
    {
        return $this->cause;
    }

    /**
     * Function must be public to call on caused exceptions
     *
     * @param array $causes Array that gets filled.
     *
     * @return void
     */
    public function getCauseMessage(&$causes)
    {
        $trace = $this->getTraceSafe();
        $cause = array('class'   => get_class($this),
                       'message' => $this->message,
                       'file' => 'unknown',
                       'line' => 'unknown');
        if (isset($trace[0])) {
            if (isset($trace[0]['file'])) {
                $cause['file'] = $trace[0]['file'];
                $cause['line'] = $trace[0]['line'];
            }
        }
        $causes[] = $cause;
        if ($this->cause instanceof PEAR_Exception) {
            $this->cause->getCauseMessage($causes);
        } elseif ($this->cause instanceof Exception) {
            $causes[] = array('class'   => get_class($this->cause),
                              'message' => $this->cause->getMessage(),
                              'file' => $this->cause->getFile(),
                              'line' => $this->cause->getLine());
        } elseif (class_exists('PEAR_Error') && $this->cause instanceof PEAR_Error) {
            $causes[] = array('class' => get_class($this->cause),
                              'message' => $this->cause->getMessage(),
                              'file' => 'unknown',
                              'line' => 'unknown');
        } elseif (is_array($this->cause)) {
            foreach ($this->cause as $cause) {
                if ($cause instanceof PEAR_Exception) {
                    $cause->getCauseMessage($causes);
                } elseif ($cause instanceof Exception) {
                    $causes[] = array('class'   => get_class($cause),
                                   'message' => $cause->getMessage(),
                                   'file' => $cause->getFile(),
                                   'line' => $cause->getLine());
                } elseif (class_exists('PEAR_Error')
                    && $cause instanceof PEAR_Error
                ) {
                    $causes[] = array('class' => get_class($cause),
                                      'message' => $cause->getMessage(),
                                      'file' => 'unknown',
                                      'line' => 'unknown');
                } elseif (is_array($cause) && isset($cause['message'])) {
                    // PEAR_ErrorStack warning
                    $causes[] = array(
                        'class' => $cause['package'],
                        'message' => $cause['message'],
                        'file' => isset($cause['context']['file']) ?
                                            $cause['context']['file'] :
                                            'unknown',
                        'line' => isset($cause['context']['line']) ?
                                            $cause['context']['line'] :
                                            'unknown',
                    );
                }
            }
        }
    }

    /**
     * Build a backtrace and return it
     *
     * @return array Backtrace
     */
    public function getTraceSafe()
    {
        if (!isset($this->_trace)) {
            $this->_trace = $this->getTrace();
            if (empty($this->_trace)) {
                $backtrace = debug_backtrace();
                $this->_trace = array($backtrace[count($backtrace)-1]);
            }
        }
        return $this->_trace;
    }

    /**
     * Gets the first class of the backtrace
     *
     * @return string Class name
     */
    public function getErrorClass()
    {
        $trace = $this->getTraceSafe();
        return $trace[0]['class'];
    }

    /**
     * Gets the first method of the backtrace
     *
     * @return string Method/function name
     */
    public function getErrorMethod()
    {
        $trace = $this->getTraceSafe();
        return $trace[0]['function'];
    }

    /**
     * Converts the exception to a string (HTML or plain text)
     *
     * @return string String representation
     *
     * @see toHtml()
     * @see toText()
     */
    public function __toString()
    {
        if (isset($_SERVER['REQUEST_URI'])) {
            return $this->toHtml();
        }
        return $this->toText();
    }

    /**
     * Generates a HTML representation of the exception
     *
     * @return string HTML code
     */
    public function toHtml()
    {
        $trace = $this->getTraceSafe();
        $causes = array();
        $this->getCauseMessage($causes);
        $html =  '<table style="border: 1px" cellspacing="0">' . "\n";
        foreach ($causes as $i => $cause) {
            $html .= '<tr><td colspan="3" style="background: #ff9999">'
               . str_repeat('-', $i) . ' <b>' . $cause['class'] . '</b>: '
               . htmlspecialchars($cause['message'])
                . ' in <b>' . $cause['file'] . '</b> '
               . 'on line <b>' . $cause['line'] . '</b>'
               . "</td></tr>\n";
        }
        $html .= '<tr><td colspan="3" style="background-color: #aaaaaa; text-align: center; font-weight: bold;">Exception trace</td></tr>' . "\n"
               . '<tr><td style="text-align: center; background: #cccccc; width:20px; font-weight: bold;">#</td>'
               . '<td style="text-align: center; background: #cccccc; font-weight: bold;">Function</td>'
               . '<td style="text-align: center; background: #cccccc; font-weight: bold;">Location</td></tr>' . "\n";

        foreach ($trace as $k => $v) {
            $html .= '<tr><td style="text-align: center;">' . $k . '</td>'
                   . '<td>';
            if (!empty($v['class'])) {
                $html .= $v['class'] . $v['type'];
            }
            $html .= $v['function'];
            $args = array();
            if (!empty($v['args'])) {
                foreach ($v['args'] as $arg) {
                    if (is_null($arg)) {
                        $args[] = 'null';
                    } else if (is_array($arg)) {
                        $args[] = 'Array';
                    } else if (is_object($arg)) {
                        $args[] = 'Object('.get_class($arg).')';
                    } else if (is_bool($arg)) {
                        $args[] = $arg ? 'true' : 'false';
                    } else if (is_int($arg) || is_double($arg)) {
                        $args[] = $arg;
                    } else {
                        $arg = (string)$arg;
                        $str = htmlspecialchars(substr($arg, 0, 16));
                        if (strlen($arg) > 16) {
                            $str .= '&hellip;';
                        }
                        $args[] = "'" . $str . "'";
                    }
                }
            }
            $html .= '(' . implode(', ', $args) . ')'
                   . '</td>'
                   . '<td>' . (isset($v['file']) ? $v['file'] : 'unknown')
                   . ':' . (isset($v['line']) ? $v['line'] : 'unknown')
                   . '</td></tr>' . "\n";
        }
        $html .= '<tr><td style="text-align: center;">' . ($k+1) . '</td>'
               . '<td>{main}</td>'
               . '<td>&nbsp;</td></tr>' . "\n"
               . '</table>';
        return $html;
    }

    /**
     * Generates text representation of the exception and stack trace
     *
     * @return string
     */
    public function toText()
    {
        $causes = array();
        $this->getCauseMessage($causes);
        $causeMsg = '';
        foreach ($causes as $i => $cause) {
            $causeMsg .= str_repeat(' ', $i) . $cause['class'] . ': '
                   . $cause['message'] . ' in ' . $cause['file']
                   . ' on line ' . $cause['line'] . "\n";
        }
        return $causeMsg . $this->getTraceAsString();
    }
}
?>
PK|c�\�5oI��pear/auth_sasl/composer.jsonnu�[���{
    "authors": [
        {
            "email": "amistry@am-productions.biz",
            "name": "Anish Mistry",
            "role": "Lead"
        },
        {
            "email": "richard@php.net",
            "name": "Richard Heyes",
            "role": "Lead"
        },
        {
            "email": "michael@bretterklieber.com",
            "name": "Michael Bretterklieber",
            "role": "Lead"
        }
    ],
    "autoload": {
        "psr-0": {
            "Auth": "./"
        }
    },
    "description": "Abstraction of various SASL mechanism responses",
    "include-path": [
        "./"
    ],
    "license": "BSD",
    "name": "pear/auth_sasl",
    "support": {
        "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Auth_SASL",
        "source": "https://github.com/pear/Auth_SASL"
    },
    "type": "library",
    "require": {
        "pear/pear_exception": "@stable"
    },
    "require-dev": {
        "phpunit/phpunit": "@stable"
    }
}
PK|c�\cd��\\pear/auth_sasl/package.xmlnu�[���<?xml version="1.0" encoding="UTF-8"?>
<package version="2.0"
 xmlns="http://pear.php.net/dtd/package-2.0"
 xmlns:tasks="http://pear.php.net/dtd/tasks-1.0"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
  http://pear.php.net/dtd/tasks-1.0.xsd
  http://pear.php.net/dtd/package-2.0
  http://pear.php.net/dtd/package-2.0.xsd"
>
 <name>Auth_SASL</name>
 <channel>pear.php.net</channel>
 <summary>Abstraction of various SASL mechanism responses</summary>
 <description>
Provides code to generate responses to common SASL mechanisms, including:
- Digest-MD5
- Cram-MD5
- Plain
- Anonymous
- Login (Pseudo mechanism)
- SCRAM
 </description>

 <lead>
  <name>Anish Mistry</name>
  <user>amistry</user>
  <email>amistry@am-productions.biz</email>
  <active>no</active>
 </lead>
 <lead>
  <name>Richard Heyes</name>
  <user>richard</user>
  <email>richard@php.net</email>
  <active>no</active>
 </lead>
 <lead>
  <name>Michael Bretterklieber</name>
  <user>mbretter</user>
  <email>michael@bretterklieber.com</email>
  <active>no</active>
 </lead>

 <date>2017-03-07</date>
 <version>
  <release>1.1.0</release>
  <api>1.1.0</api>
 </version>
 <stability>
  <release>stable</release>
  <api>stable</api>
 </stability>
 <license uri="http://www.opensource.org/licenses/bsd-license.php">BSD</license>
 <notes>
* Set minimum PHP version to 5.4.0
* Set minimum PEAR version to 1.10.1

* Request #21033: PHP warning depreciated
 </notes>

 <contents>
  <dir name="/">
   <dir name="Auth">
    <dir name="SASL">
     <file name="Anonymous.php" role="php" />
     <file name="Common.php" role="php" />
     <file name="CramMD5.php" role="php" />
     <file name="DigestMD5.php" role="php" />
     <file name="External.php" role="php" />
     <file name="Login.php" role="php" />
     <file name="Plain.php" role="php" />
     <file name="SCRAM.php" role="php" />
    </dir> <!-- //SASL -->
    <file name="SASL.php" role="php" />
   </dir><!-- /Auth -->
  </dir> <!-- / -->
 </contents>

 <dependencies>
  <required>
   <php>
    <min>5.4.0</min>
   </php>
   <pearinstaller>
    <min>1.10.1</min>
   </pearinstaller>
  </required>
 </dependencies>

 <phprelease />

 <changelog>

  <release>
   <version>
    <release>1.1.0</release>
    <api>1.1.0</api>
   </version>
   <stability>
    <release>stable</release>
    <api>stable</api>
   </stability>
   <date>2017-03-07</date>
   <license uri="http://www.opensource.org/licenses/bsd-license.php">BSD</license>
   <notes>
* Set minimum PHP version to 5.4.0
* Set minimum PEAR version to 1.10.1

* Request #21033: PHP warning depreciated
   </notes>
  </release>

  <release>
   <version>
    <release>1.0.6</release>
    <api>1.0.3</api>
   </version>
   <stability>
    <release>stable</release>
    <api>stable</api>
   </stability>
   <date>2011-09-27</date>
   <license uri="http://www.opensource.org/licenses/bsd-license.php">BSD</license>
   <notes>
QA release
* Bug #18856: Authentication warnings because of wrong Auth_SASL::factory argument [kguest]
   </notes>
  </release>

  <release>
   <version>
    <release>1.0.5</release>
    <api>1.0.3</api>
   </version>
   <stability>
    <release>stable</release>
    <api>stable</api>
   </stability>
   <date>2011-09-04</date>
   <license uri="http://www.opensource.org/licenses/bsd-license.php">BSD</license>
   <notes>
QA release
* Added support for any mechanism of the SCRAM family; with thanks to Jehan Pagès. [kguest]
* crammd5 and digestmd5 mechanisms name deprecated in favour of IANA registered names 'cram-md5' and 'digest-md5'; with thanks to Jehan Pagès. [kguest]
   </notes>
  </release>

  <release>
   <version>
    <release>1.0.4</release>
    <api>1.0.3</api>
   </version>
   <stability>
    <release>stable</release>
    <api>stable</api>
   </stability>
   <date>2010-02-07</date>
   <license uri="http://www.opensource.org/licenses/bsd-license.php">BSD</license>
   <notes>
QA release
* Fix bug #16624: open_basedir restriction warning in DigestMD5.php [till]
   </notes>
  </release>

  <release>
   <version>
    <release>1.0.3</release>
    <api>1.0.3</api>
   </version>
   <stability>
    <release>stable</release>
    <api>stable</api>
   </stability>
   <date>2009-08-05</date>
   <license uri="http://www.opensource.org/licenses/bsd-license.php">BSD</license>
   <notes>
QA release
* Move SVN to proper directory structure [cweiske]
* Fix Bug #8775: Error in package.xml
* Fix Bug #14671: Security issue due to seeding random number generator [cweiske]
   </notes>
  </release>

  <release>
   <version>
    <release>1.0.2</release>
    <api>1.0.2</api>
   </version>
   <stability>
    <release>stable</release>
    <api>stable</api>
   </stability>
   <date>2006-05-21</date>
   <license uri="http://www.opensource.org/licenses/bsd-license.php">BSD</license>
   <notes>
* Fixed Bug #2143 Auth_SASL_DigestMD5::getResponse() generates invalid response
* Fixed Bug #6611 Suppress PHP 5 Notice Errors
* Fixed Bug #2154 realm isn't contained in challange
   </notes>
  </release>

  <release>
   <version>
    <release>1.0.1</release>
    <api>1.0.1</api>
   </version>
   <stability>
    <release>stable</release>
    <api>stable</api>
   </stability>
   <date>2003-09-11</date>
   <license uri="http://www.opensource.org/licenses/bsd-license.php">BSD</license>
   <notes>* Added authcid/authzid separation in PLAIN and DIGEST-MD5.
   </notes>
  </release>

 </changelog>
</package>
PK|c�\2r����pear/auth_sasl/Auth/SASL.phpnu�[���<?php
// +-----------------------------------------------------------------------+
// | Copyright (c) 2002-2003 Richard Heyes                                 |
// | All rights reserved.                                                  |
// |                                                                       |
// | Redistribution and use in source and binary forms, with or without    |
// | modification, are permitted provided that the following conditions    |
// | are met:                                                              |
// |                                                                       |
// | o Redistributions of source code must retain the above copyright      |
// |   notice, this list of conditions and the following disclaimer.       |
// | o Redistributions in binary form must reproduce the above copyright   |
// |   notice, this list of conditions and the following disclaimer in the |
// |   documentation and/or other materials provided with the distribution.|
// | o The names of the authors may not be used to endorse or promote      |
// |   products derived from this software without specific prior written  |
// |   permission.                                                         |
// |                                                                       |
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   |
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     |
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  |
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      |
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   |
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  |
// |                                                                       |
// +-----------------------------------------------------------------------+
// | Author: Richard Heyes <richard@php.net>                               |
// +-----------------------------------------------------------------------+
//
// $Id$

/**
* Client implementation of various SASL mechanisms
*
* @author  Richard Heyes <richard@php.net>
* @access  public
* @version 1.0
* @package Auth_SASL
*/

require_once('PEAR.php');

class Auth_SASL
{
    /**
    * Factory class. Returns an object of the request
    * type.
    *
    * @param string $type One of: Anonymous
    *                             Plain
    *                             CramMD5
    *                             DigestMD5
    *                             SCRAM-* (any mechanism of the SCRAM family)
    *                     Types are not case sensitive
    */
    public static function factory($type)
    {
        switch (strtolower($type)) {
            case 'anonymous':
                $filename  = 'Auth/SASL/Anonymous.php';
                $classname = 'Auth_SASL_Anonymous';
                break;

            case 'login':
                $filename  = 'Auth/SASL/Login.php';
                $classname = 'Auth_SASL_Login';
                break;

            case 'plain':
                $filename  = 'Auth/SASL/Plain.php';
                $classname = 'Auth_SASL_Plain';
                break;

            case 'external':
                $filename  = 'Auth/SASL/External.php';
                $classname = 'Auth_SASL_External';
                break;

            case 'crammd5':
                // $msg = 'Deprecated mechanism name. Use IANA-registered name: CRAM-MD5.';
                // trigger_error($msg, E_USER_DEPRECATED);
            case 'cram-md5':
                $filename  = 'Auth/SASL/CramMD5.php';
                $classname = 'Auth_SASL_CramMD5';
                break;

            case 'digestmd5':
                // $msg = 'Deprecated mechanism name. Use IANA-registered name: DIGEST-MD5.';
                // trigger_error($msg, E_USER_DEPRECATED);
            case 'digest-md5':
                // $msg = 'DIGEST-MD5 is a deprecated SASL mechanism as per RFC-6331. Using it could be a security risk.';
                // trigger_error($msg, E_USER_NOTICE);
                $filename  = 'Auth/SASL/DigestMD5.php';
                $classname = 'Auth_SASL_DigestMD5';
                break;

            default:
                $scram = '/^SCRAM-(.{1,9})$/i';
                if (preg_match($scram, $type, $matches))
                {
                    $hash = $matches[1];
                    $filename = dirname(__FILE__) .'/SASL/SCRAM.php';
                    $classname = 'Auth_SASL_SCRAM';
                    $parameter = $hash;
                    break;
                }
                return PEAR::raiseError('Invalid SASL mechanism type');
                break;
        }

        require_once($filename);
        if (isset($parameter))
            $obj = new $classname($parameter);
        else
            $obj = new $classname();
        return $obj;
    }
}

?>
PK|c�\����=="pear/auth_sasl/Auth/SASL/Plain.phpnu�[���<?php
// +-----------------------------------------------------------------------+ 
// | Copyright (c) 2002-2003 Richard Heyes                                 | 
// | All rights reserved.                                                  | 
// |                                                                       | 
// | Redistribution and use in source and binary forms, with or without    | 
// | modification, are permitted provided that the following conditions    | 
// | are met:                                                              | 
// |                                                                       | 
// | o Redistributions of source code must retain the above copyright      | 
// |   notice, this list of conditions and the following disclaimer.       | 
// | o Redistributions in binary form must reproduce the above copyright   | 
// |   notice, this list of conditions and the following disclaimer in the | 
// |   documentation and/or other materials provided with the distribution.| 
// | o The names of the authors may not be used to endorse or promote      | 
// |   products derived from this software without specific prior written  | 
// |   permission.                                                         | 
// |                                                                       | 
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   | 
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     | 
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  | 
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      | 
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   | 
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  | 
// |                                                                       | 
// +-----------------------------------------------------------------------+ 
// | Author: Richard Heyes <richard@php.net>                               | 
// +-----------------------------------------------------------------------+ 
// 
// $Id$

/**
* Implmentation of PLAIN SASL mechanism
*
* @author  Richard Heyes <richard@php.net>
* @access  public
* @version 1.0
* @package Auth_SASL
*/

require_once('Auth/SASL/Common.php');

class Auth_SASL_Plain extends Auth_SASL_Common
{
    /**
    * Returns PLAIN response
    *
    * @param  string $authcid   Authentication id (username)
    * @param  string $pass      Password
    * @param  string $authzid   Autorization id
    * @return string            PLAIN Response
    */
    function getResponse($authcid, $pass, $authzid = '')
    {
        return $authzid . chr(0) . $authcid . chr(0) . $pass;
    }
}
?>
PK|c�\�<� OO"pear/auth_sasl/Auth/SASL/Login.phpnu�[���<?php
// +-----------------------------------------------------------------------+ 
// | Copyright (c) 2002-2003 Richard Heyes                                 | 
// | All rights reserved.                                                  | 
// |                                                                       | 
// | Redistribution and use in source and binary forms, with or without    | 
// | modification, are permitted provided that the following conditions    | 
// | are met:                                                              | 
// |                                                                       | 
// | o Redistributions of source code must retain the above copyright      | 
// |   notice, this list of conditions and the following disclaimer.       | 
// | o Redistributions in binary form must reproduce the above copyright   | 
// |   notice, this list of conditions and the following disclaimer in the | 
// |   documentation and/or other materials provided with the distribution.| 
// | o The names of the authors may not be used to endorse or promote      | 
// |   products derived from this software without specific prior written  | 
// |   permission.                                                         | 
// |                                                                       | 
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   | 
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     | 
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  | 
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      | 
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   | 
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  | 
// |                                                                       | 
// +-----------------------------------------------------------------------+ 
// | Author: Richard Heyes <richard@php.net>                               | 
// +-----------------------------------------------------------------------+ 
// 
// $Id$

/**
* This is technically not a SASL mechanism, however
* it's used by Net_Sieve, Net_Cyrus and potentially
* other protocols , so here is a good place to abstract
* it.
*
* @author  Richard Heyes <richard@php.net>
* @access  public
* @version 1.0
* @package Auth_SASL
*/

require_once('Auth/SASL/Common.php');

class Auth_SASL_Login extends Auth_SASL_Common
{
    /**
    * Pseudo SASL LOGIN mechanism
    *
    * @param  string $user Username
    * @param  string $pass Password
    * @return string       LOGIN string
    */
    function getResponse($user, $pass)
    {
        return sprintf('LOGIN %s %s', $user, $pass);
    }
}
?>PK|c�\�J�}
}
&pear/auth_sasl/Auth/SASL/Anonymous.phpnu�[���<?php
// +-----------------------------------------------------------------------+ 
// | Copyright (c) 2002-2003 Richard Heyes                                 | 
// | All rights reserved.                                                  | 
// |                                                                       | 
// | Redistribution and use in source and binary forms, with or without    | 
// | modification, are permitted provided that the following conditions    | 
// | are met:                                                              | 
// |                                                                       | 
// | o Redistributions of source code must retain the above copyright      | 
// |   notice, this list of conditions and the following disclaimer.       | 
// | o Redistributions in binary form must reproduce the above copyright   | 
// |   notice, this list of conditions and the following disclaimer in the | 
// |   documentation and/or other materials provided with the distribution.| 
// | o The names of the authors may not be used to endorse or promote      | 
// |   products derived from this software without specific prior written  | 
// |   permission.                                                         | 
// |                                                                       | 
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   | 
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     | 
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  | 
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      | 
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   | 
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  | 
// |                                                                       | 
// +-----------------------------------------------------------------------+ 
// | Author: Richard Heyes <richard@php.net>                               | 
// +-----------------------------------------------------------------------+ 
// 
// $Id$

/**
* Implmentation of ANONYMOUS SASL mechanism
*
* @author  Richard Heyes <richard@php.net>
* @access  public
* @version 1.0
* @package Auth_SASL
*/

require_once('Auth/SASL/Common.php');

class Auth_SASL_Anonymous extends Auth_SASL_Common
{
    /**
    * Not much to do here except return the token supplied.
    * No encoding, hashing or encryption takes place for this
    * mechanism, simply one of:
    *  o An email address
    *  o An opaque string not containing "@" that can be interpreted
    *    by the sysadmin
    *  o Nothing
    *
    * We could have some logic here for the second option, but this
    * would by no means create something interpretable.
    *
    * @param  string $token Optional email address or string to provide
    *                       as trace information.
    * @return string        The unaltered input token
    */
    function getResponse($token = '')
    {
        return $token;
    }
}
?>PK|c�\%ׅtg!g!&pear/auth_sasl/Auth/SASL/DigestMD5.phpnu�[���<?php
// +-----------------------------------------------------------------------+ 
// | Copyright (c) 2002-2003 Richard Heyes                                 | 
// | All rights reserved.                                                  | 
// |                                                                       | 
// | Redistribution and use in source and binary forms, with or without    | 
// | modification, are permitted provided that the following conditions    | 
// | are met:                                                              | 
// |                                                                       | 
// | o Redistributions of source code must retain the above copyright      | 
// |   notice, this list of conditions and the following disclaimer.       | 
// | o Redistributions in binary form must reproduce the above copyright   | 
// |   notice, this list of conditions and the following disclaimer in the | 
// |   documentation and/or other materials provided with the distribution.| 
// | o The names of the authors may not be used to endorse or promote      | 
// |   products derived from this software without specific prior written  | 
// |   permission.                                                         | 
// |                                                                       | 
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   | 
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     | 
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  | 
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      | 
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   | 
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  | 
// |                                                                       | 
// +-----------------------------------------------------------------------+ 
// | Author: Richard Heyes <richard@php.net>                               | 
// +-----------------------------------------------------------------------+ 
// 
// $Id$

/**
* Implmentation of DIGEST-MD5 SASL mechanism
*
* @author  Richard Heyes <richard@php.net>
* @access  public
* @version 1.0
* @package Auth_SASL
*/

require_once('Auth/SASL/Common.php');

class Auth_SASL_DigestMD5 extends Auth_SASL_Common
{
    /**
    * Provides the (main) client response for DIGEST-MD5
    * requires a few extra parameters than the other
    * mechanisms, which are unavoidable.
    * 
    * @param  string $authcid   Authentication id (username)
    * @param  string $pass      Password
    * @param  string $challenge The digest challenge sent by the server
    * @param  string $hostname  The hostname of the machine you're connecting to
    * @param  string $service   The servicename (eg. imap, pop, acap etc)
    * @param  string $authzid   Authorization id (username to proxy as)
    * @return string            The digest response (NOT base64 encoded)
    * @access public
    */
    function getResponse($authcid, $pass, $challenge, $hostname, $service, $authzid = '')
    {
        $challenge = $this->_parseChallenge($challenge);
        $authzid_string = '';
        if ($authzid != '') {
            $authzid_string = ',authzid="' . $authzid . '"'; 
        }

        if (!empty($challenge)) {
            $cnonce         = $this->_getCnonce();
            $digest_uri     = sprintf('%s/%s', $service, $hostname);
            $response_value = $this->_getResponseValue($authcid, $pass, $challenge['realm'], $challenge['nonce'], $cnonce, $digest_uri, $authzid);

            if ($challenge['realm']) {
                return sprintf('username="%s",realm="%s"' . $authzid_string  .
',nonce="%s",cnonce="%s",nc=00000001,qop=auth,digest-uri="%s",response=%s,maxbuf=%d', $authcid, $challenge['realm'], $challenge['nonce'], $cnonce, $digest_uri, $response_value, $challenge['maxbuf']);
            } else {
                return sprintf('username="%s"' . $authzid_string  . ',nonce="%s",cnonce="%s",nc=00000001,qop=auth,digest-uri="%s",response=%s,maxbuf=%d', $authcid, $challenge['nonce'], $cnonce, $digest_uri, $response_value, $challenge['maxbuf']);
            }
        } else {
            return PEAR::raiseError('Invalid digest challenge');
        }
    }
    
    /**
    * Parses and verifies the digest challenge*
    *
    * @param  string $challenge The digest challenge
    * @return array             The parsed challenge as an assoc
    *                           array in the form "directive => value".
    * @access private
    */
    function _parseChallenge($challenge)
    {
        $tokens = array();
        while (preg_match('/^([a-z-]+)=("[^"]+(?<!\\\)"|[^,]+)/i', $challenge, $matches)) {

            // Ignore these as per rfc2831
            if ($matches[1] == 'opaque' OR $matches[1] == 'domain') {
                $challenge = substr($challenge, strlen($matches[0]) + 1);
                continue;
            }

            // Allowed multiple "realm" and "auth-param"
            if (!empty($tokens[$matches[1]]) AND ($matches[1] == 'realm' OR $matches[1] == 'auth-param')) {
                if (is_array($tokens[$matches[1]])) {
                    $tokens[$matches[1]][] = preg_replace('/^"(.*)"$/', '\\1', $matches[2]);
                } else {
                    $tokens[$matches[1]] = array($tokens[$matches[1]], preg_replace('/^"(.*)"$/', '\\1', $matches[2]));
                }

            // Any other multiple instance = failure
            } elseif (!empty($tokens[$matches[1]])) {
                $tokens = array();
                break;

            } else {
                $tokens[$matches[1]] = preg_replace('/^"(.*)"$/', '\\1', $matches[2]);
            }

            // Remove the just parsed directive from the challenge
            $challenge = substr($challenge, strlen($matches[0]) + 1);
        }

        /**
        * Defaults and required directives
        */
        // Realm
        if (empty($tokens['realm'])) {
            $tokens['realm'] = "";
        }

        // Maxbuf
        if (empty($tokens['maxbuf'])) {
            $tokens['maxbuf'] = 65536;
        }

        // Required: nonce, algorithm
        if (empty($tokens['nonce']) OR empty($tokens['algorithm'])) {
            return array();
        }

        return $tokens;
    }

    /**
    * Creates the response= part of the digest response
    *
    * @param  string $authcid    Authentication id (username)
    * @param  string $pass       Password
    * @param  string $realm      Realm as provided by the server
    * @param  string $nonce      Nonce as provided by the server
    * @param  string $cnonce     Client nonce
    * @param  string $digest_uri The digest-uri= value part of the response
    * @param  string $authzid    Authorization id
    * @return string             The response= part of the digest response
    * @access private
    */    
    function _getResponseValue($authcid, $pass, $realm, $nonce, $cnonce, $digest_uri, $authzid = '')
    {
        if ($authzid == '') {
            $A1 = sprintf('%s:%s:%s', pack('H32', md5(sprintf('%s:%s:%s', $authcid, $realm, $pass))), $nonce, $cnonce);
        } else {
            $A1 = sprintf('%s:%s:%s:%s', pack('H32', md5(sprintf('%s:%s:%s', $authcid, $realm, $pass))), $nonce, $cnonce, $authzid);
        }
        $A2 = 'AUTHENTICATE:' . $digest_uri;
        return md5(sprintf('%s:%s:00000001:%s:auth:%s', md5($A1), $nonce, $cnonce, md5($A2)));
    }

    /**
    * Creates the client nonce for the response
    *
    * @return string  The cnonce value
    * @access private
    */
    function _getCnonce()
    {
        if (@file_exists('/dev/urandom') && $fd = @fopen('/dev/urandom', 'r')) {
            return base64_encode(fread($fd, 32));

        } elseif (@file_exists('/dev/random') && $fd = @fopen('/dev/random', 'r')) {
            return base64_encode(fread($fd, 32));

        } else {
            $str = '';
            for ($i=0; $i<32; $i++) {
                $str .= chr(mt_rand(0, 255));
            }
            
            return base64_encode($str);
        }
    }
}
?>
PK|c�\�T�#pear/auth_sasl/Auth/SASL/Common.phpnu�[���<?php
// +-----------------------------------------------------------------------+
// | Copyright (c) 2002-2003 Richard Heyes                                 |
// | All rights reserved.                                                  |
// |                                                                       |
// | Redistribution and use in source and binary forms, with or without    |
// | modification, are permitted provided that the following conditions    |
// | are met:                                                              |
// |                                                                       |
// | o Redistributions of source code must retain the above copyright      |
// |   notice, this list of conditions and the following disclaimer.       |
// | o Redistributions in binary form must reproduce the above copyright   |
// |   notice, this list of conditions and the following disclaimer in the |
// |   documentation and/or other materials provided with the distribution.|
// | o The names of the authors may not be used to endorse or promote      |
// |   products derived from this software without specific prior written  |
// |   permission.                                                         |
// |                                                                       |
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   |
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     |
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  |
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      |
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   |
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  |
// |                                                                       |
// +-----------------------------------------------------------------------+
// | Author: Richard Heyes <richard@php.net>                               |
// +-----------------------------------------------------------------------+
//
// $Id$

/**
* Common functionality to SASL mechanisms
*
* @author  Richard Heyes <richard@php.net>
* @access  public
* @version 1.0
* @package Auth_SASL
*/

class Auth_SASL_Common
{
    /**
    * Function which implements HMAC MD5 digest
    *
    * @param  string $key  The secret key
    * @param  string $data The data to hash
    * @param  bool $raw_output Whether the digest is returned in binary or hexadecimal format.
    *
    * @return string       The HMAC-MD5 digest
    */
    function _HMAC_MD5($key, $data, $raw_output = FALSE)
    {
        if (strlen($key) > 64) {
            $key = pack('H32', md5($key));
        }

        if (strlen($key) < 64) {
            $key = str_pad($key, 64, chr(0));
        }

        $k_ipad = substr($key, 0, 64) ^ str_repeat(chr(0x36), 64);
        $k_opad = substr($key, 0, 64) ^ str_repeat(chr(0x5C), 64);

        $inner  = pack('H32', md5($k_ipad . $data));
        $digest = md5($k_opad . $inner, $raw_output);

        return $digest;
    }

    /**
    * Function which implements HMAC-SHA-1 digest
    *
    * @param  string $key  The secret key
    * @param  string $data The data to hash
    * @param  bool $raw_output Whether the digest is returned in binary or hexadecimal format.
    * @return string       The HMAC-SHA-1 digest
    * @author Jehan <jehan.marmottard@gmail.com>
    * @access protected
    */
    protected function _HMAC_SHA1($key, $data, $raw_output = FALSE)
    {
        if (strlen($key) > 64) {
            $key = sha1($key, TRUE);
        }

        if (strlen($key) < 64) {
            $key = str_pad($key, 64, chr(0));
        }

        $k_ipad = substr($key, 0, 64) ^ str_repeat(chr(0x36), 64);
        $k_opad = substr($key, 0, 64) ^ str_repeat(chr(0x5C), 64);

        $inner  = pack('H40', sha1($k_ipad . $data));
        $digest = sha1($k_opad . $inner, $raw_output);

         return $digest;
     }
}
?>
PK|c�\vR�r,,%pear/auth_sasl/Auth/SASL/External.phpnu�[���<?php
// +-----------------------------------------------------------------------+ 
// | Copyright (c) 2008 Christoph Schulz                                   | 
// | All rights reserved.                                                  | 
// |                                                                       | 
// | Redistribution and use in source and binary forms, with or without    | 
// | modification, are permitted provided that the following conditions    | 
// | are met:                                                              | 
// |                                                                       | 
// | o Redistributions of source code must retain the above copyright      | 
// |   notice, this list of conditions and the following disclaimer.       | 
// | o Redistributions in binary form must reproduce the above copyright   | 
// |   notice, this list of conditions and the following disclaimer in the | 
// |   documentation and/or other materials provided with the distribution.| 
// | o The names of the authors may not be used to endorse or promote      | 
// |   products derived from this software without specific prior written  | 
// |   permission.                                                         | 
// |                                                                       | 
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   | 
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     | 
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  | 
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      | 
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   | 
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  | 
// |                                                                       | 
// +-----------------------------------------------------------------------+ 
// | Author: Christoph Schulz <develop@kristov.de>                         | 
// +-----------------------------------------------------------------------+ 
// 
// $Id$

/**
* Implmentation of EXTERNAL SASL mechanism
*
* @author  Christoph Schulz <develop@kristov.de>
* @access  public
* @version 1.0.3
* @package Auth_SASL
*/

require_once('Auth/SASL/Common.php');

class Auth_SASL_External extends Auth_SASL_Common
{
    /**
    * Returns EXTERNAL response
    *
    * @param  string $authcid   Authentication id (username)
    * @param  string $pass      Password
    * @param  string $authzid   Autorization id
    * @return string            EXTERNAL Response
    */
    function getResponse($authcid, $pass, $authzid = '')
    {
        return $authzid;
    }
}
?>
PK}c�\ќ��0�0"pear/auth_sasl/Auth/SASL/SCRAM.phpnu�[���<?php
// +-----------------------------------------------------------------------+
// | Copyright (c) 2011 Jehan                                              |
// | All rights reserved.                                                  |
// |                                                                       |
// | Redistribution and use in source and binary forms, with or without    |
// | modification, are permitted provided that the following conditions    |
// | are met:                                                              |
// |                                                                       |
// | o Redistributions of source code must retain the above copyright      |
// |   notice, this list of conditions and the following disclaimer.       |
// | o Redistributions in binary form must reproduce the above copyright   |
// |   notice, this list of conditions and the following disclaimer in the |
// |   documentation and/or other materials provided with the distribution.|
// | o The names of the authors may not be used to endorse or promote      |
// |   products derived from this software without specific prior written  |
// |   permission.                                                         |
// |                                                                       |
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   |
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     |
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  |
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      |
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   |
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  |
// |                                                                       |
// +-----------------------------------------------------------------------+
// | Author: Jehan <jehan.marmottard@gmail.com                             |
// +-----------------------------------------------------------------------+
//
// $Id$

/**
* Implementation of SCRAM-* SASL mechanisms.
* SCRAM mechanisms have 3 main steps (initial response, response to the server challenge, then server signature
* verification) which keep state-awareness. Therefore a single class instanciation must be done and reused for the whole
* authentication process.
*
* @author  Jehan <jehan.marmottard@gmail.com>
* @access  public
* @version 1.0
* @package Auth_SASL
*/

require_once('Auth/SASL/Common.php');

class Auth_SASL_SCRAM extends Auth_SASL_Common
{
    /**
    * Construct a SCRAM-H client where 'H' is a cryptographic hash function.
    *
    * @param string $hash The name cryptographic hash function 'H' as registered by IANA in the "Hash Function Textual
    * Names" registry.
    * @link http://www.iana.org/assignments/hash-function-text-names/hash-function-text-names.xml "Hash Function Textual
    * Names"
    * format of core PHP hash function.
    * @access public
    */
    function __construct($hash)
    {
        // Though I could be strict, I will actually also accept the naming used in the PHP core hash framework.
        // For instance "sha1" is accepted, while the registered hash name should be "SHA-1".
        $hash = strtolower($hash);
        $hashes = array('md2' => 'md2',
            'md5' => 'md5',
            'sha-1' => 'sha1',
            'sha1' => 'sha1',
            'sha-224' > 'sha224',
            'sha224' > 'sha224',
            'sha-256' => 'sha256',
            'sha256' => 'sha256',
            'sha-384' => 'sha384',
            'sha384' => 'sha384',
            'sha-512' => 'sha512',
            'sha512' => 'sha512');
        if (function_exists('hash_hmac') && isset($hashes[$hash]))
        {
            $this->hash = create_function('$data', 'return hash("' . $hashes[$hash] . '", $data, TRUE);');
            $this->hmac = create_function('$key,$str,$raw', 'return hash_hmac("' . $hashes[$hash] . '", $str, $key, $raw);');
        }
        elseif ($hash == 'md5')
        {
            $this->hash = create_function('$data', 'return md5($data, true);');
            $this->hmac = array($this, '_HMAC_MD5');
        }
        elseif (in_array($hash, array('sha1', 'sha-1')))
        {
            $this->hash = create_function('$data', 'return sha1($data, true);');
            $this->hmac = array($this, '_HMAC_SHA1');
        }
        else
            return PEAR::raiseError('Invalid SASL mechanism type');
    }

    /**
    * Provides the (main) client response for SCRAM-H.
    *
    * @param  string $authcid   Authentication id (username)
    * @param  string $pass      Password
    * @param  string $challenge The challenge sent by the server.
    * If the challenge is NULL or an empty string, the result will be the "initial response".
    * @param  string $authzid   Authorization id (username to proxy as)
    * @return string|false      The response (binary, NOT base64 encoded)
    * @access public
    */
    public function getResponse($authcid, $pass, $challenge = NULL, $authzid = NULL)
    {
        $authcid = $this->_formatName($authcid);
        if (empty($authcid))
        {
            return false;
        }
        if (!empty($authzid))
        {
            $authzid = $this->_formatName($authzid);
            if (empty($authzid))
            {
                return false;
            }
        }

        if (empty($challenge))
        {
            return $this->_generateInitialResponse($authcid, $authzid);
        }
        else
        {
            return $this->_generateResponse($challenge, $pass);
        }

    }

    /**
    * Prepare a name for inclusion in a SCRAM response.
    *
    * @param string $username a name to be prepared.
    * @return string the reformated name.
    * @access private
    */
    private function _formatName($username)
    {
        // TODO: prepare through the SASLprep profile of the stringprep algorithm.
        // See RFC-4013.

        $username = str_replace('=', '=3D', $username);
        $username = str_replace(',', '=2C', $username);
        return $username;
    }

    /**
    * Generate the initial response which can be either sent directly in the first message or as a response to an empty
    * server challenge.
    *
    * @param string $authcid Prepared authentication identity.
    * @param string $authzid Prepared authorization identity.
    * @return string The SCRAM response to send.
    * @access private
    */
    private function _generateInitialResponse($authcid, $authzid)
    {
        $init_rep = '';
        $gs2_cbind_flag = 'n,'; // TODO: support channel binding.
        $this->gs2_header = $gs2_cbind_flag . (!empty($authzid)? 'a=' . $authzid : '') . ',';

        // I must generate a client nonce and "save" it for later comparison on second response.
        $this->cnonce = $this->_getCnonce();
        // XXX: in the future, when mandatory and/or optional extensions are defined in any updated RFC,
        // this message can be updated.
        $this->first_message_bare = 'n=' . $authcid . ',r=' . $this->cnonce;
        return $this->gs2_header . $this->first_message_bare;
    }

    /**
    * Parses and verifies a non-empty SCRAM challenge.
    *
    * @param  string $challenge The SCRAM challenge
    * @return string|false      The response to send; false in case of wrong challenge or if an initial response has not
    * been generated first.
    * @access private
    */
    private function _generateResponse($challenge, $password)
    {
        // XXX: as I don't support mandatory extension, I would fail on them.
        // And I simply ignore any optional extension.
        $server_message_regexp = "#^r=([\x21-\x2B\x2D-\x7E]+),s=((?:[A-Za-z0-9/+]{4})*(?:[A-Za-z0-9]{3}=|[A-Xa-z0-9]{2}==)?),i=([0-9]*)(,[A-Za-z]=[^,])*$#";
        if (!isset($this->cnonce, $this->gs2_header)
            || !preg_match($server_message_regexp, $challenge, $matches))
        {
            return false;
        }
        $nonce = $matches[1];
        $salt = base64_decode($matches[2]);
        if (!$salt)
        {
            // Invalid Base64.
            return false;
        }
        $i = intval($matches[3]);

        $cnonce = substr($nonce, 0, strlen($this->cnonce));
        if ($cnonce <> $this->cnonce)
        {
            // Invalid challenge! Are we under attack?
            return false;
        }

        $channel_binding = 'c=' . base64_encode($this->gs2_header); // TODO: support channel binding.
        $final_message = $channel_binding . ',r=' . $nonce; // XXX: no extension.

        // TODO: $password = $this->normalize($password); // SASLprep profile of stringprep.
        $saltedPassword = $this->hi($password, $salt, $i);
        $this->saltedPassword = $saltedPassword;
        $clientKey = call_user_func($this->hmac, $saltedPassword, "Client Key", TRUE);
        $storedKey = call_user_func($this->hash, $clientKey, TRUE);
        $authMessage = $this->first_message_bare . ',' . $challenge . ',' . $final_message;
        $this->authMessage = $authMessage;
        $clientSignature = call_user_func($this->hmac, $storedKey, $authMessage, TRUE);
        $clientProof = $clientKey ^ $clientSignature;
        $proof = ',p=' . base64_encode($clientProof);

        return $final_message . $proof;
    }

    /**
    * SCRAM has also a server verification step. On a successful outcome, it will send additional data which must
    * absolutely be checked against this function. If this fails, the entity which we are communicating with is probably
    * not the server as it has not access to your ServerKey.
    *
    * @param string $data The additional data sent along a successful outcome.
    * @return bool Whether the server has been authenticated.
    * If false, the client must close the connection and consider to be under a MITM attack.
    * @access public
    */
    public function processOutcome($data)
    {
        $verifier_regexp = '#^v=((?:[A-Za-z0-9/+]{4})*(?:[A-Za-z0-9]{3}=|[A-Xa-z0-9]{2}==)?)$#';
        if (!isset($this->saltedPassword, $this->authMessage)
            || !preg_match($verifier_regexp, $data, $matches))
        {
            // This cannot be an outcome, you never sent the challenge's response.
            return false;
        }

        $verifier = $matches[1];
        $proposed_serverSignature = base64_decode($verifier);
        $serverKey = call_user_func($this->hmac, $this->saltedPassword, "Server Key", true);
        $serverSignature = call_user_func($this->hmac, $serverKey, $this->authMessage, TRUE);
        return ($proposed_serverSignature === $serverSignature);
    }

    /**
    * Hi() call, which is essentially PBKDF2 (RFC-2898) with HMAC-H() as the pseudorandom function.
    *
    * @param string $str The string to hash.
    * @param string $hash The hash value.
    * @param int $i The iteration count.
    * @access private
    */
    private function hi($str, $salt, $i)
    {
        $int1 = "\0\0\0\1";
        $ui = call_user_func($this->hmac, $str, $salt . $int1, true);
        $result = $ui;
        for ($k = 1; $k < $i; $k++)
        {
            $ui = call_user_func($this->hmac, $str, $ui, true);
            $result = $result ^ $ui;
        }
        return $result;
    }


    /**
    * Creates the client nonce for the response
    *
    * @return string  The cnonce value
    * @access private
    * @author  Richard Heyes <richard@php.net>
    */
    private function _getCnonce()
    {
        // TODO: I reused the nonce function from the DigestMD5 class.
        // I should probably make this a protected function in Common.
        if (@file_exists('/dev/urandom') && $fd = @fopen('/dev/urandom', 'r')) {
            return base64_encode(fread($fd, 32));

        } elseif (@file_exists('/dev/random') && $fd = @fopen('/dev/random', 'r')) {
            return base64_encode(fread($fd, 32));

        } else {
            $str = '';
            for ($i=0; $i<32; $i++) {
                $str .= chr(mt_rand(0, 255));
            }

            return base64_encode($str);
        }
    }

}

?>
PK}c�\P(~�T
T
$pear/auth_sasl/Auth/SASL/CramMD5.phpnu�[���<?php
// +-----------------------------------------------------------------------+ 
// | Copyright (c) 2002-2003 Richard Heyes                                 | 
// | All rights reserved.                                                  | 
// |                                                                       | 
// | Redistribution and use in source and binary forms, with or without    | 
// | modification, are permitted provided that the following conditions    | 
// | are met:                                                              | 
// |                                                                       | 
// | o Redistributions of source code must retain the above copyright      | 
// |   notice, this list of conditions and the following disclaimer.       | 
// | o Redistributions in binary form must reproduce the above copyright   | 
// |   notice, this list of conditions and the following disclaimer in the | 
// |   documentation and/or other materials provided with the distribution.| 
// | o The names of the authors may not be used to endorse or promote      | 
// |   products derived from this software without specific prior written  | 
// |   permission.                                                         | 
// |                                                                       | 
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   | 
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     | 
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  | 
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      | 
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   | 
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  | 
// |                                                                       | 
// +-----------------------------------------------------------------------+ 
// | Author: Richard Heyes <richard@php.net>                               | 
// +-----------------------------------------------------------------------+ 
// 
// $Id$

/**
* Implmentation of CRAM-MD5 SASL mechanism
*
* @author  Richard Heyes <richard@php.net>
* @access  public
* @version 1.0
* @package Auth_SASL
*/

require_once('Auth/SASL/Common.php');

class Auth_SASL_CramMD5 extends Auth_SASL_Common
{
    /**
    * Implements the CRAM-MD5 SASL mechanism
    * This DOES NOT base64 encode the return value,
    * you will need to do that yourself.
    *
    * @param string $user      Username
    * @param string $pass      Password
    * @param string $challenge The challenge supplied by the server.
    *                          this should be already base64_decoded.
    *
    * @return string The string to pass back to the server, of the form
    *                "<user> <digest>". This is NOT base64_encoded.
    */
    function getResponse($user, $pass, $challenge)
    {
        return $user . ' ' . $this->_HMAC_MD5($pass, $challenge);
    }
}
?>PK}c�\�G׭��pear/auth_sasl/README.mdnu�[���# Auth_SASL - Abstraction of various SASL mechanism responses

[![Build Status](https://travis-ci.org/pear/Auth_SASL.svg?branch=master)](https://travis-ci.org/pear/Auth_SASL)
    

Provides code to generate responses to common SASL mechanisms, including:
- Digest-MD5
- Cram-MD5
- Plain
- Anonymous
- Login (Pseudo mechanism)
- SCRAM	

[Homepage](http://pear.php.net/package/Auth_SASL/)


## Installation
For a PEAR installation that downloads from the PEAR channel:

`$ pear install pear/auth_sasl`

For a PEAR installation from a previously downloaded tarball:

`$ pear install Auth_SASL-*.tgz`

For a PEAR installation from a code clone:

`$ pear install package.xml`

For a local composer installation:

`$ composer install`

To add as a dependency to your composer-managed application:

`$composer require pear/auth_sasl`


## Tests
Run  the tests from a local composer installation:

`$ ./vendor/bin/phpunit`


## License
BSD license
PK}c�\�
{����0pear/console_commandline/Console/CommandLine.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * This file is part of the PEAR Console_CommandLine package.
 *
 * A full featured package for managing command-line options and arguments
 * hightly inspired from python optparse module, it allows the developper to
 * easily build complex command line interfaces.
 *
 * PHP version 5
 *
 * LICENSE: This source file is subject to the MIT license that is available
 * through the world-wide-web at the following URI:
 * http://opensource.org/licenses/mit-license.php
 *
 * @category  Console
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License
 * @version   CVS: $Id$
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     Class available since release 0.1.0
 * @filesource
 */

/**
 * Required unconditionally
 */
require_once 'Console/CommandLine/Exception.php';
require_once 'Console/CommandLine/Outputter/Default.php';
require_once 'Console/CommandLine/Renderer/Default.php';
require_once 'Console/CommandLine/MessageProvider/Default.php';

/**
 * Main class for parsing command line options and arguments.
 *
 * There are three ways to create parsers with this class:
 * <code>
 * // direct usage
 * $parser = new Console_CommandLine();
 *
 * // with an xml definition file
 * $parser = Console_CommandLine::fromXmlFile('path/to/file.xml');
 *
 * // with an xml definition string
 * $validXmlString = '..your xml string...';
 * $parser = Console_CommandLine::fromXmlString($validXmlString);
 * </code>
 *
 * @category  Console
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License
 * @version   Release: @package_version@
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     File available since release 0.1.0
 * @example   docs/examples/ex1.php
 * @example   docs/examples/ex2.php
 */
class Console_CommandLine
{
    // Public properties {{{

    /**
     * Error messages.
     *
     * @var array $errors Error messages
     * @todo move this to Console_CommandLine_MessageProvider
     */
    public static $errors = array(
        'option_bad_name'                    => 'option name must be a valid php variable name (got: {$name})',
        'argument_bad_name'                  => 'argument name must be a valid php variable name (got: {$name})',
        'argument_no_default'                => 'only optional arguments can have a default value',
        'option_long_and_short_name_missing' => 'you must provide at least an option short name or long name for option "{$name}"',
        'option_bad_short_name'              => 'option "{$name}" short name must be a dash followed by a letter (got: "{$short_name}")',
        'option_bad_long_name'               => 'option "{$name}" long name must be 2 dashes followed by a word (got: "{$long_name}")',
        'option_unregistered_action'         => 'unregistered action "{$action}" for option "{$name}".',
        'option_bad_action'                  => 'invalid action for option "{$name}".',
        'option_invalid_callback'            => 'you must provide a valid callback for option "{$name}"',
        'action_class_does_not_exists'       => 'action "{$name}" class "{$class}" not found, make sure that your class is available before calling Console_CommandLine::registerAction()',
        'invalid_xml_file'                   => 'XML definition file "{$file}" does not exists or is not readable',
        'invalid_rng_file'                   => 'RNG file "{$file}" does not exists or is not readable'
    );

    /**
     * The name of the program, if not given it defaults to argv[0].
     *
     * @var string $name Name of your program
     */
    public $name;

    /**
     * A description text that will be displayed in the help message.
     *
     * @var string $description Description of your program
     */
    public $description = '';

    /**
     * A string that represents the version of the program, if this property is
     * not empty and property add_version_option is not set to false, the
     * command line parser will add a --version option, that will display the
     * property content.
     *
     * @var    string $version
     * @access public
     */
    public $version = '';

    /**
     * Boolean that determine if the command line parser should add the help
     * (-h, --help) option automatically.
     *
     * @var bool $add_help_option Whether to add a help option or not
     */
    public $add_help_option = true;

    /**
     * Boolean that determine if the command line parser should add the version
     * (-v, --version) option automatically.
     * Note that the version option is also generated only if the version
     * property is not empty, it's up to you to provide a version string of
     * course.
     *
     * @var bool $add_version_option Whether to add a version option or not
     */
    public $add_version_option = true;

    /**
     * Boolean that determine if providing a subcommand is mandatory.
     *
     * @var bool $subcommand_required Whether a subcommand is required or not
     */
    public $subcommand_required = false;

    /**
     * The command line parser renderer instance.
     *
     * @var    object that implements Console_CommandLine_Renderer interface
     * @access protected
     */
    public $renderer = false;

    /**
     * The command line parser outputter instance.
     *
     * @var Console_CommandLine_Outputter An outputter
     */
    public $outputter = false;

    /**
     * The command line message provider instance.
     *
     * @var Console_CommandLine_MessageProvider A message provider instance
     */
    public $message_provider = false;

    /**
     * Boolean that tells the parser to be POSIX compliant, POSIX demands the
     * following behavior: the first non-option stops option processing.
     *
     * @var bool $force_posix Whether to force posix compliance or not
     */
    public $force_posix = false;

    /**
     * Boolean that tells the parser to set relevant options default values,
     * according to the option action.
     *
     * @see Console_CommandLine_Option::setDefaults()
     * @var bool $force_options_defaults Whether to force option default values
     */
    public $force_options_defaults = false;


   /**
    * Boolean that tells the parser to treat a single - option as an argument
    * instead of trying to read STDIN.
    *
    * @var bool $avoid_reading_stdin Whether to treat - as an argument
    */
    public $avoid_reading_stdin = false;

    /**
     * An array of Console_CommandLine_Option objects.
     *
     * @var array $options The options array
     */
    public $options = array();

    /**
     * An array of Console_CommandLine_Argument objects.
     *
     * @var array $args The arguments array
     */
    public $args = array();

    /**
     * An array of Console_CommandLine_Command objects (sub commands).
     *
     * @var array $commands The commands array
     */
    public $commands = array();

    /**
     * Parent, only relevant in Command objects but left here for interface
     * convenience.
     *
     * @var Console_CommandLine The parent instance
     * @todo move Console_CommandLine::parent to Console_CommandLine_Command
     */
    public $parent = false;

    /**
     * Array of valid actions for an option, this array will also store user
     * registered actions.
     *
     * The array format is:
     * <pre>
     * array(
     *     <ActionName:string> => array(<ActionClass:string>, <builtin:bool>)
     * )
     * </pre>
     *
     * @var array $actions List of valid actions
     */
    public static $actions = array(
        'StoreTrue'   => array('Console_CommandLine_Action_StoreTrue', true),
        'StoreFalse'  => array('Console_CommandLine_Action_StoreFalse', true),
        'StoreString' => array('Console_CommandLine_Action_StoreString', true),
        'StoreInt'    => array('Console_CommandLine_Action_StoreInt', true),
        'StoreFloat'  => array('Console_CommandLine_Action_StoreFloat', true),
        'StoreArray'  => array('Console_CommandLine_Action_StoreArray', true),
        'Callback'    => array('Console_CommandLine_Action_Callback', true),
        'Counter'     => array('Console_CommandLine_Action_Counter', true),
        'Help'        => array('Console_CommandLine_Action_Help', true),
        'Version'     => array('Console_CommandLine_Action_Version', true),
        'Password'    => array('Console_CommandLine_Action_Password', true),
        'List'        => array('Console_CommandLine_Action_List', true),
    );

    /**
     * Custom errors messages for this command
     *
     * This array is of the form:
     * <code>
     * <?php
     * array(
     *     $messageName => $messageText,
     *     $messageName => $messageText,
     *     ...
     * );
     * ?>
     * </code>
     *
     * If specified, these messages override the messages provided by the
     * default message provider. For example:
     * <code>
     * <?php
     * $messages = array(
     *     'ARGUMENT_REQUIRED' => 'The argument foo is required.',
     * );
     * ?>
     * </code>
     *
     * @var array
     * @see Console_CommandLine_MessageProvider_Default
     */
    public $messages = array();

    // }}}
    // {{{ Private properties

    /**
     * Array of options that must be dispatched at the end.
     *
     * @var array $_dispatchLater Options to be dispatched
     */
    private $_dispatchLater = array();

    private $_lastopt = false;
    private $_stopflag = false;

    // }}}
    // __construct() {{{

    /**
     * Constructor.
     * Example:
     *
     * <code>
     * $parser = new Console_CommandLine(array(
     *     'name'               => 'yourprogram', // defaults to argv[0]
     *     'description'        => 'Description of your program',
     *     'version'            => '0.0.1', // your program version
     *     'add_help_option'    => true, // or false to disable --help option
     *     'add_version_option' => true, // or false to disable --version option
     *     'force_posix'        => false // or true to force posix compliance
     * ));
     * </code>
     *
     * @param array $params An optional array of parameters
     *
     * @return void
     */
    public function __construct(array $params = array())
    {
        if (isset($params['name'])) {
            $this->name = $params['name'];
        } else if (isset($argv) && count($argv) > 0) {
            $this->name = $argv[0];
        } else if (isset($_SERVER['argv']) && count($_SERVER['argv']) > 0) {
            $this->name = $_SERVER['argv'][0];
        } else if (isset($_SERVER['SCRIPT_NAME'])) {
            $this->name = basename($_SERVER['SCRIPT_NAME']);
        }
        if (isset($params['description'])) {
            $this->description = $params['description'];
        }
        if (isset($params['version'])) {
            $this->version = $params['version'];
        }
        if (isset($params['add_version_option'])) {
            $this->add_version_option = $params['add_version_option'];
        }
        if (isset($params['add_help_option'])) {
            $this->add_help_option = $params['add_help_option'];
        }
        if (isset($params['subcommand_required'])) {
            $this->subcommand_required = $params['subcommand_required'];
        }
        if (isset($params['force_posix'])) {
            $this->force_posix = $params['force_posix'];
        } else if (getenv('POSIXLY_CORRECT')) {
            $this->force_posix = true;
        }
        if (isset($params['messages']) && is_array($params['messages'])) {
            $this->messages = $params['messages'];
        }
        // set default instances
        $this->renderer         = new Console_CommandLine_Renderer_Default($this);
        $this->outputter        = new Console_CommandLine_Outputter_Default();
        $this->message_provider = new Console_CommandLine_MessageProvider_Default();
    }

    // }}}
    // accept() {{{

    /**
     * Method to allow Console_CommandLine to accept either:
     *  + a custom renderer,
     *  + a custom outputter,
     *  + or a custom message provider
     *
     * @param mixed $instance The custom instance
     *
     * @return void
     * @throws Console_CommandLine_Exception if wrong argument passed
     */
    public function accept($instance)
    {
        if ($instance instanceof Console_CommandLine_Renderer) {
            if (property_exists($instance, 'parser') && !$instance->parser) {
                $instance->parser = $this;
            }
            $this->renderer = $instance;
        } else if ($instance instanceof Console_CommandLine_Outputter) {
            $this->outputter = $instance;
        } else if ($instance instanceof Console_CommandLine_MessageProvider) {
            $this->message_provider = $instance;
        } else {
            throw Console_CommandLine_Exception::factory(
                'INVALID_CUSTOM_INSTANCE',
                array(),
                $this,
                $this->messages
            );
        }
    }

    // }}}
    // fromXmlFile() {{{

    /**
     * Returns a command line parser instance built from an xml file.
     *
     * Example:
     * <code>
     * require_once 'Console/CommandLine.php';
     * $parser = Console_CommandLine::fromXmlFile('path/to/file.xml');
     * $result = $parser->parse();
     * </code>
     *
     * @param string $file Path to the xml file
     *
     * @return Console_CommandLine The parser instance
     */
    public static function fromXmlFile($file)
    {
        include_once 'Console/CommandLine/XmlParser.php';
        return Console_CommandLine_XmlParser::parse($file);
    }

    // }}}
    // fromXmlString() {{{

    /**
     * Returns a command line parser instance built from an xml string.
     *
     * Example:
     * <code>
     * require_once 'Console/CommandLine.php';
     * $xmldata = '<?xml version="1.0" encoding="utf-8" standalone="yes"?>
     * <command>
     *   <description>Compress files</description>
     *   <option name="quiet">
     *     <short_name>-q</short_name>
     *     <long_name>--quiet</long_name>
     *     <description>be quiet when run</description>
     *     <action>StoreTrue/action>
     *   </option>
     *   <argument name="files">
     *     <description>a list of files</description>
     *     <multiple>true</multiple>
     *   </argument>
     * </command>';
     * $parser = Console_CommandLine::fromXmlString($xmldata);
     * $result = $parser->parse();
     * </code>
     *
     * @param string $string The xml data
     *
     * @return Console_CommandLine The parser instance
     */
    public static function fromXmlString($string)
    {
        include_once 'Console/CommandLine/XmlParser.php';
        return Console_CommandLine_XmlParser::parseString($string);
    }

    // }}}
    // addArgument() {{{

    /**
     * Adds an argument to the command line parser and returns it.
     *
     * Adds an argument with the name $name and set its attributes with the
     * array $params, then return the Console_CommandLine_Argument instance
     * created.
     * The method accepts another form: you can directly pass a
     * Console_CommandLine_Argument object as the sole argument, this allows
     * you to contruct the argument separately, in order to reuse it in
     * different command line parsers or commands for example.
     *
     * Example:
     * <code>
     * $parser = new Console_CommandLine();
     * // add an array argument
     * $parser->addArgument('input_files', array('multiple'=>true));
     * // add a simple argument
     * $parser->addArgument('output_file');
     * $result = $parser->parse();
     * print_r($result->args['input_files']);
     * print_r($result->args['output_file']);
     * // will print:
     * // array('file1', 'file2')
     * // 'file3'
     * // if the command line was:
     * // myscript.php file1 file2 file3
     * </code>
     *
     * In a terminal, the help will be displayed like this:
     * <code>
     * $ myscript.php install -h
     * Usage: myscript.php <input_files...> <output_file>
     * </code>
     *
     * @param mixed $name   A string containing the argument name or an
     *                      instance of Console_CommandLine_Argument
     * @param array $params An array containing the argument attributes
     *
     * @return Console_CommandLine_Argument the added argument
     * @see Console_CommandLine_Argument
     */
    public function addArgument($name, $params = array())
    {
        if ($name instanceof Console_CommandLine_Argument) {
            $argument = $name;
        } else {
            include_once 'Console/CommandLine/Argument.php';
            $argument = new Console_CommandLine_Argument($name, $params);
        }
        $argument->validate();
        $this->args[$argument->name] = $argument;
        return $argument;
    }

    // }}}
    // addCommand() {{{

    /**
     * Adds a sub-command to the command line parser.
     *
     * Adds a command with the given $name to the parser and returns the
     * Console_CommandLine_Command instance, you can then populate the command
     * with options, configure it, etc... like you would do for the main parser
     * because the class Console_CommandLine_Command inherits from
     * Console_CommandLine.
     *
     * An example:
     * <code>
     * $parser = new Console_CommandLine();
     * $install_cmd = $parser->addCommand('install');
     * $install_cmd->addOption(
     *     'verbose',
     *     array(
     *         'short_name'  => '-v',
     *         'long_name'   => '--verbose',
     *         'description' => 'be noisy when installing stuff',
     *         'action'      => 'StoreTrue'
     *      )
     * );
     * $parser->parse();
     * </code>
     * Then in a terminal:
     * <code>
     * $ myscript.php install -h
     * Usage: myscript.php install [options]
     *
     * Options:
     *   -h, --help     display this help message and exit
     *   -v, --verbose  be noisy when installing stuff
     *
     * $ myscript.php install --verbose
     * Installing whatever...
     * $
     * </code>
     *
     * @param mixed $name   A string containing the command name or an
     *                      instance of Console_CommandLine_Command
     * @param array $params An array containing the command attributes
     *
     * @return Console_CommandLine_Command the added subcommand
     * @see    Console_CommandLine_Command
     */
    public function addCommand($name, $params = array())
    {
        if ($name instanceof Console_CommandLine_Command) {
            $command = $name;
        } else {
            include_once 'Console/CommandLine/Command.php';
            $params['name'] = $name;
            $command        = new Console_CommandLine_Command($params);
            // some properties must cascade to the child command if not
            // passed explicitely. This is done only in this case, because if
            // we have a Command object we have no way to determine if theses
            // properties have already been set
            $cascade = array(
                'add_help_option',
                'add_version_option',
                'outputter',
                'message_provider',
                'force_posix',
                'force_options_defaults'
            );
            foreach ($cascade as $property) {
                if (!isset($params[$property])) {
                    $command->$property = $this->$property;
                }
            }
            if (!isset($params['renderer'])) {
                $renderer          = clone $this->renderer;
                $renderer->parser  = $command;
                $command->renderer = $renderer;
            }
        }
        $command->parent = $this;
        $this->commands[$command->name] = $command;
        return $command;
    }

    // }}}
    // addOption() {{{

    /**
     * Adds an option to the command line parser and returns it.
     *
     * Adds an option with the name $name and set its attributes with the
     * array $params, then return the Console_CommandLine_Option instance
     * created.
     * The method accepts another form: you can directly pass a
     * Console_CommandLine_Option object as the sole argument, this allows
     * you to contruct the option separately, in order to reuse it in different
     * command line parsers or commands for example.
     *
     * Example:
     * <code>
     * $parser = new Console_CommandLine();
     * $parser->addOption('path', array(
     *     'short_name'  => '-p',  // a short name
     *     'long_name'   => '--path', // a long name
     *     'description' => 'path to the dir', // a description msg
     *     'action'      => 'StoreString',
     *     'default'     => '/tmp' // a default value
     * ));
     * $parser->parse();
     * </code>
     *
     * In a terminal, the help will be displayed like this:
     * <code>
     * $ myscript.php --help
     * Usage: myscript.php [options]
     *
     * Options:
     *   -h, --help  display this help message and exit
     *   -p, --path  path to the dir
     *
     * </code>
     *
     * Various methods to specify an option, these 3 commands are equivalent:
     * <code>
     * $ myscript.php --path=some/path
     * $ myscript.php -p some/path
     * $ myscript.php -psome/path
     * </code>
     *
     * @param mixed $name   A string containing the option name or an
     *                      instance of Console_CommandLine_Option
     * @param array $params An array containing the option attributes
     *
     * @return Console_CommandLine_Option The added option
     * @see    Console_CommandLine_Option
     */
    public function addOption($name, $params = array())
    {
        include_once 'Console/CommandLine/Option.php';
        if ($name instanceof Console_CommandLine_Option) {
            $opt = $name;
        } else {
            $opt = new Console_CommandLine_Option($name, $params);
        }
        $opt->validate();
        if ($this->force_options_defaults) {
            $opt->setDefaults();
        }
        $this->options[$opt->name] = $opt;
        if (!empty($opt->choices) && $opt->add_list_option) {
            $this->addOption('list_' . $opt->name, array(
                'long_name'     => '--list-' . $opt->name,
                'description'   => $this->message_provider->get(
                    'LIST_OPTION_MESSAGE',
                    array('name' => $opt->name)
                ),
                'action'        => 'List',
                'action_params' => array('list' => $opt->choices),
            ));
        }
        return $opt;
    }

    // }}}
    // displayError() {{{

    /**
     * Displays an error to the user via stderr and exit with $exitCode if its
     * value is not equals to false.
     *
     * @param string $error    The error message
     * @param int    $exitCode The exit code number (default: 1). If set to
     *                         false, the exit() function will not be called
     *
     * @return void
     */
    public function displayError($error, $exitCode = 1)
    {
        $this->outputter->stderr($this->renderer->error($error));
        if ($exitCode !== false) {
            exit($exitCode);
        }
    }

    // }}}
    // displayUsage() {{{

    /**
     * Displays the usage help message to the user via stdout and exit with
     * $exitCode if its value is not equals to false.
     *
     * @param int $exitCode The exit code number (default: 0). If set to
     *                      false, the exit() function will not be called
     *
     * @return void
     */
    public function displayUsage($exitCode = 0)
    {
        $this->outputter->stdout($this->renderer->usage());
        if ($exitCode !== false) {
            exit($exitCode);
        }
    }

    // }}}
    // displayVersion() {{{

    /**
     * Displays the program version to the user via stdout and exit with
     * $exitCode if its value is not equals to false.
     *
     *
     * @param int $exitCode The exit code number (default: 0). If set to
     *                      false, the exit() function will not be called
     *
     * @return void
     */
    public function displayVersion($exitCode = 0)
    {
        $this->outputter->stdout($this->renderer->version());
        if ($exitCode !== false) {
            exit($exitCode);
        }
    }

    // }}}
    // findOption() {{{

    /**
     * Finds the option that matches the given short_name (ex: -v), long_name
     * (ex: --verbose) or name (ex: verbose).
     *
     * @param string $str The option identifier
     *
     * @return mixed A Console_CommandLine_Option instance or false
     */
    public function findOption($str)
    {
        $str = trim((string) $str);
        if ($str === '') {
            return false;
        }
        $matches = array();
        foreach ($this->options as $opt) {
            if ($opt->short_name == $str || $opt->long_name == $str ||
                $opt->name == $str) {
                // exact match
                return $opt;
            }
            if (substr($opt->long_name, 0, strlen($str)) === $str) {
                // abbreviated long option
                $matches[] = $opt;
            }
        }
        if ($count = count($matches)) {
            if ($count > 1) {
                $matches_str = '';
                $padding     = '';
                foreach ($matches as $opt) {
                    $matches_str .= $padding . $opt->long_name;
                    $padding      = ', ';
                }
                throw Console_CommandLine_Exception::factory(
                    'OPTION_AMBIGUOUS',
                    array('name' => $str, 'matches' => $matches_str),
                    $this,
                    $this->messages
                );
            }
            return $matches[0];
        }
        return false;
    }
    // }}}
    // registerAction() {{{

    /**
     * Registers a custom action for the parser, an example:
     *
     * <code>
     *
     * // in this example we create a "range" action:
     * // the user will be able to enter something like:
     * // $ <program> -r 1,5
     * // and in the result we will have:
     * // $result->options['range']: array(1, 5)
     *
     * require_once 'Console/CommandLine.php';
     * require_once 'Console/CommandLine/Action.php';
     *
     * class ActionRange extends Console_CommandLine_Action
     * {
     *     public function execute($value=false, $params=array())
     *     {
     *         $range = explode(',', str_replace(' ', '', $value));
     *         if (count($range) != 2) {
     *             throw new Exception(sprintf(
     *                 'Option "%s" must be 2 integers separated by a comma',
     *                 $this->option->name
     *             ));
     *         }
     *         $this->setResult($range);
     *     }
     * }
     * // then we can register our action
     * Console_CommandLine::registerAction('Range', 'ActionRange');
     * // and now our action is available !
     * $parser = new Console_CommandLine();
     * $parser->addOption('range', array(
     *     'short_name'  => '-r',
     *     'long_name'   => '--range',
     *     'action'      => 'Range', // note our custom action
     *     'description' => 'A range of two integers separated by a comma'
     * ));
     * // etc...
     *
     * </code>
     *
     * @param string $name  The name of the custom action
     * @param string $class The class name of the custom action
     *
     * @return void
     */
    public static function registerAction($name, $class)
    {
        if (!isset(self::$actions[$name])) {
            if (!class_exists($class)) {
                self::triggerError('action_class_does_not_exists',
                    E_USER_ERROR,
                    array('{$name}' => $name, '{$class}' => $class));
            }
            self::$actions[$name] = array($class, false);
        }
    }

    // }}}
    // triggerError() {{{

    /**
     * A wrapper for programming errors triggering.
     *
     * @param string $msgId  Identifier of the message
     * @param int    $level  The php error level
     * @param array  $params An array of search=>replaces entries
     *
     * @return void
     * @todo remove Console::triggerError() and use exceptions only
     */
    public static function triggerError($msgId, $level, $params=array())
    {
        if (isset(self::$errors[$msgId])) {
            $msg = str_replace(array_keys($params),
                array_values($params), self::$errors[$msgId]);
            trigger_error($msg, $level);
        } else {
            trigger_error('unknown error', $level);
        }
    }

    // }}}
    // parse() {{{

    /**
     * Parses the command line arguments and returns a
     * Console_CommandLine_Result instance.
     *
     * @param integer $userArgc Number of arguments (optional)
     * @param array   $userArgv Array containing arguments (optional)
     *
     * @return Console_CommandLine_Result The result instance
     * @throws Exception on user errors
     */
    public function parse($userArgc=null, $userArgv=null)
    {
        $this->_lastopt  = false;
        $this->_stopflag = false;

        $this->addBuiltinOptions();
        if ($userArgc !== null && $userArgv !== null) {
            $argc = $userArgc;
            $argv = $userArgv;
        } else {
            list($argc, $argv) = $this->getArgcArgv();
        }
        // build an empty result
        include_once 'Console/CommandLine/Result.php';
        $result = new Console_CommandLine_Result();
        if (!($this instanceof Console_CommandLine_Command)) {
            // remove script name if we're not in a subcommand
            array_shift($argv);
            $argc--;
        }
        // will contain arguments
        $args = array();
        foreach ($this->options as $name=>$option) {
            $result->options[$name] = $option->default;
        }
        // parse command line tokens
        while ($argc--) {
            $token = array_shift($argv);
            try {
                if ($cmd = $this->_getSubCommand($token)) {
                    $result->command_name = $cmd->name;
                    $result->command      = $cmd->parse($argc, $argv);
                    break;
                } else {
                    $this->parseToken($token, $result, $args, $argc);
                }
            } catch (Exception $exc) {
                throw $exc;
            }
        }
        // Parse a null token to allow any undespatched actions to be despatched.
        $this->parseToken(null, $result, $args, 0);
        // Check if an invalid subcommand was specified. If there are
        // subcommands and no arguments, but an argument was provided, it is
        // an invalid subcommand.
        if (   count($this->commands) > 0
            && count($this->args) === 0
            && count($args) > 0
        ) {
            throw Console_CommandLine_Exception::factory(
                'INVALID_SUBCOMMAND',
                array('command' => $args[0]),
                $this,
                $this->messages
            );
        }
        // if subcommand_required is set to true we must check that we have a
        // subcommand.
        if (   count($this->commands)
            && $this->subcommand_required
            && !$result->command_name
        ) {
            throw Console_CommandLine_Exception::factory(
                'SUBCOMMAND_REQUIRED',
                array('commands' => implode(', ', array_keys($this->commands))),
                $this,
                $this->messages
            );
        }
        // minimum argument number check
        $argnum = 0;
        foreach ($this->args as $name=>$arg) {
            if (!$arg->optional) {
                $argnum++;
            }
        }
        if (count($args) < $argnum) {
            throw Console_CommandLine_Exception::factory(
                'ARGUMENT_REQUIRED',
                array('argnum' => $argnum, 'plural' => $argnum>1 ? 's': ''),
                $this,
                $this->messages
            );
        }
        // handle arguments
        $c = count($this->args);
        foreach ($this->args as $name=>$arg) {
            $c--;
            if ($arg->multiple) {
                $result->args[$name] = $c ? array_splice($args, 0, -$c) : $args;
            } else {
                $result->args[$name] = array_shift($args);
            }
            if (!$result->args[$name] && $arg->optional && $arg->default) {
                $result->args[$name] = $arg->default;
            }
            // check value is in argument choices
            if (!empty($this->args[$name]->choices)) {
                foreach ($result->args[$name] as $value) {
                    if (!in_array($value, $arg->choices)) {
                        throw Console_CommandLine_Exception::factory(
                            'ARGUMENT_VALUE_NOT_VALID',
                            array(
                                'name'    => $name,
                                'choices' => implode('", "', $arg->choices),
                                'value'   => implode(' ', $result->args[$name]),
                            ),
                            $this,
                            $arg->messages
                        );
                    }
                }
            }
        }
        // dispatch deferred options
        foreach ($this->_dispatchLater as $optArray) {
            $optArray[0]->dispatchAction($optArray[1], $optArray[2], $this);
        }
        return $result;
    }

    // }}}
    // parseToken() {{{

    /**
     * Parses the command line token and modifies *by reference* the $options
     * and $args arrays.
     *
     * @param string $token  The command line token to parse
     * @param object $result The Console_CommandLine_Result instance
     * @param array  &$args  The argv array
     * @param int    $argc   Number of lasting args
     *
     * @return void
     * @access protected
     * @throws Exception on user errors
     */
    protected function parseToken($token, $result, &$args, $argc)
    {
        $last  = $argc === 0;
        if (!$this->_stopflag && $this->_lastopt) {
            if ($token !== null
                && strlen($token) > ($this->avoid_reading_stdin ? 1 : 0)
                && substr($token, 0, 1) == '-'
            ) {
                if ($this->_lastopt->argument_optional) {
                    $this->_dispatchAction($this->_lastopt, '', $result);
                    if ($this->_lastopt->action != 'StoreArray') {
                        $this->_lastopt = false;
                    }
                } else if (isset($result->options[$this->_lastopt->name])) {
                    // case of an option that expect a list of args
                    $this->_lastopt = false;
                } else {
                    throw Console_CommandLine_Exception::factory(
                        'OPTION_VALUE_REQUIRED',
                        array('name' => $this->_lastopt->name),
                        $this,
                        $this->messages
                    );
                }
            } else {
                // when a StoreArray option is positioned last, the behavior
                // is to consider that if there's already an element in the
                // array, and the commandline expects one or more args, we
                // leave last tokens to arguments
                if ($this->_lastopt->action == 'StoreArray'
                    && !empty($result->options[$this->_lastopt->name])
                    && count($this->args) > ($argc + count($args))
                ) {
                    if (!is_null($token)) {
                        $args[] = $token;
                    }
                    return;
                }
                if (!is_null($token) || $this->_lastopt->action == 'Password') {
                    $this->_dispatchAction($this->_lastopt, $token, $result);
                }
                if ($this->_lastopt->action != 'StoreArray') {
                    $this->_lastopt = false;
                }
                return;
            }
        }
        if ($token !== null && !$this->_stopflag && substr($token, 0, 2) == '--') {
            // a long option
            $optkv = explode('=', $token, 2);
            if (trim($optkv[0]) == '--') {
                // the special argument "--" forces in all cases the end of
                // option scanning.
                $this->_stopflag = true;
                return;
            }
            $opt = $this->findOption($optkv[0]);
            if (!$opt) {
                throw Console_CommandLine_Exception::factory(
                    'OPTION_UNKNOWN',
                    array('name' => $optkv[0]),
                    $this,
                    $this->messages
                );
            }
            $value = isset($optkv[1]) ? $optkv[1] : false;
            if (!$opt->expectsArgument() && $value !== false) {
                throw Console_CommandLine_Exception::factory(
                    'OPTION_VALUE_UNEXPECTED',
                    array('name' => $opt->name, 'value' => $value),
                    $this,
                    $this->messages
                );
            }
            if ($opt->expectsArgument() && $value === false) {
                // maybe the long option argument is separated by a space, if
                // this is the case it will be the next arg
                if ($last && !$opt->argument_optional) {
                    throw Console_CommandLine_Exception::factory(
                        'OPTION_VALUE_REQUIRED',
                        array('name' => $opt->name),
                        $this,
                        $this->messages
                    );
                }
                // we will have a value next time
                $this->_lastopt = $opt;
                return;
            }
            if ($opt->action == 'StoreArray') {
                $this->_lastopt = $opt;
            }
            $this->_dispatchAction($opt, $value, $result);
        } else if (!$this->_stopflag
            && $token !== null
            && strlen($token) > ($this->avoid_reading_stdin ? 1 : 0)
            && substr($token, 0, 1) == '-'
        ) {
            // a short option
            $optname = substr($token, 0, 2);
            if ($optname == '-' && !$this->avoid_reading_stdin) {
                // special case of "-": try to read stdin
                $args[] = file_get_contents('php://stdin');
                return;
            }
            $opt = $this->findOption($optname);
            if (!$opt) {
                throw Console_CommandLine_Exception::factory(
                    'OPTION_UNKNOWN',
                    array('name' => $optname),
                    $this,
                    $this->messages
                );
            }
            // parse other options or set the value
            // in short: handle -f<value> and -f <value>
            $next = substr($token, 2, 1);
            // check if we must wait for a value
            if (!$next) {
                if ($opt->expectsArgument()) {
                    if ($last && !$opt->argument_optional) {
                        throw Console_CommandLine_Exception::factory(
                            'OPTION_VALUE_REQUIRED',
                            array('name' => $opt->name),
                            $this,
                            $this->messages
                        );
                    }
                    // we will have a value next time
                    $this->_lastopt = $opt;
                    return;
                }
                $value = false;
            } else {
                if (!$opt->expectsArgument()) {
                    if ($nextopt = $this->findOption('-' . $next)) {
                        $this->_dispatchAction($opt, false, $result);
                        $this->parseToken('-' . substr($token, 2), $result,
                            $args, $last);
                        return;
                    } else {
                        throw Console_CommandLine_Exception::factory(
                            'OPTION_UNKNOWN',
                            array('name' => $next),
                            $this,
                            $this->messages
                        );
                    }
                }
                if ($opt->action == 'StoreArray') {
                    $this->_lastopt = $opt;
                }
                $value = substr($token, 2);
            }
            $this->_dispatchAction($opt, $value, $result);
        } else {
            // We have an argument.
            // if we are in POSIX compliant mode, we must set the stop flag to
            // true in order to stop option parsing.
            if (!$this->_stopflag && $this->force_posix) {
                $this->_stopflag = true;
            }
            if (!is_null($token)) {
                $args[] = $token;
            }
        }
    }

    // }}}
    // addBuiltinOptions() {{{

    /**
     * Adds the builtin "Help" and "Version" options if needed.
     *
     * @return void
     */
    public function addBuiltinOptions()
    {
        if ($this->add_help_option) {
            $helpOptionParams = array(
                'long_name'   => '--help',
                'description' => 'show this help message and exit',
                'action'      => 'Help'
            );
            if (!($option = $this->findOption('-h')) || $option->action == 'Help') {
                // short name is available, take it
                $helpOptionParams['short_name'] = '-h';
            }
            $this->addOption('help', $helpOptionParams);
        }
        if ($this->add_version_option && !empty($this->version)) {
            $versionOptionParams = array(
                'long_name'   => '--version',
                'description' => 'show the program version and exit',
                'action'      => 'Version'
            );
            if (!$this->findOption('-v')) {
                // short name is available, take it
                $versionOptionParams['short_name'] = '-v';
            }
            $this->addOption('version', $versionOptionParams);
        }
    }

    // }}}
    // getArgcArgv() {{{

    /**
     * Tries to return an array containing argc and argv, or trigger an error
     * if it fails to get them.
     *
     * @return array The argc/argv array
     * @throws Console_CommandLine_Exception
     */
    protected function getArgcArgv()
    {
        if (php_sapi_name() != 'cli') {
            // we have a web request
            $argv = array($this->name);
            if (isset($_REQUEST)) {
                foreach ($_REQUEST as $key => $value) {
                    if (!is_array($value)) {
                        $value = array($value);
                    }
                    $opt = $this->findOption($key);
                    if ($opt instanceof Console_CommandLine_Option) {
                        // match a configured option
                        $argv[] = $opt->short_name ?
                            $opt->short_name : $opt->long_name;
                        foreach ($value as $v) {
                            if ($opt->expectsArgument()) {
                                $argv[] = isset($_REQUEST[$key]) ? urldecode($v) : $v;
                            } else if ($v == '0' || $v == 'false') {
                                array_pop($argv);
                            }
                        }
                    } else if (isset($this->args[$key])) {
                        // match a configured argument
                        foreach ($value as $v) {
                            $argv[] = isset($_REQUEST[$key]) ? urldecode($v) : $v;
                        }
                    }
                }
            }
            return array(count($argv), $argv);
        }
        if (isset($argc) && isset($argv)) {
            // case of register_argv_argc = 1
            return array($argc, $argv);
        }
        if (isset($_SERVER['argc']) && isset($_SERVER['argv'])) {
            return array($_SERVER['argc'], $_SERVER['argv']);
        }
        return array(0, array());
    }

    // }}}
    // _dispatchAction() {{{

    /**
     * Dispatches the given option or store the option to dispatch it later.
     *
     * @param Console_CommandLine_Option $option The option instance
     * @param string                     $token  Command line token to parse
     * @param Console_CommandLine_Result $result The result instance
     *
     * @return void
     */
    private function _dispatchAction($option, $token, $result)
    {
        if ($option->action == 'Password') {
            $this->_dispatchLater[] = array($option, $token, $result);
        } else {
            $option->dispatchAction($token, $result, $this);
        }
    }
    // }}}
    // _getSubCommand() {{{

    /**
     * Tries to return the subcommand that matches the given token or returns
     * false if no subcommand was found.
     *
     * @param string $token Current command line token
     *
     * @return mixed An instance of Console_CommandLine_Command or false
     */
    private function _getSubCommand($token)
    {
        foreach ($this->commands as $cmd) {
            if ($cmd->name == $token || in_array($token, $cmd->aliases)) {
                return $cmd;
            }
        }
        return false;
    }

    // }}}
}
PK}c�\}uuz��9pear/console_commandline/Console/CommandLine/Renderer.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * This file is part of the PEAR Console_CommandLine package.
 *
 * PHP version 5
 *
 * LICENSE: This source file is subject to the MIT license that is available
 * through the world-wide-web at the following URI:
 * http://opensource.org/licenses/mit-license.php
 *
 * @category  Console 
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   CVS: $Id$
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     File available since release 0.1.0
 * @filesource
 */

/**
 * Renderers common interface, all renderers must implement this interface.
 *
 * @category  Console
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   Release: @package_version@
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     Class available since release 0.1.0
 */
interface Console_CommandLine_Renderer
{
    // usage() {{{

    /**
     * Returns the full usage message.
     *
     * @return string The usage message
     */
    public function usage();

    // }}}
    // error() {{{

    /**
     * Returns a formatted error message.
     *
     * @param string $error The error message to format
     *
     * @return string The error string
     */
    public function error($error);

    // }}}
    // version() {{{

    /**
     * Returns the program version string.
     *
     * @return string The version string
     */
    public function version();

    // }}}
}
PK}c�\���Ɏ�Fpear/console_commandline/Console/CommandLine/CustomMessageProvider.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * This file is part of the PEAR Console_CommandLine package.
 *
 * PHP version 5
 *
 * LICENSE: This source file is subject to the MIT license that is available
 * through the world-wide-web at the following URI:
 * http://opensource.org/licenses/mit-license.php
 *
 * @category  Console
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2007 David JEAN LOUIS, 2009 silverorange
 * @license   http://opensource.org/licenses/mit-license.php MIT License
 * @version   CVS: $Id$
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     File available since release 1.1.0
 * @filesource
 */

/**
 * Common interfacefor message providers that allow overriding with custom
 * messages
 *
 * Message providers may optionally implement this interface.
 *
 * @category  Console
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2007 David JEAN LOUIS, 2009 silverorange
 * @license   http://opensource.org/licenses/mit-license.php MIT License
 * @version   Release: @package_version@
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     Interface available since release 1.1.0
 */
interface Console_CommandLine_CustomMessageProvider
{
    // getWithCustomMesssages() {{{

    /**
     * Retrieves the given string identifier corresponding message.
     *
     * For a list of identifiers please see the provided default message
     * provider.
     *
     * @param string $code     The string identifier of the message
     * @param array  $vars     An array of template variables
     * @param array  $messages An optional array of messages to use. Array
     *                         indexes are message codes.
     *
     * @return string
     * @see Console_CommandLine_MessageProvider
     * @see Console_CommandLine_MessageProvider_Default
     */
    public function getWithCustomMessages(
        $code, $vars = array(), $messages = array()
    );

    // }}}
}
PK}c�\o�R^#-#-7pear/console_commandline/Console/CommandLine/Option.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * This file is part of the PEAR Console_CommandLine package.
 *
 * PHP version 5
 *
 * LICENSE: This source file is subject to the MIT license that is available
 * through the world-wide-web at the following URI:
 * http://opensource.org/licenses/mit-license.php
 *
 * @category  Console 
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   CVS: $Id$
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     File available since release 0.1.0
 * @filesource
 */

/**
 * Required by this class.
 */
require_once 'Console/CommandLine.php';
require_once 'Console/CommandLine/Element.php';

/**
 * Class that represent a commandline option.
 *
 * @category  Console
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   Release: @package_version@
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     Class available since release 0.1.0
 */
class Console_CommandLine_Option extends Console_CommandLine_Element
{
    // Public properties {{{

    /**
     * The option short name (ex: -v).
     *
     * @var string $short_name Short name of the option
     */
    public $short_name;

    /**
     * The option long name (ex: --verbose).
     *
     * @var string $long_name Long name of the option
     */
    public $long_name;

    /**
     * The option action, defaults to "StoreString".
     *
     * @var string $action Option action
     */
    public $action = 'StoreString';

    /**
     * An array of possible values for the option. If this array is not empty 
     * and the value passed is not in the array an exception is raised.
     * This only make sense for actions that accept values of course.
     *
     * @var array $choices Valid choices for the option
     */
    public $choices = array();

    /**
     * The callback function (or method) to call for an action of type 
     * Callback, this can be any callable supported by the php function 
     * call_user_func.
     * 
     * Example:
     *
     * <code>
     * $parser->addOption('myoption', array(
     *     'short_name' => '-m',
     *     'long_name'  => '--myoption',
     *     'action'     => 'Callback',
     *     'callback'   => 'myCallbackFunction'
     * ));
     * </code>
     *
     * @var callable $callback The option callback
     */
    public $callback;

    /**
     * An associative array of additional params to pass to the class 
     * corresponding to the action, this array will also be passed to the 
     * callback defined for an action of type Callback, Example:
     *
     * <code>
     * // for a custom action
     * $parser->addOption('myoption', array(
     *     'short_name'    => '-m',
     *     'long_name'     => '--myoption',
     *     'action'        => 'MyCustomAction',
     *     'action_params' => array('foo'=>true, 'bar'=>false)
     * ));
     *
     * // if the user type:
     * // $ <yourprogram> -m spam
     * // in your MyCustomAction class the execute() method will be called
     * // with the value 'spam' as first parameter and 
     * // array('foo'=>true, 'bar'=>false) as second parameter
     * </code>
     *
     * @var array $action_params Additional parameters to pass to the action
     */
    public $action_params = array();

    /**
     * For options that expect an argument, this property tells the parser if 
     * the option argument is optional and can be ommited.
     *
     * @var bool $argumentOptional Whether the option arg is optional or not
     */
    public $argument_optional = false;

    /**
     * For options that uses the "choice" property only.
     * Adds a --list-<choice> option to the parser that displays the list of 
     * choices for the option.
     *
     * @var bool $add_list_option Whether to add a list option or not
     */
    public $add_list_option = false;

    // }}}
    // Private properties {{{

    /**
     * When an action is called remember it to allow for multiple calls.
     *
     * @var object $action_instance Placeholder for action
     */
    private $_action_instance = null;

    // }}}
    // __construct() {{{

    /**
     * Constructor.
     *
     * @param string $name   The name of the option
     * @param array  $params An optional array of parameters
     *
     * @return void
     */
    public function __construct($name = null, $params = array()) 
    {
        parent::__construct($name, $params);
        if ($this->action == 'Password') {
            // special case for Password action, password can be passed to the 
            // commandline or prompted by the parser
            $this->argument_optional = true;
        }
    }

    // }}}
    // toString() {{{

    /**
     * Returns the string representation of the option.
     *
     * @param string $delim Delimiter to use between short and long option
     *
     * @return string The string representation of the option
     * @todo use __toString() instead
     */
    public function toString($delim = ", ")
    {
        $ret     = '';
        $padding = '';
        if ($this->short_name != null) {
            $ret .= $this->short_name;
            if ($this->expectsArgument()) {
                $ret .= ' ' . $this->help_name;
            }
            $padding = $delim;
        }
        if ($this->long_name != null) {
            $ret .= $padding . $this->long_name;
            if ($this->expectsArgument()) {
                $ret .= '=' . $this->help_name;
            }
        }
        return $ret;
    }

    // }}}
    // expectsArgument() {{{

    /**
     * Returns true if the option requires one or more argument and false 
     * otherwise.
     *
     * @return bool Whether the option expects an argument or not
     */
    public function expectsArgument()
    {
        if ($this->action == 'StoreTrue' || $this->action == 'StoreFalse' ||
            $this->action == 'Help' || $this->action == 'Version' ||
            $this->action == 'Counter' || $this->action == 'List') {
            return false;
        }
        return true;
    }

    // }}}
    // dispatchAction() {{{

    /**
     * Formats the value $value according to the action of the option and 
     * updates the passed Console_CommandLine_Result object.
     *
     * @param mixed                      $value  The value to format
     * @param Console_CommandLine_Result $result The result instance
     * @param Console_CommandLine        $parser The parser instance
     *
     * @return void
     * @throws Console_CommandLine_Exception
     */
    public function dispatchAction($value, $result, $parser)
    {
        $actionInfo = Console_CommandLine::$actions[$this->action];
        if (true === $actionInfo[1]) {
            // we have a "builtin" action
            $tokens = explode('_', $actionInfo[0]);
            include_once implode('/', $tokens) . '.php';
        }
        $clsname = $actionInfo[0];
        if ($this->_action_instance === null) {
            $this->_action_instance  = new $clsname($result, $this, $parser);
        }

        // check value is in option choices
        if (!empty($this->choices) && !in_array($this->_action_instance->format($value), $this->choices)) {
            throw Console_CommandLine_Exception::factory(
                'OPTION_VALUE_NOT_VALID',
                array(
                    'name'    => $this->name,
                    'choices' => implode('", "', $this->choices),
                    'value'   => $value,
                ),
                $parser,
                $this->messages
            );
        }
        $this->_action_instance->execute($value, $this->action_params);
    }

    // }}}
    // validate() {{{

    /**
     * Validates the option instance.
     *
     * @return void
     * @throws Console_CommandLine_Exception
     * @todo use exceptions instead
     */
    public function validate()
    {
        // check if the option name is valid
        if (!preg_match('/^[a-zA-Z_\x7f-\xff]+[a-zA-Z0-9_\x7f-\xff]*$/',
            $this->name)) {
            Console_CommandLine::triggerError('option_bad_name',
                E_USER_ERROR, array('{$name}' => $this->name));
        }
        // call the parent validate method
        parent::validate();
        // a short_name or a long_name must be provided
        if ($this->short_name == null && $this->long_name == null) {
            Console_CommandLine::triggerError('option_long_and_short_name_missing',
                E_USER_ERROR, array('{$name}' => $this->name));
        }
        // check if the option short_name is valid
        if ($this->short_name != null && 
            !(preg_match('/^\-[a-zA-Z]{1}$/', $this->short_name))) {
            Console_CommandLine::triggerError('option_bad_short_name',
                E_USER_ERROR, array(
                    '{$name}' => $this->name, 
                    '{$short_name}' => $this->short_name
                ));
        }
        // check if the option long_name is valid
        if ($this->long_name != null && 
            !preg_match('/^\-\-[a-zA-Z]+[a-zA-Z0-9_\-]*$/', $this->long_name)) {
            Console_CommandLine::triggerError('option_bad_long_name',
                E_USER_ERROR, array(
                    '{$name}' => $this->name, 
                    '{$long_name}' => $this->long_name
                ));
        }
        // check if we have a valid action
        if (!is_string($this->action)) {
            Console_CommandLine::triggerError('option_bad_action',
                E_USER_ERROR, array('{$name}' => $this->name));
        }
        if (!isset(Console_CommandLine::$actions[$this->action])) {
            Console_CommandLine::triggerError('option_unregistered_action',
                E_USER_ERROR, array(
                    '{$action}' => $this->action,
                    '{$name}' => $this->name
                ));
        }
        // if the action is a callback, check that we have a valid callback
        if ($this->action == 'Callback' && !is_callable($this->callback)) {
            Console_CommandLine::triggerError('option_invalid_callback',
                E_USER_ERROR, array('{$name}' => $this->name));
        }
    }

    // }}}
    // setDefaults() {{{

    /**
     * Set the default value according to the configured action.
     *
     * Note that for backward compatibility issues this method is only called 
     * when the 'force_options_defaults' is set to true, it will become the
     * default behaviour in the next major release of Console_CommandLine.
     *
     * @return void
     */
    public function setDefaults()
    {
        if ($this->default !== null) {
            // already set
            return;
        }
        switch ($this->action) {
        case 'Counter':
        case 'StoreInt':
            $this->default = 0;
            break;
        case 'StoreFloat':
            $this->default = 0.0;
            break;
        case 'StoreArray':
            $this->default = array();
            break;
        case 'StoreTrue':
            $this->default = false;
            break;
        case 'StoreFalse':
            $this->default = true;
            break;
        default:
            return;
        }
    }

    // }}}
}
PK}c�\HSAޭ�@pear/console_commandline/Console/CommandLine/MessageProvider.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * This file is part of the PEAR Console_CommandLine package.
 *
 * PHP version 5
 *
 * LICENSE: This source file is subject to the MIT license that is available
 * through the world-wide-web at the following URI:
 * http://opensource.org/licenses/mit-license.php
 *
 * @category  Console 
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   CVS: $Id$
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     File available since release 0.1.0
 * @filesource
 */

/**
 * Message providers common interface, all message providers must implement
 * this interface.
 *
 * @category  Console
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   Release: @package_version@
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     Class available since release 0.1.0
 */
interface Console_CommandLine_MessageProvider
{
    // get() {{{

    /**
     * Retrieves the given string identifier corresponding message.
     * For a list of identifiers please see the provided default message 
     * provider.
     *
     * @param string $code The string identifier of the message
     * @param array  $vars An array of template variables
     *
     * @return string
     * @see Console_CommandLine_MessageProvider_Default
     */
    public function get($code, $vars=array());

    // }}}
}
PK}c�\�3����:pear/console_commandline/Console/CommandLine/Exception.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * This file is part of the PEAR Console_CommandLine package.
 *
 * PHP version 5
 *
 * LICENSE: This source file is subject to the MIT license that is available
 * through the world-wide-web at the following URI:
 * http://opensource.org/licenses/mit-license.php
 *
 * @category  Console 
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   CVS: $Id$
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     File available since release 0.1.0
 * @filesource
 */

/**
 * Include the PEAR_Exception class
 */
require_once 'PEAR/Exception.php';

/**
 * Interface for custom message provider.
 */
require_once 'Console/CommandLine/CustomMessageProvider.php';

/**
 * Class for exceptions raised by the Console_CommandLine package.
 *
 * @category  Console
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   Release: @package_version@
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     Class available since release 0.1.0
 */
class Console_CommandLine_Exception extends PEAR_Exception
{
    // Codes constants {{{

    /**#@+
     * Exception code constants.
     */
    const OPTION_VALUE_REQUIRED   = 1;
    const OPTION_VALUE_UNEXPECTED = 2;
    const OPTION_VALUE_TYPE_ERROR = 3;
    const OPTION_UNKNOWN          = 4;
    const ARGUMENT_REQUIRED       = 5;
    const INVALID_SUBCOMMAND      = 6;
    /**#@-*/

    // }}}
    // factory() {{{

    /**
     * Convenience method that builds the exception with the array of params by
     * calling the message provider class.
     *
     * @param string              $code     The string identifier of the
     *                                      exception.
     * @param array               $params   Array of template vars/values
     * @param Console_CommandLine $parser   An instance of the parser
     * @param array               $messages An optional array of messages
     *                                      passed to the message provider.
     *
     * @return object an instance of Console_CommandLine_Exception
     */
    public static function factory(
        $code, $params, $parser, array $messages = array()
    ) {
        $provider = $parser->message_provider;
        if ($provider instanceof Console_CommandLine_CustomMessageProvider) {
            $msg = $provider->getWithCustomMessages(
                $code,
                $params,
                $messages
            );
        } else {
            $msg = $provider->get($code, $params);
        }
        $const = 'Console_CommandLine_Exception::' . $code;
        $code  = defined($const) ? constant($const) : 0;
        return new Console_CommandLine_Exception($msg, $code);
    }

    // }}}
}
PK}c�\둶���Bpear/console_commandline/Console/CommandLine/Outputter/Default.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * This file is part of the PEAR Console_CommandLine package.
 *
 * PHP version 5
 *
 * LICENSE: This source file is subject to the MIT license that is available
 * through the world-wide-web at the following URI:
 * http://opensource.org/licenses/mit-license.php
 *
 * @category  Console 
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   CVS: $Id$
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     File available since release 0.1.0
 * @filesource
 */

/**
 * The Outputter interface.
 */
require_once 'Console/CommandLine/Outputter.php';

/**
 * Console_CommandLine default Outputter.
 *
 * @category  Console
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   Release: @package_version@
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     Class available since release 0.1.0
 */
class Console_CommandLine_Outputter_Default implements Console_CommandLine_Outputter
{
    // stdout() {{{

    /**
     * Writes the message $msg to STDOUT.
     *
     * @param string $msg The message to output
     *
     * @return void
     */
    public function stdout($msg)
    {
        if (defined('STDOUT')) {
            fwrite(STDOUT, $msg);
        } else {
            echo $msg;
        }
    }

    // }}}
    // stderr() {{{

    /**
     * Writes the message $msg to STDERR.
     *
     * @param string $msg The message to output
     *
     * @return void
     */
    public function stderr($msg)
    {
        if (defined('STDERR')) {
            fwrite(STDERR, $msg);
        } else {
            echo $msg;
        }
    }

    // }}}
}
PK}c�\��f�GGApear/console_commandline/Console/CommandLine/Action/StoreTrue.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * This file is part of the PEAR Console_CommandLine package.
 *
 * PHP version 5
 *
 * LICENSE: This source file is subject to the MIT license that is available
 * through the world-wide-web at the following URI:
 * http://opensource.org/licenses/mit-license.php
 *
 * @category  Console 
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   CVS: $Id$
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     File available since release 0.1.0
 * @filesource
 */

/**
 * Required by this class.
 */
require_once 'Console/CommandLine/Action.php';

/**
 * Class that represent the StoreTrue action.
 *
 * The execute method store the boolean 'true' in the corrsponding result
 * option array entry (the value is false if the option is not present in the 
 * command line entered by the user).
 *
 * @category  Console
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   Release: @package_version@
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     Class available since release 0.1.0
 */
class Console_CommandLine_Action_StoreTrue extends Console_CommandLine_Action
{
    // execute() {{{

    /**
     * Executes the action with the value entered by the user.
     *
     * @param mixed $value  The option value
     * @param array $params An array of optional parameters
     *
     * @return string
     */
    public function execute($value = false, $params = array())
    {
        $this->setResult(true);
    }
    // }}}
}
PK}c�\�o*��?pear/console_commandline/Console/CommandLine/Action/Version.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * This file is part of the PEAR Console_CommandLine package.
 *
 * PHP version 5
 *
 * LICENSE: This source file is subject to the MIT license that is available
 * through the world-wide-web at the following URI:
 * http://opensource.org/licenses/mit-license.php
 *
 * @category  Console 
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   CVS: $Id$
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     File available since release 0.1.0
 * @filesource
 */

/**
 * Required by this class.
 */
require_once 'Console/CommandLine/Action.php';

/**
 * Class that represent the Version action, a special action that displays the
 * version string of the program.
 *
 * @category  Console
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   Release: @package_version@
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     Class available since release 0.1.0
 */
class Console_CommandLine_Action_Version extends Console_CommandLine_Action
{
    // execute() {{{

    /**
     * Executes the action with the value entered by the user.
     *
     * @param mixed $value  The option value
     * @param array $params An array of optional parameters
     *
     * @return string
     */
    public function execute($value = false, $params = array())
    {
        return $this->parser->displayVersion();
    }
    // }}}
}
PK}c�\�{-�		Bpear/console_commandline/Console/CommandLine/Action/StoreFloat.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * This file is part of the PEAR Console_CommandLine package.
 *
 * PHP version 5
 *
 * LICENSE: This source file is subject to the MIT license that is available
 * through the world-wide-web at the following URI:
 * http://opensource.org/licenses/mit-license.php
 *
 * @category  Console 
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   CVS: $Id$
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     File available since release 0.1.0
 * @filesource
 */

/**
 * Required by this class.
 */
require_once 'Console/CommandLine/Action.php';

/**
 * Class that represent the StoreFloat action.
 *
 * The execute method store the value of the option entered by the user as a
 * float in the result option array entry, if the value passed is not a float
 * an Exception is raised.
 *
 * @category  Console
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   Release: @package_version@
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     Class available since release 0.1.0
 */
class Console_CommandLine_Action_StoreFloat extends Console_CommandLine_Action
{
    // execute() {{{

    /**
     * Executes the action with the value entered by the user.
     *
     * @param mixed $value  The option value
     * @param array $params An array of optional parameters
     *
     * @return string
     * @throws Console_CommandLine_Exception
     */
    public function execute($value = false, $params = array())
    {
        if (!is_numeric($value)) {
            include_once 'Console/CommandLine/Exception.php';
            throw Console_CommandLine_Exception::factory(
                'OPTION_VALUE_TYPE_ERROR',
                array(
                    'name'  => $this->option->name,
                    'type'  => 'float',
                    'value' => $value
                ),
                $this->parser
            );
        }
        $this->setResult((float)$value);
    }
    // }}}
}
PK}c�\�?���<pear/console_commandline/Console/CommandLine/Action/Help.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * This file is part of the PEAR Console_CommandLine package.
 *
 * PHP version 5
 *
 * LICENSE: This source file is subject to the MIT license that is available
 * through the world-wide-web at the following URI:
 * http://opensource.org/licenses/mit-license.php
 *
 * @category  Console 
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   CVS: $Id$
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     File available since release 0.1.0
 * @filesource
 */

/**
 * Required by this class.
 */
require_once 'Console/CommandLine/Action.php';

/**
 * Class that represent the Help action, a special action that displays the
 * help message, telling the user how to use the program.
 *
 * @category  Console
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   Release: @package_version@
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     Class available since release 0.1.0
 */
class Console_CommandLine_Action_Help extends Console_CommandLine_Action
{
    // execute() {{{

    /**
     * Executes the action with the value entered by the user.
     *
     * @param mixed $value  The option value
     * @param array $params An optional array of parameters
     *
     * @return string
     */
    public function execute($value = false, $params = array())
    {
        return $this->parser->displayUsage();
    }
    // }}}
}
PK}c�\��KKBpear/console_commandline/Console/CommandLine/Action/StoreFalse.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * This file is part of the PEAR Console_CommandLine package.
 *
 * PHP version 5
 *
 * LICENSE: This source file is subject to the MIT license that is available
 * through the world-wide-web at the following URI:
 * http://opensource.org/licenses/mit-license.php
 *
 * @category  Console 
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   CVS: $Id$
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     File available since release 0.1.0
 * @filesource
 */

/**
 * Required by this class.
 */
require_once 'Console/CommandLine/Action.php';

/**
 * Class that represent the StoreFalse action.
 *
 * The execute method store the boolean 'false' in the corrsponding result
 * option array entry (the value is true if the option is not present in the 
 * command line entered by the user).
 *
 * @category  Console
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   Release: @package_version@
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     Class available since release 0.1.0
 */
class Console_CommandLine_Action_StoreFalse extends Console_CommandLine_Action
{
    // execute() {{{

    /**
     * Executes the action with the value entered by the user.
     *
     * @param mixed $value  The option value
     * @param array $params An array of optional parameters
     *
     * @return string
     */
    public function execute($value = false, $params = array())
    {
        $this->setResult(false);
    }

    // }}}
}
PK}c�\��TO�	�	@pear/console_commandline/Console/CommandLine/Action/Callback.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * This file is part of the PEAR Console_CommandLine package.
 *
 * PHP version 5
 *
 * LICENSE: This source file is subject to the MIT license that is available
 * through the world-wide-web at the following URI:
 * http://opensource.org/licenses/mit-license.php
 *
 * @category  Console 
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   CVS: $Id$
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     File available since release 0.1.0
 * @filesource
 */

/**
 * Required by this class.
 */
require_once 'Console/CommandLine/Action.php';

/**
 * Class that represent the Callback action.
 *
 * The result option array entry value is set to the return value of the
 * callback defined in the option.
 *
 * There are two steps to defining a callback option:
 *   - define the option itself using the callback action
 *   - write the callback; this is a function (or method) that takes five
 *     arguments, as described below.
 *
 * All callbacks are called as follows:
 * <code>
 * callable_func(
 *     $value,           // the value of the option
 *     $option_instance, // the option instance
 *     $result_instance, // the result instance
 *     $parser_instance, // the parser instance
 *     $params           // an array of params as specified in the option
 * );
 * </code>
 * and *must* return the option value.
 *
 * @category  Console
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   Release: @package_version@
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     Class available since release 0.1.0
 */
class Console_CommandLine_Action_Callback extends Console_CommandLine_Action
{
    // execute() {{{

    /**
     * Executes the action with the value entered by the user.
     *
     * @param mixed $value  The value of the option
     * @param array $params An optional array of parameters
     *
     * @return string
     */
    public function execute($value = false, $params = array())
    {
        $this->setResult(call_user_func($this->option->callback, $value,
            $this->option, $this->result, $this->parser, $params));
    }
    // }}}
}
PK}c�\�b���
�
@pear/console_commandline/Console/CommandLine/Action/Password.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * This file is part of the PEAR Console_CommandLine package.
 *
 * PHP version 5
 *
 * LICENSE: This source file is subject to the MIT license that is available
 * through the world-wide-web at the following URI:
 * http://opensource.org/licenses/mit-license.php
 *
 * @category  Console 
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   CVS: $Id$
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     File available since release 0.1.0
 * @filesource
 */

/**
 * Required by this class.
 */
require_once 'Console/CommandLine/Action.php';

/**
 * Class that represent the Password action, a special action that allow the 
 * user to specify the password on the commandline or to be prompted for 
 * entering it.
 *
 * @category  Console
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   Release: @package_version@
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     Class available since release 0.1.0
 */
class Console_CommandLine_Action_Password extends Console_CommandLine_Action
{
    // execute() {{{

    /**
     * Executes the action with the value entered by the user.
     *
     * @param mixed $value  The option value
     * @param array $params An array of optional parameters
     *
     * @return string
     */
    public function execute($value = false, $params = array())
    {
        $this->setResult(empty($value) ? $this->_promptPassword() : $value);
    }
    // }}}
    // _promptPassword() {{{

    /**
     * Prompts the password to the user without echoing it.
     *
     * @return string
     * @todo not echo-ing the password does not work on windows is there a way 
     *       to make this work ?
     */
    private function _promptPassword()
    {
        if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
            fwrite(STDOUT,
                $this->parser->message_provider->get('PASSWORD_PROMPT_ECHO'));
            @flock(STDIN, LOCK_EX);
            $passwd = fgets(STDIN);
            @flock(STDIN, LOCK_UN);
        } else {
            fwrite(STDOUT, $this->parser->message_provider->get('PASSWORD_PROMPT'));
            // disable echoing
            system('stty -echo');
            @flock(STDIN, LOCK_EX);
            $passwd = fgets(STDIN);
            @flock(STDIN, LOCK_UN);
            system('stty echo');
        }
        return trim($passwd);
    }
    // }}}
}
PK}c�\�#�		@pear/console_commandline/Console/CommandLine/Action/StoreInt.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * This file is part of the PEAR Console_CommandLine package.
 *
 * PHP version 5
 *
 * LICENSE: This source file is subject to the MIT license that is available
 * through the world-wide-web at the following URI:
 * http://opensource.org/licenses/mit-license.php
 *
 * @category  Console 
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   CVS: $Id$
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     File available since release 0.1.0
 * @filesource
 */

/**
 * Required by this class.
 */
require_once 'Console/CommandLine/Action.php';

/**
 * Class that represent the StoreInt action.
 *
 * The execute method store the value of the option entered by the user as an
 * integer in the result option array entry, if the value passed is not an 
 * integer an Exception is raised.
 *
 * @category  Console
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   Release: @package_version@
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     Class available since release 0.1.0
 */
class Console_CommandLine_Action_StoreInt extends Console_CommandLine_Action
{
    // execute() {{{

    /**
     * Executes the action with the value entered by the user.
     *
     * @param mixed $value  The option value
     * @param array $params An array of optional parameters
     *
     * @return string
     * @throws Console_CommandLine_Exception
     */
    public function execute($value = false, $params = array())
    {
        if (!is_numeric($value)) {
            include_once 'Console/CommandLine/Exception.php';
            throw Console_CommandLine_Exception::factory(
                'OPTION_VALUE_TYPE_ERROR',
                array(
                    'name'  => $this->option->name,
                    'type'  => 'int',
                    'value' => $value
                ),
                $this->parser
            );
        }
        $this->setResult((int)$value);
    }
    // }}}
}
PK}c�\W>���?pear/console_commandline/Console/CommandLine/Action/Counter.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * This file is part of the PEAR Console_CommandLine package.
 *
 * PHP version 5
 *
 * LICENSE: This source file is subject to the MIT license that is available
 * through the world-wide-web at the following URI:
 * http://opensource.org/licenses/mit-license.php
 *
 * @category  Console 
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   CVS: $Id$
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     File available since release 0.1.0
 * @filesource
 */

/**
 * Required by this class.
 */
require_once 'Console/CommandLine/Action.php';

/**
 * Class that represent the Version action.
 *
 * The execute methode add 1 to the value of the result option array entry.
 * The value is incremented each time the option is found, for example
 * with an option defined like that:
 *
 * <code>
 * $parser->addOption(
 *     'verbose',
 *     array(
 *         'short_name' => '-v',
 *         'action'     => 'Counter'
 *     )
 * );
 * </code>
 * If the user type:
 * <code>
 * $ script.php -v -v -v
 * </code>
 * or: 
 * <code>
 * $ script.php -vvv
 * </code>
 * the verbose variable will be set to to 3.
 *
 * @category  Console
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   Release: @package_version@
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     Class available since release 0.1.0
 */
class Console_CommandLine_Action_Counter extends Console_CommandLine_Action
{
    // execute() {{{

    /**
     * Executes the action with the value entered by the user.
     *
     * @param mixed $value  The option value
     * @param array $params An optional array of parameters
     *
     * @return string
     */
    public function execute($value = false, $params = array())
    {
        $result = $this->getResult();
        if ($result === null) {
            $result = 0;
        }
        $this->setResult(++$result);
    }
    // }}}
}
PK}c�\й6l�	�	<pear/console_commandline/Console/CommandLine/Action/List.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * This file is part of the PEAR Console_CommandLine package.
 *
 * PHP version 5
 *
 * LICENSE: This source file is subject to the MIT license that is available
 * through the world-wide-web at the following URI:
 * http://opensource.org/licenses/mit-license.php
 *
 * @category  Console 
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   CVS: $Id$
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     File available since release 0.1.0
 * @filesource
 */

/**
 * Required by this class.
 */
require_once 'Console/CommandLine/Action.php';

/**
 * Class that represent the List action, a special action that simply output an 
 * array as a list.
 *
 * @category  Console
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   Release: @package_version@
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     Class available since release 0.1.0
 */
class Console_CommandLine_Action_List extends Console_CommandLine_Action
{
    // execute() {{{

    /**
     * Executes the action with the value entered by the user.
     * Possible parameters are:
     * - message: an alternative message to display instead of the default 
     *   message,
     * - delimiter: an alternative delimiter instead of the comma,
     * - post: a string to append after the message (default is the new line 
     *   char).
     *
     * @param mixed $value  The option value
     * @param array $params An optional array of parameters
     *
     * @return string
     */
    public function execute($value = false, $params = array())
    {
        $list = isset($params['list']) ? $params['list'] : array();
        $msg  = isset($params['message']) 
            ? $params['message'] 
            : $this->parser->message_provider->get('LIST_DISPLAYED_MESSAGE');
        $del  = isset($params['delimiter']) ? $params['delimiter'] : ', ';
        $post = isset($params['post']) ? $params['post'] : "\n";
        $this->parser->outputter->stdout($msg . implode($del, $list) . $post);
        exit(0);
    }
    // }}}
}
PK}c�\I �j��Bpear/console_commandline/Console/CommandLine/Action/StoreArray.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * This file is part of the PEAR Console_CommandLine package.
 *
 * PHP version 5
 *
 * LICENSE: This source file is subject to the MIT license that is available
 * through the world-wide-web at the following URI:
 * http://opensource.org/licenses/mit-license.php
 *
 * @category  Console 
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   CVS: $Id$
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     File available since release 0.1.0
 * @filesource
 */

/**
 * Required by this class.
 */
require_once 'Console/CommandLine/Action.php';

/**
 * Class that represent the StoreArray action.
 *
 * The execute method appends the value of the option entered by the user to 
 * the result option array entry.
 *
 * @category  Console
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   Release: @package_version@
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     Class available since release 0.1.0
 */
class Console_CommandLine_Action_StoreArray extends Console_CommandLine_Action
{
    // Protected properties {{{

    /**
     * Force a clean result when first called, overriding any defaults assigned.
     *
     * @var object $firstPass First time this action has been called.
     */
    protected $firstPass = true;

    // }}}
    // execute() {{{

    /**
     * Executes the action with the value entered by the user.
     *
     * @param mixed $value  The option value
     * @param array $params An optional array of parameters
     *
     * @return string
     */
    public function execute($value = false, $params = array())
    {
        $result = $this->getResult();
        if (null === $result || $this->firstPass) {
            $result          = array();
            $this->firstPass = false;
        }
        $result[] = $value;
        $this->setResult($result);
    }
    // }}}
}
PK}c�\�7LCpear/console_commandline/Console/CommandLine/Action/StoreString.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * This file is part of the PEAR Console_CommandLine package.
 *
 * PHP version 5
 *
 * LICENSE: This source file is subject to the MIT license that is available
 * through the world-wide-web at the following URI:
 * http://opensource.org/licenses/mit-license.php
 *
 * @category  Console 
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   CVS: $Id$
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     File available since release 0.1.0
 * @filesource
 */

/**
 * Required by this class.
 */
require_once 'Console/CommandLine/Action.php';

/**
 * Class that represent the StoreString action.
 *
 * The execute method store the value of the option entered by the user as a 
 * string in the result option array entry.
 *
 * @category  Console
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   Release: @package_version@
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     Class available since release 0.1.0
 */
class Console_CommandLine_Action_StoreString extends Console_CommandLine_Action
{
    // execute() {{{

    /**
     * Executes the action with the value entered by the user.
     *
     * @param mixed $value  The option value
     * @param array $params An array of optional parameters
     *
     * @return string
     */
    public function execute($value = false, $params = array())
    {
        $this->setResult((string)$value);
    }
    // }}}
}
PK}c�\���qqHpear/console_commandline/Console/CommandLine/MessageProvider/Default.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * This file is part of the PEAR Console_CommandLine package.
 *
 * PHP version 5
 *
 * LICENSE: This source file is subject to the MIT license that is available
 * through the world-wide-web at the following URI:
 * http://opensource.org/licenses/mit-license.php
 *
 * @category  Console 
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   CVS: $Id$
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     File available since release 0.1.0
 * @filesource
 */

/**
 * The message provider interface.
 */
require_once 'Console/CommandLine/MessageProvider.php';

/**
 * The custom message provider interface.
 */
require_once 'Console/CommandLine/CustomMessageProvider.php';

/**
 * Lightweight class that manages messages used by Console_CommandLine package, 
 * allowing the developper to customize these messages, for example to 
 * internationalize a command line frontend.
 *
 * @category  Console
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   Release: @package_version@
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     Class available since release 0.1.0
 */
class Console_CommandLine_MessageProvider_Default
    implements Console_CommandLine_MessageProvider,
    Console_CommandLine_CustomMessageProvider
{
    // Properties {{{

    /**
     * Associative array of messages
     *
     * @var array $messages
     */
    protected $messages = array(
        'OPTION_VALUE_REQUIRED'   => 'Option "{$name}" requires a value.',
        'OPTION_VALUE_UNEXPECTED' => 'Option "{$name}" does not expect a value (got "{$value}").',
        'OPTION_VALUE_NOT_VALID'  => 'Option "{$name}" must be one of the following: "{$choices}" (got "{$value}").',
        'ARGUMENT_VALUE_NOT_VALID'=> 'Argument "{$name}" must be one of the following: "{$choices}" (got "{$value}").',
        'OPTION_VALUE_TYPE_ERROR' => 'Option "{$name}" requires a value of type {$type} (got "{$value}").',
        'OPTION_AMBIGUOUS'        => 'Ambiguous option "{$name}", can be one of the following: {$matches}.',
        'OPTION_UNKNOWN'          => 'Unknown option "{$name}".',
        'ARGUMENT_REQUIRED'       => 'You must provide at least {$argnum} argument{$plural}.',
        'PROG_HELP_LINE'          => 'Type "{$progname} --help" to get help.',
        'PROG_VERSION_LINE'       => '{$progname} version {$version}.',
        'COMMAND_HELP_LINE'       => 'Type "{$progname} <command> --help" to get help on specific command.',
        'USAGE_WORD'              => 'Usage',
        'OPTION_WORD'             => 'Options',
        'ARGUMENT_WORD'           => 'Arguments',
        'COMMAND_WORD'            => 'Commands',
        'PASSWORD_PROMPT'         => 'Password: ',
        'PASSWORD_PROMPT_ECHO'    => 'Password (warning: will echo): ',
        'INVALID_CUSTOM_INSTANCE' => 'Instance does not implement the required interface',
        'LIST_OPTION_MESSAGE'     => 'lists valid choices for option {$name}',
        'LIST_DISPLAYED_MESSAGE'  => 'Valid choices are: ',
        'INVALID_SUBCOMMAND'      => 'Command "{$command}" is not valid.',
        'SUBCOMMAND_REQUIRED'     => 'Please enter one of the following command: {$commands}.',
    );

    // }}}
    // get() {{{

    /**
     * Retrieve the given string identifier corresponding message.
     *
     * @param string $code The string identifier of the message
     * @param array  $vars An array of template variables
     *
     * @return string
     */
    public function get($code, $vars = array())
    {
        if (!isset($this->messages[$code])) {
            return 'UNKNOWN';
        }
        return $this->replaceTemplateVars($this->messages[$code], $vars);
    }

    // }}}
    // getWithCustomMessages() {{{

    /**
     * Retrieve the given string identifier corresponding message.
     *
     * @param string $code     The string identifier of the message
     * @param array  $vars     An array of template variables
     * @param array  $messages An optional array of messages to use. Array
     *                         indexes are message codes.
     *
     * @return string
     */
    public function getWithCustomMessages(
        $code, $vars = array(), $messages = array()
    ) {
        // get message
        if (isset($messages[$code])) {
            $message = $messages[$code];
        } elseif (isset($this->messages[$code])) {
            $message = $this->messages[$code];
        } else {
            $message = 'UNKNOWN';
        }
        return $this->replaceTemplateVars($message, $vars);
    }

    // }}}
    // replaceTemplateVars() {{{

    /**
     * Replaces template vars in a message
     *
     * @param string $message The message
     * @param array  $vars    An array of template variables
     *
     * @return string
     */
    protected function replaceTemplateVars($message, $vars = array())
    {
        $tmpkeys = array_keys($vars);
        $keys    = array();
        foreach ($tmpkeys as $key) {
            $keys[] = '{$' . $key . '}';
        }
        return str_replace($keys, array_values($vars), $message);
    }

    // }}}
}
PK}c�\F��&&:pear/console_commandline/Console/CommandLine/XmlParser.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * This file is part of the PEAR Console_CommandLine package.
 *
 * PHP version 5
 *
 * LICENSE: This source file is subject to the MIT license that is available
 * through the world-wide-web at the following URI:
 * http://opensource.org/licenses/mit-license.php
 *
 * @category  Console
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License
 * @version   CVS: $Id$
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     File available since release 0.1.0
 * @filesource
 */

/**
 * Required file
 */
require_once 'Console/CommandLine.php';

/**
 * Parser for command line xml definitions.
 *
 * @category  Console
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License
 * @version   Release: @package_version@
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     Class available since release 0.1.0
 */
class Console_CommandLine_XmlParser
{
    // parse() {{{

    /**
     * Parses the given xml definition file and returns a
     * Console_CommandLine instance constructed with the xml data.
     *
     * @param string $xmlfile The xml file to parse
     *
     * @return Console_CommandLine A parser instance
     */
    public static function parse($xmlfile)
    {
        if (!is_readable($xmlfile)) {
            Console_CommandLine::triggerError('invalid_xml_file',
                E_USER_ERROR, array('{$file}' => $xmlfile));
        }
        $doc = new DomDocument();
        $doc->load($xmlfile);
        self::validate($doc);
        $nodes = $doc->getElementsByTagName('command');
        $root  = $nodes->item(0);
        return self::_parseCommandNode($root, true);
    }

    // }}}
    // parseString() {{{

    /**
     * Parses the given xml definition string and returns a
     * Console_CommandLine instance constructed with the xml data.
     *
     * @param string $xmlstr The xml string to parse
     *
     * @return Console_CommandLine A parser instance
     */
    public static function parseString($xmlstr)
    {
        $doc = new DomDocument();
        $doc->loadXml($xmlstr);
        self::validate($doc);
        $nodes = $doc->getElementsByTagName('command');
        $root  = $nodes->item(0);
        return self::_parseCommandNode($root, true);
    }

    // }}}
    // validate() {{{

    /**
     * Validates the xml definition using Relax NG.
     *
     * @param DomDocument $doc The document to validate
     *
     * @return boolean Whether the xml data is valid or not.
     * @throws Console_CommandLine_Exception
     * @todo use exceptions
     */
    public static function validate($doc)
    {
        $pkgRoot  = __DIR__ . '/../../';
        $paths = array(
            // PEAR/Composer
            '@data_dir@/Console_CommandLine/data/xmlschema.rng',
            // Composer
            $pkgRoot . 'data/Console_CommandLine/data/xmlschema.rng',
            $pkgRoot . 'data/console_commandline/data/xmlschema.rng',
            // Git
            $pkgRoot . 'data/xmlschema.rng',
            'xmlschema.rng',
        );

        foreach ($paths as $path) {
            if (is_readable($path)) {
                return $doc->relaxNGValidate($path);
            }
        }
        Console_CommandLine::triggerError(
            'invalid_xml_file',
            E_USER_ERROR, array('{$file}' => $rngfile));
    }

    // }}}
    // _parseCommandNode() {{{

    /**
     * Parses the root command node or a command node and returns the
     * constructed Console_CommandLine or Console_CommandLine_Command instance.
     *
     * @param DomDocumentNode $node       The node to parse
     * @param bool            $isRootNode Whether it is a root node or not
     *
     * @return mixed Console_CommandLine or Console_CommandLine_Command
     */
    private static function _parseCommandNode($node, $isRootNode = false)
    {
        if ($isRootNode) {
            $obj = new Console_CommandLine();
        } else {
            include_once 'Console/CommandLine/Command.php';
            $obj = new Console_CommandLine_Command();
        }
        foreach ($node->childNodes as $cNode) {
            $cNodeName = $cNode->nodeName;
            switch ($cNodeName) {
            case 'name':
            case 'description':
            case 'version':
                $obj->$cNodeName = trim($cNode->nodeValue);
                break;
            case 'add_help_option':
            case 'add_version_option':
            case 'force_posix':
                $obj->$cNodeName = self::_bool(trim($cNode->nodeValue));
                break;
            case 'option':
                $obj->addOption(self::_parseOptionNode($cNode));
                break;
            case 'argument':
                $obj->addArgument(self::_parseArgumentNode($cNode));
                break;
            case 'command':
                $obj->addCommand(self::_parseCommandNode($cNode));
                break;
            case 'aliases':
                if (!$isRootNode) {
                    foreach ($cNode->childNodes as $subChildNode) {
                        if ($subChildNode->nodeName == 'alias') {
                            $obj->aliases[] = trim($subChildNode->nodeValue);
                        }
                    }
                }
                break;
            case 'messages':
                $obj->messages = self::_messages($cNode);
                break;
            default:
                break;
            }
        }
        return $obj;
    }

    // }}}
    // _parseOptionNode() {{{

    /**
     * Parses an option node and returns the constructed
     * Console_CommandLine_Option instance.
     *
     * @param DomDocumentNode $node The node to parse
     *
     * @return Console_CommandLine_Option The built option
     */
    private static function _parseOptionNode($node)
    {
        include_once 'Console/CommandLine/Option.php';
        $obj = new Console_CommandLine_Option($node->getAttribute('name'));
        foreach ($node->childNodes as $cNode) {
            $cNodeName = $cNode->nodeName;
            switch ($cNodeName) {
            case 'choices':
                foreach ($cNode->childNodes as $subChildNode) {
                    if ($subChildNode->nodeName == 'choice') {
                        $obj->choices[] = trim($subChildNode->nodeValue);
                    }
                }
                break;
            case 'messages':
                $obj->messages = self::_messages($cNode);
                break;
            default:
                if (property_exists($obj, $cNodeName)) {
                    $obj->$cNodeName = trim($cNode->nodeValue);
                }
                break;
            }
        }
        if ($obj->action == 'Password') {
            $obj->argument_optional = true;
        }
        return $obj;
    }

    // }}}
    // _parseArgumentNode() {{{

    /**
     * Parses an argument node and returns the constructed
     * Console_CommandLine_Argument instance.
     *
     * @param DomDocumentNode $node The node to parse
     *
     * @return Console_CommandLine_Argument The built argument
     */
    private static function _parseArgumentNode($node)
    {
        include_once 'Console/CommandLine/Argument.php';
        $obj = new Console_CommandLine_Argument($node->getAttribute('name'));
        foreach ($node->childNodes as $cNode) {
            $cNodeName = $cNode->nodeName;
            switch ($cNodeName) {
            case 'description':
            case 'help_name':
            case 'default':
                $obj->$cNodeName = trim($cNode->nodeValue);
                break;
            case 'multiple':
                $obj->multiple = self::_bool(trim($cNode->nodeValue));
                break;
            case 'optional':
                $obj->optional = self::_bool(trim($cNode->nodeValue));
                break;
            case 'choices':
                foreach ($cNode->childNodes as $subChildNode) {
                    if ($subChildNode->nodeName == 'choice') {
                        $obj->choices[] = trim($subChildNode->nodeValue);
                    }
                }
                break;
            case 'messages':
                $obj->messages = self::_messages($cNode);
                break;
            default:
                break;
            }
        }
        return $obj;
    }

    // }}}
    // _bool() {{{

    /**
     * Returns a boolean according to true/false possible strings.
     *
     * @param string $str The string to process
     *
     * @return boolean
     */
    private static function _bool($str)
    {
        return in_array(strtolower((string)$str), array('true', '1', 'on', 'yes'));
    }

    // }}}
    // _messages() {{{

    /**
     * Returns an array of custom messages for the element
     *
     * @param DOMNode $node The messages node to process
     *
     * @return array an array of messages
     *
     * @see Console_CommandLine::$messages
     * @see Console_CommandLine_Element::$messages
     */
    private static function _messages(DOMNode $node)
    {
        $messages = array();

        foreach ($node->childNodes as $cNode) {
            if ($cNode->nodeType == XML_ELEMENT_NODE) {
                $name  = $cNode->getAttribute('name');
                $value = trim($cNode->nodeValue);

                $messages[$name] = $value;
            }
        }

        return $messages;
    }

    // }}}
}
PK}c�\��:pear/console_commandline/Console/CommandLine/Outputter.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * This file is part of the PEAR Console_CommandLine package.
 *
 * PHP version 5
 *
 * LICENSE: This source file is subject to the MIT license that is available
 * through the world-wide-web at the following URI:
 * http://opensource.org/licenses/mit-license.php
 *
 * @category  Console 
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   CVS: $Id$
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     File available since release 0.1.0
 * @filesource
 */

/**
 * Outputters common interface, all outputters must implement this interface.
 *
 * @category  Console
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   Release: @package_version@
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     Class available since release 0.1.0
 */
interface Console_CommandLine_Outputter
{
    // stdout() {{{

    /**
     * Processes the output for a message that should be displayed on STDOUT.
     *
     * @param string $msg The message to output
     *
     * @return void
     */
    public function stdout($msg);

    // }}}
    // stderr() {{{

    /**
     * Processes the output for a message that should be displayed on STDERR.
     *
     * @param string $msg The message to output
     *
     * @return void
     */
    public function stderr($msg);

    // }}}
}
PK}c�\�	�8pear/console_commandline/Console/CommandLine/Command.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * This file is part of the PEAR Console_CommandLine package.
 *
 * PHP version 5
 *
 * LICENSE: This source file is subject to the MIT license that is available
 * through the world-wide-web at the following URI:
 * http://opensource.org/licenses/mit-license.php
 *
 * @category  Console 
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   CVS: $Id$
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     File available since release 0.1.0
 * @filesource
 */

/**
 * File containing the parent class.
 */
require_once 'Console/CommandLine.php';

/**
 * Class that represent a command with option and arguments.
 *
 * This class exist just to clarify the interface but at the moment it is 
 * strictly identical to Console_CommandLine class, it could change in the
 * future though.
 *
 * @category  Console
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   Release: @package_version@
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     Class available since release 0.1.0
 */
class Console_CommandLine_Command extends Console_CommandLine
{
    // Public properties {{{

    /**
     * An array of aliases for the subcommand.
     *
     * @var array $aliases Aliases for the subcommand.
     */
    public $aliases = array();

    // }}}
    // __construct() {{{

    /**
     * Constructor.
     *
     * @param array  $params An optional array of parameters
     *
     * @return void
     */
    public function __construct($params = array()) 
    {
        if (isset($params['aliases'])) {
            $this->aliases = $params['aliases'];
        }
        parent::__construct($params);
    }

    // }}}
}
PK}c�\|ר��9pear/console_commandline/Console/CommandLine/Argument.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * This file is part of the PEAR Console_CommandLine package.
 *
 * PHP version 5
 *
 * LICENSE: This source file is subject to the MIT license that is available
 * through the world-wide-web at the following URI:
 * http://opensource.org/licenses/mit-license.php
 *
 * @category  Console
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License
 * @version   CVS: $Id$
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     File available since release 0.1.0
 * @filesource
 */

/**
 * Include base element class.
 */
require_once 'Console/CommandLine/Element.php';

/**
 * Class that represent a command line argument.
 *
 * @category  Console
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License
 * @version   Release: @package_version@
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     Class available since release 0.1.0
 */
class Console_CommandLine_Argument extends Console_CommandLine_Element
{
    // Public properties {{{

    /**
     * Setting this to true will tell the parser that the argument expects more
     * than one argument and that argument values should be stored in an array.
     *
     * @var boolean $multiple Whether the argument expects multiple values
     */
    public $multiple = false;

    /**
     * Setting this to true will tell the parser that the argument is optional
     * and can be ommited.
     * Note that it is not a good practice to make arguments optional, it is
     * the role of the options to be optional, by essence.
     *
     * @var boolean $optional Whether the argument is optional or not.
     */
    public $optional = false;

    /**
     * An array of possible values for the argument.
     *
     * @var array $choices Valid choices for the argument
     */
    public $choices = array();

    // }}}
    // validate() {{{

    /**
     * Validates the argument instance.
     *
     * @return void
     * @throws Console_CommandLine_Exception
     * @todo use exceptions
     */
    public function validate()
    {
        // check if the argument name is valid
        if (!preg_match('/^[a-zA-Z_\x7f-\xff]+[a-zA-Z0-9_\x7f-\xff]*$/',
            $this->name)) {
            Console_CommandLine::triggerError(
                'argument_bad_name',
                E_USER_ERROR,
                array('{$name}' => $this->name)
            );
        }
        if (!$this->optional && $this->default !== null) {
            Console_CommandLine::triggerError(
                'argument_no_default',
                E_USER_ERROR
            );
        }
        parent::validate();
    }

    // }}}
}
PK}c�\CE�7pear/console_commandline/Console/CommandLine/Action.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * This file is part of the PEAR Console_CommandLine package.
 *
 * PHP version 5
 *
 * LICENSE: This source file is subject to the MIT license that is available
 * through the world-wide-web at the following URI:
 * http://opensource.org/licenses/mit-license.php
 *
 * @category  Console 
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   CVS: $Id$
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     File available since release 0.1.0
 * @filesource
 */

/**
 * Class that represent an option action.
 *
 * @category  Console
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   Release: @package_version@
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     Class available since release 0.1.0
 */
abstract class Console_CommandLine_Action
{
    // Properties {{{

    /**
     * A reference to the result instance.
     *
     * @var Console_CommandLine_Result $result The result instance
     */
    protected $result;

    /**
     * A reference to the option instance.
     *
     * @var Console_CommandLine_Option $option The action option
     */
    protected $option;

    /**
     * A reference to the parser instance.
     *
     * @var Console_CommandLine $parser The parser
     */
    protected $parser;

    // }}}
    // __construct() {{{

    /**
     * Constructor
     *
     * @param Console_CommandLine_Result $result The result instance
     * @param Console_CommandLine_Option $option The action option
     * @param Console_CommandLine        $parser The current parser
     *
     * @return void
     */
    public function __construct($result, $option, $parser)
    {
        $this->result = $result;
        $this->option = $option;
        $this->parser = $parser;
    }

    // }}}
    // getResult() {{{

    /**
     * Convenience method to retrieve the value of result->options[name].
     *
     * @return mixed The result value or null
     */
    public function getResult()
    {
        if (isset($this->result->options[$this->option->name])) {
            return $this->result->options[$this->option->name];
        }
        return null;
    }

    // }}}
    // format() {{{

    /**
     * Allow a value to be pre-formatted prior to being used in a choices test.
     * Setting $value to the new format will keep the formatting.
     *
     * @param mixed &$value The value to format
     *
     * @return mixed The formatted value
     */
    public function format(&$value)
    {
        return $value;
    }

    // }}}
    // setResult() {{{

    /**
     * Convenience method to assign the result->options[name] value.
     *
     * @param mixed $result The result value
     *
     * @return void
     */
    public function setResult($result)
    {
        $this->result->options[$this->option->name] = $result;
    }

    // }}}
    // execute() {{{

    /**
     * Executes the action with the value entered by the user.
     * All children actions must implement this method.
     *
     * @param mixed $value  The option value
     * @param array $params An optional array of parameters
     *
     * @return string
     */
    abstract public function execute($value = false, $params = array());
    // }}}
}
PK}c�\U�*��8pear/console_commandline/Console/CommandLine/Element.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * This file is part of the PEAR Console_CommandLine package.
 *
 * PHP version 5
 *
 * LICENSE: This source file is subject to the MIT license that is available
 * through the world-wide-web at the following URI:
 * http://opensource.org/licenses/mit-license.php
 *
 * @category  Console 
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   CVS: $Id$
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     File available since release 0.1.0
 * @filesource
 */

/**
 * Class that represent a command line element (an option, or an argument).
 *
 * @category  Console
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   Release: @package_version@
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     Class available since release 0.1.0
 */
abstract class Console_CommandLine_Element
{
    // Public properties {{{

    /**
     * The element name.
     *
     * @var string $name Element name
     */
    public $name;

    /**
     * The name of variable displayed in the usage message, if no set it 
     * defaults to the "name" property.
     *
     * @var string $help_name Element "help" variable name
     */
    public $help_name;

    /**
     * The element description.
     *
     * @var string $description Element description
     */
    public $description;

    /**
     * The default value of the element if not provided on the command line.
     *
     * @var mixed $default Default value of the option.
     */
    public $default;

    /**
     * Custom errors messages for this element
     *
     * This array is of the form:
     * <code>
     * <?php
     * array(
     *     $messageName => $messageText,
     *     $messageName => $messageText,
     *     ...
     * );
     * ?>
     * </code>
     *
     * If specified, these messages override the messages provided by the
     * default message provider. For example:
     * <code>
     * <?php
     * $messages = array(
     *     'ARGUMENT_REQUIRED' => 'The argument foo is required.',
     * );
     * ?>
     * </code>
     *
     * @var array
     * @see Console_CommandLine_MessageProvider_Default
     */
    public $messages = array();

    // }}}
    // __construct() {{{

    /**
     * Constructor.
     *
     * @param string $name   The name of the element
     * @param array  $params An optional array of parameters
     *
     * @return void
     */
    public function __construct($name = null, $params = array()) 
    {
        $this->name = $name;
        foreach ($params as $attr => $value) {
            if (property_exists($this, $attr)) {
                $this->$attr = $value;
            }
        }
    }

    // }}}
    // toString() {{{

    /**
     * Returns the string representation of the element.
     *
     * @return string The string representation of the element
     * @todo use __toString() instead
     */
    public function toString()
    {
        return $this->help_name;
    }
    // }}}
    // validate() {{{

    /**
     * Validates the element instance and set it's default values.
     *
     * @return void
     * @throws Console_CommandLine_Exception
     */
    public function validate()
    {
        // if no help_name passed, default to name
        if ($this->help_name == null) {
            $this->help_name = $this->name;
        }
    }

    // }}}
}
PK}c�\Rb�00Apear/console_commandline/Console/CommandLine/Renderer/Default.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * This file is part of the PEAR Console_CommandLine package.
 *
 * PHP version 5
 *
 * LICENSE: This source file is subject to the MIT license that is available
 * through the world-wide-web at the following URI:
 * http://opensource.org/licenses/mit-license.php
 *
 * @category  Console 
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   CVS: $Id$
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     File available since release 0.1.0
 * @filesource
 */

/**
 * The renderer interface.
 */
require_once 'Console/CommandLine/Renderer.php';

/**
 * Console_CommandLine default renderer.
 *
 * @category  Console
 * @package   Console_CommandLine
 * @author    David JEAN LOUIS <izimobil@gmail.com>
 * @copyright 2007 David JEAN LOUIS
 * @license   http://opensource.org/licenses/mit-license.php MIT License 
 * @version   Release: @package_version@
 * @link      http://pear.php.net/package/Console_CommandLine
 * @since     Class available since release 0.1.0
 */
class Console_CommandLine_Renderer_Default implements Console_CommandLine_Renderer
{
    // Properties {{{

    /**
     * Integer that define the max width of the help text.
     *
     * @var integer $line_width Line width
     */
    public $line_width = 75;

    /**
     * Integer that define the max width of the help text.
     *
     * @var integer $line_width Line width
     */
    public $options_on_different_lines = false;

    /**
     * An instance of Console_CommandLine.
     *
     * @var Console_CommandLine $parser The parser
     */
    public $parser = false;

    // }}}
    // __construct() {{{

    /**
     * Constructor.
     *
     * @param object $parser A Console_CommandLine instance
     *
     * @return void
     */
    public function __construct($parser = false) 
    {
        $this->parser = $parser;
    }

    // }}}
    // usage() {{{

    /**
     * Returns the full usage message.
     *
     * @return string The usage message
     */
    public function usage()
    {
        $ret = '';
        if (!empty($this->parser->description)) { 
            $ret .= $this->description() . "\n\n";
        }
        $ret .= $this->usageLine() . "\n";
        if (count($this->parser->commands) > 0) {
            $ret .= $this->commandUsageLine() . "\n";
        }
        if (count($this->parser->options) > 0) {
            $ret .= "\n" . $this->optionList() . "\n";
        }
        if (count($this->parser->args) > 0) {
            $ret .= "\n" . $this->argumentList() . "\n";
        }
        if (count($this->parser->commands) > 0) {
            $ret .= "\n" . $this->commandList() . "\n";
        }
        $ret .= "\n";
        return $ret;
    }
    // }}}
    // error() {{{

    /**
     * Returns a formatted error message.
     *
     * @param string $error The error message to format
     *
     * @return string The error string
     */
    public function error($error)
    {
        $ret = 'Error: ' . $error . "\n";
        if ($this->parser->add_help_option) {
            $name = $this->name();
            $ret .= $this->wrap($this->parser->message_provider->get('PROG_HELP_LINE',
                array('progname' => $name))) . "\n";
            if (count($this->parser->commands) > 0) {
                $ret .= $this->wrap($this->parser->message_provider->get('COMMAND_HELP_LINE',
                    array('progname' => $name))) . "\n";
            }
        }
        return $ret;
    }

    // }}}
    // version() {{{

    /**
     * Returns the program version string.
     *
     * @return string The version string
     */
    public function version()
    {
        return $this->parser->message_provider->get('PROG_VERSION_LINE', array(
            'progname' => $this->name(),
            'version'  => $this->parser->version
        )) . "\n";
    }

    // }}}
    // name() {{{

    /**
     * Returns the full name of the program or the sub command
     *
     * @return string The name of the program
     */
    protected function name()
    {
        $name   = $this->parser->name;
        $parent = $this->parser->parent;
        while ($parent) {
            if (count($parent->options) > 0) {
                $name = '[' 
                    . strtolower($this->parser->message_provider->get('OPTION_WORD',
                          array('plural' => 's'))) 
                    . '] ' . $name;
            }
            $name = $parent->name . ' ' . $name;
            $parent = $parent->parent;
        }
        return $this->wrap($name);
    }

    // }}}
    // description() {{{

    /**
     * Returns the command line description message.
     *
     * @return string The description message
     */
    protected function description()
    {
        return $this->wrap($this->parser->description);
    }

    // }}}
    // usageLine() {{{

    /**
     * Returns the command line usage message
     *
     * @return string the usage message
     */
    protected function usageLine()
    {
        $usage = $this->parser->message_provider->get('USAGE_WORD') . ":\n";
        $ret   = $usage . '  ' . $this->name();
        if (count($this->parser->options) > 0) {
            $ret .= ' [' 
                . strtolower($this->parser->message_provider->get('OPTION_WORD'))
                . ']';
        }
        if (count($this->parser->args) > 0) {
            foreach ($this->parser->args as $name=>$arg) {
                $arg_str = $arg->help_name;
                if ($arg->multiple) {
                    $arg_str .= '1 ' . $arg->help_name . '2 ...';
                }
                if ($arg->optional) {
                    $arg_str = '[' . $arg_str . ']';
                }
                $ret .= ' ' . $arg_str;
            }
        }
        return $this->columnWrap($ret, 2);
    }

    // }}}
    // commandUsageLine() {{{

    /**
     * Returns the command line usage message for subcommands.
     *
     * @return string The usage line
     */
    protected function commandUsageLine()
    {
        if (count($this->parser->commands) == 0) {
            return '';
        }
        $ret = '  ' . $this->name();
        if (count($this->parser->options) > 0) {
            $ret .= ' [' 
                . strtolower($this->parser->message_provider->get('OPTION_WORD'))
                . ']';
        }
        $ret       .= " <command>";
        $hasArgs    = false;
        $hasOptions = false;
        foreach ($this->parser->commands as $command) {
            if (!$hasArgs && count($command->args) > 0) {
                $hasArgs = true;
            }
            if (!$hasOptions && ($command->add_help_option || 
                $command->add_version_option || count($command->options) > 0)) {
                $hasOptions = true;
            }
        }
        if ($hasOptions) {
            $ret .= ' [options]';
        }
        if ($hasArgs) {
            $ret .= ' [args]';
        }
        return $this->columnWrap($ret, 2);
    }

    // }}}
    // argumentList() {{{

    /**
     * Render the arguments list that will be displayed to the user, you can 
     * override this method if you want to change the look of the list.
     *
     * @return string The formatted argument list
     */
    protected function argumentList()
    {
        $col  = 0;
        $args = array();
        foreach ($this->parser->args as $arg) {
            $argstr = '  ' . $arg->toString();
            $args[] = array($argstr, $arg->description);
            $ln     = strlen($argstr);
            if ($col < $ln) {
                $col = $ln;
            }
        }
        $ret = $this->parser->message_provider->get('ARGUMENT_WORD') . ":";
        foreach ($args as $arg) {
            $text = str_pad($arg[0], $col) . '  ' . $arg[1];
            $ret .= "\n" . $this->columnWrap($text, $col+2);
        }
        return $ret;
    }

    // }}}
    // optionList() {{{

    /**
     * Render the options list that will be displayed to the user, you can 
     * override this method if you want to change the look of the list.
     *
     * @return string The formatted option list
     */
    protected function optionList()
    {
        $col     = 0;
        $options = array();
        foreach ($this->parser->options as $option) {
            $delim    = $this->options_on_different_lines ? "\n" : ', ';
            $optstr   = $option->toString($delim);
            $lines    = explode("\n", $optstr);
            $lines[0] = '  ' . $lines[0];
            if (count($lines) > 1) {
                $lines[1] = '  ' . $lines[1];
                $ln       = strlen($lines[1]);
            } else {
                $ln = strlen($lines[0]);
            }
            $options[] = array($lines, $option->description);
            if ($col < $ln) {
                $col = $ln;
            }
        }
        $ret = $this->parser->message_provider->get('OPTION_WORD') . ":";
        foreach ($options as $option) {
            if (count($option[0]) > 1) {
                $text = str_pad($option[0][1], $col) . '  ' . $option[1];
                $pre  = $option[0][0] . "\n";
            } else {
                $text = str_pad($option[0][0], $col) . '  ' . $option[1];
                $pre  = '';
            }
            $ret .= "\n" . $pre . $this->columnWrap($text, $col+2);
        }
        return $ret;
    }

    // }}}
    // commandList() {{{

    /**
     * Render the command list that will be displayed to the user, you can 
     * override this method if you want to change the look of the list.
     *
     * @return string The formatted subcommand list
     */
    protected function commandList()
    {

        $commands = array();
        $col      = 0;
        foreach ($this->parser->commands as $cmdname=>$command) {
            $cmdname    = '  ' . $cmdname;
            $commands[] = array($cmdname, $command->description, $command->aliases);
            $ln         = strlen($cmdname);
            if ($col < $ln) {
                $col = $ln;
            }
        }
        $ret = $this->parser->message_provider->get('COMMAND_WORD') . ":";
        foreach ($commands as $command) {
            $text = str_pad($command[0], $col) . '  ' . $command[1];
            if ($aliasesCount = count($command[2])) {
                $pad = '';
                $text .= ' (';
                $text .= $aliasesCount > 1 ? 'aliases: ' : 'alias: ';
                foreach ($command[2] as $alias) {
                    $text .= $pad . $alias;
                    $pad   = ', ';
                }
                $text .= ')';
            }
            $ret .= "\n" . $this->columnWrap($text, $col+2);
        }
        return $ret;
    }

    // }}}
    // wrap() {{{

    /**
     * Wraps the text passed to the method.
     *
     * @param string $text The text to wrap
     * @param int    $lw   The column width (defaults to line_width property)
     *
     * @return string The wrapped text
     */
    protected function wrap($text, $lw=null)
    {
        if ($this->line_width > 0) {
            if ($lw === null) {
                $lw = $this->line_width;
            }
            return wordwrap($text, $lw, "\n", false);
        }
        return $text;
    }

    // }}}
    // columnWrap() {{{

    /**
     * Wraps the text passed to the method at the specified width.
     *
     * @param string $text The text to wrap
     * @param int    $cw   The wrap width
     *
     * @return string The wrapped text
     */
    protected function columnWrap($text, $cw)
    {
        $tokens = explode("\n", $this->wrap($text));
        $ret    = $tokens[0];
        $text   = trim(substr($text, strlen($ret)));
        if (empty($text)) {
            return $ret;
        }

        $chunks = $this->wrap($text, $this->line_width - $cw);
        $tokens = explode("\n", $chunks);
        foreach ($tokens as $token) {
            if (!empty($token)) {
                $ret .= "\n" . str_repeat(' ', $cw) . $token;
            } else {
                $ret .= "\n";
            }
        }
        return $ret;
    }

    // }}}
}
PK}c�\ęR�%%&pear/console_commandline/composer.jsonnu�[���{
    "name": "pear/console_commandline",
    "description": "A full featured command line options and arguments parser.",
    "type": "library",
    "keywords": [
        "console"
    ],
    "homepage": "https://github.com/pear/Console_CommandLine",
    "license": "MIT",
    "authors": [
        {
            "name": "Richard Quadling",
            "email": "rquadling@gmail.com"
        },
        {
            "name": "David Jean Louis",
            "email": "izimobil@gmail.com"
        }
    ],
    "require": {
        "php": ">=5.3.0",
        "pear/pear_exception": "^1.0.0",
        "ext-dom": "*",
        "ext-xml": "*"
    },
    "autoload": {
        "psr-0": {
            "Console": "./"
        },
        "exclude-from-classmap": ["tests/"]
    },
    "include-path": [
        ""
    ],
    "support": {
        "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Console_CommandLine",
        "source": "https://github.com/pear/Console_CommandLine"
    },
    "require-dev": {
        "phpunit/phpunit": "*"
    }
}
PK}c�\H��-LL#pear/console_commandline/README.rstnu�[���*******************
Console_CommandLine
*******************
A full featured command line options and arguments parser.

``Console_CommandLine`` is a full featured package for managing command-line
options and arguments highly inspired from python ``optparse`` module, it allows
the developer to easily build complex command line interfaces.


=============
Main features
=============
* handles sub commands (ie. ``$ myscript.php -q subcommand -f file``),
* can be completely built from an XML definition file,
* generate ``--help`` and ``--version`` options automatically,
* can be completely customized,
* builtin support for i18n,
* and much more...


============
Installation
============

PEAR
====
::

    $ pear install Console_CommandLine


Composer
========
::

    $ composer require pear/console_commandline


=====
Links
=====
Homepage
  http://pear.php.net/package/Console_CommandLine
Bug tracker
  http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Console_CommandLine
Documentation
  http://pear.php.net/manual/en/package.console.console-commandline.php
Unit test status
  https://travis-ci.org/pear/Console_CommandLine

  .. image:: https://travis-ci.org/pear/Console_CommandLine.svg?branch=stable
     :target: https://travis-ci.org/pear/Console_CommandLine
Packagist
  https://packagist.org/packages/pear/console_commandline
PK}c�\DI�o��$pear/console_commandline/phpunit.xmlnu�[���<phpunit>
 <testsuites>
  <testsuite name="tests">
   <directory suffix=".phpt">tests/</directory>
  </testsuite>
 </testsuites>
 <php>
  <env name="XDEBUG_MODE" value="off"/>
 </php>
</phpunit>
PK}c�\7^����+pear/console_commandline/data/xmlschema.rngnu�[���<?xml version="1.0" encoding="UTF-8"?>

<!-- 
  This is the RNG file for validating Console_CommandLine xml definitions.

  Author  : David JEAN LOUIS
  Licence : MIT License
  Version : CVS: $Id$
-->

<grammar xmlns="http://relaxng.org/ns/structure/1.0" 
         datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">

  <!-- structure -->
  <start>
      <ref name="ref_command"/>
  </start>

  <!-- Command node -->
  <define name="ref_command_subcommand_common">
    <interleave>
      <optional>
        <element name="name">
          <text/>
        </element>
      </optional>
      <optional>
        <element name="description">
          <text/>
        </element>
      </optional>
      <optional>
        <element name="version">
          <text/>
        </element>
      </optional>
      <optional>
        <element name="add_help_option">
          <ref name="ref_bool_choices"/>
        </element>
      </optional>
      <optional>
        <element name="add_version_option">
          <ref name="ref_bool_choices"/>
        </element>
      </optional>
      <optional>
        <element name="force_posix">
          <ref name="ref_bool_choices"/>
        </element>
      </optional>
      <optional>
        <ref name="ref_messages_common"/>
      </optional>
      <zeroOrMore>
        <ref name="ref_option"/>
      </zeroOrMore>
      <zeroOrMore>
        <ref name="ref_argument"/>
      </zeroOrMore>
      <zeroOrMore>
        <ref name="ref_subcommand"/>
      </zeroOrMore>
    </interleave>
  </define>

  <!-- command element -->

  <define name="ref_command">
    <element name="command">
      <interleave>
        <ref name="ref_command_subcommand_common"/>
      </interleave>
    </element>
  </define>

  <!-- subcommand element -->

  <define name="ref_subcommand">
    <element name="command">
      <interleave>
        <ref name="ref_command_subcommand_common"/>
        <optional>
          <element name="aliases">
            <zeroOrMore>
              <element name="alias">
                <text/>
              </element>
            </zeroOrMore>
          </element>
        </optional>
      </interleave>
    </element>
  </define>

  <!-- custom messages common element -->

  <define name="ref_messages_common">
    <element name="messages">
      <oneOrMore>
        <element name="message">
          <attribute name="name">
            <data type="string"/>
          </attribute>
          <text/>
        </element>
      </oneOrMore>
    </element>
  </define>

  <!-- options and arguments common elements -->

  <define name="ref_option_argument_common">
    <interleave>
      <optional>
        <element name="description">
          <text/>
        </element>
      </optional>
      <optional>
        <element name="help_name">
          <text/>
        </element>
      </optional>
      <optional>
        <element name="default">
          <text/>
        </element>
      </optional>
      <optional>
        <ref name="ref_messages_common"/>
      </optional>
    </interleave>
  </define>

  <!-- Option node -->
  <define name="ref_option">
    <element name="option">
      <attribute name="name">
        <data type="string"/>
      </attribute>
      <interleave>
        <optional>
          <element name="short_name">
            <text/>
          </element>
        </optional>
        <optional>
          <element name="long_name">
            <text/>
          </element>
        </optional>
        <ref name="ref_option_argument_common"/>
        <optional>
          <element name="action">
            <text/>
          </element>
        </optional>
        <optional>
          <element name="choices">
            <zeroOrMore>
              <element name="choice">
                <text/>
              </element>
            </zeroOrMore>
          </element>
        </optional>
        <optional>
          <element name="add_list_option">
            <ref name="ref_bool_choices"/>
          </element>
        </optional>
      </interleave>
    </element>
  </define>

  <!-- Argument node -->
  <define name="ref_argument">
    <element name="argument">
      <attribute name="name">
        <data type="string"/>
      </attribute>
      <interleave>
        <ref name="ref_option_argument_common"/>
        <optional>
          <element name="multiple">
            <ref name="ref_bool_choices"/>
          </element>
        </optional>
        <optional>
          <element name="optional">
            <ref name="ref_bool_choices"/>
          </element>
        </optional>
        <optional>
          <element name="choices">
            <zeroOrMore>
              <element name="choice">
                <text/>
              </element>
            </zeroOrMore>
           </element>
         </optional>
      </interleave>
    </element>
  </define>

  <!-- boolean choices -->
  <define name="ref_bool_choices">
    <choice>
      <data type="token">
        <param name="pattern">[Tt][Rr][Uu][Ee]</param>
      </data>
      <data type="token">
        <param name="pattern">[On][Nn]</param>
      </data>
      <data type="token">
        <param name="pattern">[Yy][Ee][Ss]</param>
      </data>
      <value>1</value>
      <data type="token">
        <param name="pattern">[Ff][Aa][Ll][Ss][Ee]</param>
      </data>
      <data type="token">
        <param name="pattern">[Of][Ff][Ff]</param>
      </data>
      <data type="token">
        <param name="pattern">[Nn][Oo]</param>
      </data>
      <value>0</value>
    </choice>
  </define>

</grammar>
PK}c�\`���pear/crypt_gpg/composer.jsonnu�[���{
    "name": "pear/crypt_gpg",
    "description": "Provides an object oriented interface to the GNU Privacy Guard (GnuPG). It requires the GnuPG executable to be on the system.",
    "type": "library",
    "keywords": [
        "gpg",
        "gnupg",
        "encryption",
        "pgp"
    ],
    "homepage": "https://github.com/pear/Crypt_GPG",
    "license": "LGPL-2.1",
    "authors": [
        {
            "name": "Michael Gauthier",
            "email": "mike@silverorange.com"
        },
        {
            "name": "Nathan Fredrickson",
            "email": "nathan@silverorange.com"
        },
        {
            "name": "Aleksander Machniak",
            "email": "alec@alec.pl"
        }
    ],
    "require": {
        "php": ">=5.4.8",
        "ext-mbstring": "*",
        "pear/console_commandline": "*",
        "pear/pear_exception": "*"
    },
    "suggest": {
        "ext-posix": "May require the posix PHP extension"
    },
    "bin": [
        "scripts/crypt-gpg-pinentry"
    ],
    "autoload": {
        "classmap": ["Crypt/"]
    },
    "include-path": [
        "./"
    ],
    "support": {
        "issues": "https://pear.php.net/bugs/search.php?cmd=display&package_name[]=Crypt_GPG",
        "source": "https://github.com/pear/Crypt_GPG"
    },
    "require-dev": {
        "phpstan/phpstan": "^1.2",
        "phpunit/phpunit": "^9"
    },
    "archive": {
        "exclude": ["tools", "package.php"]
    }
}
PK}c�\��xRxR)pear/crypt_gpg/Crypt/GPG/KeyGenerator.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * Crypt_GPG is a package to use GPG from PHP
 *
 * This file contains an object that handles GnuPG key generation.
 *
 * LICENSE:
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see
 * <http://www.gnu.org/licenses/>
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2011-2013 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 * @link      http://www.gnupg.org/
 */

/**
 * Base class for GPG methods
 */
require_once 'Crypt/GPGAbstract.php';

/**
 * GnuPG key generator
 *
 * This class provides an object oriented interface for generating keys with
 * the GNU Privacy Guard (GPG).
 *
 * Secure key generation requires true random numbers, and as such can be slow.
 * If the operating system runs out of entropy, key generation will block until
 * more entropy is available.
 *
 * If quick key generation is important, a hardware entropy generator, or an
 * entropy gathering daemon may be installed. For example, administrators of
 * Debian systems may want to install the 'randomsound' package.
 *
 * This class uses the experimental automated key generation support available
 * in GnuPG. See <b>doc/DETAILS</b> in the
 * {@link http://www.gnupg.org/download/ GPG distribution} for detailed
 * information on the key generation format.
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Nathan Fredrickson <nathan@silverorange.com>
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2005-2013 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 * @link      http://www.gnupg.org/
 */
class Crypt_GPG_KeyGenerator extends Crypt_GPGAbstract
{
    /**
     * The expiration date of generated keys
     *
     * @var int
     *
     * @see Crypt_GPG_KeyGenerator::setExpirationDate()
     */
    protected $expirationDate = 0;

    /**
     * The passphrase of generated keys
     *
     * @var string
     *
     * @see Crypt_GPG_KeyGenerator::setPassphrase()
     */
    protected $passphrase = '';

    /**
     * The algorithm for generated primary keys
     *
     * @var int
     *
     * @see Crypt_GPG_KeyGenerator::setKeyParams()
     */
    protected $keyAlgorithm = Crypt_GPG_SubKey::ALGORITHM_DSA;

    /**
     * The size of generated primary keys
     *
     * @var int
     *
     * @see Crypt_GPG_KeyGenerator::setKeyParams()
     */
    protected $keySize = 1024;

    /**
     * The usages of generated primary keys
     *
     * This is a bitwise combination of the usage constants in
     * {@link Crypt_GPG_SubKey}.
     *
     * @var int
     *
     * @see Crypt_GPG_KeyGenerator::setKeyParams()
     */
    protected $keyUsage = 6; // USAGE_SIGN | USAGE_CERTIFY

    /**
     * The algorithm for generated sub-keys
     *
     * @var int
     *
     * @see Crypt_GPG_KeyGenerator::setSubKeyParams()
     */
    protected $subKeyAlgorithm = Crypt_GPG_SubKey::ALGORITHM_ELGAMAL_ENC;

    /**
     * The size of generated sub-keys
     *
     * @var int
     *
     * @see Crypt_GPG_KeyGenerator::setSubKeyParams()
     */
    protected $subKeySize = 2048;

    /**
     * The usages of generated sub-keys
     *
     * This is a bitwise combination of the usage constants in
     * {@link Crypt_GPG_SubKey}.
     *
     * @var int
     *
     * @see Crypt_GPG_KeyGenerator::setSubKeyParams()
     */
    protected $subKeyUsage = Crypt_GPG_SubKey::USAGE_ENCRYPT;

    /**
     * Creates a new GnuPG key generator
     *
     * @param array $options An array of options used to create the object.
     *                       All options are optional and are represented as key-value
     *                       pairs. See Crypt_GPGAbstract::__construct() for more info.
     *
     * @throws Crypt_GPG_FileException if the <kbd>homedir</kbd> does not exist
     *         and cannot be created. This can happen if <kbd>homedir</kbd> is
     *         not specified, Crypt_GPG is run as the web user, and the web
     *         user has no home directory. This exception is also thrown if any
     *         of the options <kbd>publicKeyring</kbd>,
     *         <kbd>privateKeyring</kbd> or <kbd>trustDb</kbd> options are
     *         specified but the files do not exist or are are not readable.
     *         This can happen if the user running the Crypt_GPG process (for
     *         example, the Apache user) does not have permission to read the
     *         files.
     *
     * @throws PEAR_Exception if the provided <kbd>binary</kbd> is invalid, or
     *         if no <kbd>binary</kbd> is provided and no suitable binary could
     *         be found.
     *
     * @throws PEAR_Exception if the provided <kbd>agent</kbd> is invalid, or
     *         if no <kbd>agent</kbd> is provided and no suitable gpg-agent
     *         could be found.
     */
    public function __construct(array $options = array())
    {
        parent::__construct($options);
    }

    /**
     * Sets the expiration date of generated keys
     *
     * @param string|int $date Either a string that may be parsed by
     *                         PHP's strtotime() function, or an integer
     *                         timestamp representing the number of seconds
     *                         since the UNIX epoch. This date must be at
     *                         least one date in the future. Keys that
     *                         expire in the past may not be generated. Use
     *                         an expiration date of 0 for keys that do not
     *                         expire.
     *
     * @throws InvalidArgumentException If the date is not a valid format, or
     *                                  if the date is not at least one day in
     *                                  the future, or if the date is greater
     *                                  than 2038-01-19T03:14:07.
     *
     * @return Crypt_GPG_KeyGenerator The current object, for fluent interface.
     */
    public function setExpirationDate($date)
    {
        if (is_int($date) || ctype_digit(strval($date))) {
            $expirationDate = intval($date);
        } else {
            $expirationDate = strtotime($date);
        }

        if ($expirationDate === false) {
            throw new InvalidArgumentException(
                sprintf(
                    'Invalid expiration date format: "%s". Please use a ' .
                    'format compatible with PHP\'s strtotime().',
                    $date
                )
            );
        }

        if ($expirationDate !== 0 && $expirationDate < time() + 86400) {
            throw new InvalidArgumentException(
                'Expiration date must be at least a day in the future.'
            );
        }

        // GnuPG suffers from the 2038 bug
        if ($expirationDate > 2147483647) {
            throw new InvalidArgumentException(
                'Expiration date must not be greater than 2038-01-19T03:14:07.'
            );
        }

        $this->expirationDate = $expirationDate;

        return $this;
    }

    /**
     * Sets the passphrase of generated keys
     *
     * @param string $passphrase the passphrase to use for generated keys. Use
     *                           null or an empty string for no passphrase.
     *
     * @return Crypt_GPG_KeyGenerator the current object, for fluent interface.
     */
    public function setPassphrase($passphrase)
    {
        $this->passphrase = strval($passphrase);
        return $this;
    }

    /**
     * Sets the parameters for the primary key of generated key-pairs
     *
     * @param int $algorithm the algorithm used by the key. This should be
     *                       one of the Crypt_GPG_SubKey::ALGORITHM_*
     *                       constants.
     * @param int $size      optional. The size of the key. Different
     *                       algorithms have different size requirements.
     *                       If not specified, the default size for the
     *                       specified algorithm will be used. If an
     *                       invalid key size is used, GnuPG will do its
     *                       best to round it to a valid size.
     * @param int $usage     optional. A bitwise combination of key usages.
     *                       If not specified, the primary key will be used
     *                       only to sign and certify. This is the default
     *                       behavior of GnuPG in interactive mode. Use
     *                       the Crypt_GPG_SubKey::USAGE_* constants here.
     *                       The primary key may be used to certify even
     *                       if the certify usage is not specified.
     *
     * @return Crypt_GPG_KeyGenerator the current object, for fluent interface.
     */
    public function setKeyParams($algorithm, $size = 0, $usage = 0)
    {
        $algorithm = intval($algorithm);

        if ($algorithm === Crypt_GPG_SubKey::ALGORITHM_ELGAMAL_ENC) {
            throw new Crypt_GPG_InvalidKeyParamsException(
                'Primary key algorithm must be capable of signing. The ' .
                'Elgamal algorithm can only encrypt.',
                0,
                $algorithm,
                $size,
                $usage
            );
        }

        if ($size != 0) {
            $size = intval($size);
        }

        if ($usage != 0) {
            $usage = intval($usage);
        }

        $usageEncrypt = Crypt_GPG_SubKey::USAGE_ENCRYPT;

        if ($algorithm === Crypt_GPG_SubKey::ALGORITHM_DSA
            && ($usage & $usageEncrypt) === $usageEncrypt
        ) {
            throw new Crypt_GPG_InvalidKeyParamsException(
                'The DSA algorithm is not capable of encrypting. Please ' .
                'specify a different algorithm or do not include encryption ' .
                'as a usage for the primary key.',
                0,
                $algorithm,
                $size,
                $usage
            );
        }

        $this->keyAlgorithm = $algorithm;

        if ($size != 0) {
            $this->keySize = $size;
        }

        if ($usage != 0) {
            $this->keyUsage = $usage;
        }

        return $this;
    }

    /**
     * Sets the parameters for the sub-key of generated key-pairs
     *
     * @param int $algorithm The algorithm used by the key. This should be
     *                       one of the Crypt_GPG_SubKey::ALGORITHM_*
     *                       constants.
     * @param int $size      Optional size of the key. Different
     *                       algorithms have different size requirements.
     *                       If not specified, the default size for the
     *                       specified algorithm will be used. If an
     *                       invalid key size is used, GnuPG will do its
     *                       best to round it to a valid size.
     * @param int $usage     Optional bitwise combination of key usages.
     *                       If not specified, the sub-key will be used
     *                       only to encrypt. This is the default behavior
     *                       of GnuPG in interactive mode. Use the
     *                       Crypt_GPG_SubKey::USAGE_* constants here.
     *
     * @return Crypt_GPG_KeyGenerator The current object, for fluent interface.
     */
    public function setSubKeyParams($algorithm, $size = 0, $usage = 0)
    {
        $algorithm = intval($algorithm);

        if ($size != 0) {
            $size = intval($size);
        }

        if ($usage != 0) {
            $usage = intval($usage);
        }

        $usageSign = Crypt_GPG_SubKey::USAGE_SIGN;

        if ($algorithm === Crypt_GPG_SubKey::ALGORITHM_ELGAMAL_ENC
            && ($usage & $usageSign) === $usageSign
        ) {
            throw new Crypt_GPG_InvalidKeyParamsException(
                'The Elgamal algorithm is not capable of signing. Please ' .
                'specify a different algorithm or do not include signing ' .
                'as a usage for the sub-key.',
                0,
                $algorithm,
                $size,
                $usage
            );
        }

        $usageEncrypt = Crypt_GPG_SubKey::USAGE_ENCRYPT;

        if ($algorithm === Crypt_GPG_SubKey::ALGORITHM_DSA
            && ($usage & $usageEncrypt) === $usageEncrypt
        ) {
            throw new Crypt_GPG_InvalidKeyParamsException(
                'The DSA algorithm is not capable of encrypting. Please ' .
                'specify a different algorithm or do not include encryption ' .
                'as a usage for the sub-key.',
                0,
                $algorithm,
                $size,
                $usage
            );
        }

        $this->subKeyAlgorithm = $algorithm;

        if ($size != 0) {
            $this->subKeySize = $size;
        }

        if ($usage != 0) {
            $this->subKeyUsage = $usage;
        }

        return $this;
    }

    /**
     * Generates a new key-pair in the current keyring
     *
     * Secure key generation requires true random numbers, and as such can be
     * solw. If the operating system runs out of entropy, key generation will
     * block until more entropy is available.
     *
     * If quick key generation is important, a hardware entropy generator, or
     * an entropy gathering daemon may be installed. For example,
     * administrators of Debian systems may want to install the 'randomsound'
     * package.
     *
     * @param string|Crypt_GPG_UserId $name    either a {@link Crypt_GPG_UserId}
     *                                         object, or a string containing
     *                                         the name of the user id.
     * @param string                  $email   optional. If <i>$name</i> is
     *                                         specified as a string, this is
     *                                         the email address of the user id.
     * @param string                  $comment optional. If <i>$name</i> is
     *                                         specified as a string, this is
     *                                         the comment of the user id.
     *
     * @return Crypt_GPG_Key the newly generated key.
     *
     * @throws Crypt_GPG_KeyNotCreatedException if the key parameters are
     *         incorrect, if an unknown error occurs during key generation, or
     *         if the newly generated key is not found in the keyring.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    public function generateKey($name, $email = '', $comment = '')
    {
        $handle = uniqid('key', true);

        $userId = $this->getUserId($name, $email, $comment);

        $keyParams = array(
            'Key-Type'      => $this->keyAlgorithm,
            'Key-Length'    => $this->keySize,
            'Key-Usage'     => $this->getUsage($this->keyUsage),
            'Subkey-Type'   => $this->subKeyAlgorithm,
            'Subkey-Length' => $this->subKeySize,
            'Subkey-Usage'  => $this->getUsage($this->subKeyUsage),
            'Handle'        => $handle,
        );

        if ($this->expirationDate != 0) {
            // GnuPG only accepts granularity of days
            $expirationDate = date('Y-m-d', $this->expirationDate);
            $keyParams['Expire-Date'] = $expirationDate;
        }

        if (strlen($this->passphrase)) {
            $keyParams['Passphrase'] = $this->passphrase;
        }

        $name    = $userId->getName();
        $email   = $userId->getEmail();
        $comment = $userId->getComment();

        if (strlen($name) > 0) {
            $keyParams['Name-Real'] = $name;
        }

        if (strlen($email) > 0) {
            $keyParams['Name-Email'] = $email;
        }

        if (strlen($comment) > 0) {
            $keyParams['Name-Comment'] = $comment;
        }

        $keyParamsFormatted = array();
        foreach ($keyParams as $name => $value) {
            $keyParamsFormatted[] = $name . ': ' . $value;
        }

        // This is required in GnuPG 2.1
        if (!strlen($this->passphrase)) {
            $keyParamsFormatted[] = '%no-protection';
        }

        $input = implode("\n", $keyParamsFormatted) . "\n%commit\n";

        $this->engine->reset();
        $this->engine->setProcessData('Handle', $handle);
        $this->engine->setInput($input);
        $this->engine->setOutput($output);
        $this->engine->setOperation('--gen-key', array('--batch'));

        try {
            $this->engine->run();
        } catch (Crypt_GPG_InvalidKeyParamsException $e) {
            switch ($this->engine->getProcessData('LineNumber')) {
            case 1:
                throw new Crypt_GPG_InvalidKeyParamsException(
                    'Invalid primary key algorithm specified.',
                    0,
                    $this->keyAlgorithm,
                    $this->keySize,
                    $this->keyUsage
                );
            case 4:
                throw new Crypt_GPG_InvalidKeyParamsException(
                    'Invalid sub-key algorithm specified.',
                    0,
                    $this->subKeyAlgorithm,
                    $this->subKeySize,
                    $this->subKeyUsage
                );
            default:
                throw $e;
            }
        }

        $fingerprint = $this->engine->getProcessData('KeyCreated');
        $keys        = $this->_getKeys($fingerprint);

        if (count($keys) === 0) {
            throw new Crypt_GPG_KeyNotCreatedException(
                sprintf(
                    'Newly created key "%s" not found in keyring.',
                    $fingerprint
                )
            );
        }

        return $keys[0];
    }

    /**
     * Builds a GnuPG key usage string suitable for key generation
     *
     * See <b>doc/DETAILS</b> in the
     * {@link http://www.gnupg.org/download/ GPG distribution} for detailed
     * information on the key usage format.
     *
     * @param int $usage A bitwise combination of the key usages. This is
     *                   a combination of the Crypt_GPG_SubKey::USAGE_*
     *                   constants.
     *
     * @return string The key usage string.
     */
    protected function getUsage($usage)
    {
        $map = array(
            Crypt_GPG_SubKey::USAGE_ENCRYPT        => 'encrypt',
            Crypt_GPG_SubKey::USAGE_SIGN           => 'sign',
            Crypt_GPG_SubKey::USAGE_CERTIFY        => 'cert',
            Crypt_GPG_SubKey::USAGE_AUTHENTICATION => 'auth',
        );

        // cert is always used for primary keys and does not need to be
        // specified
        $usage &= ~Crypt_GPG_SubKey::USAGE_CERTIFY;

        $usageArray = array();

        foreach ($map as $key => $value) {
            if (($usage & $key) === $key) {
                $usageArray[] = $value;
            }
        }

        return implode(',', $usageArray);
    }

    /**
     * Gets a user id object from parameters
     *
     * @param string|Crypt_GPG_UserId $name    either a {@link Crypt_GPG_UserId}
     *                                         object, or a string containing
     *                                         the name of the user id.
     * @param string                  $email   optional. If <i>$name</i> is
     *                                         specified as a string, this is
     *                                         the email address of the user id.
     * @param string                  $comment optional. If <i>$name</i> is
     *                                         specified as a string, this is
     *                                         the comment of the user id.
     *
     * @return Crypt_GPG_UserId a user id object for the specified parameters.
     */
    protected function getUserId($name, $email = '', $comment = '')
    {
        if ($name instanceof Crypt_GPG_UserId) {
            $userId = $name;
        } else {
            $userId = new Crypt_GPG_UserId();
            $userId->setName($name)->setEmail($email)->setComment($comment);
        }

        return $userId;
    }
}
PK}c�\�J�ڠ#�##pear/crypt_gpg/Crypt/GPG/UserId.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * Contains a data class representing a GPG user id
 *
 * LICENSE:
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see
 * <http://www.gnu.org/licenses/>
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2008-2010 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 */

/**
 * A class for GPG user id information
 *
 * This class is used to store the results of the {@link Crypt_GPG::getKeys()}
 * method. User id objects are members of a {@link Crypt_GPG_Key} object.
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2008-2010 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 * @see       Crypt_GPG::getKeys()
 * @see       Crypt_GPG_Key::getUserIds()
 */
class Crypt_GPG_UserId
{
    /**
     * The name field of this user id
     *
     * @var string
     */
    private $_name = '';

    /**
     * The comment field of this user id
     *
     * @var string
     */
    private $_comment = '';

    /**
     * The email field of this user id
     *
     * @var string
     */
    private $_email = '';

    /**
     * Whether or not this user id is revoked
     *
     * @var bool
     */
    private $_isRevoked = false;

    /**
     * Whether or not this user id is valid
     *
     * @var bool
     */
    private $_isValid = true;

    /**
     * Creates a new user id
     *
     * User ids can be initialized from an array of named values. Available
     * names are:
     *
     * - <kbd>string  name</kbd>    - the name field of the user id.
     * - <kbd>string  comment</kbd> - the comment field of the user id.
     * - <kbd>string  email</kbd>   - the email field of the user id.
     * - <kbd>bool    valid</kbd>   - whether or not the user id is valid.
     * - <kbd>bool    revoked</kbd> - whether or not the user id is revoked.
     *
     * @param Crypt_GPG_UserId|string|array|null $userId Either an existing user id object,
     *                                                   which is copied; a user id string,
     *                                                   which is parsed; or an array of
     *                                                   initial values.
     */
    public function __construct($userId = null)
    {
        // parse from string
        if (is_string($userId)) {
            $userId = self::parse($userId);
        }

        // copy from object
        if ($userId instanceof Crypt_GPG_UserId) {
            $this->_name      = $userId->_name;
            $this->_comment   = $userId->_comment;
            $this->_email     = $userId->_email;
            $this->_isRevoked = $userId->_isRevoked;
            $this->_isValid   = $userId->_isValid;
        }

        // initialize from array
        if (is_array($userId)) {
            if (array_key_exists('name', $userId)) {
                $this->setName($userId['name']);
            }

            if (array_key_exists('comment', $userId)) {
                $this->setComment($userId['comment']);
            }

            if (array_key_exists('email', $userId)) {
                $this->setEmail($userId['email']);
            }

            if (array_key_exists('revoked', $userId)) {
                $this->setRevoked($userId['revoked']);
            }

            if (array_key_exists('valid', $userId)) {
                $this->setValid($userId['valid']);
            }
        }
    }

    /**
     * Gets the name field of this user id
     *
     * @return string the name field of this user id.
     */
    public function getName()
    {
        return $this->_name;
    }

    /**
     * Gets the comments field of this user id
     *
     * @return string the comments field of this user id.
     */
    public function getComment()
    {
        return $this->_comment;
    }

    /**
     * Gets the email field of this user id
     *
     * @return string the email field of this user id.
     */
    public function getEmail()
    {
        return $this->_email;
    }

    /**
     * Gets whether or not this user id is revoked
     *
     * @return bool True if this user id is revoked and false if it is not.
     */
    public function isRevoked()
    {
        return $this->_isRevoked;
    }

    /**
     * Gets whether or not this user id is valid
     *
     * @return bool True if this user id is valid and false if it is not.
     */
    public function isValid()
    {
        return $this->_isValid;
    }

    /**
     * Gets a string representation of this user id
     *
     * The string is formatted as:
     * <b><kbd>name (comment) <email-address></kbd></b>.
     *
     * @return string a string representation of this user id.
     */
    public function __toString()
    {
        $components = array();

        if (mb_strlen($this->_name, '8bit') > 0) {
            $components[] = $this->_name;
        }

        if (mb_strlen($this->_comment, '8bit') > 0) {
            $components[] = '(' . $this->_comment . ')';
        }

        if (mb_strlen($this->_email, '8bit') > 0) {
            $components[] = '<' . $this->_email. '>';
        }

        return implode(' ', $components);
    }

    /**
     * Sets the name field of this user id
     *
     * @param string $name the name field of this user id.
     *
     * @return Crypt_GPG_UserId the current object, for fluent interface.
     */
    public function setName($name)
    {
        $this->_name = strval($name);
        return $this;
    }

    /**
     * Sets the comment field of this user id
     *
     * @param string $comment the comment field of this user id.
     *
     * @return Crypt_GPG_UserId the current object, for fluent interface.
     */
    public function setComment($comment)
    {
        $this->_comment = strval($comment);
        return $this;
    }

    /**
     * Sets the email field of this user id
     *
     * @param string $email the email field of this user id.
     *
     * @return Crypt_GPG_UserId the current object, for fluent interface.
     */
    public function setEmail($email)
    {
        $this->_email = strval($email);
        return $this;
    }

    /**
     * Sets whether or not this user id is revoked
     *
     * @param bool $isRevoked Whether or not this user id is revoked.
     *
     * @return Crypt_GPG_UserId The current object, for fluent interface.
     */
    public function setRevoked($isRevoked)
    {
        $this->_isRevoked = ($isRevoked) ? true : false;
        return $this;
    }

    /**
     * Sets whether or not this user id is valid
     *
     * @param bool $isValid Whether or not this user id is valid.
     *
     * @return Crypt_GPG_UserId The current object, for fluent interface.
     */
    public function setValid($isValid)
    {
        $this->_isValid = ($isValid) ? true : false;
        return $this;
    }

    /**
     * Parses a user id object from a user id string
     *
     * A user id string is of the form:
     * <b><kbd>name (comment) <email-address></kbd></b> with the <i>comment</i>
     * and <i>email-address</i> fields being optional.
     *
     * @param string $string the user id string to parse.
     *
     * @return Crypt_GPG_UserId the user id object parsed from the string.
     */
    public static function parse($string)
    {
        $userId  = new Crypt_GPG_UserId();
        $name    = '';
        $email   = '';
        $comment = '';

        // get email address from end of string if it exists
        $matches = array();
        if (preg_match('/^(.*?)<([^>]+)>$/', $string, $matches) === 1) {
            $string = trim($matches[1]);
            $email  = $matches[2];
        }

        // get comment from end of string if it exists
        $matches = array();
        if (preg_match('/^(.+?) \(([^\)]+)\)$/', $string, $matches) === 1) {
            $string  = $matches[1];
            $comment = $matches[2];
        }

        // there can be an email without a name
        if (!$email && preg_match('/^[\S]+@[\S]+$/', $string, $matches) === 1) {
            $email = $string;
        } else {
            $name = $string;
        }

        $userId->setName($name);
        $userId->setComment($comment);
        $userId->setEmail($email);

        return $userId;
    }
}
PK}c�\ֈ�m�� pear/crypt_gpg/Crypt/GPG/Key.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * Contains a class representing GPG keys
 *
 * LICENSE:
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see
 * <http://www.gnu.org/licenses/>
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2008-2010 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 */

/**
 * Sub-key class definition
 */
require_once 'Crypt/GPG/SubKey.php';

/**
 * User id class definition
 */
require_once 'Crypt/GPG/UserId.php';

/**
 * A data class for GPG key information
 *
 * This class is used to store the results of the {@link Crypt_GPG::getKeys()}
 * method.
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2008-2010 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 * @see       Crypt_GPG::getKeys()
 */
class Crypt_GPG_Key
{
    /**
     * The user ids associated with this key
     *
     * This is an array of {@link Crypt_GPG_UserId} objects.
     *
     * @var array<Crypt_GPG_UserId>
     *
     * @see Crypt_GPG_Key::addUserId()
     * @see Crypt_GPG_Key::getUserIds()
     */
    private $_userIds = array();

    /**
     * The subkeys of this key
     *
     * This is an array of {@link Crypt_GPG_SubKey} objects.
     *
     * @var array<Crypt_GPG_SubKey>
     *
     * @see Crypt_GPG_Key::addSubKey()
     * @see Crypt_GPG_Key::getSubKeys()
     */
    private $_subKeys = array();

    /**
     * Gets the sub-keys of this key
     *
     * @return array<Crypt_GPG_SubKey> the sub-keys of this key.
     *
     * @see Crypt_GPG_Key::addSubKey()
     */
    public function getSubKeys()
    {
        return $this->_subKeys;
    }

    /**
     * Gets the user ids of this key
     *
     * @return array<Crypt_GPG_UserId> the user ids of this key.
     *
     * @see Crypt_GPG_Key::addUserId()
     */
    public function getUserIds()
    {
        return $this->_userIds;
    }

    /**
     * Gets the primary sub-key of this key
     *
     * The primary key is the first added sub-key.
     *
     * @return Crypt_GPG_SubKey the primary sub-key of this key.
     */
    public function getPrimaryKey()
    {
        $primary_key = null;
        if (count($this->_subKeys) > 0) {
            $primary_key = $this->_subKeys[0];
        }
        return $primary_key;
    }

    /**
     * Gets whether or not this key can sign data
     *
     * This key can sign data if any sub-key of this key can sign data.
     *
     * @return bool True if this key can sign data and false if this key
     *              cannot sign data.
     */
    public function canSign()
    {
        $canSign = false;
        foreach ($this->_subKeys as $subKey) {
            if ($subKey->canSign()) {
                $canSign = true;
                break;
            }
        }
        return $canSign;
    }

    /**
     * Gets whether or not this key can encrypt data
     *
     * This key can encrypt data if any sub-key of this key can encrypt data.
     *
     * @return bool True if this key can encrypt data and false if this
     *              key cannot encrypt data.
     */
    public function canEncrypt()
    {
        $canEncrypt = false;
        foreach ($this->_subKeys as $subKey) {
            if ($subKey->canEncrypt()) {
                $canEncrypt = true;
                break;
            }
        }
        return $canEncrypt;
    }

    /**
     * Adds a sub-key to this key
     *
     * The first added sub-key will be the primary key of this key.
     *
     * @param Crypt_GPG_SubKey $subKey the sub-key to add.
     *
     * @return Crypt_GPG_Key the current object, for fluent interface.
     */
    public function addSubKey(Crypt_GPG_SubKey $subKey)
    {
        $this->_subKeys[] = $subKey;
        return $this;
    }

    /**
     * Adds a user id to this key
     *
     * @param Crypt_GPG_UserId $userId the user id to add.
     *
     * @return Crypt_GPG_Key the current object, for fluent interface.
     */
    public function addUserId(Crypt_GPG_UserId $userId)
    {
        $this->_userIds[] = $userId;
        return $this;
    }

    /**
     * String representation of the key
     *
     * @return string The key ID.
     */
    public function __toString()
    {
        foreach ($this->_subKeys as $subKey) {
            if ($id = $subKey->getId()) {
                return $id;
            }
        }

        return '';
    }
}
PK}c�\4]r��#pear/crypt_gpg/Crypt/GPG/Engine.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * Crypt_GPG is a package to use GPG from PHP
 *
 * This file contains an engine that handles GPG subprocess control and I/O.
 * PHP's process manipulation functions are used to handle the GPG subprocess.
 *
 * LICENSE:
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see
 * <http://www.gnu.org/licenses/>
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Nathan Fredrickson <nathan@silverorange.com>
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2005-2013 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 * @link      http://www.gnupg.org/
 */

/**
 * Crypt_GPG base class.
 */
require_once 'Crypt/GPG.php';

/**
 * GPG exception classes.
 */
require_once 'Crypt/GPG/Exceptions.php';

/**
 * Status/Error handler class.
 */
require_once 'Crypt/GPG/ProcessHandler.php';

/**
 * Process control methods.
 */
require_once 'Crypt/GPG/ProcessControl.php';

/**
 * Information about a created signature
 */
require_once 'Crypt/GPG/SignatureCreationInfo.php';

/**
 * Standard PEAR exception is used if GPG binary is not found.
 */
require_once 'PEAR/Exception.php';

/**
 * Native PHP Crypt_GPG I/O engine
 *
 * This class is used internally by Crypt_GPG and does not need be used
 * directly. See the {@link Crypt_GPG} class for end-user API.
 *
 * This engine uses PHP's native process control functions to directly control
 * the GPG process. The GPG executable is required to be on the system.
 *
 * All data is passed to the GPG subprocess using file descriptors. This is the
 * most secure method of passing data to the GPG subprocess.
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Nathan Fredrickson <nathan@silverorange.com>
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2005-2013 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 * @link      http://www.gnupg.org/
 */
class Crypt_GPG_Engine
{
    /**
     * Size of data chunks that are sent to and retrieved from the IPC pipes.
     *
     * The value of 65536 has been chosen empirically
     * as the one with best performance.
     *
     * @see https://pear.php.net/bugs/bug.php?id=21077
     */
    const CHUNK_SIZE = 65536;

    /**
     * Standard input file descriptor. This is used to pass data to the GPG
     * process.
     */
    const FD_INPUT = 0;

    /**
     * Standard output file descriptor. This is used to receive normal output
     * from the GPG process.
     */
    const FD_OUTPUT = 1;

    /**
     * Standard output file descriptor. This is used to receive error output
     * from the GPG process.
     */
    const FD_ERROR = 2;

    /**
     * GPG status output file descriptor. The status file descriptor outputs
     * detailed information for many GPG commands. See the second section of
     * the file <b>doc/DETAILS</b> in the
     * {@link http://www.gnupg.org/download/ GPG package} for a detailed
     * description of GPG's status output.
     */
    const FD_STATUS = 3;

    /**
     * Command input file descriptor. This is used for methods requiring
     * passphrases.
     */
    const FD_COMMAND = 4;

    /**
     * Extra message input file descriptor. This is used for passing signed
     * data when verifying a detached signature.
     */
    const FD_MESSAGE = 5;

    /**
     * Minimum version of GnuPG that is supported.
     */
    const MIN_VERSION = '1.0.2';

    /**
     * Whether or not to use strict mode
     *
     * When set to true, any clock problems (e.g. keys generate in future)
     * are errors, otherwise they are just warnings.
     *
     * Strict mode is disabled by default.
     *
     * @var bool
     * @see Crypt_GPG_Engine::__construct()
     */
    private $_strict = false;

    /**
     * Whether or not to use debugging mode
     *
     * When set to true, every GPG command is echoed before it is run. Sensitive
     * data is always handled using pipes and is not specified as part of the
     * command. As a result, sensitive data is never displayed when debug is
     * enabled. Sensitive data includes private key data and passphrases.
     *
     * This can be set to a callable function where first argument is the
     * debug line to process.
     *
     * Debugging is off by default.
     *
     * @var mixed
     * @see Crypt_GPG_Engine::__construct()
     */
    private $_debug = false;

    /**
     * Location of GPG binary
     *
     * @var string
     * @see Crypt_GPG_Engine::__construct()
     * @see Crypt_GPG_Engine::_getBinary()
     */
    private $_binary = '';

    /**
     * Location of GnuPG agent binary
     *
     * Only used for GnuPG 2.x
     *
     * @var string
     * @see Crypt_GPG_Engine::__construct()
     * @see Crypt_GPG_Engine::_getAgent()
     */
    private $_agent = '';

    /**
     * Location of GnuPG conf binary
     *
     * Only used for GnuPG 2.1.x
     *
     * @var string|false|null
     * @see Crypt_GPG_Engine::__construct()
     * @see Crypt_GPG_Engine::_getGPGConf()
     */
    private $_gpgconf = null;

    /**
     * Directory containing the GPG key files
     *
     * This property only contains the path when the <i>homedir</i> option
     * is specified in the constructor.
     *
     * @var string
     * @see Crypt_GPG_Engine::__construct()
     */
    private $_homedir = '';

    /**
     * File path of the public keyring
     *
     * This property only contains the file path when the <i>public_keyring</i>
     * option is specified in the constructor.
     *
     * If the specified file path starts with <kbd>~/</kbd>, the path is
     * relative to the <i>homedir</i> if specified, otherwise to
     * <kbd>~/.gnupg</kbd>.
     *
     * @var string
     * @see Crypt_GPG_Engine::__construct()
     */
    private $_publicKeyring = '';

    /**
     * File path of the private (secret) keyring
     *
     * This property only contains the file path when the <i>private_keyring</i>
     * option is specified in the constructor.
     *
     * If the specified file path starts with <kbd>~/</kbd>, the path is
     * relative to the <i>homedir</i> if specified, otherwise to
     * <kbd>~/.gnupg</kbd>.
     *
     * @var string
     * @see Crypt_GPG_Engine::__construct()
     */
    private $_privateKeyring = '';

    /**
     * File path of the trust database
     *
     * This property only contains the file path when the <i>trust_db</i>
     * option is specified in the constructor.
     *
     * If the specified file path starts with <kbd>~/</kbd>, the path is
     * relative to the <i>homedir</i> if specified, otherwise to
     * <kbd>~/.gnupg</kbd>.
     *
     * @var string
     * @see Crypt_GPG_Engine::__construct()
     */
    private $_trustDb = '';

    /**
     * Array of pipes used for communication with the GPG binary
     *
     * This is an array of file descriptor resources.
     *
     * @var array
     */
    private $_pipes = array();

    /**
     * Array of pipes used for communication with the gpg-agent binary
     *
     * This is an array of file descriptor resources.
     *
     * @var array
     */
    private $_agentPipes = array();

    /**
     * Array of currently opened pipes
     *
     * This array is used to keep track of remaining opened pipes so they can
     * be closed when the GPG subprocess is finished. This array is a subset of
     * the {@link Crypt_GPG_Engine::$_pipes} array and contains opened file
     * descriptor resources.
     *
     * @var array
     * @see Crypt_GPG_Engine::_closePipe()
     */
    private $_openPipes = array();

    /**
     * A handle for the GPG process
     *
     * @var resource|null
     */
    private $_process = null;

    /**
     * A handle for the gpg-agent process
     *
     * @var resource|null
     */
    private $_agentProcess = null;

    /**
     * GPG agent daemon socket and PID for running gpg-agent
     *
     * @var string|null
     */
    private $_agentInfo = null;

    /**
     * Whether or not the operating system is Darwin (OS X)
     *
     * @var bool
     */
    private $_isDarwin = false;

    /**
     * Message digest algorithm.
     *
     * @var string
     */
    private $_digest_algo = null;

    /**
     * Symmetric cipher algorithm.
     *
     * @var string
     */
    private $_cipher_algo = null;

    /**
     * Compress algorithm.
     *
     * @var string
     */
    private $_compress_algo = null;

    /**
     * Additional per-command arguments
     *
     * @var array
     */
    private $_options = array();

    /**
     * Commands to be sent to GPG's command input stream
     *
     * @var string
     * @see Crypt_GPG_Engine::sendCommand()
     */
    private $_commandBuffer = '';

    /**
     * A status/error handler
     *
     * @var Crypt_GPG_ProcessHandler|null
     */
    private $_processHandler = null;

    /**
     * Array of status line handlers
     *
     * @var array
     * @see Crypt_GPG_Engine::addStatusHandler()
     */
    private $_statusHandlers = array();

    /**
     * Array of error line handlers
     *
     * @var array
     * @see Crypt_GPG_Engine::addErrorHandler()
     */
    private $_errorHandlers = array();

    /**
     * The input source
     *
     * This is data to send to GPG. Either a string or a stream resource.
     *
     * @var string|resource|null
     * @see Crypt_GPG_Engine::setInput()
     */
    private $_input = null;

    /**
     * The extra message input source
     *
     * Either a string or a stream resource.
     *
     * @var string|resource|null
     * @see Crypt_GPG_Engine::setMessage()
     */
    private $_message = null;

    /**
     * The output location
     *
     * This is where the output from GPG is sent. Either a string or a stream
     * resource.
     *
     * @var string|resource
     * @see Crypt_GPG_Engine::setOutput()
     */
    private $_output = '';

    /**
     * The GPG operation to execute
     *
     * @var string
     * @see Crypt_GPG_Engine::setOperation()
     */
    private $_operation;

    /**
     * Arguments for the current operation
     *
     * @var array
     * @see Crypt_GPG_Engine::setOperation()
     */
    private $_arguments = array();

    /**
     * The version number of the GPG binary
     *
     * @var string
     * @see Crypt_GPG_Engine::getVersion()
     */
    private $_version = '';

    /**
     * Creates a new GPG engine
     *
     * @param array $options An array of options used to create the engine object.
     *                       All options are optional and are represented as key-value
     *                       pairs. See Crypt_GPGAbstract::__construct() for more info.
     *
     * @throws Crypt_GPG_FileException if the <kbd>homedir</kbd> does not exist
     *         and cannot be created. This can happen if <kbd>homedir</kbd> is
     *         not specified, Crypt_GPG is run as the web user, and the web
     *         user has no home directory. This exception is also thrown if any
     *         of the options <kbd>publicKeyring</kbd>,
     *         <kbd>privateKeyring</kbd> or <kbd>trustDb</kbd> options are
     *         specified but the files do not exist or are are not readable.
     *         This can happen if the user running the Crypt_GPG process (for
     *         example, the Apache user) does not have permission to read the
     *         files.
     *
     * @throws PEAR_Exception if the provided <kbd>binary</kbd> is invalid, or
     *         if no <kbd>binary</kbd> is provided and no suitable binary could
     *         be found.
     *
     * @throws PEAR_Exception if the provided <kbd>agent</kbd> is invalid, or
     *         if no <kbd>agent</kbd> is provided and no suitable gpg-agent
     *         could be found.
     */
    public function __construct(array $options = array())
    {
        $this->_isDarwin = (strncmp(strtoupper(PHP_OS), 'DARWIN', 6) === 0);

        // get homedir
        if (array_key_exists('homedir', $options)) {
            $this->_homedir = (string)$options['homedir'];
        } else {
            if (extension_loaded('posix')) {
                // note: this requires the package OS dep exclude 'windows'
                $info = posix_getpwuid(posix_getuid());
                $this->_homedir = $info['dir'].'/.gnupg';
            } else {
                if (isset($_SERVER['HOME'])) {
                    $this->_homedir = $_SERVER['HOME'];
                } else {
                    $this->_homedir = getenv('HOME');
                }
            }

            if ($this->_homedir === false) {
                throw new Crypt_GPG_FileException(
                    'Could not locate homedir. Please specify the homedir ' .
                    'to use with the \'homedir\' option when instantiating ' .
                    'the Crypt_GPG object.'
                );
            }
        }

        // attempt to create homedir if it does not exist
        if (!is_dir($this->_homedir)) {
            if (@mkdir($this->_homedir, 0777, true)) {
                // Set permissions on homedir. Parent directories are created
                // with 0777, homedir is set to 0700.
                chmod($this->_homedir, 0700);
            } else {
                throw new Crypt_GPG_FileException(
                    'The \'homedir\' "' . $this->_homedir . '" is not ' .
                    'readable or does not exist and cannot be created. This ' .
                    'can happen if \'homedir\' is not specified in the ' .
                    'Crypt_GPG options, Crypt_GPG is run as the web user, ' .
                    'and the web user has no home directory.',
                    0,
                    $this->_homedir
                );
            }
        }

        // check homedir permissions (See Bug #19833)
        if (!is_executable($this->_homedir)) {
            throw new Crypt_GPG_FileException(
                'The \'homedir\' "' . $this->_homedir . '" is not enterable ' .
                'by the current user. Please check the permissions on your ' .
                'homedir and make sure the current user can both enter and ' .
                'write to the directory.',
                0,
                $this->_homedir
            );
        }
        if (!is_writeable($this->_homedir)) {
            throw new Crypt_GPG_FileException(
                'The \'homedir\' "' . $this->_homedir . '" is not writable ' .
                'by the current user. Please check the permissions on your ' .
                'homedir and make sure the current user can both enter and ' .
                'write to the directory.',
                0,
                $this->_homedir
            );
        }

        // get binary
        if (array_key_exists('binary', $options)) {
            $this->_binary = (string)$options['binary'];
        } elseif (array_key_exists('gpgBinary', $options)) {
            // deprecated alias
            $this->_binary = (string)$options['gpgBinary'];
        } else {
            $this->_binary = $this->_getBinary();
        }

        if ($this->_binary == '' || !is_executable($this->_binary)) {
            throw new PEAR_Exception(
                'GPG binary not found. If you are sure the GPG binary is ' .
                'installed, please specify the location of the GPG binary ' .
                'using the \'binary\' driver option.'
            );
        }

        // get agent
        if (array_key_exists('agent', $options)) {
            $this->_agent = (string)$options['agent'];

            if ($this->_agent && !is_executable($this->_agent)) {
                throw new PEAR_Exception(
                    'Specified gpg-agent binary is not executable.'
                );
            }
        } else {
            $this->_agent = $this->_getAgent();
        }

        if (array_key_exists('gpgconf', $options)) {
            $this->_gpgconf = $options['gpgconf'];

            if ($this->_gpgconf && !is_executable($this->_gpgconf)) {
                throw new PEAR_Exception(
                    'Specified gpgconf binary is not executable.'
                );
            }
        }

        /*
         * Note:
         *
         * Normally, GnuPG expects keyrings to be in the homedir and expects
         * to be able to write temporary files in the homedir. Sometimes,
         * keyrings are not in the homedir, or location of the keyrings does
         * not allow writing temporary files. In this case, the <i>homedir</i>
         * option by itself is not enough to specify the keyrings because GnuPG
         * can not write required temporary files. Additional options are
         * provided so you can specify the location of the keyrings separately
         * from the homedir.
         */

        // get public keyring
        if (array_key_exists('publicKeyring', $options)) {
            $this->_publicKeyring = (string)$options['publicKeyring'];
            if (!is_readable($this->_publicKeyring)) {
                throw new Crypt_GPG_FileException(
                    'The \'publicKeyring\' "' . $this->_publicKeyring .
                    '" does not exist or is not readable. Check the location ' .
                    'and ensure the file permissions are correct.',
                    0, $this->_publicKeyring
                );
            }
        }

        // get private keyring
        if (array_key_exists('privateKeyring', $options)) {
            $this->_privateKeyring = (string)$options['privateKeyring'];
            if (!is_readable($this->_privateKeyring)) {
                throw new Crypt_GPG_FileException(
                    'The \'privateKeyring\' "' . $this->_privateKeyring .
                    '" does not exist or is not readable. Check the location ' .
                    'and ensure the file permissions are correct.',
                    0, $this->_privateKeyring
                );
            }
        }

        // get trust database
        if (array_key_exists('trustDb', $options)) {
            $this->_trustDb = (string)$options['trustDb'];
            if (!is_readable($this->_trustDb)) {
                throw new Crypt_GPG_FileException(
                    'The \'trustDb\' "' . $this->_trustDb .
                    '" does not exist or is not readable. Check the location ' .
                    'and ensure the file permissions are correct.',
                    0, $this->_trustDb
                );
            }
        }

        if (array_key_exists('debug', $options)) {
            $this->_debug = $options['debug'];
        }

        $this->_strict = !empty($options['strict']);

        if (!empty($options['digest-algo'])) {
            $this->_digest_algo = $options['digest-algo'];
        }

        if (!empty($options['cipher-algo'])) {
            $this->_cipher_algo = $options['cipher-algo'];
        }

        if (!empty($options['compress-algo'])) {
            $this->_compress_algo = $options['compress-algo'];
        }

        if (!empty($options['options'])) {
            $this->_options = $options['options'];
        }
    }

    /**
     * Closes open GPG subprocesses when this object is destroyed
     *
     * Subprocesses should never be left open by this class unless there is
     * an unknown error and unexpected script termination occurs.
     */
    public function __destruct()
    {
        $this->_closeSubprocess();
        $this->_closeIdleAgents();
    }

    /**
     * Adds an error handler method
     *
     * The method is run every time a new error line is received from the GPG
     * subprocess. The handler method must accept the error line to be handled
     * as its first parameter.
     *
     * @param callable $callback the callback method to use.
     * @param array    $args     optional. Additional arguments to pass as
     *                           parameters to the callback method.
     *
     * @return void
     */
    public function addErrorHandler($callback, array $args = array())
    {
        $this->_errorHandlers[] = array(
            'callback' => $callback,
            'args'     => $args
        );
    }

    /**
     * Adds a status handler method
     *
     * The method is run every time a new status line is received from the
     * GPG subprocess. The handler method must accept the status line to be
     * handled as its first parameter.
     *
     * @param callable $callback the callback method to use.
     * @param array    $args     optional. Additional arguments to pass as
     *                           parameters to the callback method.
     *
     * @return void
     */
    public function addStatusHandler($callback, array $args = array())
    {
        $this->_statusHandlers[] = array(
            'callback' => $callback,
            'args'     => $args
        );
    }

    /**
     * Sends a command to the GPG subprocess over the command file-descriptor
     * pipe
     *
     * @param string $command the command to send.
     *
     * @return void
     *
     * @sensitive $command
     */
    public function sendCommand($command)
    {
        if (array_key_exists(self::FD_COMMAND, $this->_openPipes)) {
            $this->_commandBuffer .= $command . PHP_EOL;
        }
    }

    /**
     * Resets the GPG engine, preparing it for a new operation
     *
     * @return void
     *
     * @see Crypt_GPG_Engine::run()
     * @see Crypt_GPG_Engine::setOperation()
     */
    public function reset()
    {
        $this->_operation      = '';
        $this->_arguments      = array();
        $this->_input          = null;
        $this->_message        = null;
        $this->_output         = '';
        $this->_commandBuffer  = '';

        $this->_statusHandlers = array();
        $this->_errorHandlers  = array();

        if ($this->_debug) {
            $this->addStatusHandler(array($this, '_handleDebugStatus'));
            $this->addErrorHandler(array($this, '_handleDebugError'));
        }

        $this->_processHandler = new Crypt_GPG_ProcessHandler($this);

        $this->addStatusHandler(array($this->_processHandler, 'handleStatus'));
        $this->addErrorHandler(array($this->_processHandler, 'handleError'));
    }

    /**
     * Runs the current GPG operation.
     *
     * This creates and manages the GPG subprocess.
     * This will close input/output file handles.
     *
     * The operation must be set with {@link Crypt_GPG_Engine::setOperation()}
     * before this method is called.
     *
     * @return void
     *
     * @throws Crypt_GPG_InvalidOperationException if no operation is specified.
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *
     * @see Crypt_GPG_Engine::reset()
     * @see Crypt_GPG_Engine::setOperation()
     */
    public function run()
    {
        if ($this->_operation === '') {
            throw new Crypt_GPG_InvalidOperationException(
                'No GPG operation specified. Use Crypt_GPG_Engine::setOperation() ' .
                'before calling Crypt_GPG_Engine::run().'
            );
        }

        $this->_openSubprocess();
        $this->_process();
        $this->_closeSubprocess();
    }

    /**
     * Sets the input source for the current GPG operation
     *
     * @param string|resource &$input Either a reference to the string
     *                                containing the input data or an open
     *                                stream resource containing the input data
     *
     * @return void
     */
    public function setInput(&$input)
    {
        $this->_input =& $input;
    }

    /**
     * Sets the message source for the current GPG operation
     *
     * Detached signature data should be specified here.
     *
     * @param string|resource &$message Either a reference to the string
     *                                  containing the message data or an open
     *                                  stream resource containing the message data
     *
     * @return void
     */
    public function setMessage(&$message)
    {
        $this->_message =& $message;
    }

    /**
     * Sets the output destination for the current GPG operation
     *
     * @param string|resource &$output Either a reference to the string in
     *                                 which to store GPG output or an open
     *                                 stream resource to which the output data
     *                                 should be written
     *
     * @return void
     */
    public function setOutput(&$output)
    {
        $this->_output =& $output;
    }

    /**
     * Sets the operation to perform
     *
     * @param string $operation the operation to perform. This should be one
     *                          of GPG's operations. For example,
     *                          <kbd>--encrypt</kbd>, <kbd>--decrypt</kbd>,
     *                          <kbd>--sign</kbd>, etc.
     * @param array  $arguments optional. Additional arguments for the GPG
     *                          subprocess. See the GPG manual for specific
     *                          values.
     *
     * @return void
     *
     * @see Crypt_GPG_Engine::reset()
     * @see Crypt_GPG_Engine::run()
     */
    public function setOperation($operation, array $arguments = array())
    {
        $this->_operation = $operation;
        $this->_arguments = $arguments;

        foreach ($this->_options as $optname => $args) {
            if (strpos($operation, '--' . $optname) !== false) {
                $this->_arguments[] = $args;
            }
        }

        $this->_processHandler->setOperation($operation);
    }

    /**
     * Sets the PINENTRY_USER_DATA environment variable with the currently
     * added keys and passphrases
     *
     * Keys and passphrases are stored as an indexed array of passphrases
     * in JSON encoded to a flat string.
     *
     * For GnuPG 2.x this is how passphrases are passed. For GnuPG 1.x the
     * environment variable is set but not used.
     *
     * @param array $keys the internal key array to use.
     *
     * @return void
     */
    public function setPins(array $keys)
    {
        $envKeys = array();

        foreach ($keys as $keyId => $key) {
            $envKeys[$keyId] = is_array($key) ? $key['passphrase'] : $key;
        }

        $_ENV['PINENTRY_USER_DATA'] = json_encode($envKeys);
    }

    /**
     * Sets per-command additional arguments
     *
     * @param array $options Additional per-command options for GPG command.
     *                       Note: This will unset options set previously.
     *                       Key of the array is a command (e.g.
     *                       gen-key, import, sign, encrypt, list-keys).
     *                       Value is a string containing command line arguments to be
     *                       added to the related command. For example:
     *                       array('sign' => '--emit-version').
     *
     * @return void
     */
    public function setOptions(array $options)
    {
        $this->_options = $options;
    }

    /**
     * Gets the version of the GnuPG binary
     *
     * @return string a version number string containing the version of GnuPG
     *                being used. This value is suitable to use with PHP's
     *                version_compare() function.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     *
     * @throws Crypt_GPG_Exception if the provided binary is not
     *         GnuPG or if the GnuPG version is less than 1.0.2.
     */
    public function getVersion()
    {
        if ($this->_version == '') {
            $options = array(
                'homedir' => $this->_homedir,
                'binary'  => $this->_binary,
                'debug'   => $this->_debug,
                'agent'   => $this->_agent,
            );

            $engine = new self($options);
            $info   = '';

            // Set a garbage version so we do not end up looking up the version
            // recursively.
            $engine->_version = '1.0.0';

            $engine->reset();
            $engine->setOutput($info);
            $engine->setOperation('--version');
            $engine->run();

            $matches    = array();
            $expression = '#gpg \(GnuPG[A-Za-z0-9/]*?\) (\S+)#';

            if (preg_match($expression, $info, $matches) === 1) {
                $this->_version = $matches[1];
            } else {
                throw new Crypt_GPG_Exception(
                    'No GnuPG version information provided by the binary "' .
                    $this->_binary . '". Are you sure it is GnuPG?'
                );
            }

            if (version_compare($this->_version, self::MIN_VERSION, 'lt')) {
                throw new Crypt_GPG_Exception(
                    'The version of GnuPG being used (' . $this->_version .
                    ') is not supported by Crypt_GPG. The minimum version ' .
                    'required by Crypt_GPG is ' . self::MIN_VERSION
                );
            }
        }


        return $this->_version;
    }

    /**
     * Get data from the last process execution.
     *
     * @param string $name Data element name (e.g. 'SignatureInfo')
     *
     * @return mixed
     * @see    Crypt_GPG_ProcessHandler::getData()
     */
    public function getProcessData($name)
    {
        if ($this->_processHandler) {
            switch ($name) {
            case 'SignatureInfo':
                if ($data = $this->_processHandler->getData('SigCreated')) {
                    return new Crypt_GPG_SignatureCreationInfo($data);
                }
                break;

            case 'Signatures':
            case 'Warnings':
                return (array) $this->_processHandler->getData($name);

            default:
                return $this->_processHandler->getData($name);
            }
        }
    }

    /**
     * Set some data for the process execution.
     *
     * @param string $name  Data element name (e.g. 'Handle')
     * @param mixed  $value Data value
     *
     * @return void
     */
    public function setProcessData($name, $value)
    {
        if ($this->_processHandler) {
            $this->_processHandler->setData($name, $value);
        }
    }

    /**
     * Displays debug output for status lines
     *
     * @param string $line the status line to handle.
     *
     * @return void
     */
    private function _handleDebugStatus($line)
    {
        $this->_debug('STATUS: ' . $line);
    }

    /**
     * Displays debug output for error lines
     *
     * @param string $line the error line to handle.
     *
     * @return void
     */
    private function _handleDebugError($line)
    {
        $this->_debug('ERROR: ' . $line);
    }

    /**
     * Performs internal streaming operations for the subprocess using either
     * strings or streams as input / output points
     *
     * This is the main I/O loop for streaming to and from the GPG subprocess.
     *
     * The implementation of this method is verbose mainly for performance
     * reasons. Adding streams to a lookup array and looping the array inside
     * the main I/O loop would be siginficantly slower for large streams.
     *
     * @return void
     *
     * @throws Crypt_GPG_Exception if there is an error selecting streams for
     *         reading or writing. If this occurs, please file a bug report at
     *         http://pear.php.net/bugs/report.php?package=Crypt_GPG.
     */
    private function _process()
    {
        $this->_debug('BEGIN PROCESSING');

        $this->_commandBuffer = '';    // buffers input to GPG
        $messageBuffer        = '';    // buffers input to GPG
        $inputBuffer          = '';    // buffers input to GPG
        $outputBuffer         = '';    // buffers output from GPG
        $statusBuffer         = '';    // buffers output from GPG
        $errorBuffer          = '';    // buffers output from GPG
        $inputComplete        = false; // input stream is completely buffered
        $messageComplete      = false; // message stream is completely buffered

        if (is_string($this->_input)) {
            $inputBuffer   = $this->_input;
            $inputComplete = true;
        }

        if (is_string($this->_message)) {
            $messageBuffer   = $this->_message;
            $messageComplete = true;
        }

        if (is_string($this->_output)) {
            $outputBuffer =& $this->_output;
        }

        // convenience variables
        $fdInput   = $this->_pipes[self::FD_INPUT];
        $fdOutput  = $this->_pipes[self::FD_OUTPUT];
        $fdError   = $this->_pipes[self::FD_ERROR];
        $fdStatus  = $this->_pipes[self::FD_STATUS];
        $fdCommand = $this->_pipes[self::FD_COMMAND];
        $fdMessage = $this->_pipes[self::FD_MESSAGE];

        // select loop delay in milliseconds
        $delay         = 0;
        $inputPosition = 0;
        $eolLength     = mb_strlen(PHP_EOL, '8bit');

        while (true) {
            $inputStreams     = array();
            $outputStreams    = array();
            $exceptionStreams = array();

            // set up input streams
            if (is_resource($this->_input) && !$inputComplete) {
                if (feof($this->_input)) {
                    $inputComplete = true;
                } else {
                    $inputStreams[] = $this->_input;
                }
            }

            // close GPG input pipe if there is no more data
            if ($inputBuffer == '' && $inputComplete) {
                $this->_debug('=> closing GPG input pipe');
                $this->_closePipe(self::FD_INPUT);
            }

            if (is_resource($this->_message) && !$messageComplete) {
                if (feof($this->_message)) {
                    $messageComplete = true;
                } else {
                    $inputStreams[] = $this->_message;
                }
            }

            // close GPG message pipe if there is no more data
            if ($messageBuffer == '' && $messageComplete) {
                $this->_debug('=> closing GPG message pipe');
                $this->_closePipe(self::FD_MESSAGE);
            }

            if (!feof($fdOutput)) {
                $inputStreams[] = $fdOutput;
            }

            if (!feof($fdStatus)) {
                $inputStreams[] = $fdStatus;
            }

            if (!feof($fdError)) {
                $inputStreams[] = $fdError;
            }

            // set up output streams
            if ($outputBuffer != '' && is_resource($this->_output)) {
                $outputStreams[] = $this->_output;
            }

            if ($this->_commandBuffer != '' && is_resource($fdCommand)) {
                $outputStreams[] = $fdCommand;
            }

            if ($messageBuffer != '' && is_resource($fdMessage)) {
                $outputStreams[] = $fdMessage;
            }

            if ($inputBuffer != '' && is_resource($fdInput)) {
                $outputStreams[] = $fdInput;
            }

            // no streams left to read or write, we're all done
            if (count($inputStreams) === 0 && count($outputStreams) === 0) {
                break;
            }

            $this->_debug('selecting streams');

            $ready = stream_select(
                $inputStreams,
                $outputStreams,
                $exceptionStreams,
                null
            );

            $this->_debug('=> got ' . $ready);

            if ($ready === false) {
                throw new Crypt_GPG_Exception(
                    'Error selecting stream for communication with GPG ' .
                    'subprocess. Please file a bug report at: ' .
                    'http://pear.php.net/bugs/report.php?package=Crypt_GPG'
                );
            }

            if ($ready === 0) {
                throw new Crypt_GPG_Exception(
                    'stream_select() returned 0. This can not happen! Please ' .
                    'file a bug report at: ' .
                    'http://pear.php.net/bugs/report.php?package=Crypt_GPG'
                );
            }

            // write input (to GPG)
            if (in_array($fdInput, $outputStreams, true)) {
                $this->_debug('GPG is ready for input');

                $chunk  = mb_substr($inputBuffer, $inputPosition, self::CHUNK_SIZE, '8bit');
                $length = mb_strlen($chunk, '8bit');

                $this->_debug(
                    '=> about to write ' . $length . ' bytes to GPG input'
                );

                $length = fwrite($fdInput, $chunk, $length);
                if ($length === 0 || $length === false) {
                    // If we wrote 0 bytes it was either EAGAIN or EPIPE. Since
                    // the pipe was seleted for writing, we assume it was EPIPE.
                    // There's no way to get the actual error code in PHP. See
                    // PHP Bug #39598. https://bugs.php.net/bug.php?id=39598
                    $this->_debug('=> broken pipe on GPG input');
                    $this->_debug('=> closing pipe GPG input');
                    $this->_closePipe(self::FD_INPUT);
                } else {
                    $this->_debug('=> wrote ' . $length . ' bytes');
                    // Move the position pointer, don't modify $inputBuffer (#21081)
                    if (is_string($this->_input)) {
                        $inputPosition += $length;
                    } else {
                        $inputPosition = 0;
                        $inputBuffer   = mb_substr($inputBuffer, $length, null, '8bit');
                    }
                }
            }

            // read input (from PHP stream)
            // If the buffer is too big wait until it's smaller, we don't want
            // to use too much memory
            if (in_array($this->_input, $inputStreams, true)
                && mb_strlen($inputBuffer, '8bit') < self::CHUNK_SIZE
            ) {
                $this->_debug('input stream is ready for reading');
                $this->_debug(
                    '=> about to read ' . self::CHUNK_SIZE .
                    ' bytes from input stream'
                );

                $chunk        = fread($this->_input, self::CHUNK_SIZE);
                $length       = mb_strlen($chunk, '8bit');
                $inputBuffer .= $chunk;

                $this->_debug('=> read ' . $length . ' bytes');
            }

            // write message (to GPG)
            if (in_array($fdMessage, $outputStreams, true)) {
                $this->_debug('GPG is ready for message data');

                $chunk  = mb_substr($messageBuffer, 0, self::CHUNK_SIZE, '8bit');
                $length = mb_strlen($chunk, '8bit');

                $this->_debug(
                    '=> about to write ' . $length . ' bytes to GPG message'
                );

                $length = fwrite($fdMessage, $chunk, $length);

                if ($length === 0 || $length === false) {
                    // If we wrote 0 bytes it was either EAGAIN or EPIPE. Since
                    // the pipe was seleted for writing, we assume it was EPIPE.
                    // There's no way to get the actual error code in PHP. See
                    // PHP Bug #39598. https://bugs.php.net/bug.php?id=39598
                    $this->_debug('=> broken pipe on GPG message');
                    $this->_debug('=> closing pipe GPG message');
                    $this->_closePipe(self::FD_MESSAGE);
                } else {
                    $this->_debug('=> wrote ' . $length . ' bytes');
                    $messageBuffer = mb_substr($messageBuffer, $length, null, '8bit');
                }
            }

            // read message (from PHP stream)
            if (in_array($this->_message, $inputStreams, true)) {
                $this->_debug('message stream is ready for reading');
                $this->_debug(
                    '=> about to read ' . self::CHUNK_SIZE .
                    ' bytes from message stream'
                );

                $chunk          = fread($this->_message, self::CHUNK_SIZE);
                $length         = mb_strlen($chunk, '8bit');
                $messageBuffer .= $chunk;

                $this->_debug('=> read ' . $length . ' bytes');
            }

            // read output (from GPG)
            if (in_array($fdOutput, $inputStreams, true)) {
                $this->_debug('GPG output stream ready for reading');
                $this->_debug(
                    '=> about to read ' . self::CHUNK_SIZE .
                    ' bytes from GPG output'
                );

                $chunk         = fread($fdOutput, self::CHUNK_SIZE);
                $length        = mb_strlen($chunk, '8bit');
                $outputBuffer .= $chunk;

                $this->_debug('=> read ' . $length . ' bytes');
            }

            // write output (to PHP stream)
            if (in_array($this->_output, $outputStreams, true)) {
                $this->_debug('output stream is ready for data');

                $chunk  = mb_substr($outputBuffer, 0, self::CHUNK_SIZE, '8bit');
                $length = mb_strlen($chunk, '8bit');

                $this->_debug(
                    '=> about to write ' . $length . ' bytes to output stream'
                );

                $length = fwrite($this->_output, $chunk, $length);

                if ($length === 0 || $length === false) {
                    // If we wrote 0 bytes it was either EAGAIN or EPIPE. Since
                    // the pipe was seleted for writing, we assume it was EPIPE.
                    // There's no way to get the actual error code in PHP. See
                    // PHP Bug #39598. https://bugs.php.net/bug.php?id=39598
                    $this->_debug('=> broken pipe on output stream');
                    $this->_debug('=> closing pipe output stream');
                    $this->_closePipe(self::FD_OUTPUT);
                } else {
                    $this->_debug('=> wrote ' . $length . ' bytes');
                    $outputBuffer = mb_substr($outputBuffer, $length, null, '8bit');
                }
            }

            // read error (from GPG)
            if (in_array($fdError, $inputStreams, true)) {
                $this->_debug('GPG error stream ready for reading');
                $this->_debug(
                    '=> about to read ' . self::CHUNK_SIZE .
                    ' bytes from GPG error'
                );

                $chunk        = fread($fdError, self::CHUNK_SIZE);
                $length       = mb_strlen($chunk, '8bit');
                $errorBuffer .= $chunk;

                $this->_debug('=> read ' . $length . ' bytes');

                // pass lines to error handlers
                while (($pos = strpos($errorBuffer, PHP_EOL)) !== false) {
                    $line = mb_substr($errorBuffer, 0, $pos, '8bit');
                    foreach ($this->_errorHandlers as $handler) {
                        array_unshift($handler['args'], $line);
                        call_user_func_array(
                            $handler['callback'],
                            $handler['args']
                        );

                        array_shift($handler['args']);
                    }

                    $errorBuffer = mb_substr($errorBuffer, $pos + $eolLength, null, '8bit');
                }
            }

            // read status (from GPG)
            if (in_array($fdStatus, $inputStreams, true)) {
                $this->_debug('GPG status stream ready for reading');
                $this->_debug(
                    '=> about to read ' . self::CHUNK_SIZE .
                    ' bytes from GPG status'
                );

                $chunk         = fread($fdStatus, self::CHUNK_SIZE);
                $length        = mb_strlen($chunk, '8bit');
                $statusBuffer .= $chunk;

                $this->_debug('=> read ' . $length . ' bytes');

                // pass lines to status handlers
                while (($pos = strpos($statusBuffer, PHP_EOL)) !== false) {
                    $line = mb_substr($statusBuffer, 0, $pos, '8bit');
                    // only pass lines beginning with magic prefix
                    if (mb_substr($line, 0, 9, '8bit') == '[GNUPG:] ') {
                        $line = mb_substr($line, 9, null, '8bit');
                        foreach ($this->_statusHandlers as $handler) {
                            array_unshift($handler['args'], $line);
                            call_user_func_array(
                                $handler['callback'],
                                $handler['args']
                            );

                            array_shift($handler['args']);
                        }
                    }

                    $statusBuffer = mb_substr($statusBuffer, $pos + $eolLength, null, '8bit');
                }
            }

            // write command (to GPG)
            if (in_array($fdCommand, $outputStreams, true)) {
                $this->_debug('GPG is ready for command data');

                // send commands
                $chunk  = mb_substr($this->_commandBuffer, 0, self::CHUNK_SIZE, '8bit');
                $length = mb_strlen($chunk, '8bit');

                $this->_debug(
                    '=> about to write ' . $length . ' bytes to GPG command'
                );

                $length = fwrite($fdCommand, $chunk, $length);

                if ($length === 0 || $length === false) {
                    // If we wrote 0 bytes it was either EAGAIN or EPIPE. Since
                    // the pipe was seleted for writing, we assume it was EPIPE.
                    // There's no way to get the actual error code in PHP. See
                    // PHP Bug #39598. https://bugs.php.net/bug.php?id=39598
                    $this->_debug('=> broken pipe on GPG command');
                    $this->_debug('=> closing pipe GPG command');
                    $this->_closePipe(self::FD_COMMAND);
                } else {
                    $this->_debug('=> wrote ' . $length);
                    $this->_commandBuffer = mb_substr($this->_commandBuffer, $length, null, '8bit');
                }
            }

            if (count($outputStreams) === 0 || count($inputStreams) === 0) {
                // we have an I/O imbalance, increase the select loop delay
                // to smooth things out
                $delay += 10;
            } else {
                // things are running smoothly, decrease the delay
                $delay -= 8;
                $delay = max(0, $delay);
            }

            if ($delay > 0) {
                usleep($delay);
            }

        } // end loop while streams are open

        $this->_debug('END PROCESSING');
    }

    /**
     * Opens an internal GPG subprocess for the current operation
     *
     * Opens a GPG subprocess, then connects the subprocess to some pipes. Sets
     * the private class property {@link Crypt_GPG_Engine::$_process} to
     * the new subprocess.
     *
     * @return void
     *
     * @throws Crypt_GPG_OpenSubprocessException if the subprocess could not be
     *         opened.
     *
     * @see Crypt_GPG_Engine::setOperation()
     * @see Crypt_GPG_Engine::_closeSubprocess()
     * @see Crypt_GPG_Engine::$_process
     */
    private function _openSubprocess()
    {
        $version = $this->getVersion();

        // log versions, but not when looking for the version number
        if ($version !== '1.0.0') {
            $this->_debug('USING GPG ' . $version . ' with PHP ' . PHP_VERSION);
        }

        // Binary operations will not work on Windows with PHP < 5.2.6. This is
        // in case stream_select() ever works on Windows.
        $rb = (version_compare(PHP_VERSION, '5.2.6') < 0) ? 'r' : 'rb';
        $wb = (version_compare(PHP_VERSION, '5.2.6') < 0) ? 'w' : 'wb';

        $env = $_ENV;

        // Newer versions of GnuPG return localized results. Crypt_GPG only
        // works with English, so set the locale to 'C' for the subprocess.
        $env['LC_ALL'] = 'C';

        // If using GnuPG 2.x < 2.1.13 start the gpg-agent
        if (version_compare($version, '2.0.0', 'ge')
            && version_compare($version, '2.1.13', 'lt')
        ) {
            if (!$this->_agent) {
                throw new Crypt_GPG_OpenSubprocessException(
                    'Unable to open gpg-agent subprocess (gpg-agent not found). ' .
                    'Please specify location of the gpg-agent binary ' .
                    'using the \'agent\' driver option.'
                );
            }

            $agentArguments = array(
                '--daemon',
                '--options /dev/null', // ignore any saved options
                '--csh', // output is easier to parse
                '--keep-display', // prevent passing --display to pinentry
                '--no-grab',
                '--ignore-cache-for-signing',
                '--pinentry-touch-file /dev/null',
                '--disable-scdaemon',
                '--no-use-standard-socket',
                '--pinentry-program ' . escapeshellarg($this->_getPinEntry())
            );

            if ($this->_homedir) {
                $agentArguments[] = '--homedir ' .
                    escapeshellarg($this->_homedir);
            }

            if ($version21 = version_compare($version, '2.1.0', 'ge')) {
                // This is needed to get socket file location in stderr output
                // Note: This does not help when the agent already is running
                $agentArguments[] = '--verbose';
            }

            $agentCommandLine = $this->_agent . ' ' . implode(' ', $agentArguments);

            $agentDescriptorSpec = array(
                self::FD_INPUT   => array('pipe', $rb), // stdin
                self::FD_OUTPUT  => array('pipe', $wb), // stdout
                self::FD_ERROR   => array('pipe', $wb)  // stderr
            );

            $this->_debug('OPENING GPG-AGENT SUBPROCESS WITH THE FOLLOWING COMMAND:');
            $this->_debug($agentCommandLine);

            $this->_agentProcess = proc_open(
                $agentCommandLine,
                $agentDescriptorSpec,
                $this->_agentPipes,
                null,
                $env,
                array('binary_pipes' => true)
            );

            if (!is_resource($this->_agentProcess)) {
                throw new Crypt_GPG_OpenSubprocessException(
                    'Unable to open gpg-agent subprocess.',
                    0,
                    $agentCommandLine
                );
            }

            // Get GPG_AGENT_INFO and set environment variable for gpg process.
            // This is a blocking read, but is only 1 line.
            $agentInfo = fread($this->_agentPipes[self::FD_OUTPUT], self::CHUNK_SIZE);

            // For GnuPG 2.1 we need to read both stderr and stdout
            if ($version21) {
                $agentInfo .= "\n" . fread($this->_agentPipes[self::FD_ERROR], self::CHUNK_SIZE);
            }

            if ($agentInfo) {
                foreach (explode("\n", $agentInfo) as $line) {
                    if ($version21) {
                        if (preg_match('/listening on socket \'([^\']+)/', $line, $m)) {
                            $this->_agentInfo = $m[1];
                        } else if (preg_match('/gpg-agent\[([0-9]+)\].* started/', $line, $m)) {
                            $this->_agentInfo .= ':' . $m[1] . ':1';
                        }
                    } else if (preg_match('/GPG_AGENT_INFO[=\s]([^;]+)/', $line, $m)) {
                        $this->_agentInfo = $m[1];
                        break;
                    }
                }
            }

            $this->_debug('GPG-AGENT-INFO: ' . $this->_agentInfo);

            $env['GPG_AGENT_INFO'] = $this->_agentInfo;

            // gpg-agent daemon is started, we can close the launching process
            $this->_closeAgentLaunchProcess();

            // Terminate processes if something went wrong
            register_shutdown_function(array($this, '__destruct'));
        }

        // "Register" GPGConf existence for _closeIdleAgents()
        if (version_compare($version, '2.1.0', 'ge')) {
            if ($this->_gpgconf === null) {
                $this->_gpgconf = $this->_getGPGConf();
            }
        } else {
            $this->_gpgconf = false;
        }

        $commandLine = $this->_binary;

        $defaultArguments = array(
            '--status-fd ' . escapeshellarg(self::FD_STATUS),
            '--command-fd ' . escapeshellarg(self::FD_COMMAND),
            '--no-secmem-warning',
            '--no-tty',
            '--no-default-keyring', // ignored if keying files are not specified
            '--no-options'          // prevent creation of ~/.gnupg directory
        );

        if (version_compare($version, '1.0.7', 'ge')) {
            if (version_compare($version, '2.0.0', 'lt')) {
                $defaultArguments[] = '--no-use-agent';
            }
            $defaultArguments[] = '--no-permission-warning';
        }

        if (version_compare($version, '1.4.2', 'ge')) {
            $defaultArguments[] = '--exit-on-status-write-error';
        }

        if (version_compare($version, '1.3.2', 'ge')) {
            $defaultArguments[] = '--trust-model always';
        } else {
            $defaultArguments[] = '--always-trust';
        }

        // Since 2.1.13 we can use "loopback mode" instead of gpg-agent
        if (version_compare($version, '2.1.13', 'ge')) {
            $defaultArguments[] = '--pinentry-mode loopback';
        }

        if (!$this->_strict) {
            $defaultArguments[] = '--ignore-time-conflict';
            $defaultArguments[] = '--ignore-valid-from';
        }

        if (!empty($this->_digest_algo)) {
            $defaultArguments[] = '--digest-algo ' . escapeshellarg($this->_digest_algo);
            $defaultArguments[] = '--s2k-digest-algo ' . escapeshellarg($this->_digest_algo);
        }

        if (!empty($this->_cipher_algo)) {
            $defaultArguments[] = '--cipher-algo ' . escapeshellarg($this->_cipher_algo);
            $defaultArguments[] = '--s2k-cipher-algo ' . escapeshellarg($this->_cipher_algo);
        }

        if (!empty($this->_compress_algo)) {
            $defaultArguments[] = '--compress-algo ' . escapeshellarg($this->_compress_algo);
        }

        $arguments = array_merge($defaultArguments, $this->_arguments);

        if ($this->_homedir) {
            $arguments[] = '--homedir ' . escapeshellarg($this->_homedir);

            // the random seed file makes subsequent actions faster so only
            // disable it if we have to.
            if (!is_writeable($this->_homedir)) {
                $arguments[] = '--no-random-seed-file';
            }
        }

        if ($this->_publicKeyring) {
            $arguments[] = '--keyring ' . escapeshellarg($this->_publicKeyring);
        }

        if ($this->_privateKeyring) {
            $arguments[] = '--secret-keyring ' .
                escapeshellarg($this->_privateKeyring);
        }

        if ($this->_trustDb) {
            $arguments[] = '--trustdb-name ' . escapeshellarg($this->_trustDb);
        }

        $commandLine .= ' ' . implode(' ', $arguments) . ' ' .
            $this->_operation;

        $descriptorSpec = array(
            self::FD_INPUT   => array('pipe', $rb), // stdin
            self::FD_OUTPUT  => array('pipe', $wb), // stdout
            self::FD_ERROR   => array('pipe', $wb), // stderr
            self::FD_STATUS  => array('pipe', $wb), // status
            self::FD_COMMAND => array('pipe', $rb), // command
            self::FD_MESSAGE => array('pipe', $rb)  // message
        );

        $this->_debug('OPENING GPG SUBPROCESS WITH THE FOLLOWING COMMAND:');
        $this->_debug($commandLine);

        $this->_process = proc_open(
            $commandLine,
            $descriptorSpec,
            $this->_pipes,
            null,
            $env,
            array('binary_pipes' => true)
        );

        if (!is_resource($this->_process)) {
            throw new Crypt_GPG_OpenSubprocessException(
                'Unable to open GPG subprocess.', 0, $commandLine
            );
        }

        // Set streams as non-blocking. See Bug #18618.
        foreach ($this->_pipes as $pipe) {
            stream_set_blocking($pipe, 0);
            stream_set_write_buffer($pipe, self::CHUNK_SIZE);
            stream_set_chunk_size($pipe, self::CHUNK_SIZE);
            stream_set_read_buffer($pipe, self::CHUNK_SIZE);
        }

        $this->_openPipes = $this->_pipes;
    }

    /**
     * Closes the internal GPG subprocess
     *
     * Closes the internal GPG subprocess. Sets the private class property
     * {@link Crypt_GPG_Engine::$_process} to null.
     *
     * @return void
     *
     * @see Crypt_GPG_Engine::_openSubprocess()
     * @see Crypt_GPG_Engine::$_process
     */
    private function _closeSubprocess()
    {
        // clear PINs from environment if they were set
        $_ENV['PINENTRY_USER_DATA'] = null;

        if (is_resource($this->_process)) {
            $this->_debug('CLOSING GPG SUBPROCESS');

            // close remaining open pipes
            foreach (array_keys($this->_openPipes) as $pipeNumber) {
                $this->_closePipe($pipeNumber);
            }

            $status   = proc_get_status($this->_process);
            $exitCode = proc_close($this->_process);

            // proc_close() can return -1 in some cases,
            // get the real exit code from the process status
            if ($exitCode < 0 && !$status['running']) {
                $exitCode = $status['exitcode'];
            }

            if ($exitCode > 0) {
                $this->_debug(
                    '=> subprocess returned an unexpected exit code: ' .
                    $exitCode
                );
            }

            $this->_process = null;
            $this->_pipes   = array();

            // close file handles before throwing an exception
            if (is_resource($this->_input)) {
                fclose($this->_input);
            }

            if (is_resource($this->_output)) {
                fclose($this->_output);
            }

            $this->_processHandler->throwException($exitCode);
        }

        $this->_closeAgentLaunchProcess();

        if ($this->_agentInfo !== null) {
            $parts = explode(':', $this->_agentInfo, 3);

            if (!empty($parts[1])) {
                $this->_debug('STOPPING GPG-AGENT DAEMON');

                $process = new Crypt_GPG_ProcessControl($parts[1]);

                // terminate agent daemon
                $process->terminate();

                while ($process->isRunning()) {
                    usleep(10000); // 10 ms
                    $process->terminate();
                }

                $this->_debug('GPG-AGENT DAEMON STOPPED');
            }

            $this->_agentInfo = null;
        }
    }

    /**
     * Closes a the internal GPG-AGENT subprocess
     *
     * Closes the internal GPG-AGENT subprocess. Sets the private class property
     * {@link Crypt_GPG_Engine::$_agentProcess} to null.
     *
     * @return void
     *
     * @see Crypt_GPG_Engine::_openSubprocess()
     * @see Crypt_GPG_Engine::$_agentProcess
     */
    private function _closeAgentLaunchProcess()
    {
        if (is_resource($this->_agentProcess)) {
            $this->_debug('CLOSING GPG-AGENT LAUNCH PROCESS');

            // close agent pipes
            foreach ($this->_agentPipes as $pipe) {
                fflush($pipe);
                fclose($pipe);
            }

            // close agent launching process
            proc_close($this->_agentProcess);

            $this->_agentProcess = null;
            $this->_agentPipes   = array();

            $this->_debug('GPG-AGENT LAUNCH PROCESS CLOSED');
        }
    }

    /**
     * Closes an opened pipe used to communicate with the GPG subprocess
     *
     * If the pipe is already closed, it is ignored. If the pipe is open, it
     * is flushed and then closed.
     *
     * @param int $pipeNumber The file descriptor number of the pipe to close
     *
     * @return void
     */
    private function _closePipe($pipeNumber)
    {
        $pipeNumber = intval($pipeNumber);
        if (array_key_exists($pipeNumber, $this->_openPipes)) {
            fflush($this->_openPipes[$pipeNumber]);
            fclose($this->_openPipes[$pipeNumber]);
            unset($this->_openPipes[$pipeNumber]);
        }
    }

    /**
     * Forces automatically started gpg-agent process to cleanup and exit
     * within a minute.
     *
     * This is needed in GnuPG 2.1 where agents are started
     * automatically by gpg process, not our code.
     *
     * @return void
     */
    private function _closeIdleAgents()
    {
        if ($this->_gpgconf) {
            // before 2.1.13 --homedir wasn't supported, use env variable
            $env = array('GNUPGHOME' => $this->_homedir);
            $cmd = $this->_gpgconf . ' --kill gpg-agent';

            if ($process = proc_open($cmd, array(), $pipes, null, $env)) {
                proc_close($process);
            }
        }
    }

    /**
     * Gets the name of the GPG binary for the current operating system
     *
     * This method is called if the '<kbd>binary</kbd>' option is <i>not</i>
     * specified when creating this driver.
     *
     * @return string the name of the GPG binary for the current operating
     *                system. If no suitable binary could be found, an empty
     *                string is returned.
     */
    private function _getBinary()
    {
        if ($binary = $this->_findBinary('gpg')) {
            return $binary;
        }

        return $this->_findBinary('gpg2');
    }

    /**
     * Gets the name of the GPG-AGENT binary for the current operating system
     *
     * @return string the name of the GPG-AGENT binary for the current operating
     *                system. If no suitable binary could be found, an empty
     *                string is returned.
     */
    private function _getAgent()
    {
        return $this->_findBinary('gpg-agent');
    }

    /**
     * Gets the name of the GPGCONF binary for the current operating system
     *
     * @return string the name of the GPGCONF binary for the current operating
     *                system. If no suitable binary could be found, an empty
     *                string is returned.
     */
    private function _getGPGConf()
    {
        return $this->_findBinary('gpgconf');
    }

    /**
     * Gets the location of a binary for the current operating system
     *
     * @param string $name Name of a binary program
     *
     * @return string The location of the binary for the current operating
     *                system. If no suitable binary could be found, an empty
     *                string is returned.
     */
    private function _findBinary($name)
    {
        $binary = '';

        if ($this->_isDarwin) {
            $locations = array(
                '/opt/local/bin/', // MacPorts
                '/usr/local/bin/', // Mac GPG
                '/sw/bin/',        // Fink
                '/usr/bin/'
            );
        } else {
            $locations = array(
                '/usr/bin/',
                '/usr/local/bin/',
                '/run/current-system/sw/bin/' // NixOS
            );
        }

        foreach ($locations as $location) {
            if (is_executable($location . $name)) {
                $binary = $location . $name;
                break;
            }
        }

        return $binary;
    }

    /**
     * Gets the location of the PinEntry script
     *
     * @return string|null the location of the PinEntry script.
     */
    private function _getPinEntry()
    {
        // Find PinEntry program depending on the way how the package is installed
        $ds    = DIRECTORY_SEPARATOR;
        $root  = __DIR__ . $ds . '..' . $ds . '..' . $ds;
        $paths = array(
            '@bin-dir@', // PEAR
             $root . 'scripts', // Git
             $root . 'bin', // Composer
        );

        foreach ($paths as $path) {
            if (file_exists($path . $ds . 'crypt-gpg-pinentry')) {
                return $path . $ds . 'crypt-gpg-pinentry';
            }
        }

        return null;
    }

    /**
     * Displays debug text if debugging is turned on
     *
     * Debugging text is prepended with a debug identifier and echoed to stdout.
     *
     * @param string $text the debugging text to display.
     *
     * @return void
     */
    private function _debug($text)
    {
        if ($this->_debug) {
            if (is_callable($this->_debug)) {
                call_user_func($this->_debug, $text);
            } elseif (php_sapi_name() === 'cli') {
                foreach (explode(PHP_EOL, $text) as $line) {
                    echo "Crypt_GPG DEBUG: ", $line, PHP_EOL;
                }
            } else {
                // running on a web server, format debug output nicely
                foreach (explode(PHP_EOL, $text) as $line) {
                    echo "Crypt_GPG DEBUG: <strong>", htmlspecialchars($line),
                        '</strong><br />', PHP_EOL;
                }
            }
        }
    }
}
PK}c�\��;>W>W%pear/crypt_gpg/Crypt/GPG/PinEntry.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * Contains a class implementing automatic pinentry for gpg-agent
 *
 * LICENSE:
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see
 * <http://www.gnu.org/licenses/>
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2013 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 */

/**
 * CLI user-interface and parser.
 */
require_once 'Console/CommandLine.php';

/**
 * A command-line dummy pinentry program for use with gpg-agent and Crypt_GPG
 *
 * This pinentry receives passphrases through en environment variable and
 * automatically enters the PIN in response to gpg-agent requests. No user-
 * interaction required.
 *
 * The pinentry can be run independently for testing and debugging with the
 * following syntax:
 *
 * <pre>
 * Usage:
 *   crypt-gpg-pinentry [options]
 *
 * Options:
 *   -l log, --log=log  Optional location to log pinentry activity.
 *   -v, --verbose      Sets verbosity level. Use multiples for more detail
 *                      (e.g. "-vv").
 *   -h, --help         show this help message and exit
 *   --version          show the program version and exit
 * </pre>
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2013 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 * @see       Crypt_GPG::getKeys()
 */
class Crypt_GPG_PinEntry
{
    /**
     * Verbosity level for showing no output.
     */
    const VERBOSITY_NONE = 0;

    /**
     * Verbosity level for showing error output.
     */
    const VERBOSITY_ERRORS = 1;

    /**
     * Verbosity level for showing all output, including Assuan protocol
     * messages.
     */
    const VERBOSITY_ALL = 2;

    /**
     * Length of buffer for reading lines from the Assuan server.
     *
     * PHP reads 8192 bytes. If this is set to less than 8192, PHP reads 8192
     * and buffers the rest so we might as well just read 8192.
     *
     * Using values other than 8192 also triggers PHP bugs.
     *
     * @see http://bugs.php.net/bug.php?id=35224
     */
    const CHUNK_SIZE = 8192;

    /**
     * File handle for the input stream
     *
     * @var resource|null
     */
    protected $stdin = null;

    /**
     * File handle for the output stream
     *
     * @var resource|null
     */
    protected $stdout = null;

    /**
     * File handle for the log file if a log file is used
     *
     * @var resource|null
     */
    protected $logFile = null;

    /**
     * Whether or not this pinentry is finished and is exiting
     *
     * @var bool
     */
    protected $moribund = false;

    /**
     * Verbosity level
     *
     * One of:
     * - {@link Crypt_GPG_PinEntry::VERBOSITY_NONE},
     * - {@link Crypt_GPG_PinEntry::VERBOSITY_ERRORS}, or
     * - {@link Crypt_GPG_PinEntry::VERBOSITY_ALL}
     *
     * @var int
     */
    protected $verbosity = self::VERBOSITY_NONE;

    /**
     * The command-line interface parser for this pinentry
     *
     * @var Console_CommandLine
     *
     * @see Crypt_GPG_PinEntry::getParser()
     */
    protected $parser = null;

    /**
     * PINs to be entered by this pinentry
     *
     * An indexed array of associative arrays in the form:
     * <code>
     * <?php
     *   array(
     *     array(
     *       'keyId'      => $keyId,
     *       'passphrase' => $passphrase
     *     ),
     *     ...
     *   );
     * ?>
     * </code>
     *
     * This array is parsed from the environment variable
     * <kbd>PINENTRY_USER_DATA</kbd>.
     *
     * @var array
     *
     * @see Crypt_GPG_PinEntry::initPinsFromENV()
     */
    protected $pins = array();

    /**
     * The PIN currently being requested by the Assuan server
     *
     * If set, this is an associative array in the form:
     * <code>
     * <?php
     *   array(
     *     'keyId'  => $shortKeyId,
     *     'userId' => $userIdString
     *   );
     * ?>
     * </code>
     *
     * @var array|null
     */
    protected $currentPin = null;

    /**
     * Runs this pinentry
     *
     * @return void
     */
    public function __invoke()
    {
        $this->parser = $this->getCommandLineParser();

        try {
            $result = $this->parser->parse();

            $this->setVerbosity($result->options['verbose']);
            $this->setLogFilename($result->options['log']);

            $this->connect();
            $this->initPinsFromENV();

            while (($line = fgets($this->stdin, self::CHUNK_SIZE)) !== false) {
                $this->parseCommand(mb_substr($line, 0, -1, '8bit'));
                if ($this->moribund) {
                    break;
                }
            }

            $this->disconnect();

        } catch (Console_CommandLine_Exception $e) {
            $this->log($e->getMessage() . PHP_EOL, self::VERBOSITY_ERRORS);
            exit(1);
        } catch (Exception $e) {
            $this->log($e->getMessage() . PHP_EOL, self::VERBOSITY_ERRORS);
            $this->log($e->getTraceAsString() . PHP_EOL, self::VERBOSITY_ERRORS);
            exit(1);
        }
    }

    /**
     * Sets the verbosity of logging for this pinentry
     *
     * Verbosity levels are:
     *
     * - {@link Crypt_GPG_PinEntry::VERBOSITY_NONE}   - no logging.
     * - {@link Crypt_GPG_PinEntry::VERBOSITY_ERRORS} - log errors only.
     * - {@link Crypt_GPG_PinEntry::VERBOSITY_ALL}    - log everything, including
     *                                                  the assuan protocol.
     *
     * @param int $verbosity The level of verbosity of this pinentry.
     *
     * @return Crypt_GPG_PinEntry the current object, for fluent interface.
     */
    public function setVerbosity($verbosity)
    {
        $this->verbosity = (int) $verbosity;
        return $this;
    }

    /**
     * Sets the log file location
     *
     * @param string $filename the new log filename to use. If an empty string
     *                         is used, file-based logging is disabled.
     *
     * @return Crypt_GPG_PinEntry the current object, for fluent interface.
     */
    public function setLogFilename($filename)
    {
        if (is_resource($this->logFile)) {
            fflush($this->logFile);
            fclose($this->logFile);
            $this->logFile = null;
        }

        if ($filename != '') {
            if (($this->logFile = fopen($filename, 'w')) === false) {
                $this->log(
                    'Unable to open log file "' . $filename . '" '
                    . 'for writing.' . PHP_EOL,
                    self::VERBOSITY_ERRORS
                );
                exit(1);
            } else {
                stream_set_write_buffer($this->logFile, 0);
            }
        }

        return $this;
    }

    /**
     * Gets the CLI user-interface definition for this pinentry
     *
     * Detects whether or not this package is PEAR-installed and appropriately
     * locates the XML UI definition.
     *
     * @return string|null The location of the CLI user-interface definition XML.
     */
    protected function getUIXML()
    {
        // Find PinEntry config depending on the way how the package is installed
        $ds    = DIRECTORY_SEPARATOR;
        $root  = __DIR__ . $ds . '..' . $ds . '..' . $ds;
        $paths = array(
            '@data-dir@' . $ds . '@package-name@' . $ds . 'data', // PEAR
            $root . 'data', // Git
            $root . 'data' . $ds . 'Crypt_GPG' . $ds . 'data', // Composer
        );

        foreach ($paths as $path) {
            if (file_exists($path . $ds . 'pinentry-cli.xml')) {
                return $path . $ds . 'pinentry-cli.xml';
            }
        }

        return null;
    }

    /**
     * Gets the CLI parser for this pinentry
     *
     * @return Console_CommandLine the CLI parser for this pinentry.
     */
    protected function getCommandLineParser()
    {
        return Console_CommandLine::fromXmlFile($this->getUIXML());
    }

    /**
     * Logs a message at the specified verbosity level
     *
     * If a log file is used, the message is written to the log. Otherwise,
     * the message is sent to STDERR.
     *
     * @param string $data  The message to log.
     * @param int    $level The verbosity level above which the message should
     *                      be logged.
     *
     * @return Crypt_GPG_PinEntry the current object, for fluent interface.
     */
    protected function log($data, $level)
    {
        if ($this->verbosity >= $level) {
            if (is_resource($this->logFile)) {
                fwrite($this->logFile, $data);
                fflush($this->logFile);
            } else {
                $this->parser->outputter->stderr($data);
            }
        }

        return $this;
    }

    /**
     * Connects this pinentry to the assuan server
     *
     * Opens I/O streams and sends initial handshake.
     *
     * @return Crypt_GPG_PinEntry the current object, for fluent interface.
     */
    protected function connect()
    {
        // Binary operations will not work on Windows with PHP < 5.2.6.
        $rb = (version_compare(PHP_VERSION, '5.2.6') < 0) ? 'r' : 'rb';
        $wb = (version_compare(PHP_VERSION, '5.2.6') < 0) ? 'w' : 'wb';

        $this->stdin  = fopen('php://stdin', $rb);
        $this->stdout = fopen('php://stdout', $wb);

        if (function_exists('stream_set_read_buffer')) {
            stream_set_read_buffer($this->stdin, 0);
        }
        stream_set_write_buffer($this->stdout, 0);

        // initial handshake
        $this->send($this->getOK('Crypt_GPG pinentry ready and waiting'));

        return $this;
    }

    /**
     * Parses an assuan command and performs the appropriate action
     *
     * Documentation of the assuan commands for pinentry is limited to
     * non-existent. Most of these commands were taken from the C source code
     * to gpg-agent and pinentry.
     *
     * Additional context was provided by using strace -f when calling the
     * gpg-agent.
     *
     * @param string $line the assuan command line to parse
     *
     * @return Crypt_GPG_PinEntry the current object, for fluent interface.
     */
    protected function parseCommand($line)
    {
        $this->log('<- ' . $line . PHP_EOL, self::VERBOSITY_ALL);

        $parts = explode(' ', $line, 2);

        $command = $parts[0];

        if (count($parts) === 2) {
            $data = $parts[1];
        } else {
            $data = null;
        }

        switch ($command) {
        case 'SETDESC':
            return $this->sendSetDescription($data);

        case 'MESSAGE':
            return $this->sendMessage();

        case 'CONFIRM':
            return $this->sendConfirm();

        case 'GETINFO':
            return $this->sendGetInfo($data);

        case 'GETPIN':
            return $this->sendGetPin();

        case 'RESET':
            return $this->sendReset();

        case 'BYE':
            return $this->sendBye();

        default:
            return $this->sendNotImplementedOK();
        }
    }

    /**
     * Initializes the PINs to be entered by this pinentry from the environment
     * variable PINENTRY_USER_DATA
     *
     * The PINs are parsed from a JSON-encoded string.
     *
     * @return Crypt_GPG_PinEntry the current object, for fluent interface.
     */
    protected function initPinsFromENV()
    {
        if (($userData = getenv('PINENTRY_USER_DATA')) !== false) {
            $pins = json_decode($userData, true);
            if ($pins === null) {
                $this->log(
                    '-- failed to parse user data' . PHP_EOL,
                    self::VERBOSITY_ERRORS
                );
            } else {
                $this->pins = $pins;
                $this->log(
                    '-- got user data [not showing passphrases]' . PHP_EOL,
                    self::VERBOSITY_ALL
                );
            }
        }

        return $this;
    }

    /**
     * Disconnects this pinentry from the Assuan server
     *
     * @return Crypt_GPG_PinEntry the current object, for fluent interface.
     */
    protected function disconnect()
    {
        $this->log('-- disconnecting' . PHP_EOL, self::VERBOSITY_ALL);

        fflush($this->stdout);
        fclose($this->stdout);
        fclose($this->stdin);

        $this->stdin  = null;
        $this->stdout = null;

        $this->log('-- disconnected' . PHP_EOL, self::VERBOSITY_ALL);

        if (is_resource($this->logFile)) {
            fflush($this->logFile);
            fclose($this->logFile);
            $this->logFile = null;
        }

        return $this;
    }

    /**
     * Sends an OK response for a not implemented feature
     *
     * @return Crypt_GPG_PinEntry the current object, for fluent interface.
     */
    protected function sendNotImplementedOK()
    {
        return $this->send($this->getOK());
    }

    /**
     * Parses the currently requested key identifier and user identifier from
     * the description passed to this pinentry
     *
     * @param string $text the raw description sent from gpg-agent.
     *
     * @return Crypt_GPG_PinEntry the current object, for fluent interface.
     */
    protected function sendSetDescription($text)
    {
        $text = rawurldecode($text);
        $matches = array();
        // TODO: handle user id with quotation marks
        $exp = '/\n"(.+)"\n.*\sID ([A-Z0-9]+),\n/mu';
        if (preg_match($exp, $text, $matches) === 1) {
            $userId = $matches[1];
            $keyId  = $matches[2];

            if ($this->currentPin === null || $this->currentPin['keyId'] !== $keyId) {
                $this->currentPin = array(
                    'userId' => $userId,
                    'keyId'  => $keyId
                );
                $this->log(
                    '-- looking for PIN for ' . $keyId . PHP_EOL,
                    self::VERBOSITY_ALL
                );
            }
        }

        return $this->send($this->getOK());
    }

    /**
     * Tells the assuan server to confirm the operation
     *
     * @return Crypt_GPG_PinEntry the current object, for fluent interface.
     */
    protected function sendConfirm()
    {
        return $this->send($this->getOK());
    }

    /**
     * Tells the assuan server that any requested pop-up messages were confirmed
     * by pressing the fake 'close' button
     *
     * @return Crypt_GPG_PinEntry the current object, for fluent interface.
     */
    protected function sendMessage()
    {
        return $this->sendButtonInfo('close');
    }

    /**
     * Sends information about pressed buttons to the assuan server
     *
     * This is used to fake a user-interface for this pinentry.
     *
     * @param string $text the button status to send.
     *
     * @return Crypt_GPG_PinEntry the current object, for fluent interface.
     */
    protected function sendButtonInfo($text)
    {
        return $this->send('BUTTON_INFO ' . $text . "\n");
    }

    /**
     * Sends the PIN value for the currently requested key
     *
     * @return Crypt_GPG_PinEntry the current object, for fluent interface.
     */
    protected function sendGetPin()
    {
        $foundPin = '';

        if (is_array($this->currentPin)) {
            $keyIdLength = mb_strlen($this->currentPin['keyId'], '8bit');

            // search for the pin
            foreach ($this->pins as $_keyId => $pin) {
                // Warning: GnuPG 2.1 asks 3 times for passphrase if it is invalid
                $keyId        = $this->currentPin['keyId'];
                $_keyIdLength = mb_strlen($_keyId, '8bit');

                // Get last X characters of key identifier to compare
                // Most GnuPG versions use 8 characters, but recent ones can use 16,
                // We support 8 for backward compatibility
                if ($keyIdLength < $_keyIdLength) {
                    $_keyId = mb_substr($_keyId, -$keyIdLength, $keyIdLength, '8bit');
                } else if ($keyIdLength > $_keyIdLength) {
                    $keyId = mb_substr($keyId, -$_keyIdLength, $_keyIdLength, '8bit');
                }

                if ($_keyId === $keyId) {
                    $foundPin = $pin;
                    break;
                }
            }
        }

        return $this
            ->send($this->getData($foundPin))
            ->send($this->getOK());
    }

    /**
     * Sends information about this pinentry
     *
     * @param string $data the information requested by the assuan server.
     *                     Currently only 'pid' is supported. Other requests
     *                     return no information.
     *
     * @return Crypt_GPG_PinEntry the current object, for fluent interface.
     */
    protected function sendGetInfo($data)
    {
        $parts   = explode(' ', $data, 2);
        $command = reset($parts);

        switch ($command) {
        case 'pid':
            return $this->sendGetInfoPID();
        default:
            return $this->send($this->getOK());
        }
    }

    /**
     * Sends the PID of this pinentry to the assuan server
     *
     * @return Crypt_GPG_PinEntry the current object, for fluent interface.
     */
    protected function sendGetInfoPID()
    {
        return $this
            ->send($this->getData(getmypid()))
            ->send($this->getOK());
    }

    /**
     * Flags this pinentry for disconnection and sends an OK response
     *
     * @return Crypt_GPG_PinEntry the current object, for fluent interface.
     */
    protected function sendBye()
    {
        $return = $this->send($this->getOK('closing connection'));
        $this->moribund = true;
        return $return;
    }

    /**
     * Resets this pinentry and sends an OK response
     *
     * @return Crypt_GPG_PinEntry the current object, for fluent interface.
     */
    protected function sendReset()
    {
        $this->currentPin = null;
        return $this->send($this->getOK());
    }

    /**
     * Gets an OK response to send to the assuan server
     *
     * @param string $data an optional message to include with the OK response.
     *
     * @return string the OK response.
     */
    protected function getOK($data = null)
    {
        $return = 'OK';

        if ($data) {
            $return .= ' ' . $data;
        }

        return $return . "\n";
    }

    /**
     * Gets data ready to send to the assuan server
     *
     * Data is appropriately escaped and long lines are wrapped.
     *
     * @param string $data the data to send to the assuan server.
     *
     * @return string the properly escaped, formatted data.
     *
     * @see http://www.gnupg.org/documentation/manuals/assuan/Server-responses.html
     */
    protected function getData($data)
    {
        // Escape data. Only %, \n and \r need to be escaped but other
        // values are allowed to be escaped. See
        // http://www.gnupg.org/documentation/manuals/assuan/Server-responses.html
        $data = rawurlencode($data);
        $data = $this->getWordWrappedData($data, 'D');
        return $data;
    }

    /**
     * Gets a comment ready to send to the assuan server
     *
     * @param string $data the comment to send to the assuan server.
     *
     * @return string the properly formatted comment.
     *
     * @see http://www.gnupg.org/documentation/manuals/assuan/Server-responses.html
     */
    protected function getComment($data)
    {
        return $this->getWordWrappedData($data, '#');
    }

    /**
     * Wraps strings at 1,000 bytes without splitting UTF-8 multibyte
     * characters
     *
     * Each line is prepended with the specified line prefix. Wrapped lines
     * are automatically appended with \ characters.
     *
     * Protocol strings are UTF-8 but maximum line length is 1,000 bytes.
     * <kbd>mb_strcut()</kbd> is used so we can limit line length by bytes
     * and not split characters across multiple lines.
     *
     * @param string $data   the data to wrap.
     * @param string $prefix a single character to use as the line prefix. For
     *                       example, 'D' or '#'.
     *
     * @return string the word-wrapped, prefixed string.
     *
     * @see http://www.gnupg.org/documentation/manuals/assuan/Server-responses.html
     */
    protected function getWordWrappedData($data, $prefix)
    {
        $lines = array();

        do {
            if (mb_strlen($data, '8bit') > 997) {
                $line = $prefix . ' ' . mb_strcut($data, 0, 996, 'utf-8') . "\\\n";
                $lines[] = $line;
                $lineLength = mb_strlen($line, '8bit') - 1;
                $dataLength = mb_strlen($data, '8bit');
                $data = mb_substr(
                    $data,
                    $lineLength,
                    $dataLength - $lineLength,
                    '8bit'
                );
            } else {
                $lines[] = $prefix . ' ' . $data . "\n";
                $data = '';
            }
        } while ($data != '');

        return implode('', $lines);
    }

    /**
     * Sends raw data to the assuan server
     *
     * @param string $data the data to send.
     *
     * @return Crypt_GPG_PinEntry the current object, for fluent interface.
     */
    protected function send($data)
    {
        $this->log('-> ' . $data, self::VERBOSITY_ALL);
        fwrite($this->stdout, $data);
        fflush($this->stdout);
        return $this;
    }
}
PK}c�\w���,�,&pear/crypt_gpg/Crypt/GPG/Signature.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * A class representing GPG signatures
 *
 * This file contains a data class representing a GPG signature.
 *
 * LICENSE:
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see
 * <http://www.gnu.org/licenses/>
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Nathan Fredrickson <nathan@silverorange.com>
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2005-2013 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 */

/**
 * User id class definition
 */
require_once 'Crypt/GPG/UserId.php';

/**
 * A class for GPG signature information
 *
 * This class is used to store the results of the Crypt_GPG::verify() method.
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Nathan Fredrickson <nathan@silverorange.com>
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2005-2013 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 * @see       Crypt_GPG::verify()
 */
class Crypt_GPG_Signature
{
    /**
     * A base64-encoded string containing a unique id for this signature if
     * this signature has been verified as ok
     *
     * This id is used to prevent replay attacks and is not present for all
     * types of signatures.
     *
     * @var string
     */
    private $_id = '';

    /**
     * The fingerprint of the key used to create the signature
     *
     * @var string
     */
    private $_keyFingerprint = '';

    /**
     * The id of the key used to create the signature
     *
     * @var string
     */
    private $_keyId = '';

    /**
     * The creation date of this signature
     *
     * This is a Unix timestamp.
     *
     * @var int
     */
    private $_creationDate = 0;

    /**
     * The expiration date of the signature
     *
     * This is a Unix timestamp. If this signature does not expire, this will
     * be zero.
     *
     * @var int
     */
    private $_expirationDate = 0;

    /**
     * The user id associated with this signature
     *
     * @var Crypt_GPG_UserId
     */
    private $_userId = null;

    /**
     * Whether or not this signature is valid
     *
     * @var bool
     */
    private $_isValid = false;

    /**
     * Creates a new signature
     *
     * Signatures can be initialized from an array of named values. Available
     * names are:
     *
     * - <kbd>string  id</kbd>          - the unique id of this signature.
     * - <kbd>string  fingerprint</kbd> - the fingerprint of the key used to
     *                                    create the signature. The fingerprint
     *                                    should not contain formatting
     *                                    characters.
     * - <kbd>string  keyId</kbd>       - the id of the key used to create the
     *                                    the signature.
     * - <kbd>int     creation</kbd>    - the date the signature was created.
     *                                    This is a UNIX timestamp.
     * - <kbd>int     expiration</kbd>  - the date the signature expired. This
     *                                    is a UNIX timestamp. If the signature
     *                                    does not expire, use 0.
     * - <kbd>bool    valid</kbd>       - whether or not the signature is valid.
     * - <kbd>string  userId</kbd>      - the user id associated with the
     *                                    signature. This may also be a
     *                                    {@link Crypt_GPG_UserId} object.
     *
     * @param Crypt_GPG_Signature|array|null $signature Either an existing signature object,
     *                                                  which is copied; or an array
     *                                                  of initial values.
     */
    public function __construct($signature = null)
    {
        // copy from object
        if ($signature instanceof Crypt_GPG_Signature) {
            $this->_id             = $signature->_id;
            $this->_keyFingerprint = $signature->_keyFingerprint;
            $this->_keyId          = $signature->_keyId;
            $this->_creationDate   = $signature->_creationDate;
            $this->_expirationDate = $signature->_expirationDate;
            $this->_isValid        = $signature->_isValid;

            if ($signature->_userId instanceof Crypt_GPG_UserId) {
                $this->_userId = clone $signature->_userId;
            }
        }

        // initialize from array
        if (is_array($signature)) {
            if (array_key_exists('id', $signature)) {
                $this->setId($signature['id']);
            }

            if (array_key_exists('fingerprint', $signature)) {
                $this->setKeyFingerprint($signature['fingerprint']);
            }

            if (array_key_exists('keyId', $signature)) {
                $this->setKeyId($signature['keyId']);
            }

            if (array_key_exists('creation', $signature)) {
                $this->setCreationDate($signature['creation']);
            }

            if (array_key_exists('expiration', $signature)) {
                $this->setExpirationDate($signature['expiration']);
            }

            if (array_key_exists('valid', $signature)) {
                $this->setValid($signature['valid']);
            }

            if (array_key_exists('userId', $signature)) {
                $userId = new Crypt_GPG_UserId($signature['userId']);
                $this->setUserId($userId);
            }
        }
    }

    /**
     * Gets the id of this signature
     *
     * @return string A base64-encoded string containing a unique id for this
     *                signature. This id is used to prevent replay attacks and
     *                is not present for all types of signatures.
     */
    public function getId()
    {
        return $this->_id;
    }

    /**
     * Gets the fingerprint of the key used to create this signature
     *
     * @return string The fingerprint of the key used to create this signature.
     */
    public function getKeyFingerprint()
    {
        return $this->_keyFingerprint;
    }

    /**
     * Gets the id of the key used to create this signature
     *
     * Whereas the fingerprint of the signing key may not always be available
     * (for example if the signature is bad), the id should always be
     * available.
     *
     * @return string The id of the key used to create this signature.
     */
    public function getKeyId()
    {
        return $this->_keyId;
    }

    /**
     * Gets the creation date of this signature
     *
     * @return int The creation date of this signature. This is a Unix
     *             timestamp.
     */
    public function getCreationDate()
    {
        return $this->_creationDate;
    }

    /**
     * Gets the expiration date of the signature
     *
     * @return int The expiration date of this signature. This is a Unix
     *             timestamp. If this signature does not expire, this will
     *             be zero.
     */
    public function getExpirationDate()
    {
        return $this->_expirationDate;
    }

    /**
     * Gets the user id associated with this signature
     *
     * @return Crypt_GPG_UserId|null The user id associated with this signature.
     */
    public function getUserId()
    {
        return $this->_userId;
    }

    /**
     * Gets whether or no this signature is valid
     *
     * @return bool True if this signature is valid and false if it is not.
     */
    public function isValid()
    {
        return $this->_isValid;
    }

    /**
     * Sets the id of this signature
     *
     * @param string $id a base64-encoded string containing a unique id for
     *                   this signature.
     *
     * @return Crypt_GPG_Signature the current object, for fluent interface.
     *
     * @see Crypt_GPG_Signature::getId()
     */
    public function setId($id)
    {
        $this->_id = strval($id);
        return $this;
    }

    /**
     * Sets the key fingerprint of this signature
     *
     * @param string $fingerprint the key fingerprint of this signature. This
     *                            is the fingerprint of the primary key used to
     *                            create this signature.
     *
     * @return Crypt_GPG_Signature the current object, for fluent interface.
     */
    public function setKeyFingerprint($fingerprint)
    {
        $this->_keyFingerprint = strval($fingerprint);
        return $this;
    }

    /**
     * Sets the key id of this signature
     *
     * @param string $id the key id of this signature. This is the id of the
     *                   primary key used to create this signature.
     *
     * @return Crypt_GPG_Signature the current object, for fluent interface.
     */
    public function setKeyId($id)
    {
        $this->_keyId = strval($id);
        return $this;
    }

    /**
     * Sets the creation date of this signature
     *
     * @param int $creationDate The creation date of this signature. This
     *                          is a Unix timestamp.
     *
     * @return Crypt_GPG_Signature the current object, for fluent interface.
     */
    public function setCreationDate($creationDate)
    {
        $this->_creationDate = intval($creationDate);
        return $this;
    }

    /**
     * Sets the expiration date of this signature
     *
     * @param int $expirationDate the expiration date of this signature.
     *                            This is a Unix timestamp. Specify zero if
     *                            this signature does not expire.
     *
     * @return Crypt_GPG_Signature the current object, for fluent interface.
     */
    public function setExpirationDate($expirationDate)
    {
        $this->_expirationDate = intval($expirationDate);
        return $this;
    }

    /**
     * Sets the user id associated with this signature
     *
     * @param Crypt_GPG_UserId $userId the user id associated with this
     *                                 signature.
     *
     * @return Crypt_GPG_Signature the current object, for fluent interface.
     */
    public function setUserId(Crypt_GPG_UserId $userId)
    {
        $this->_userId = $userId;
        return $this;
    }

    /**
     * Sets whether or not this signature is valid
     *
     * @param bool $isValid True if this signature is valid and false if it
     *                      is not.
     *
     * @return Crypt_GPG_Signature the current object, for fluent interface.
     */
    public function setValid($isValid)
    {
        $this->_isValid = ($isValid) ? true : false;
        return $this;
    }
}
PK}c�\C�7��4�4'pear/crypt_gpg/Crypt/GPG/Exceptions.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * Various exception handling classes for Crypt_GPG
 *
 * Crypt_GPG provides an object oriented interface to GNU Privacy
 * Guard (GPG). It requires the GPG executable to be on the system.
 *
 * This file contains various exception classes used by the Crypt_GPG package.
 *
 * LICENSE:
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see
 * <http://www.gnu.org/licenses/>
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Nathan Fredrickson <nathan@silverorange.com>
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2005-2011 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 */

/**
 * PEAR Exception handler and base class
 */
require_once 'PEAR/Exception.php';

/**
 * An exception thrown by the Crypt_GPG package
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2005 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 */
class Crypt_GPG_Exception extends PEAR_Exception
{
}

/**
 * An exception thrown when a file is used in ways it cannot be used
 *
 * For example, if an output file is specified and the file is not writeable, or
 * if an input file is specified and the file is not readable, this exception
 * is thrown.
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2007-2008 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 */
class Crypt_GPG_FileException extends Crypt_GPG_Exception
{
    /**
     * The name of the file that caused this exception
     *
     * @var string
     */
    private $_filename = '';

    /**
     * Creates a new Crypt_GPG_FileException
     *
     * @param string $message  An error message.
     * @param int    $code     A user defined error code.
     * @param string $filename The name of the file that caused this exception.
     */
    public function __construct($message, $code = 0, $filename = '')
    {
        $this->_filename = $filename;
        parent::__construct($message, $code);
    }

    /**
     * Returns the filename of the file that caused this exception
     *
     * @return string the filename of the file that caused this exception.
     *
     * @see Crypt_GPG_FileException::$_filename
     */
    public function getFilename()
    {
        return $this->_filename;
    }
}

/**
 * An exception thrown when the GPG subprocess cannot be opened
 *
 * This exception is thrown when the {@link Crypt_GPG_Engine} tries to open a
 * new subprocess and fails.
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2005 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 */
class Crypt_GPG_OpenSubprocessException extends Crypt_GPG_Exception
{
    /**
     * The command used to try to open the subprocess
     *
     * @var string
     */
    private $_command = '';

    /**
     * Creates a new Crypt_GPG_OpenSubprocessException
     *
     * @param string $message An error message.
     * @param int    $code    A user defined error code.
     * @param string $command The command that was called to open the
     *                        new subprocess.
     *
     * @see Crypt_GPG::_openSubprocess()
     */
    public function __construct($message, $code = 0, $command = '')
    {
        $this->_command = $command;
        parent::__construct($message, $code);
    }

    /**
     * Returns the contents of the internal _command property
     *
     * @return string the command used to open the subprocess.
     *
     * @see Crypt_GPG_OpenSubprocessException::$_command
     */
    public function getCommand()
    {
        return $this->_command;
    }
}

/**
 * An exception thrown when an invalid GPG operation is attempted
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2008 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 */
class Crypt_GPG_InvalidOperationException extends Crypt_GPG_Exception
{
    /**
     * The attempted operation
     *
     * @var string
     */
    private $_operation = '';

    /**
     * Creates a new Crypt_GPG_OpenSubprocessException
     *
     * @param string $message   An error message.
     * @param int    $code      A user defined error code.
     * @param string $operation The operation.
     */
    public function __construct($message, $code = 0, $operation = '')
    {
        $this->_operation = $operation;
        parent::__construct($message, $code);
    }

    /**
     * Returns the contents of the internal _operation property
     *
     * @return string the attempted operation.
     *
     * @see Crypt_GPG_InvalidOperationException::$_operation
     */
    public function getOperation()
    {
        return $this->_operation;
    }
}

/**
 * An exception thrown when Crypt_GPG fails to find the key for various
 * operations
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2005 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 */
class Crypt_GPG_KeyNotFoundException extends Crypt_GPG_Exception
{
    /**
     * The key identifier that was searched for
     *
     * @var string
     */
    private $_keyId = '';

    /**
     * Creates a new Crypt_GPG_KeyNotFoundException
     *
     * @param string $message An error message.
     * @param int    $code    A user defined error code.
     * @param string $keyId   The key identifier of the key.
     */
    public function __construct($message, $code = 0, $keyId= '')
    {
        $this->_keyId = $keyId;
        parent::__construct($message, $code);
    }

    /**
     * Gets the key identifier of the key that was not found
     *
     * @return string the key identifier of the key that was not found.
     */
    public function getKeyId()
    {
        return $this->_keyId;
    }
}

/**
 * An exception thrown when Crypt_GPG cannot find valid data for various
 * operations
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2006 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 */
class Crypt_GPG_NoDataException extends Crypt_GPG_Exception
{
}

/**
 * An exception thrown when a required passphrase is incorrect or missing
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2006-2008 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 */
class Crypt_GPG_BadPassphraseException extends Crypt_GPG_Exception
{
    /**
     * Keys for which the passhprase is missing
     *
     * This contains primary user ids indexed by sub-key id.
     *
     * @var array
     */
    private $_missingPassphrases = array();

    /**
     * Keys for which the passhprase is incorrect
     *
     * This contains primary user ids indexed by sub-key id.
     *
     * @var array
     */
    private $_badPassphrases = array();

    /**
     * Creates a new Crypt_GPG_BadPassphraseException
     *
     * @param string $message            An error message.
     * @param int    $code               A user defined error code.
     * @param array  $badPassphrases     An array containing user ids of keys
     *                                   for which the passphrase is incorrect.
     * @param array  $missingPassphrases An array containing user ids of keys
     *                                   for which the passphrase is missing.
     */
    public function __construct($message, $code = 0,
        array $badPassphrases = array(), array $missingPassphrases = array()
    ) {
        $this->_badPassphrases     = (array) $badPassphrases;
        $this->_missingPassphrases = (array) $missingPassphrases;

        parent::__construct($message, $code);
    }

    /**
     * Gets keys for which the passhprase is incorrect
     *
     * @return array an array of keys for which the passphrase is incorrect.
     *               The array contains primary user ids indexed by the sub-key
     *               id.
     */
    public function getBadPassphrases()
    {
        return $this->_badPassphrases;
    }

    /**
     * Gets keys for which the passhprase is missing 
     *
     * @return array an array of keys for which the passphrase is missing.
     *               The array contains primary user ids indexed by the sub-key
     *               id.
     */
    public function getMissingPassphrases()
    {
        return $this->_missingPassphrases;
    }
}

/**
 * An exception thrown when an attempt is made to delete public key that has an
 * associated private key on the keyring
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2008 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 */
class Crypt_GPG_DeletePrivateKeyException extends Crypt_GPG_Exception
{
    /**
     * The key identifier the deletion attempt was made upon
     *
     * @var string
     */
    private $_keyId = '';

    /**
     * Creates a new Crypt_GPG_DeletePrivateKeyException
     *
     * @param string $message An error message.
     * @param int    $code    A user defined error code.
     * @param string $keyId   The key identifier of the public key that was
     *                        attempted to delete.
     *
     * @see Crypt_GPG::deletePublicKey()
     */
    public function __construct($message, $code = 0, $keyId = '')
    {
        $this->_keyId = $keyId;
        parent::__construct($message, $code);
    }

    /**
     * Gets the key identifier of the key that was not found
     *
     * @return string the key identifier of the key that was not found.
     */
    public function getKeyId()
    {
        return $this->_keyId;
    }
}

/**
 * An exception thrown when an attempt is made to generate a key and the
 * attempt fails
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2011 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 */
class Crypt_GPG_KeyNotCreatedException extends Crypt_GPG_Exception
{
}

/**
 * An exception thrown when an attempt is made to generate a key and the
 * key parameters set on the key generator are invalid
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2011 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 */
class Crypt_GPG_InvalidKeyParamsException extends Crypt_GPG_Exception
{
    /**
     * The key algorithm
     *
     * @var int
     */
    private $_algorithm = 0;

    /**
     * The key size
     *
     * @var int
     */
    private $_size = 0;

    /**
     * The key usage
     *
     * @var int
     */
    private $_usage = 0;

    /**
     * Creates a new Crypt_GPG_InvalidKeyParamsException
     *
     * @param string $message   An error message.
     * @param int    $code      A user defined error code.
     * @param int    $algorithm The key algorithm.
     * @param int    $size      The key size.
     * @param int    $usage     The key usage.
     */
    public function __construct(
        $message,
        $code = 0,
        $algorithm = 0,
        $size = 0,
        $usage = 0
    ) {
        parent::__construct($message, $code);

        $this->_algorithm = $algorithm;
        $this->_size      = $size;
        $this->_usage     = $usage;
    }

    /**
     * Gets the key algorithm
     *
     * @return int The key algorithm.
     */
    public function getAlgorithm()
    {
        return $this->_algorithm;
    }

    /**
     * Gets the key size
     *
     * @return int The key size.
     */
    public function getSize()
    {
        return $this->_size;
    }

    /**
     * Gets the key usage
     *
     * @return int The key usage.
     */
    public function getUsage()
    {
        return $this->_usage;
    }
}
PK}c�\J�%�H�H#pear/crypt_gpg/Crypt/GPG/SubKey.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * Contains a class representing GPG sub-keys and constants for GPG algorithms
 *
 * LICENSE:
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see
 * <http://www.gnu.org/licenses/>
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @author    Nathan Fredrickson <nathan@silverorange.com>
 * @copyright 2005-2010 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 */

/**
 * A class for GPG sub-key information
 *
 * This class is used to store the results of the {@link Crypt_GPG::getKeys()}
 * method. Sub-key objects are members of a {@link Crypt_GPG_Key} object.
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @author    Nathan Fredrickson <nathan@silverorange.com>
 * @copyright 2005-2010 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 * @see       Crypt_GPG::getKeys()
 * @see       Crypt_GPG_Key::getSubKeys()
 */
class Crypt_GPG_SubKey
{
    /**
     * RSA encryption algorithm.
     */
    const ALGORITHM_RSA = 1;

    /**
     * Elgamal encryption algorithm (encryption only).
     */
    const ALGORITHM_ELGAMAL_ENC = 16;

    /**
     * DSA encryption algorithm (sometimes called DH, sign only).
     */
    const ALGORITHM_DSA = 17;

    /**
     * Elgamal encryption algorithm (signage and encryption - should not be
     * used).
     */
    const ALGORITHM_ELGAMAL_ENC_SGN = 20;

    /**
     * Key can be used to encrypt
     */
    const USAGE_ENCRYPT = 1;

    /**
     * Key can be used to sign
     */
    const USAGE_SIGN = 2;

    /**
     * Key can be used to certify other keys
     */
    const USAGE_CERTIFY = 4;

    /**
     * Key can be used for authentication
     */
    const USAGE_AUTHENTICATION = 8;

    /**
     * The id of this sub-key
     *
     * @var string
     */
    private $_id = '';

    /**
     * The algorithm used to create this sub-key
     *
     * The value is one of the Crypt_GPG_SubKey::ALGORITHM_* constants.
     *
     * @var int
     */
    private $_algorithm = 0;

    /**
     * The fingerprint of this sub-key
     *
     * @var string
     */
    private $_fingerprint = '';

    /**
     * Length of this sub-key in bits
     *
     * @var int
     */
    private $_length = 0;

    /**
     * Date this sub-key was created
     *
     * This is a Unix timestamp.
     *
     * @var DateTime|null
     */
    private $_creationDate = null;

    /**
     * Date this sub-key expires
     *
     * This is a Unix timestamp. If this sub-key does not expire, this will be
     * null.
     *
     * @var DateTime|null
     */
    private $_expirationDate = null;

    /**
     * Contains usage flags of this sub-key
     *
     * @var int
     */
    private $_usage = 0;

    /**
     * Whether or not the private key for this sub-key exists in the keyring
     *
     * @var bool
     */
    private $_hasPrivate = false;

    /**
     * Whether or not this sub-key is revoked
     *
     * @var bool
     */
    private $_isRevoked = false;

    /**
     * Creates a new sub-key object
     *
     * Sub-keys can be initialized from an array of named values. Available
     * names are:
     *
     * - <kbd>string id</kbd>          - the key id of the sub-key.
     * - <kbd>int    algorithm</kbd>   - the encryption algorithm of the
     *                                   sub-key.
     * - <kbd>string fingerprint</kbd> - the fingerprint of the sub-key. The
     *                                   fingerprint should not contain
     *                                   formatting characters.
     * - <kbd>int    length</kbd>      - the length of the sub-key in bits.
     * - <kbd>int    creation</kbd>    - the date the sub-key was created.
     *                                   This is a UNIX timestamp.
     * - <kbd>int    expiration</kbd>  - the date the sub-key expires. This
     *                                   is a UNIX timestamp. If the sub-key
     *                                   does not expire, use 0.
     * - <kbd>bool   canSign</kbd>     - whether or not the sub-key can be
     *                                   used to sign data.
     * - <kbd>bool   canEncrypt</kbd>  - whether or not the sub-key can be
     *                                   used to encrypt data.
     * - <kbd>int    usage</kbd>       - the sub-key usage flags
     * - <kbd>bool   hasPrivate</kbd>  - whether or not the private key for
     *                                   the sub-key exists in the keyring.
     * - <kbd>bool   isRevoked</kbd>   - whether or not this sub-key is
     *                                   revoked.
     *
     * @param Crypt_GPG_SubKey|string|array|null $key Either an existing sub-key object,
     *                                                which is copied; a sub-key string,
     *                                                which is parsed; or an array
     *                                                of initial values.
     */
    public function __construct($key = null)
    {
        // parse from string
        if (is_string($key)) {
            $key = self::parse($key);
        }

        // copy from object
        if ($key instanceof Crypt_GPG_SubKey) {
            $this->_id             = $key->_id;
            $this->_algorithm      = $key->_algorithm;
            $this->_fingerprint    = $key->_fingerprint;
            $this->_length         = $key->_length;
            $this->_creationDate   = $key->_creationDate;
            $this->_expirationDate = $key->_expirationDate;
            $this->_usage          = $key->_usage;
            $this->_hasPrivate     = $key->_hasPrivate;
            $this->_isRevoked      = $key->_isRevoked;
        }

        // initialize from array
        if (is_array($key)) {
            if (array_key_exists('id', $key)) {
                $this->setId($key['id']);
            }

            if (array_key_exists('algorithm', $key)) {
                $this->setAlgorithm($key['algorithm']);
            }

            if (array_key_exists('fingerprint', $key)) {
                $this->setFingerprint($key['fingerprint']);
            }

            if (array_key_exists('length', $key)) {
                $this->setLength($key['length']);
            }

            if (array_key_exists('creation', $key)) {
                $this->setCreationDate($key['creation']);
            }

            if (array_key_exists('expiration', $key)) {
                $this->setExpirationDate($key['expiration']);
            }

            if (array_key_exists('usage', $key)) {
                $this->setUsage($key['usage']);
            }

            if (array_key_exists('canSign', $key)) {
                $this->setCanSign($key['canSign']);
            }

            if (array_key_exists('canEncrypt', $key)) {
                $this->setCanEncrypt($key['canEncrypt']);
            }

            if (array_key_exists('hasPrivate', $key)) {
                $this->setHasPrivate($key['hasPrivate']);
            }

            if (array_key_exists('isRevoked', $key)) {
                $this->setRevoked($key['isRevoked']);
            }
        }
    }

    /**
     * Gets the id of this sub-key
     *
     * @return string The id of this sub-key.
     */
    public function getId()
    {
        return $this->_id;
    }

    /**
     * Gets the algorithm used by this sub-key
     *
     * The algorithm should be one of the Crypt_GPG_SubKey::ALGORITHM_*
     * constants.
     *
     * @return int The algorithm used by this sub-key.
     */
    public function getAlgorithm()
    {
        return $this->_algorithm;
    }

    /**
     * Gets the creation date of this sub-key
     *
     * This is a Unix timestamp. Warning: On 32-bit systems it returns
     * invalid value for dates after 2038-01-19. Use getCreationDateTime().
     *
     * @return int The creation date of this sub-key.
     */
    public function getCreationDate()
    {
        return $this->_creationDate ? (int) $this->_creationDate->format('U') : 0;
    }

    /**
     * Gets the creation date-time (UTC) of this sub-key
     *
     * @return DateTime|null The creation date of this sub-key.
     */
    public function getCreationDateTime()
    {
        return $this->_creationDate ? $this->_creationDate : null;
    }

    /**
     * Gets the date this sub-key expires
     *
     * This is a Unix timestamp. If this sub-key does not expire, this will be
     * zero. Warning: On 32-bit systems it returns invalid value for dates
     * after 2038-01-19. Use getExpirationDateTime().
     *
     * @return int The date this sub-key expires.
     */
    public function getExpirationDate()
    {
        return $this->_expirationDate ? (int) $this->_expirationDate->format('U') : 0;
    }

    /**
     * Gets the date-time (UTC) this sub-key expires
     *
     * @return DateTime|null The date this sub-key expires.
     */
    public function getExpirationDateTime()
    {
        return $this->_expirationDate ? $this->_expirationDate : null;
    }

    /**
     * Gets the fingerprint of this sub-key
     *
     * @return string The fingerprint of this sub-key.
     */
    public function getFingerprint()
    {
        return $this->_fingerprint;
    }

    /**
     * Gets the length of this sub-key in bits
     *
     * @return int The length of this sub-key in bits.
     */
    public function getLength()
    {
        return $this->_length;
    }

    /**
     * Gets whether or not this sub-key can sign data
     *
     * @return bool True if this sub-key can sign data and false if this
     *              sub-key can not sign data.
     */
    public function canSign()
    {
        return ($this->_usage & self::USAGE_SIGN) != 0;
    }

    /**
     * Gets whether or not this sub-key can encrypt data
     *
     * @return bool True if this sub-key can encrypt data and false if this
     *              sub-key can not encrypt data.
     */
    public function canEncrypt()
    {
        return ($this->_usage & self::USAGE_ENCRYPT) != 0;
    }

    /**
     * Gets usage flags of this sub-key
     *
     * @return int Sum of usage flags
     */
    public function usage()
    {
        return $this->_usage;
    }

    /**
     * Gets whether or not the private key for this sub-key exists in the
     * keyring
     *
     * @return bool True the private key for this sub-key exists in the
     *              keyring and false if it does not.
     */
    public function hasPrivate()
    {
        return $this->_hasPrivate;
    }

    /**
     * Gets whether or not this sub-key is revoked
     *
     * @return bool True if this sub-key is revoked and false if it is not.
     */
    public function isRevoked()
    {
        return $this->_isRevoked;
    }

    /**
     * Sets the creation date of this sub-key
     *
     * The creation date is a Unix timestamp or DateTime object.
     *
     * @param int|DateTime $creationDate The creation date of this sub-key.
     *
     * @return Crypt_GPG_SubKey The current object, for fluent interface.
     */
    public function setCreationDate($creationDate)
    {
        if (empty($creationDate)) {
            $this->_creationDate = null;
            return $this;
        }

        if ($creationDate instanceof DateTime) {
            $this->_creationDate = $creationDate;
        } else {
            $tz = new DateTimeZone('UTC');
            $this->_creationDate = new DateTime("@$creationDate", $tz);
        }

        return $this;
    }

    /**
     * Sets the expiration date of this sub-key
     *
     * The expiration date is a Unix timestamp. Specify zero if this sub-key
     * does not expire.
     *
     * @param int|DateTime $expirationDate The expiration date of this sub-key.
     *
     * @return Crypt_GPG_SubKey The current object, for fluent interface.
     */
    public function setExpirationDate($expirationDate)
    {
        if (empty($expirationDate)) {
            $this->_expirationDate = null;
            return $this;
        }

        if ($expirationDate instanceof DateTime) {
            $this->_expirationDate = $expirationDate;
        } else {
            $tz = new DateTimeZone('UTC');
            $this->_expirationDate = new DateTime("@$expirationDate", $tz);
        }

        return $this;
    }

    /**
     * Sets the id of this sub-key
     *
     * @param string $id The id of this sub-key.
     *
     * @return Crypt_GPG_SubKey The current object, for fluent interface.
     */
    public function setId($id)
    {
        $this->_id = strval($id);
        return $this;
    }

    /**
     * Sets the algorithm used by this sub-key
     *
     * @param int $algorithm The algorithm used by this sub-key.
     *
     * @return Crypt_GPG_SubKey The current object, for fluent interface.
     */
    public function setAlgorithm($algorithm)
    {
        $this->_algorithm = intval($algorithm);
        return $this;
    }

    /**
     * Sets the fingerprint of this sub-key
     *
     * @param string $fingerprint The fingerprint of this sub-key.
     *
     * @return Crypt_GPG_SubKey The current object, for fluent interface.
     */
    public function setFingerprint($fingerprint)
    {
        $this->_fingerprint = strval($fingerprint);
        return $this;
    }

    /**
     * Sets the length of this sub-key in bits
     *
     * @param int $length The length of this sub-key in bits.
     *
     * @return Crypt_GPG_SubKey The current object, for fluent interface.
     */
    public function setLength($length)
    {
        $this->_length = intval($length);
        return $this;
    }

    /**
     * Sets whether or not this sub-key can sign data
     *
     * @param bool $canSign True if this sub-key can sign data and false if
     *                      it can not.
     *
     * @return Crypt_GPG_SubKey The current object, for fluent interface.
     */
    public function setCanSign($canSign)
    {
        if ($canSign) {
            $this->_usage |= self::USAGE_SIGN;
        } else {
            $this->_usage &= ~self::USAGE_SIGN;
        }

        return $this;
    }

    /**
     * Sets whether or not this sub-key can encrypt data
     *
     * @param bool $canEncrypt True if this sub-key can encrypt data and
     *                         false if it can not.
     *
     * @return Crypt_GPG_SubKey The current object, for fluent interface.
     */
    public function setCanEncrypt($canEncrypt)
    {
        if ($canEncrypt) {
            $this->_usage |= self::USAGE_ENCRYPT;
        } else {
            $this->_usage &= ~self::USAGE_ENCRYPT;
        }

        return $this;
    }

    /**
     * Sets usage flags of the sub-key
     *
     * @param int $usage Usage flags
     *
     * @return Crypt_GPG_SubKey The current object, for fluent interface.
     */
    public function setUsage($usage)
    {
        $this->_usage = (int) $usage;
        return $this;
    }

    /**
     * Sets whether of not the private key for this sub-key exists in the
     * keyring
     *
     * @param bool $hasPrivate True if the private key for this sub-key
     *                         exists in the keyring and false if it does
     *                         not.
     *
     * @return Crypt_GPG_SubKey The current object, for fluent interface.
     */
    public function setHasPrivate($hasPrivate)
    {
        $this->_hasPrivate = ($hasPrivate) ? true : false;
        return $this;
    }

    /**
     * Sets whether or not this sub-key is revoked
     *
     * @param bool $isRevoked Whether or not this sub-key is revoked.
     *
     * @return Crypt_GPG_SubKey The current object, for fluent interface.
     */
    public function setRevoked($isRevoked)
    {
        $this->_isRevoked = ($isRevoked) ? true : false;
        return $this;
    }

    /**
     * Parses a sub-key object from a sub-key string
     *
     * See <b>doc/DETAILS</b> in the
     * {@link http://www.gnupg.org/download/ GPG distribution} for information
     * on how the sub-key string is parsed.
     *
     * @param string $string The string containing the sub-key.
     *
     * @return Crypt_GPG_SubKey The sub-key object parsed from the string.
     */
    public static function parse($string)
    {
        $tokens = explode(':', $string);

        $subKey = new Crypt_GPG_SubKey();

        $subKey->setId($tokens[4]);
        $subKey->setLength($tokens[2]);
        $subKey->setAlgorithm($tokens[3]);
        $subKey->setCreationDate(self::_parseDate($tokens[5]));
        $subKey->setExpirationDate(self::_parseDate($tokens[6]));

        if ($tokens[1] == 'r') {
            $subKey->setRevoked(true);
        }

        $usage = 0;
        $usage_map = array(
            'a' => self::USAGE_AUTHENTICATION,
            'c' => self::USAGE_CERTIFY,
            'e' => self::USAGE_ENCRYPT,
            's' => self::USAGE_SIGN,
        );

        foreach ($usage_map as $key => $flag) {
            if (strpos($tokens[11], $key) !== false) {
                $usage |= $flag;
            }
        }

        $subKey->setUsage($usage);

        return $subKey;
    }

    /**
     * Parses a date string as provided by GPG into a UNIX timestamp
     *
     * @param string $string The date string.
     *
     * @return DateTime|null The date corresponding to the provided date string.
     */
    private static function _parseDate($string)
    {
        if (empty($string)) {
            return null;
        }

        // all times are in UTC according to GPG documentation
        $timeZone = new DateTimeZone('UTC');

        if (strpos($string, 'T') === false) {
            // interpret as UNIX timestamp
            $string = '@' . $string;
        }

        return new DateTime($string, $timeZone);
    }
}
PK}c�\. ��+pear/crypt_gpg/Crypt/GPG/ProcessControl.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * A class for monitoring and terminating processes
 *
 * LICENSE:
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see
 * <http://www.gnu.org/licenses/>
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2013 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 */

/**
 * A class for monitoring and terminating processes by PID
 *
 * This is used to safely terminate the gpg-agent for GnuPG 2.x. This class
 * is limited in its abilities and can only check if a PID is running and
 * send a PID SIGTERM.
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2013 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 */
class Crypt_GPG_ProcessControl
{
    /**
     * The PID (process identifier) being monitored
     *
     * @var int
     */
    protected $pid;

    /**
     * Creates a new process controller from the given PID (process identifier)
     *
     * @param int $pid The PID (process identifier).
     */
    public function __construct($pid)
    {
        $this->pid = $pid;
    }

    /**
     * Gets the PID (process identifier) being controlled
     *
     * @return int The PID being controlled.
     */
    public function getPid()
    {
        return $this->pid;
    }

    /**
     * Checks if the process is running
     *
     * If the <kbd>posix</kbd> extension is available, <kbd>posix_getpgid()</kbd>
     * is used. Otherwise <kbd>ps</kbd> is used on UNIX-like systems and
     * <kbd>tasklist</kbd> on Windows.
     *
     * @return bool True if the process is running, false if not.
     */
    public function isRunning()
    {
        $running = false;

        if (function_exists('posix_getpgid')) {
            $running = false !== posix_getpgid($this->pid);
        } elseif (PHP_OS === 'WINNT') {
            $command = 'tasklist /fo csv /nh /fi '
                . escapeshellarg('PID eq ' . $this->pid);

            $result  = exec($command);
            $parts   = explode(',', $result);
            $running = (count($parts) > 1 && trim($parts[1], '"') == $this->pid);
        } else {
            $result  = exec('ps -p ' . escapeshellarg($this->pid) . ' -o pid=');
            $running = (trim($result) == $this->pid);
        }

        return $running;
    }

    /**
     * Ends the process gracefully
     *
     * The signal SIGTERM is sent to the process. The gpg-agent process will
     * end gracefully upon receiving the SIGTERM signal. Upon 3 consecutive
     * SIGTERM signals the gpg-agent will forcefully shut down.
     *
     * If the <kbd>posix</kbd> extension is available, <kbd>posix_kill()</kbd>
     * is used. Otherwise <kbd>kill</kbd> is used on UNIX-like systems and
     * <kbd>taskkill</kbd> is used in Windows.
     *
     * @return void
     */
    public function terminate()
    {
        if (function_exists('posix_kill')) {
            posix_kill($this->pid, 15);
        } elseif (PHP_OS === 'WINNT') {
            exec('taskkill /PID ' . escapeshellarg($this->pid));
        } else {
            exec('kill -15 ' . escapeshellarg($this->pid));
        }
    }
}
PK}c�\ddžU�w�w+pear/crypt_gpg/Crypt/GPG/ProcessHandler.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * Crypt_GPG is a package to use GPG from PHP
 *
 * This file contains handler for status and error pipes of GPG process.
 *
 * LICENSE:
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see
 * <http://www.gnu.org/licenses/>
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Nathan Fredrickson <nathan@silverorange.com>
 * @author    Michael Gauthier <mike@silverorange.com>
 * @author    Aleksander Machniak <alec@alec.pl>
 * @copyright 2005-2013 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 * @link      http://www.gnupg.org/
 */

/**
 * GPG exception classes.
 */
require_once 'Crypt/GPG/Exceptions.php';

/**
 * Signature object class definition
 */
require_once 'Crypt/GPG/Signature.php';

/**
 * Status/Error handler for GPG process pipes.
 *
 * This class is used internally by Crypt_GPG_Engine and does not need to be used
 * directly. See the {@link Crypt_GPG} class for end-user API.
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Nathan Fredrickson <nathan@silverorange.com>
 * @author    Michael Gauthier <mike@silverorange.com>
 * @author    Aleksander Machniak <alec@alec.pl>
 * @copyright 2005-2013 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 * @link      http://www.gnupg.org/
 */
class Crypt_GPG_ProcessHandler
{
    /**
     * Engine used to control the GPG subprocess
     *
     * @var Crypt_GPG_Engine
     */
    protected $engine;

    /**
     * The error code of the current operation
     *
     * @var int
     */
    protected $errorCode = Crypt_GPG::ERROR_NONE;

    /**
     * The number of currently needed passphrases
     *
     * If this is not zero when the GPG command is completed, the error code is
     * set to {@link Crypt_GPG::ERROR_MISSING_PASSPHRASE}.
     *
     * @var int
     */
    protected $needPassphrase = 0;

    /**
     * Some data collected while processing the operation
     * or set for the operation
     *
     * @var array
     * @see self::setData()
     * @see self::getData()
     */
    protected $data = array();

    /**
     * The name of the current operation
     *
     * @var string
     * @see self::setOperation()
     */
    protected $operation = null;

    /**
     * The value of the argument of current operation
     *
     * @var string
     * @see self::setOperation()
     */
    protected $operationArg = null;

    /**
     * Creates a new instance
     *
     * @param Crypt_GPG_Engine $engine Engine object
     */
    public function __construct($engine)
    {
        $this->engine = $engine;
    }

    /**
     * Sets the operation that is being performed by the engine.
     *
     * @param string $operation The GPG operation to perform.
     *
     * @return void
     */
    public function setOperation($operation)
    {
        $op    = null;
        $opArg = null;

        // Regexp matching all GPG "operational" arguments
        $regexp = '/--('
            . 'version|import|list-public-keys|list-secret-keys'
            . '|list-keys|delete-key|delete-secret-key|encrypt|sign|clearsign'
            . '|detach-sign|decrypt|verify|export-secret-keys|export|gen-key'
            . ')/';

        if (strpos($operation, ' ') === false) {
            $op = trim($operation, '- ');
        } else if (preg_match($regexp, $operation, $matches, PREG_OFFSET_CAPTURE)) {
            $op      = trim($matches[0][0], '-');
            $op_len  = $matches[0][1] + mb_strlen($op, '8bit') + 3;
            $command = mb_substr($operation, $op_len, null, '8bit');

            // we really need the argument if it is a key ID/fingerprint or email
            // address se we can use simplified regexp to "revert escapeshellarg()"
            if (preg_match('/^[\'"]([a-zA-Z0-9:@._-]+)[\'"]/', $command, $matches)) {
                $opArg = $matches[1];
            }
        }

        $this->operation        = $op;
        $this->operationArg     = $opArg;
        $this->data['Warnings'] = array();
    }

    /**
     * Handles error values in the status output from GPG
     *
     * This method is responsible for setting the
     * {@link self::$errorCode}. See <b>doc/DETAILS</b> in the
     * {@link http://www.gnupg.org/download/ GPG distribution} for detailed
     * information on GPG's status output.
     *
     * @param string $line the status line to handle.
     *
     * @return void
     */
    public function handleStatus($line)
    {
        $tokens = explode(' ', $line);
        switch ($tokens[0]) {
        case 'NODATA':
            $this->errorCode = Crypt_GPG::ERROR_NO_DATA;
            break;

        case 'DECRYPTION_OKAY':
            // If the message is encrypted, this is the all-clear signal.
            $this->data['DecryptionOkay'] = true;
            $this->errorCode = Crypt_GPG::ERROR_NONE;
            break;

        case 'DELETE_PROBLEM':
            if ($tokens[1] == '1') {
                $this->errorCode = Crypt_GPG::ERROR_KEY_NOT_FOUND;
                break;
            } elseif ($tokens[1] == '2') {
                $this->errorCode = Crypt_GPG::ERROR_DELETE_PRIVATE_KEY;
                break;
            }
            break;

        case 'IMPORT_OK':
            $this->data['Import']['fingerprint'] = $tokens[2];

            if (empty($this->data['Import']['fingerprints'])) {
                $this->data['Import']['fingerprints'] = array($tokens[2]);
            } else if (!in_array($tokens[2], $this->data['Import']['fingerprints'])) {
                $this->data['Import']['fingerprints'][] = $tokens[2];
            }

            break;

        case 'IMPORT_RES':
            $this->data['Import']['public_imported']   = intval($tokens[3]);
            $this->data['Import']['public_unchanged']  = intval($tokens[5]);
            $this->data['Import']['private_imported']  = intval($tokens[11]);
            $this->data['Import']['private_unchanged'] = intval($tokens[12]);
            break;

        case 'NO_PUBKEY':
        case 'NO_SECKEY':
            $this->data['ErrorKeyId'] = $tokens[1];

            if ($this->errorCode != Crypt_GPG::ERROR_MISSING_PASSPHRASE
                && $this->errorCode != Crypt_GPG::ERROR_BAD_PASSPHRASE
                && !(
                    $this->operation == 'decrypt'
                    && $tokens[0] == 'NO_PUBKEY'
                    && !empty($this->data['IgnoreVerifyErrors'])
                )
            ) {
                $this->errorCode = Crypt_GPG::ERROR_KEY_NOT_FOUND;
            }

            // note: this message is also received if there are multiple
            // recipients and a previous key had a correct passphrase.
            $this->data['MissingKeys'][$tokens[1]] = $tokens[1];

            // @FIXME: remove missing passphrase registered in ENC_TO handler
            //         This is for GnuPG 2.1
            unset($this->data['MissingPassphrases'][$tokens[1]]);
            break;

        case 'KEY_CONSIDERED':
            // In GnuPG 2.1.x exporting/importing a secret key requires passphrase
            // However, no NEED_PASSPRASE is returned, https://bugs.gnupg.org/gnupg/issue2667
            // So, handling KEY_CONSIDERED and GET_HIDDEN is needed.
            if (!array_key_exists('KeyConsidered', $this->data)) {
                $this->data['KeyConsidered'] = $tokens[1];
            }
            break;

        case 'USERID_HINT':
            // remember the user id for pretty exception messages
            // GnuPG 2.1.15 gives me: "USERID_HINT 0000000000000000 [?]"
            $keyId = $tokens[1];
            if (preg_match('/[1-9A-F]/', $keyId)) {
                $username = implode(' ', array_splice($tokens, 2));
                $this->data['BadPassphrases'][$keyId] = $username;
            }
            break;

        case 'ENC_TO':
            // Now we know the message is encrypted. Set flag to check if
            // decryption succeeded.
            $this->data['DecryptionOkay'] = false;

            // this is the new key message
            $this->data['CurrentSubKeyId'] = $keyId = $tokens[1];

            // For some reason in GnuPG 2.1.11 I get only ENC_TO and no
            // NEED_PASSPHRASE/MISSING_PASSPHRASE/USERID_HINT
            // This is not needed for GnuPG 2.1.15
            if (!empty($_ENV['PINENTRY_USER_DATA'])) {
                $passphrases = json_decode($_ENV['PINENTRY_USER_DATA'], true);
            } else {
                $passphrases = array();
            }

            // @TODO: Get user name/email
            $this->data['BadPassphrases'][$keyId] = $keyId;
            if (empty($passphrases) || empty($passphrases[$keyId])) {
                $this->data['MissingPassphrases'][$keyId] = $keyId;
            }
            break;

        case 'GOOD_PASSPHRASE':
            // if we got a good passphrase, remove the key from the list of
            // bad passphrases.
            if (isset($this->data['CurrentSubKeyId'])) {
                unset($this->data['BadPassphrases'][$this->data['CurrentSubKeyId']]);
                unset($this->data['MissingPassphrases'][$this->data['CurrentSubKeyId']]);
            }

            $this->needPassphrase--;
            break;

        case 'BAD_PASSPHRASE':
            $this->errorCode = Crypt_GPG::ERROR_BAD_PASSPHRASE;
            break;

        case 'MISSING_PASSPHRASE':
            if (isset($this->data['CurrentSubKeyId'])) {
                $this->data['MissingPassphrases'][$this->data['CurrentSubKeyId']]
                    = $this->data['CurrentSubKeyId'];
            }

            $this->errorCode = Crypt_GPG::ERROR_MISSING_PASSPHRASE;
            break;

        case 'GET_HIDDEN':
            if ($tokens[1] == 'passphrase.enter' && isset($this->data['KeyConsidered'])) {
                $tokens[1] = $this->data['KeyConsidered'];
            } else {
                break;
            }
            // no break

        case 'NEED_PASSPHRASE':
            $passphrase = $this->getPin($tokens[1]);

            $this->engine->sendCommand($passphrase);

            if ($passphrase === '') {
                $this->needPassphrase++;
            }
            break;

        case 'SIG_CREATED':
            $this->data['SigCreated'] = $line;
            break;

        case 'SIG_ID':
            // note: signature id comes before new signature line and may not
            // exist for some signature types
            $this->data['SignatureId'] = $tokens[1];
            break;

        case 'EXPSIG':
        case 'EXPKEYSIG':
        case 'REVKEYSIG':
        case 'BADSIG':
        case 'ERRSIG':
            $this->errorCode = Crypt_GPG::ERROR_BAD_SIGNATURE;
            // no break
        case 'GOODSIG':
            $signature = new Crypt_GPG_Signature();

            // if there was a signature id, set it on the new signature
            if (!empty($this->data['SignatureId'])) {
                $signature->setId($this->data['SignatureId']);
                $this->data['SignatureId'] = '';
            }

            // Detect whether fingerprint or key id was returned and set
            // signature values appropriately. Key ids are strings of either
            // 16 or 8 hexadecimal characters. Fingerprints are strings of 40
            // hexadecimal characters. The key id is the last 16 characters of
            // the key fingerprint.
            if (mb_strlen($tokens[1], '8bit') > 16) {
                $signature->setKeyFingerprint($tokens[1]);
                $signature->setKeyId(mb_substr($tokens[1], -16, null, '8bit'));
            } else {
                $signature->setKeyId($tokens[1]);
            }

            // get user id string
            if ($tokens[0] != 'ERRSIG') {
                $string = implode(' ', array_splice($tokens, 2));
                $string = rawurldecode($string);

                $signature->setUserId(Crypt_GPG_UserId::parse($string));
            }

            $this->data['Signatures'][] = $signature;
            break;

        case 'VALIDSIG':
            if (empty($this->data['Signatures'])) {
                break;
            }

            $signature = end($this->data['Signatures']);

            $signature->setValid(true);
            $signature->setKeyFingerprint($tokens[1]);

            if (strpos($tokens[3], 'T') === false) {
                $signature->setCreationDate($tokens[3]);
            } else {
                $signature->setCreationDate(strtotime($tokens[3]));
            }

            if (array_key_exists(4, $tokens)) {
                if (strpos($tokens[4], 'T') === false) {
                    $signature->setExpirationDate($tokens[4]);
                } else {
                    $signature->setExpirationDate(strtotime($tokens[4]));
                }
            }

            break;

        case 'KEY_CREATED':
            if (isset($this->data['Handle']) && $tokens[3] == $this->data['Handle']) {
                $this->data['KeyCreated'] = $tokens[2];
            }
            break;

        case 'KEY_NOT_CREATED':
            if (isset($this->data['Handle']) && $tokens[1] == $this->data['Handle']) {
                $this->errorCode = Crypt_GPG::ERROR_KEY_NOT_CREATED;
            }
            break;

        case 'PROGRESS':
            // todo: at some point, support reporting status async
            break;

        // GnuPG 2.1 uses FAILURE and ERROR responses
        case 'FAILURE':
        case 'ERROR':
            $errnum  = (int) $tokens[2];
            $source  = $errnum >> 24;
            $errcode = $errnum & 0xFFFFFF;

            switch ($errcode) {
            case 11: // bad passphrase
            case 87: // bad PIN
                $this->errorCode = Crypt_GPG::ERROR_BAD_PASSPHRASE;
                break;

            case 177: // no passphrase
            case 178: // no PIN
                $this->errorCode = Crypt_GPG::ERROR_MISSING_PASSPHRASE;
                break;

            case 58:
                $this->errorCode = Crypt_GPG::ERROR_NO_DATA;
                break;
            }

            break;
        }
    }

    /**
     * Handles error values in the error output from GPG
     *
     * This method is responsible for setting the
     * {@link Crypt_GPG_Engine::$_errorCode}.
     *
     * @param string $line the error line to handle.
     *
     * @return void
     */
    public function handleError($line)
    {
        if (stripos($line, 'gpg: WARNING: ') !== false) {
            $this->data['Warnings'][] = substr($line, 14);
        }

        if ($this->errorCode !== Crypt_GPG::ERROR_NONE) {
            return;
        }

        $pattern = '/no valid OpenPGP data found/';
        if (preg_match($pattern, $line) === 1) {
            $this->errorCode = Crypt_GPG::ERROR_NO_DATA;
            return;
        }

        $pattern = '/No secret key|secret key not available/';
        if (preg_match($pattern, $line) === 1) {
            $this->errorCode = Crypt_GPG::ERROR_KEY_NOT_FOUND;
            return;
        }

        $pattern = '/No public key|public key not found/';
        if (preg_match($pattern, $line) === 1) {
            if ($this->operation == 'decrypt' && !empty($this->data['IgnoreVerifyErrors'])) {
                return;
            }

            $this->errorCode = Crypt_GPG::ERROR_KEY_NOT_FOUND;
            return;
        }

        $pattern = '/can\'t (?:access|open) `(.*?)\'/';
        if (preg_match($pattern, $line, $matches) === 1) {
            $this->data['ErrorFilename'] = $matches[1];
            $this->errorCode = Crypt_GPG::ERROR_FILE_PERMISSIONS;
            return;
        }

        // GnuPG 2.1: It should return MISSING_PASSPHRASE, but it does not
        // we have to detect it this way. This happens e.g. on private key import
        $pattern = '/key ([0-9A-F]+).* (Bad|No) passphrase/';
        if (preg_match($pattern, $line, $matches) === 1) {
            $keyId = $matches[1];
            // @TODO: Get user name/email
            if (empty($this->data['BadPassphrases'][$keyId])) {
                $this->data['BadPassphrases'][$keyId] = $keyId;
            }

            if ($matches[2] == 'Bad') {
                $this->errorCode = Crypt_GPG::ERROR_BAD_PASSPHRASE;
            } else {
                $this->errorCode = Crypt_GPG::ERROR_MISSING_PASSPHRASE;
                if (empty($this->data['MissingPassphrases'][$keyId])) {
                    $this->data['MissingPassphrases'][$keyId] = $keyId;
                }
            }

            return;
        }

        if ($this->operation == 'gen-key') {
            $pattern = '/:([0-9]+): invalid algorithm$/';
            if (preg_match($pattern, $line, $matches) === 1) {
                $this->errorCode          = Crypt_GPG::ERROR_BAD_KEY_PARAMS;
                $this->data['LineNumber'] = intval($matches[1]);
            }
        }
    }

    /**
     * On error throws exception
     *
     * @param int $exitcode GPG process exit code
     *
     * @return void
     * @throws Crypt_GPG_Exception
     */
    public function throwException($exitcode = 0)
    {
        if ($exitcode > 0 && $this->errorCode === Crypt_GPG::ERROR_NONE) {
            $this->errorCode = $this->setErrorCode($exitcode);
        }

        if ($this->errorCode === Crypt_GPG::ERROR_NONE) {
            return;
        }

        $code = $this->errorCode;
        $note = "Please use the 'debug' option when creating the Crypt_GPG " .
            "object, and file a bug report at " . Crypt_GPG::BUG_URI;

        switch ($this->operation) {
        case 'version':
            throw new Crypt_GPG_Exception(
                'Unknown error getting GnuPG version information. ' . $note,
                $code
            );

        case 'list-secret-keys':
        case 'list-public-keys':
        case 'list-keys':
            switch ($code) {
            case Crypt_GPG::ERROR_KEY_NOT_FOUND:
                // ignore not found key errors
                break;

            case Crypt_GPG::ERROR_FILE_PERMISSIONS:
                if (!empty($this->data['ErrorFilename'])) {
                    throw new Crypt_GPG_FileException(
                        sprintf(
                            'Error reading GnuPG data file \'%s\'. Check to make ' .
                            'sure it is readable by the current user.',
                            $this->data['ErrorFilename']
                        ),
                        $code,
                        $this->data['ErrorFilename']
                    );
                }
                throw new Crypt_GPG_FileException(
                    'Error reading GnuPG data file. Check to make sure that ' .
                    'GnuPG data files are readable by the current user.',
                    $code
                );

            default:
                throw new Crypt_GPG_Exception(
                    'Unknown error getting keys. ' . $note, $code
                );
            }
            break;

        case 'delete-key':
        case 'delete-secret-key':
            switch ($code) {
            case Crypt_GPG::ERROR_KEY_NOT_FOUND:
                throw new Crypt_GPG_KeyNotFoundException(
                    'Key not found: ' . $this->operationArg,
                    $code,
                    $this->operationArg
                );

            case Crypt_GPG::ERROR_DELETE_PRIVATE_KEY:
                throw new Crypt_GPG_DeletePrivateKeyException(
                    'Private key must be deleted before public key can be ' .
                    'deleted.',
                    $code,
                    $this->operationArg
                );

            default:
                throw new Crypt_GPG_Exception(
                    'Unknown error deleting key. ' . $note, $code
                );
            }
            break; // @phpstan-ignore-line

        case 'import':
            switch ($code) {
            case Crypt_GPG::ERROR_NO_DATA:
                throw new Crypt_GPG_NoDataException(
                    'No valid GPG key data found.', $code
                );

            case Crypt_GPG::ERROR_BAD_PASSPHRASE:
            case Crypt_GPG::ERROR_MISSING_PASSPHRASE:
                throw $this->badPassException($code, 'Cannot import private key.');

            default:
                throw new Crypt_GPG_Exception(
                    'Unknown error importing GPG key. ' . $note, $code
                );
            }
            break; // @phpstan-ignore-line

        case 'export':
        case 'export-secret-keys':
            switch ($code) {
            case Crypt_GPG::ERROR_BAD_PASSPHRASE:
            case Crypt_GPG::ERROR_MISSING_PASSPHRASE:
                throw $this->badPassException($code, 'Cannot export private key.');

            default:
                throw new Crypt_GPG_Exception(
                    'Unknown error exporting a key. ' . $note, $code
                );
            }
            break; // @phpstan-ignore-line

        case 'encrypt':
        case 'sign':
        case 'clearsign':
        case 'detach-sign':
            switch ($code) {
            case Crypt_GPG::ERROR_KEY_NOT_FOUND:
                throw new Crypt_GPG_KeyNotFoundException(
                    'Cannot sign data. Private key not found. Import the '.
                    'private key before trying to sign data.',
                    $code,
                    !empty($this->data['ErrorKeyId']) ? $this->data['ErrorKeyId'] : null
                );

            case Crypt_GPG::ERROR_BAD_PASSPHRASE:
                throw new Crypt_GPG_BadPassphraseException(
                    'Cannot sign data. Incorrect passphrase provided.', $code
                );

            case Crypt_GPG::ERROR_MISSING_PASSPHRASE:
                throw new Crypt_GPG_BadPassphraseException(
                    'Cannot sign data. No passphrase provided.', $code
                );

            default:
                throw new Crypt_GPG_Exception(
                    "Unknown error {$this->operation}ing data. $note", $code
                );
            }
            break; // @phpstan-ignore-line

        case 'verify':
            switch ($code) {
            case Crypt_GPG::ERROR_BAD_SIGNATURE:
                // ignore bad signature errors
                break;

            case Crypt_GPG::ERROR_NO_DATA:
                throw new Crypt_GPG_NoDataException(
                    'No valid signature data found.', $code
                );

            case Crypt_GPG::ERROR_KEY_NOT_FOUND:
                throw new Crypt_GPG_KeyNotFoundException(
                    'Public key required for data verification not in keyring.',
                    $code,
                    !empty($this->data['ErrorKeyId']) ? $this->data['ErrorKeyId'] : null
                );

            default:
                throw new Crypt_GPG_Exception(
                    'Unknown error validating signature details. ' . $note,
                    $code
                );
            }
            break;

        case 'decrypt':
            switch ($code) {
            case Crypt_GPG::ERROR_BAD_SIGNATURE:
                // ignore bad signature errors
                break;

            case Crypt_GPG::ERROR_KEY_NOT_FOUND:
                if (!empty($this->data['MissingKeys'])) {
                    $keyId = reset($this->data['MissingKeys']);
                } else {
                    $keyId = '';
                }

                throw new Crypt_GPG_KeyNotFoundException(
                    'Cannot decrypt data. No suitable private key is in the ' .
                    'keyring. Import a suitable private key before trying to ' .
                    'decrypt this data.',
                    $code,
                    $keyId
                );

            case Crypt_GPG::ERROR_BAD_PASSPHRASE:
            case Crypt_GPG::ERROR_MISSING_PASSPHRASE:
                throw $this->badPassException($code, 'Cannot decrypt data.');

            case Crypt_GPG::ERROR_NO_DATA:
                throw new Crypt_GPG_NoDataException(
                    'Cannot decrypt data. No PGP encrypted data was found in '.
                    'the provided data.',
                    $code
                );

            default:
                throw new Crypt_GPG_Exception(
                    'Unknown error decrypting data.', $code
                );
            }
            break;

        case 'gen-key':
            switch ($code) {
            case Crypt_GPG::ERROR_BAD_KEY_PARAMS:
                throw new Crypt_GPG_InvalidKeyParamsException(
                    'Invalid key algorithm specified.', $code
                );

            default:
                throw new Crypt_GPG_Exception(
                    'Unknown error generating key-pair. ' . $note, $code
                );
            }
        }
    }

    /**
     * Check exit code of the GPG operation.
     *
     * @param int $exitcode GPG process exit code
     *
     * @return int Internal error code
     */
    protected function setErrorCode($exitcode)
    {
        if ($this->needPassphrase > 0) {
            return Crypt_GPG::ERROR_MISSING_PASSPHRASE;
        }

        if ($this->operation == 'import') {
            return Crypt_GPG::ERROR_NONE;
        }

        if ($this->operation == 'decrypt' && !empty($this->data['DecryptionOkay'])) {
            if (!empty($this->data['IgnoreVerifyErrors'])) {
                return Crypt_GPG::ERROR_NONE;
            }
            if (!empty($this->data['MissingKeys'])) {
                return Crypt_GPG::ERROR_KEY_NOT_FOUND;
            }
        }

        return Crypt_GPG::ERROR_UNKNOWN;
    }

    /**
     * Get data from the last process execution.
     *
     * @param string $name Data element name:
     *                     - SigCreated: The last SIG_CREATED status.
     *                     - KeyConsidered: The last KEY_CONSIDERED status identifier.
     *                     - KeyCreated: The KEY_CREATED status (for specified Handle).
     *                     - Signatures: Signatures data from verification process.
     *                     - LineNumber: Number of the gen-key error line.
     *                     - Import: Result of IMPORT_OK/IMPORT_RES
     *                     - Warnings: An array of all collected GnuPG warnings
     *
     * @return mixed
     */
    public function getData($name)
    {
        return isset($this->data[$name]) ? $this->data[$name] : null;
    }

    /**
     * Set data for the process execution.
     *
     * @param string $name  Data element name:
     *                      - Handle: The unique key handle used by this handler
     *                      The key handle is used to track GPG status output
     *                      for a particular key on --gen-key command before
     *                      the key has its own identifier.
     *                      - IgnoreVerifyErrors: Do not throw exceptions
     *                      when signature verification failes because
     *                      of a missing public key.
     * @param mixed  $value Data element value
     *
     * @return void
     */
    public function setData($name, $value)
    {
        switch ($name) {
        case 'Handle':
            $this->data[$name] = strval($value);
            break;

        case 'IgnoreVerifyErrors':
            $this->data[$name] = (bool) $value;
            break;
        }
    }

    /**
     * Create Crypt_GPG_BadPassphraseException from operation data.
     *
     * @param int    $code    Error code
     * @param string $message Error message
     *
     * @return Crypt_GPG_BadPassphraseException
     */
    protected function badPassException($code, $message)
    {
        $badPassphrases = array_diff_key(
            isset($this->data['BadPassphrases']) ? $this->data['BadPassphrases'] : array(),
            isset($this->data['MissingPassphrases']) ? $this->data['MissingPassphrases'] : array()
        );

        $missingPassphrases = array_intersect_key(
            isset($this->data['BadPassphrases']) ? $this->data['BadPassphrases'] : array(),
            isset($this->data['MissingPassphrases']) ? $this->data['MissingPassphrases'] : array()
        );

        if (count($badPassphrases) > 0) {
            $message .= ' Incorrect passphrase provided for keys: "' .
                implode('", "', $badPassphrases) . '".';
        }
        if (count($missingPassphrases) > 0) {
            $message .= ' No passphrase provided for keys: "' .
                implode('", "', $missingPassphrases) . '".';
        }

        return new Crypt_GPG_BadPassphraseException(
            $message,
            $code,
            $badPassphrases,
            $missingPassphrases
        );
    }

    /**
     * Get registered passphrase for specified key.
     *
     * @param string $key Key identifier
     *
     * @return string Passphrase
     */
    protected function getPin($key)
    {
        $passphrase  = '';
        $keyIdLength = mb_strlen($key, '8bit');

        if ($keyIdLength && !empty($_ENV['PINENTRY_USER_DATA'])) {
            $passphrases = json_decode($_ENV['PINENTRY_USER_DATA'], true);
            foreach ($passphrases as $_keyId => $pass) {
                $keyId        = $key;
                $_keyIdLength = mb_strlen($_keyId, '8bit');

                // Get last X characters of key identifier to compare
                if ($keyIdLength < $_keyIdLength) {
                    $_keyId = mb_substr($_keyId, -$keyIdLength, null, '8bit');
                } else if ($keyIdLength > $_keyIdLength) {
                    $keyId = mb_substr($keyId, -$_keyIdLength, null, '8bit');
                }

                if ($_keyId === $keyId) {
                    $passphrase = $pass;
                    break;
                }
            }
        }

        return $passphrase;
    }
}
PK}c�\s�[&&pear/crypt_gpg/Crypt/GPG.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * Crypt_GPG is a package to use GPG from PHP
 *
 * This package provides an object oriented interface to GNU Privacy
 * Guard (GPG). It requires the GPG executable to be on the system.
 *
 * Though GPG can support symmetric-key cryptography, this package is intended
 * only to facilitate public-key cryptography.
 *
 * This file contains the main GPG class. The class in this file lets you
 * encrypt, decrypt, sign and verify data; import and delete keys; and perform
 * other useful GPG tasks.
 *
 * Example usage:
 * <code>
 * <?php
 * // encrypt some data
 * $gpg = new Crypt_GPG();
 * $gpg->addEncryptKey($mySecretKeyId);
 * $encryptedData = $gpg->encrypt($data);
 * ?>
 * </code>
 *
 * LICENSE:
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see
 * <http://www.gnu.org/licenses/>
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Nathan Fredrickson <nathan@silverorange.com>
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2005-2013 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 * @link      http://pear.php.net/manual/en/package.encryption.crypt-gpg.php
 * @link      http://www.gnupg.org/
 */

/**
 * Base class for GPG methods
 */
require_once 'Crypt/GPGAbstract.php';

/**
 * GPG exception classes.
 */
require_once 'Crypt/GPG/Exceptions.php';

/**
 * A class to use GPG from PHP
 *
 * This class provides an object oriented interface to GNU Privacy Guard (GPG).
 *
 * Though GPG can support symmetric-key cryptography, this class is intended
 * only to facilitate public-key cryptography.
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Nathan Fredrickson <nathan@silverorange.com>
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2005-2013 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 * @link      http://www.gnupg.org/
 */
class Crypt_GPG extends Crypt_GPGAbstract
{
    /**
     * Signing mode for normal signing of data. The signed message will not
     * be readable without special software.
     *
     * This is the default signing mode.
     *
     * @see Crypt_GPG::sign()
     * @see Crypt_GPG::signFile()
     */
    const SIGN_MODE_NORMAL = 1;

    /**
     * Signing mode for clearsigning data. Clearsigned signatures are ASCII
     * armored data and are readable without special software. If the signed
     * message is unencrypted, the message will still be readable. The message
     * text will be in the original encoding.
     *
     * @see Crypt_GPG::sign()
     * @see Crypt_GPG::signFile()
     */
    const SIGN_MODE_CLEAR = 2;

    /**
     * Signing mode for creating a detached signature. When using detached
     * signatures, only the signature data is returned. The original message
     * text may be distributed separately from the signature data. This is
     * useful for miltipart/signed email messages as per
     * {@link http://www.ietf.org/rfc/rfc3156.txt RFC 3156}.
     *
     * @see Crypt_GPG::sign()
     * @see Crypt_GPG::signFile()
     */
    const SIGN_MODE_DETACHED = 3;

    /**
     * No formatting is performed.
     *
     * Example: C3BC615AD9C766E5A85C1F2716D27458B1BBA1C4
     *
     * @see Crypt_GPG::getFingerprint()
     */
    const FORMAT_NONE = 1;

    /**
     * Fingerprint is formatted in the format used by the GnuPG gpg command's
     * default output.
     *
     * Example: C3BC 615A D9C7 66E5 A85C  1F27 16D2 7458 B1BB A1C4
     *
     * @see Crypt_GPG::getFingerprint()
     */
    const FORMAT_CANONICAL = 2;

    /**
     * Fingerprint is formatted in the format used when displaying X.509
     * certificates
     *
     * Example: C3:BC:61:5A:D9:C7:66:E5:A8:5C:1F:27:16:D2:74:58:B1:BB:A1:C4
     *
     * @see Crypt_GPG::getFingerprint()
     */
    const FORMAT_X509 = 3;

    /**
     * Use to specify ASCII armored mode for returned data
     */
    const ARMOR_ASCII = true;

    /**
     * Use to specify binary mode for returned data
     */
    const ARMOR_BINARY = false;

    /**
     * Use to specify that line breaks in signed text should be normalized
     */
    const TEXT_NORMALIZED = true;

    /**
     * Use to specify that line breaks in signed text should not be normalized
     */
    const TEXT_RAW = false;

    /**
     * Keys used to encrypt
     *
     * The array is of the form:
     * <code>
     * array(
     *   $key_id => array(
     *     'fingerprint' => $fingerprint,
     *     'passphrase'  => null
     *   )
     * );
     * </code>
     *
     * @var array
     * @see Crypt_GPG::addEncryptKey()
     * @see Crypt_GPG::clearEncryptKeys()
     */
    protected $encryptKeys = array();

    /**
     * Keys used to decrypt
     *
     * The array is of the form:
     * <code>
     * array(
     *   $key_id => array(
     *     'fingerprint' => $fingerprint,
     *     'passphrase'  => $passphrase
     *   )
     * );
     * </code>
     *
     * @var array
     * @see Crypt_GPG::addSignKey()
     * @see Crypt_GPG::clearSignKeys()
     */
    protected $signKeys = array();

    /**
     * Keys used to sign
     *
     * The array is of the form:
     * <code>
     * array(
     *   $key_id => array(
     *     'fingerprint' => $fingerprint,
     *     'passphrase'  => $passphrase
     *   )
     * );
     * </code>
     *
     * @var array
     * @see Crypt_GPG::addDecryptKey()
     * @see Crypt_GPG::clearDecryptKeys()
     */
    protected $decryptKeys = array();

    /**
     * Passphrases used on import/export of private keys in GnuPG 2.1
     *
     * The array is of the form:
     * <code>
     * array($key_id => $passphrase);
     * </code>
     *
     * @var array
     * @see Crypt_GPG::addPassphrase()
     * @see Crypt_GPG::clearPassphrases()
     */
    protected $passphrases = array();

    /**
     * Imports a public or private key into the keyring
     *
     * Keys may be removed from the keyring using
     * {@link Crypt_GPG::deletePublicKey()} or
     * {@link Crypt_GPG::deletePrivateKey()}.
     *
     * @param string $data the key data to be imported.
     *
     * @return array an associative array containing the following elements:
     *               - <kbd>fingerprint</kbd>       - the fingerprint of the
     *                                                imported key,
     *               - <kbd>public_imported</kbd>   - the number of public
     *                                                keys imported,
     *               - <kbd>public_unchanged</kbd>  - the number of unchanged
     *                                                public keys,
     *               - <kbd>private_imported</kbd>  - the number of private
     *                                                keys imported,
     *               - <kbd>private_unchanged</kbd> - the number of unchanged
     *                                                private keys.
     *
     * @throws Crypt_GPG_NoDataException if the key data is missing or if the
     *         data is is not valid key data.
     *
     * @throws Crypt_GPG_BadPassphraseException if a required passphrase is
     *         incorrect or if a required passphrase is not specified. See
     *         {@link Crypt_GPG::addPassphrase()}.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     *
     * @see Crypt_GPG::addPassphrase()
     * @see Crypt_GPG::clearPassphrases()
     */
    public function importKey($data)
    {
        return $this->_importKey($data, false);
    }

    /**
     * Imports a public or private key file into the keyring
     *
     * Keys may be removed from the keyring using
     * {@link Crypt_GPG::deletePublicKey()} or
     * {@link Crypt_GPG::deletePrivateKey()}.
     *
     * @param string $filename the key file to be imported.
     *
     * @return array an associative array containing the following elements:
     *               - <kbd>fingerprint</kbd>       - the fingerprint of the
     *                                                imported key,
     *               - <kbd>public_imported</kbd>   - the number of public
     *                                                keys imported,
     *               - <kbd>public_unchanged</kbd>  - the number of unchanged
     *                                                public keys,
     *               - <kbd>private_imported</kbd>  - the number of private
     *                                                keys imported,
     *               - <kbd>private_unchanged</kbd> - the number of unchanged
     *                                                private keys.
     *                                                  private keys.
     *
     * @throws Crypt_GPG_NoDataException if the key data is missing or if the
     *         data is is not valid key data.
     *
     * @throws Crypt_GPG_FileException if the key file is not readable.
     *
     * @throws Crypt_GPG_BadPassphraseException if a required passphrase is
     *         incorrect or if a required passphrase is not specified. See
     *         {@link Crypt_GPG::addPassphrase()}.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    public function importKeyFile($filename)
    {
        return $this->_importKey($filename, true);
    }

    /**
     * Exports a private key from the keyring
     *
     * The exported key remains on the keyring. To delete the key, use
     * {@link Crypt_GPG::deletePrivateKey()}.
     *
     * If more than one key fingerprint is available for the specified
     * <kbd>$keyId</kbd> (for example, if you use a non-unique uid) only the
     * first private key is exported.
     *
     * @param string  $keyId either the full uid of the private key, the email
     *                       part of the uid of the private key or the key id of
     *                       the private key. For example,
     *                       "Test User (example) <test@example.com>",
     *                       "test@example.com" or a hexadecimal string.
     * @param bool    $armor optional. If true, ASCII armored data is returned;
     *                       otherwise, binary data is returned. Defaults to
     *                       true.
     *
     * @return string the private key data.
     *
     * @throws Crypt_GPG_KeyNotFoundException if a private key with the given
     *         <kbd>$keyId</kbd> is not found.
     *
     * @throws Crypt_GPG_BadPassphraseException if a required passphrase is
     *         incorrect or if a required passphrase is not specified. See
     *         {@link Crypt_GPG::addPassphrase()}.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    public function exportPrivateKey($keyId, $armor = true)
    {
        return $this->_exportKey($keyId, $armor, true);
    }

    /**
     * Exports a public key from the keyring
     *
     * The exported key remains on the keyring. To delete the public key, use
     * {@link Crypt_GPG::deletePublicKey()}.
     *
     * If more than one key fingerprint is available for the specified
     * <kbd>$keyId</kbd> (for example, if you use a non-unique uid) only the
     * first public key is exported.
     *
     * @param string  $keyId either the full uid of the public key, the email
     *                       part of the uid of the public key or the key id of
     *                       the public key. For example,
     *                       "Test User (example) <test@example.com>",
     *                       "test@example.com" or a hexadecimal string.
     * @param bool    $armor optional. If true, ASCII armored data is returned;
     *                       otherwise, binary data is returned. Defaults to
     *                       true.
     *
     * @return string the public key data.
     *
     * @throws Crypt_GPG_KeyNotFoundException if a public key with the given
     *         <kbd>$keyId</kbd> is not found.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    public function exportPublicKey($keyId, $armor = true)
    {
        return $this->_exportKey($keyId, $armor, false);
    }

    /**
     * Deletes a public key from the keyring
     *
     * If more than one key fingerprint is available for the specified
     * <kbd>$keyId</kbd> (for example, if you use a non-unique uid) only the
     * first public key is deleted.
     *
     * The private key must be deleted first or an exception will be thrown.
     * In GnuPG >= 2.1 this limitation does not exist.
     * See {@link Crypt_GPG::deletePrivateKey()}.
     *
     * @param string $keyId either the full uid of the public key, the email
     *                      part of the uid of the public key or the key id of
     *                      the public key. For example,
     *                      "Test User (example) <test@example.com>",
     *                      "test@example.com" or a hexadecimal string.
     *
     * @return void
     *
     * @throws Crypt_GPG_KeyNotFoundException if a public key with the given
     *         <kbd>$keyId</kbd> is not found.
     *
     * @throws Crypt_GPG_DeletePrivateKeyException if the specified public key
     *         has an associated private key on the keyring. The private key
     *         must be deleted first (when using GnuPG < 2.1).
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    public function deletePublicKey($keyId)
    {
        $fingerprint = $this->getFingerprint($keyId);

        if ($fingerprint === null) {
            throw new Crypt_GPG_KeyNotFoundException(
                'Public key not found: ' . $keyId,
                self::ERROR_KEY_NOT_FOUND,
                $keyId
            );
        }

        $operation = '--delete-key -- ' . escapeshellarg($fingerprint);
        $arguments = array(
            '--batch',
            '--yes'
        );

        $this->engine->reset();
        $this->engine->setOperation($operation, $arguments);
        $this->engine->run();
    }

    /**
     * Deletes a private key from the keyring
     *
     * If more than one key fingerprint is available for the specified
     * <kbd>$keyId</kbd> (for example, if you use a non-unique uid) only the
     * first private key is deleted.
     *
     * Calls GPG with the <kbd>--delete-secret-key</kbd> command.
     *
     * @param string $keyId either the full uid of the private key, the email
     *                      part of the uid of the private key or the key id of
     *                      the private key. For example,
     *                      "Test User (example) <test@example.com>",
     *                      "test@example.com" or a hexadecimal string.
     *
     * @return void
     *
     * @throws Crypt_GPG_KeyNotFoundException if a private key with the given
     *         <kbd>$keyId</kbd> is not found.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    public function deletePrivateKey($keyId)
    {
        $fingerprint = $this->getFingerprint($keyId);

        if ($fingerprint === null) {
            throw new Crypt_GPG_KeyNotFoundException(
                'Private key not found: ' . $keyId,
                self::ERROR_KEY_NOT_FOUND,
                $keyId
            );
        }

        $operation = '--delete-secret-key -- ' . escapeshellarg($fingerprint);
        $arguments = array(
            '--batch',
            '--yes'
        );

        $this->engine->reset();
        $this->engine->setOperation($operation, $arguments);
        $this->engine->run();
    }

    /**
     * Gets the available keys in the keyring
     *
     * Calls GPG with the <kbd>--list-keys</kbd> command and grabs keys. See
     * the first section of <b>doc/DETAILS</b> in the
     * {@link http://www.gnupg.org/download/ GPG package} for a detailed
     * description of how the GPG command output is parsed.
     *
     * @param string $keyId optional. Only keys with that match the specified
     *                      pattern are returned. The pattern may be part of
     *                      a user id, a key id or a key fingerprint. If not
     *                      specified, all keys are returned.
     *
     * @return array an array of {@link Crypt_GPG_Key} objects. If no keys
     *               match the specified <kbd>$keyId</kbd> an empty array is
     *               returned.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     *
     * @see Crypt_GPG_Key
     */
    public function getKeys($keyId = '')
    {
        return parent::_getKeys($keyId);
    }

    /**
     * Gets a key fingerprint from the keyring
     *
     * If more than one key fingerprint is available (for example, if you use
     * a non-unique user id) only the first key fingerprint is returned.
     *
     * Calls the GPG <kbd>--list-keys</kbd> command with the
     * <kbd>--with-fingerprint</kbd> option to retrieve a public key
     * fingerprint.
     *
     * @param string  $keyId  either the full user id of the key, the email
     *                        part of the user id of the key, or the key id of
     *                        the key. For example,
     *                        "Test User (example) <test@example.com>",
     *                        "test@example.com" or a hexadecimal string.
     * @param int     $format optional. How the fingerprint should be formatted.
     *                        Use {@link Crypt_GPG::FORMAT_X509} for X.509
     *                        certificate format,
     *                        {@link Crypt_GPG::FORMAT_CANONICAL} for the format
     *                        used by GnuPG output and
     *                        {@link Crypt_GPG::FORMAT_NONE} for no formatting.
     *                        Defaults to <code>Crypt_GPG::FORMAT_NONE</code>.
     *
     * @return string|null The fingerprint of the key, or null if no fingerprint
     *                     is found for the given <kbd>$keyId</kbd>.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    public function getFingerprint($keyId, $format = self::FORMAT_NONE)
    {
        $output    = '';
        $operation = '--list-keys -- ' . escapeshellarg($keyId);
        $arguments = array(
            '--with-colons',
            '--with-fingerprint'
        );

        $this->engine->reset();
        $this->engine->setOutput($output);
        $this->engine->setOperation($operation, $arguments);
        $this->engine->run();

        $fingerprint = null;

        foreach (explode(PHP_EOL, $output) as $line) {
            if (mb_substr($line, 0, 3, '8bit') == 'fpr') {
                $lineExp     = explode(':', $line);
                $fingerprint = $lineExp[9];

                switch ($format) {
                case self::FORMAT_CANONICAL:
                    $fingerprintExp = str_split($fingerprint, 4);
                    $format         = '%s %s %s %s %s  %s %s %s %s %s';
                    $fingerprint    = vsprintf($format, $fingerprintExp);
                    break;

                case self::FORMAT_X509:
                    $fingerprintExp = str_split($fingerprint, 2);
                    $fingerprint    = implode(':', $fingerprintExp);
                    break;
                }

                break;
            }
        }

        return $fingerprint;
    }

    /**
     * Get information about the last signature that was created.
     *
     * @return Crypt_GPG_SignatureCreationInfo
     */
    public function getLastSignatureInfo()
    {
        return $this->engine->getProcessData('SignatureInfo');
    }

    /**
     * Encrypts string data
     *
     * Data is ASCII armored by default but may optionally be returned as
     * binary.
     *
     * @param string  $data  the data to be encrypted.
     * @param bool    $armor optional. If true, ASCII armored data is returned;
     *                       otherwise, binary data is returned. Defaults to
     *                       true.
     *
     * @return string the encrypted data.
     *
     * @throws Crypt_GPG_KeyNotFoundException if no encryption key is specified.
     *         See {@link Crypt_GPG::addEncryptKey()}.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     *
     * @sensitive $data
     */
    public function encrypt($data, $armor = self::ARMOR_ASCII)
    {
        return $this->_encrypt($data, false, null, $armor);
    }

    /**
     * Encrypts a file
     *
     * Encrypted data is ASCII armored by default but may optionally be saved
     * as binary.
     *
     * @param string  $filename      the filename of the file to encrypt.
     * @param string  $encryptedFile optional. The filename of the file in
     *                               which to store the encrypted data. If null
     *                               or unspecified, the encrypted data is
     *                               returned as a string.
     * @param bool    $armor         optional. If true, ASCII armored data is
     *                               returned; otherwise, binary data is
     *                               returned. Defaults to true.
     *
     * @return void|string if the <kbd>$encryptedFile</kbd> parameter is null,
     *                     a string containing the encrypted data is returned.
     *
     * @throws Crypt_GPG_KeyNotFoundException if no encryption key is specified.
     *         See {@link Crypt_GPG::addEncryptKey()}.
     *
     * @throws Crypt_GPG_FileException if the output file is not writeable or
     *         if the input file is not readable.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    public function encryptFile(
        $filename,
        $encryptedFile = null,
        $armor = self::ARMOR_ASCII
    ) {
        return $this->_encrypt($filename, true, $encryptedFile, $armor);
    }

    /**
     * Encrypts and signs data
     *
     * Data is encrypted and signed in a single pass.
     *
     * NOTE: Until GnuPG version 1.4.10, it was not possible to verify
     * encrypted-signed data without decrypting it at the same time. If you try
     * to use {@link Crypt_GPG::verify()} method on encrypted-signed data with
     * earlier GnuPG versions, you will get an error. Please use
     * {@link Crypt_GPG::decryptAndVerify()} to verify encrypted-signed data.
     *
     * @param string $data  The data to be encrypted and signed.
     * @param bool   $armor Optional. If true, ASCII armored data is returned;
     *                      otherwise, binary data is returned. Defaults to
     *                      true.
     *
     * @return string The encrypted signed data.
     *
     * @throws Crypt_GPG_KeyNotFoundException if no encryption key is specified
     *         or if no signing key is specified. See
     *         {@link Crypt_GPG::addEncryptKey()} and
     *         {@link Crypt_GPG::addSignKey()}.
     *
     * @throws Crypt_GPG_BadPassphraseException if a specified passphrase is
     *         incorrect or if a required passphrase is not specified.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     *
     * @see Crypt_GPG::decryptAndVerify()
     */
    public function encryptAndSign($data, $armor = self::ARMOR_ASCII)
    {
        return $this->_encryptAndSign($data, false, null, $armor);
    }

    /**
     * Encrypts and signs a file
     *
     * The file is encrypted and signed in a single pass.
     *
     * NOTE: Until GnuPG version 1.4.10, it was not possible to verify
     * encrypted-signed files without decrypting them at the same time. If you
     * try to use {@link Crypt_GPG::verify()} method on encrypted-signed files
     * with earlier GnuPG versions, you will get an error. Please use
     * {@link Crypt_GPG::decryptAndVerifyFile()} to verify encrypted-signed
     * files.
     *
     * @param string  $filename   the name of the file containing the data to
     *                            be encrypted and signed.
     * @param string  $signedFile optional. The name of the file in which the
     *                            encrypted, signed data should be stored. If
     *                            null or unspecified, the encrypted, signed
     *                            data is returned as a string.
     * @param bool    $armor      optional. If true, ASCII armored data is
     *                            returned; otherwise, binary data is returned.
     *                            Defaults to true.
     *
     * @return void|string if the <kbd>$signedFile</kbd> parameter is null, a
     *                     string containing the encrypted, signed data is
     *                     returned.
     *
     * @throws Crypt_GPG_KeyNotFoundException if no encryption key is specified
     *         or if no signing key is specified. See
     *         {@link Crypt_GPG::addEncryptKey()} and
     *         {@link Crypt_GPG::addSignKey()}.
     *
     * @throws Crypt_GPG_BadPassphraseException if a specified passphrase is
     *         incorrect or if a required passphrase is not specified.
     *
     * @throws Crypt_GPG_FileException if the output file is not writeable or
     *         if the input file is not readable.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     *
     * @see Crypt_GPG::decryptAndVerifyFile()
     */
    public function encryptAndSignFile(
        $filename,
        $signedFile = null,
        $armor = self::ARMOR_ASCII
    ) {
        return $this->_encryptAndSign($filename, true, $signedFile, $armor);
    }

    /**
     * Decrypts string data
     *
     * This method assumes the required private key is available in the keyring
     * and throws an exception if the private key is not available. To add a
     * private key to the keyring, use the {@link Crypt_GPG::importKey()} or
     * {@link Crypt_GPG::importKeyFile()} methods.
     *
     * @param string $encryptedData the data to be decrypted.
     *
     * @return string the decrypted data.
     *
     * @throws Crypt_GPG_KeyNotFoundException if the private key needed to
     *         decrypt the data is not in the user's keyring.
     *
     * @throws Crypt_GPG_NoDataException if specified data does not contain
     *         GPG encrypted data.
     *
     * @throws Crypt_GPG_BadPassphraseException if a required passphrase is
     *         incorrect or if a required passphrase is not specified. See
     *         {@link Crypt_GPG::addDecryptKey()}.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    public function decrypt($encryptedData)
    {
        return $this->_decrypt($encryptedData, false, null);
    }

    /**
     * Decrypts a file
     *
     * This method assumes the required private key is available in the keyring
     * and throws an exception if the private key is not available. To add a
     * private key to the keyring, use the {@link Crypt_GPG::importKey()} or
     * {@link Crypt_GPG::importKeyFile()} methods.
     *
     * @param string $encryptedFile the name of the encrypted file data to
     *                              decrypt.
     * @param string $decryptedFile optional. The name of the file to which the
     *                              decrypted data should be written. If null
     *                              or unspecified, the decrypted data is
     *                              returned as a string.
     *
     * @return void|string if the <kbd>$decryptedFile</kbd> parameter is null,
     *                     a string containing the decrypted data is returned.
     *
     * @throws Crypt_GPG_KeyNotFoundException if the private key needed to
     *         decrypt the data is not in the user's keyring.
     *
     * @throws Crypt_GPG_NoDataException if specified data does not contain
     *         GPG encrypted data.
     *
     * @throws Crypt_GPG_BadPassphraseException if a required passphrase is
     *         incorrect or if a required passphrase is not specified. See
     *         {@link Crypt_GPG::addDecryptKey()}.
     *
     * @throws Crypt_GPG_FileException if the output file is not writeable or
     *         if the input file is not readable.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    public function decryptFile($encryptedFile, $decryptedFile = null)
    {
        return $this->_decrypt($encryptedFile, true, $decryptedFile);
    }

    /**
     * Decrypts and verifies string data
     *
     * This method assumes the required private key is available in the keyring
     * and throws an exception if the private key is not available. To add a
     * private key to the keyring, use the {@link Crypt_GPG::importKey()} or
     * {@link Crypt_GPG::importKeyFile()} methods.
     *
     * @param string  $encryptedData      the encrypted, signed data to be decrypted
     *                                    and verified.
     * @param bool    $ignoreVerifyErrors enables ignoring of signature
     *                                    verification errors caused by missing public key
     *                                    When enabled Crypt_GPG_KeyNotFoundException
     *                                    will not be thrown.
     *
     * @return array two element array. The array has an element 'data'
     *               containing the decrypted data and an element
     *               'signatures' containing an array of
     *               {@link Crypt_GPG_Signature} objects for the signed data.
     *
     * @throws Crypt_GPG_KeyNotFoundException if the private key needed to
     *         decrypt the data or the public key to verify the signature
     *         is not in the user's keyring.
     *
     * @throws Crypt_GPG_NoDataException if specified data does not contain
     *         GPG encrypted data.
     *
     * @throws Crypt_GPG_BadPassphraseException if a required passphrase is
     *         incorrect or if a required passphrase is not specified. See
     *         {@link Crypt_GPG::addDecryptKey()}.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    public function decryptAndVerify($encryptedData, $ignoreVerifyErrors = false)
    {
        return $this->_decryptAndVerify($encryptedData, false, null, $ignoreVerifyErrors);
    }

    /**
     * Decrypts and verifies a signed, encrypted file
     *
     * This method assumes the required private key is available in the keyring
     * and throws an exception if the private key is not available. To add a
     * private key to the keyring, use the {@link Crypt_GPG::importKey()} or
     * {@link Crypt_GPG::importKeyFile()} methods.
     *
     * @param string  $encryptedFile      the name of the signed, encrypted file to
     *                                    to decrypt and verify.
     * @param string  $decryptedFile      optional. The name of the file to which the
     *                                    decrypted data should be written. If null
     *                                    or unspecified, the decrypted data is
     *                                    returned in the results array.
     * @param bool    $ignoreVerifyErrors enables ignoring of signature
     *                                    verification errors caused by missing public key
     *                                    When enabled Crypt_GPG_KeyNotFoundException
     *                                    will not be thrown.
     *
     * @return array two element array. The array has an element 'data'
     *               containing the decrypted data and an element
     *               'signatures' containing an array of
     *               {@link Crypt_GPG_Signature} objects for the signed data.
     *               If the decrypted data is written to a file, the 'data'
     *               element is null.
     *
     * @throws Crypt_GPG_KeyNotFoundException if the private key needed to
     *         decrypt the data or the public key to verify the signature
     *         is not in the user's keyring.
     *
     * @throws Crypt_GPG_NoDataException if specified data does not contain
     *         GPG encrypted data.
     *
     * @throws Crypt_GPG_BadPassphraseException if a required passphrase is
     *         incorrect or if a required passphrase is not specified. See
     *         {@link Crypt_GPG::addDecryptKey()}.
     *
     * @throws Crypt_GPG_FileException if the output file is not writeable or
     *         if the input file is not readable.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    public function decryptAndVerifyFile($encryptedFile, $decryptedFile = null, $ignoreVerifyErrors = false)
    {
        return $this->_decryptAndVerify($encryptedFile, true, $decryptedFile, $ignoreVerifyErrors);
    }

    /**
     * Signs data
     *
     * Data may be signed using any one of the three available signing modes:
     * - {@link Crypt_GPG::SIGN_MODE_NORMAL}
     * - {@link Crypt_GPG::SIGN_MODE_CLEAR}
     * - {@link Crypt_GPG::SIGN_MODE_DETACHED}
     *
     * @param string  $data     the data to be signed.
     * @param int     $mode     optional. The data signing mode to use. Should
     *                          be one of {@link Crypt_GPG::SIGN_MODE_NORMAL},
     *                          {@link Crypt_GPG::SIGN_MODE_CLEAR} or
     *                          {@link Crypt_GPG::SIGN_MODE_DETACHED}. If not
     *                          specified, defaults to
     *                          <kbd>Crypt_GPG::SIGN_MODE_NORMAL</kbd>.
     * @param bool    $armor    optional. If true, ASCII armored data is
     *                          returned; otherwise, binary data is returned.
     *                          Defaults to true. This has no effect if the
     *                          mode <kbd>Crypt_GPG::SIGN_MODE_CLEAR</kbd> is
     *                          used.
     * @param bool    $textmode optional. If true, line-breaks in signed data
     *                          are normalized. Use this option when signing
     *                          e-mail, or for greater compatibility between
     *                          systems with different line-break formats.
     *                          Defaults to false. This has no effect if the
     *                          mode <kbd>Crypt_GPG::SIGN_MODE_CLEAR</kbd> is
     *                          used as clear-signing always uses textmode.
     *
     * @return string the signed data, or the signature data if a detached
     *                signature is requested.
     *
     * @throws Crypt_GPG_KeyNotFoundException if no signing key is specified.
     *         See {@link Crypt_GPG::addSignKey()}.
     *
     * @throws Crypt_GPG_BadPassphraseException if a specified passphrase is
     *         incorrect or if a required passphrase is not specified.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    public function sign(
        $data,
        $mode = self::SIGN_MODE_NORMAL,
        $armor = self::ARMOR_ASCII,
        $textmode = self::TEXT_RAW
    ) {
        return $this->_sign($data, false, null, $mode, $armor, $textmode);
    }

    /**
     * Signs a file
     *
     * The file may be signed using any one of the three available signing
     * modes:
     * - {@link Crypt_GPG::SIGN_MODE_NORMAL}
     * - {@link Crypt_GPG::SIGN_MODE_CLEAR}
     * - {@link Crypt_GPG::SIGN_MODE_DETACHED}
     *
     * @param string  $filename   the name of the file containing the data to
     *                            be signed.
     * @param string  $signedFile optional. The name of the file in which the
     *                            signed data should be stored. If null or
     *                            unspecified, the signed data is returned as a
     *                            string.
     * @param int     $mode       optional. The data signing mode to use. Should
     *                            be one of {@link Crypt_GPG::SIGN_MODE_NORMAL},
     *                            {@link Crypt_GPG::SIGN_MODE_CLEAR} or
     *                            {@link Crypt_GPG::SIGN_MODE_DETACHED}. If not
     *                            specified, defaults to
     *                            <kbd>Crypt_GPG::SIGN_MODE_NORMAL</kbd>.
     * @param bool    $armor      optional. If true, ASCII armored data is
     *                            returned; otherwise, binary data is returned.
     *                            Defaults to true. This has no effect if the
     *                            mode <kbd>Crypt_GPG::SIGN_MODE_CLEAR</kbd> is
     *                            used.
     * @param bool    $textmode   optional. If true, line-breaks in signed data
     *                            are normalized. Use this option when signing
     *                            e-mail, or for greater compatibility between
     *                            systems with different line-break formats.
     *                            Defaults to false. This has no effect if the
     *                            mode <kbd>Crypt_GPG::SIGN_MODE_CLEAR</kbd> is
     *                            used as clear-signing always uses textmode.
     *
     * @return void|string if the <kbd>$signedFile</kbd> parameter is null, a
     *                     string containing the signed data (or the signature
     *                     data if a detached signature is requested) is
     *                     returned.
     *
     * @throws Crypt_GPG_KeyNotFoundException if no signing key is specified.
     *         See {@link Crypt_GPG::addSignKey()}.
     *
     * @throws Crypt_GPG_BadPassphraseException if a specified passphrase is
     *         incorrect or if a required passphrase is not specified.
     *
     * @throws Crypt_GPG_FileException if the output file is not writeable or
     *         if the input file is not readable.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    public function signFile(
        $filename,
        $signedFile = null,
        $mode = self::SIGN_MODE_NORMAL,
        $armor = self::ARMOR_ASCII,
        $textmode = self::TEXT_RAW
    ) {
        return $this->_sign(
            $filename,
            true,
            $signedFile,
            $mode,
            $armor,
            $textmode
        );
    }

    /**
     * Verifies signed data
     *
     * The {@link Crypt_GPG::decrypt()} method may be used to get the original
     * message if the signed data is not clearsigned and does not use a
     * detached signature.
     *
     * @param string $signedData the signed data to be verified.
     * @param string $signature  optional. If verifying data signed using a
     *                           detached signature, this must be the detached
     *                           signature data. The data that was signed is
     *                           specified in <kbd>$signedData</kbd>.
     *
     * @return array an array of {@link Crypt_GPG_Signature} objects for the
     *               signed data. For each signature that is valid, the
     *               {@link Crypt_GPG_Signature::isValid()} will return true.
     *
     * @throws Crypt_GPG_KeyNotFoundException if the public key needed for
     *         signature verification is not in the user's keyring.
     *
     * @throws Crypt_GPG_NoDataException if the provided data is not signed
     *         data.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     *
     * @see Crypt_GPG_Signature
     */
    public function verify($signedData, $signature = '')
    {
        return $this->_verify($signedData, false, $signature);
    }

    /**
     * Verifies a signed file
     *
     * The {@link Crypt_GPG::decryptFile()} method may be used to get the
     * original message if the signed data is not clearsigned and does not use
     * a detached signature.
     *
     * @param string $filename  the signed file to be verified.
     * @param string $signature optional. If verifying a file signed using a
     *                          detached signature, this must be the detached
     *                          signature data. The file that was signed is
     *                          specified in <kbd>$filename</kbd>.
     *
     * @return array an array of {@link Crypt_GPG_Signature} objects for the
     *               signed data. For each signature that is valid, the
     *               {@link Crypt_GPG_Signature::isValid()} will return true.
     *
     * @throws Crypt_GPG_KeyNotFoundException if the public key needed for
     *         signature verification is not in the user's keyring.
     *
     * @throws Crypt_GPG_NoDataException if the provided data is not signed
     *         data.
     *
     * @throws Crypt_GPG_FileException if the input file is not readable.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     *
     * @see Crypt_GPG_Signature
     */
    public function verifyFile($filename, $signature = '')
    {
        return $this->_verify($filename, true, $signature);
    }

    /**
     * Adds a key to use for decryption
     *
     * @param mixed  $key        the key to use. This may be a key identifier,
     *                           user id, fingerprint, {@link Crypt_GPG_Key} or
     *                           {@link Crypt_GPG_SubKey}. The key must be able
     *                           to encrypt.
     * @param string $passphrase optional. The passphrase of the key required
     *                           for decryption.
     *
     * @return Crypt_GPG the current object, for fluent interface.
     *
     * @see Crypt_GPG::decrypt()
     * @see Crypt_GPG::decryptFile()
     * @see Crypt_GPG::clearDecryptKeys()
     * @see Crypt_GPG::_addKey()
     *
     * @sensitive $passphrase
     */
    public function addDecryptKey($key, $passphrase = null)
    {
        $this->_addKey($this->decryptKeys, false, false, $key, $passphrase);
        return $this;
    }

    /**
     * Adds a key to use for encryption
     *
     * @param mixed $key the key to use. This may be a key identifier, user id
     *                   user id, fingerprint, {@link Crypt_GPG_Key} or
     *                   {@link Crypt_GPG_SubKey}. The key must be able to
     *                   encrypt.
     *
     * @return Crypt_GPG the current object, for fluent interface.
     *
     * @see Crypt_GPG::encrypt()
     * @see Crypt_GPG::encryptFile()
     * @see Crypt_GPG::clearEncryptKeys()
     * @see Crypt_GPG::_addKey()
     */
    public function addEncryptKey($key)
    {
        $this->_addKey($this->encryptKeys, true, false, $key);
        return $this;
    }

    /**
     * Adds a key to use for signing
     *
     * @param mixed  $key        the key to use. This may be a key identifier,
     *                           user id, fingerprint, {@link Crypt_GPG_Key} or
     *                           {@link Crypt_GPG_SubKey}. The key must be able
     *                           to sign.
     * @param string $passphrase optional. The passphrase of the key required
     *                           for signing.
     *
     * @return Crypt_GPG the current object, for fluent interface.
     *
     * @see Crypt_GPG::sign()
     * @see Crypt_GPG::signFile()
     * @see Crypt_GPG::clearSignKeys()
     * @see Crypt_GPG::_addKey()
     *
     * @sensitive $passphrase
     */
    public function addSignKey($key, $passphrase = null)
    {
        $this->_addKey($this->signKeys, false, true, $key, $passphrase);
        return $this;
    }

    /**
     * Register a private key passphrase for import/export (GnuPG 2.1)
     *
     * @param mixed  $key        The key to use. This must be a key identifier,
     *                           or fingerprint.
     * @param string $passphrase The passphrase of the key.
     *
     * @return Crypt_GPG the current object, for fluent interface.
     *
     * @see Crypt_GPG::clearPassphrases()
     * @see Crypt_GPG::importKey()
     * @see Crypt_GPG::exportKey()
     *
     * @sensitive $passphrase
     */
    public function addPassphrase($key, $passphrase)
    {
        $this->passphrases[$key] = $passphrase;
        return $this;
    }

    /**
     * Clears all decryption keys
     *
     * @return Crypt_GPG the current object, for fluent interface.
     *
     * @see Crypt_GPG::decrypt()
     * @see Crypt_GPG::addDecryptKey()
     */
    public function clearDecryptKeys()
    {
        $this->decryptKeys = array();
        return $this;
    }

    /**
     * Clears all encryption keys
     *
     * @return Crypt_GPG the current object, for fluent interface.
     *
     * @see Crypt_GPG::encrypt()
     * @see Crypt_GPG::addEncryptKey()
     */
    public function clearEncryptKeys()
    {
        $this->encryptKeys = array();
        return $this;
    }

    /**
     * Clears all signing keys
     *
     * @return Crypt_GPG the current object, for fluent interface.
     *
     * @see Crypt_GPG::sign()
     * @see Crypt_GPG::addSignKey()
     */
    public function clearSignKeys()
    {
        $this->signKeys = array();
        return $this;
    }

    /**
     * Clears all private key passphrases
     *
     * @return Crypt_GPG the current object, for fluent interface.
     *
     * @see Crypt_GPG::importKey()
     * @see Crypt_GPG::exportKey()
     * @see Crypt_GPG::addPassphrase()
     */
    public function clearPassphrases()
    {
        $this->passphrases = array();
        return $this;
    }

    /**
     * Tell if there are encryption keys registered
     *
     * @return bool True if the data shall be encrypted
     */
    public function hasEncryptKeys()
    {
        return count($this->encryptKeys) > 0;
    }

    /**
     * Tell if there are signing keys registered
     *
     * @return bool True if the data shall be signed
     */
    public function hasSignKeys()
    {
        return count($this->signKeys) > 0;
    }

    /**
     * Get list of GnuPG warnings collected on last operation.
     *
     * @return array List of warning messages
     */
    public function getWarnings()
    {
        return $this->engine->getProcessData('Warnings');
    }

    /**
     * Adds a key to one of the internal key arrays
     *
     * This handles resolving full key objects from the provided
     * <kbd>$key</kbd> value.
     *
     * @param array   &$array     The array to which the key should be added
     * @param bool    $encrypt    Whether or not the key must be able to
     *                            encrypt
     * @param bool    $sign       Whether or not the key must be able to sign
     * @param mixed   $key        The key to add. This may be a key identifier,
     *                            user id, fingerprint, {@link Crypt_GPG_Key} or
     *                            {@link Crypt_GPG_SubKey}
     * @param string  $passphrase Optional passphrase associated with the key
     *
     * @return void
     *
     * @sensitive $passphrase
     */
    protected function _addKey(array &$array, $encrypt, $sign, $key,
        $passphrase = null
    ) {
        $subKeys = array();

        if (is_scalar($key)) {
            $keys = $this->getKeys($key);
            if (count($keys) == 0) {
                throw new Crypt_GPG_KeyNotFoundException(
                    'Key not found: ' . $key,
                    self::ERROR_KEY_NOT_FOUND,
                    $key
                );
            }
            $key = $keys[0];
        }

        if ($key instanceof Crypt_GPG_Key) {
            if ($encrypt && !$key->canEncrypt()) {
                throw new InvalidArgumentException(
                    'Key "' . $key . '" cannot encrypt.'
                );
            }

            if ($sign && !$key->canSign()) {
                throw new InvalidArgumentException(
                    'Key "' . $key . '" cannot sign.'
                );
            }

            foreach ($key->getSubKeys() as $subKey) {
                $canEncrypt = $subKey->canEncrypt();
                $canSign    = $subKey->canSign();
                if (($encrypt && $sign && $canEncrypt && $canSign)
                    || ($encrypt && !$sign && $canEncrypt)
                    || (!$encrypt && $sign && $canSign)
                    || (!$encrypt && !$sign)
                ) {
                    // We add all subkeys that meet the requirements because we
                    // were not told which subkey is required.
                    $subKeys[] = $subKey;
                }
            }
        } elseif ($key instanceof Crypt_GPG_SubKey) {
            $subKeys[] = $key;
        }

        if (count($subKeys) === 0) {
            throw new InvalidArgumentException(
                'Key "' . $key . '" is not in a recognized format.'
            );
        }

        foreach ($subKeys as $subKey) {
            if ($encrypt && !$subKey->canEncrypt()) {
                throw new InvalidArgumentException(
                    'Key "' . $key . '" cannot encrypt.'
                );
            }

            if ($sign && !$subKey->canSign()) {
                throw new InvalidArgumentException(
                    'Key "' . $key . '" cannot sign.'
                );
            }

            $array[$subKey->getId()] = array(
                'fingerprint' => $subKey->getFingerprint(),
                'passphrase'  => $passphrase
            );
        }
    }

    /**
     * Imports a public or private key into the keyring
     *
     * @param string $key    The key to be imported.
     * @param bool   $isFile Whether or not the input is a filename.
     *
     * @return array an associative array containing the following elements:
     *               - <kbd>fingerprint</kbd>       - the fingerprint of the
     *                                                imported key,
     *               - <kbd>public_imported</kbd>   - the number of public
     *                                                keys imported,
     *               - <kbd>public_unchanged</kbd>  - the number of unchanged
     *                                                public keys,
     *               - <kbd>private_imported</kbd>  - the number of private
     *                                                keys imported,
     *               - <kbd>private_unchanged</kbd> - the number of unchanged
     *                                                private keys.
     *
     * @throws Crypt_GPG_NoDataException if the key data is missing or if the
     *         data is is not valid key data.
     *
     * @throws Crypt_GPG_FileException if the key file is not readable.
     *
     * @throws Crypt_GPG_BadPassphraseException if a required passphrase is
     *         incorrect or if a required passphrase is not specified. See
     *         {@link Crypt_GPG::addPassphrase()}.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    protected function _importKey($key, $isFile)
    {
        $result    = array();
        $arguments = array();
        $input     = $this->_prepareInput($key, $isFile, false);
        $version   = $this->engine->getVersion();

        if (version_compare($version, '1.0.5', 'ge')
            && version_compare($version, '1.0.7', 'lt')
        ) {
            $arguments[] = '--allow-secret-key-import';
        }

        if (empty($this->passphrases)) {
            $arguments[] = '--batch';
        }

        $this->engine->reset();
        $this->engine->setPins($this->passphrases);
        $this->engine->setOperation('--import', $arguments);
        $this->engine->setInput($input);
        $this->engine->run();

        return $this->engine->getProcessData('Import');
    }

    /**
     * Exports a private or public key from the keyring
     *
     * If more than one key fingerprint is available for the specified
     * <kbd>$keyId</kbd> (for example, if you use a non-unique uid) only the
     * first key is exported.
     *
     * @param string  $keyId   either the full uid of the key, the email
     *                         part of the uid of the key or the key id.
     * @param bool    $armor   optional. If true, ASCII armored data is returned;
     *                         otherwise, binary data is returned. Defaults to
     *                         true.
     * @param bool    $private return private instead of public key
     *
     * @return string the key data.
     *
     * @throws Crypt_GPG_KeyNotFoundException if a key with the given
     *         <kbd>$keyId</kbd> is not found.
     *
     * @throws Crypt_GPG_BadPassphraseException if a required passphrase is
     *         incorrect or if a required passphrase is not specified. See
     *         {@link Crypt_GPG::addPassphrase()}.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    protected function _exportKey($keyId, $armor = true, $private = false)
    {
        $fingerprint = $this->getFingerprint($keyId);

        if ($fingerprint === null) {
            throw new Crypt_GPG_KeyNotFoundException(
                'Key not found: ' . $keyId,
                self::ERROR_KEY_NOT_FOUND,
                $keyId
            );
        }

        $keyData   = '';
        $operation = $private ? '--export-secret-keys' : '--export';
        $operation .= ' -- ' . escapeshellarg($fingerprint);
        $arguments = $armor ? array('--armor') : array();

        $this->engine->reset();
        $this->engine->setPins($this->passphrases);
        $this->engine->setOutput($keyData);
        $this->engine->setOperation($operation, $arguments);
        $this->engine->run();

        return $keyData;
    }

    /**
     * Encrypts data
     *
     * @param string      $data       The data to encrypt.
     * @param bool        $isFile     Whether or not the data is a filename.
     * @param string|null $outputFile The filename of the file in which to store
     *                                the encrypted data. If null, the encrypted
     *                                data is returned as a string.
     * @param bool        $armor      If true, ASCII armored data is returned;
     *                                otherwise, binary data is returned.
     *
     * @return void|string if the <kbd>$outputFile</kbd> parameter is null, a
     *                     string containing the encrypted data is returned.
     *
     * @throws Crypt_GPG_KeyNotFoundException if no encryption key is specified.
     *         See {@link Crypt_GPG::addEncryptKey()}.
     *
     * @throws Crypt_GPG_FileException if the output file is not writeable or
     *         if the input file is not readable.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    protected function _encrypt($data, $isFile, $outputFile, $armor)
    {
        if (!$this->hasEncryptKeys()) {
            throw new Crypt_GPG_KeyNotFoundException(
                'No encryption keys specified.'
            );
        }

        $input     = $this->_prepareInput($data, $isFile);
        $output    = $this->_prepareOutput($outputFile, $input);
        $arguments = $armor ? array('--armor') : array();

        foreach ($this->encryptKeys as $key) {
            $arguments[] = '--recipient ' . escapeshellarg($key['fingerprint']);
        }

        $this->engine->reset();
        $this->engine->setInput($input);
        $this->engine->setOutput($output);
        $this->engine->setOperation('--encrypt', $arguments);
        $this->engine->run();

        if ($outputFile === null) {
            return $output;
        }
    }

    /**
     * Decrypts data
     *
     * @param string      $data       The data to be decrypted.
     * @param bool        $isFile     Whether or not the data is a filename.
     * @param string|null $outputFile The name of the file to which the decrypted
     *                                data should be written. If null, the decrypted
     *                                data is returned as a string.
     *
     * @return void|string if the <kbd>$outputFile</kbd> parameter is null, a
     *                     string containing the decrypted data is returned.
     *
     * @throws Crypt_GPG_KeyNotFoundException if the private key needed to
     *         decrypt the data is not in the user's keyring.
     *
     * @throws Crypt_GPG_NoDataException if specified data does not contain
     *         GPG encrypted data.
     *
     * @throws Crypt_GPG_BadPassphraseException if a required passphrase is
     *         incorrect or if a required passphrase is not specified. See
     *         {@link Crypt_GPG::addDecryptKey()}.
     *
     * @throws Crypt_GPG_FileException if the output file is not writeable or
     *         if the input file is not readable.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    protected function _decrypt($data, $isFile, $outputFile)
    {
        $input  = $this->_prepareInput($data, $isFile, false);
        $output = $this->_prepareOutput($outputFile, $input);

        $this->engine->reset();
        $this->engine->setPins($this->decryptKeys);
        $this->engine->setOperation('--decrypt --skip-verify');
        $this->engine->setInput($input);
        $this->engine->setOutput($output);
        $this->engine->run();

        if ($outputFile === null) {
            return $output;
        }
    }

    /**
     * Signs data
     *
     * @param string      $data       The data to be signed.
     * @param bool        $isFile     Whether or not the data is a filename.
     * @param string|null $outputFile The name of the file in which the signed data
     *                                should be stored. If null, the signed data is
     *                                returned as a string.
     * @param int         $mode       The data signing mode to use. Should be one of
     *                                {@link Crypt_GPG::SIGN_MODE_NORMAL},
     *                                {@link Crypt_GPG::SIGN_MODE_CLEAR} or
     *                                {@link Crypt_GPG::SIGN_MODE_DETACHED}.
     * @param bool        $armor      If true, ASCII armored data is returned;
     *                                otherwise, binary data is returned. This has
     *                                no effect if the mode
     *                                <kbd>Crypt_GPG::SIGN_MODE_CLEAR</kbd> is
     *                                used.
     * @param bool        $textmode   If true, line-breaks in signed data be
     *                                normalized. Use this option when signing
     *                                e-mail, or for greater compatibility between
     *                                systems with different line-break formats.
     *                                Defaults to false. This has no effect if the
     *                                mode <kbd>Crypt_GPG::SIGN_MODE_CLEAR</kbd> is
     *                                used as clear-signing always uses textmode.
     *
     * @return void|string if the <kbd>$outputFile</kbd> parameter is null, a
     *                     string containing the signed data (or the signature
     *                     data if a detached signature is requested) is
     *                     returned.
     *
     * @throws Crypt_GPG_KeyNotFoundException if no signing key is specified.
     *         See {@link Crypt_GPG::addSignKey()}.
     *
     * @throws Crypt_GPG_BadPassphraseException if a specified passphrase is
     *         incorrect or if a required passphrase is not specified.
     *
     * @throws Crypt_GPG_FileException if the output file is not writeable or
     *         if the input file is not readable.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    protected function _sign($data, $isFile, $outputFile, $mode, $armor,
        $textmode
    ) {
        if (!$this->hasSignKeys()) {
            throw new Crypt_GPG_KeyNotFoundException(
                'No signing keys specified.'
            );
        }

        $input  = $this->_prepareInput($data, $isFile);
        $output = $this->_prepareOutput($outputFile, $input);

        switch ($mode) {
        case self::SIGN_MODE_DETACHED:
            $operation = '--detach-sign';
            break;
        case self::SIGN_MODE_CLEAR:
            $operation = '--clearsign';
            break;
        case self::SIGN_MODE_NORMAL:
        default:
            $operation = '--sign';
            break;
        }

        $arguments = array();

        if ($armor) {
            $arguments[] = '--armor';
        }
        if ($textmode) {
            $arguments[] = '--textmode';
        }

        foreach ($this->signKeys as $key) {
            $arguments[] = '--local-user ' .
                escapeshellarg($key['fingerprint']);
        }

        $this->engine->reset();
        $this->engine->setPins($this->signKeys);
        $this->engine->setInput($input);
        $this->engine->setOutput($output);
        $this->engine->setOperation($operation, $arguments);
        $this->engine->run();

        if ($outputFile === null) {
            return $output;
        }
    }

    /**
     * Encrypts and signs data
     *
     * @param string      $data       The data to be encrypted and signed.
     * @param bool        $isFile     Whether or not the data is a filename.
     * @param string|null $outputFile The name of the file in which the encrypted,
     *                                signed data should be stored. If null, the
     *                                encrypted, signed data is returned as a
     *                                string.
     * @param bool        $armor      If true, ASCII armored data is returned;
     *                                otherwise, binary data is returned.
     *
     * @return void|string if the <kbd>$outputFile</kbd> parameter is null, a
     *                     string containing the encrypted, signed data is
     *                     returned.
     *
     * @throws Crypt_GPG_KeyNotFoundException if no encryption key is specified
     *         or if no signing key is specified. See
     *         {@link Crypt_GPG::addEncryptKey()} and
     *         {@link Crypt_GPG::addSignKey()}.
     *
     * @throws Crypt_GPG_BadPassphraseException if a specified passphrase is
     *         incorrect or if a required passphrase is not specified.
     *
     * @throws Crypt_GPG_FileException if the output file is not writeable or
     *         if the input file is not readable.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    protected function _encryptAndSign($data, $isFile, $outputFile, $armor)
    {
        if (!$this->hasSignKeys()) {
            throw new Crypt_GPG_KeyNotFoundException(
                'No signing keys specified.'
            );
        }

        if (!$this->hasEncryptKeys()) {
            throw new Crypt_GPG_KeyNotFoundException(
                'No encryption keys specified.'
            );
        }

        $input     = $this->_prepareInput($data, $isFile);
        $output    = $this->_prepareOutput($outputFile, $input);
        $arguments = $armor ? array('--armor') : array();

        foreach ($this->signKeys as $key) {
            $arguments[] = '--local-user ' .
                escapeshellarg($key['fingerprint']);
        }

        foreach ($this->encryptKeys as $key) {
            $arguments[] = '--recipient ' . escapeshellarg($key['fingerprint']);
        }

        $this->engine->reset();
        $this->engine->setPins($this->signKeys);
        $this->engine->setInput($input);
        $this->engine->setOutput($output);
        $this->engine->setOperation('--encrypt --sign', $arguments);
        $this->engine->run();

        if ($outputFile === null) {
            return $output;
        }
    }

    /**
     * Verifies data
     *
     * @param string  $data      the signed data to be verified.
     * @param bool    $isFile    whether or not the data is a filename.
     * @param string  $signature if verifying a file signed using a detached
     *                           signature, this must be the detached signature
     *                           data. Otherwise, specify ''.
     *
     * @return array an array of {@link Crypt_GPG_Signature} objects for the
     *               signed data.
     *
     * @throws Crypt_GPG_KeyNotFoundException if the public key needed for
     *         signature verification is not in the user's keyring.
     *
     * @throws Crypt_GPG_NoDataException if the provided data is not signed
     *         data.
     *
     * @throws Crypt_GPG_FileException if the input file is not readable.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     *
     * @see Crypt_GPG_Signature
     */
    protected function _verify($data, $isFile, $signature)
    {
        if ($signature == '') {
            $operation = '--verify';
            $arguments = array();
        } else {
            // Signed data goes in FD_MESSAGE, detached signature data goes in
            // FD_INPUT.
            $operation = '--verify - "-&' . Crypt_GPG_Engine::FD_MESSAGE. '"';
            $arguments = array('--enable-special-filenames');
        }

        $input = $this->_prepareInput($data, $isFile, false);

        $this->engine->reset();

        if ($signature == '') {
            // signed or clearsigned data
            $this->engine->setInput($input);
        } else {
            // detached signature
            $this->engine->setInput($signature);
            $this->engine->setMessage($input);
        }

        $this->engine->setOperation($operation, $arguments);
        $this->engine->run();

        return $this->engine->getProcessData('Signatures');
    }

    /**
     * Decrypts and verifies encrypted, signed data
     *
     * @param string      $data               The encrypted signed data to be decrypted and
     *                                        verified.
     * @param bool        $isFile             Whether or not the data is a filename.
     * @param string|null $outputFile         The name of the file to which the decrypted
     *                                        data should be written. If null, the decrypted
     *                                        data is returned in the results array.
     * @param bool        $ignoreVerifyErrors Enables ignoring of signature verification
     *                                        errors caused by missing public key.
     *                                        When enabled Crypt_GPG_KeyNotFoundException
     *                                        will not be thrown.
     *
     * @return array two element array. The array has an element 'data'
     *               containing the decrypted data and an element
     *               'signatures' containing an array of
     *               {@link Crypt_GPG_Signature} objects for the signed data.
     *               If the decrypted data is written to a file, the 'data'
     *               element is null.
     *
     * @throws Crypt_GPG_KeyNotFoundException if the private key needed to
     *         decrypt the data is not in the user's keyring or if the public
     *         key needed for verification is not in the user's keyring.
     *
     * @throws Crypt_GPG_NoDataException if specified data does not contain
     *         GPG signed, encrypted data.
     *
     * @throws Crypt_GPG_BadPassphraseException if a required passphrase is
     *         incorrect or if a required passphrase is not specified. See
     *         {@link Crypt_GPG::addDecryptKey()}.
     *
     * @throws Crypt_GPG_FileException if the output file is not writeable or
     *         if the input file is not readable.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     *
     * @see Crypt_GPG_Signature
     */
    protected function _decryptAndVerify($data, $isFile, $outputFile, $ignoreVerifyErrors = false)
    {
        $input  = $this->_prepareInput($data, $isFile, false);
        $output = $this->_prepareOutput($outputFile, $input);

        $this->engine->reset();
        $this->engine->setPins($this->decryptKeys);
        $this->engine->setInput($input);
        $this->engine->setOutput($output);
        $this->engine->setOperation('--decrypt');
        $this->engine->setProcessData('IgnoreVerifyErrors', $ignoreVerifyErrors);
        $this->engine->run();

        $return = array(
            'data'       => null,
            'signatures' => $this->engine->getProcessData('Signatures')
        );

        if ($outputFile === null) {
            $return['data'] = $output;
        }

        return $return;
    }

    /**
     * Prepares command input
     *
     * @param string $data       The input data
     * @param bool   $isFile     Whether or not the input is a filename
     * @param bool   $allowEmpty Whether to check if the input is not empty
     *
     * @throws Crypt_GPG_NoDataException if the key data is missing.
     * @throws Crypt_GPG_FileException if the file is not readable.
     *
     * @return string The command input
     */
    protected function _prepareInput($data, $isFile = false, $allowEmpty = true)
    {
        if ($isFile) {
            $input = @fopen($data, 'rb');
            if ($input === false) {
                throw new Crypt_GPG_FileException(
                    'Could not open input file "' . $data . '"',
                    0,
                    $data
                );
            }
        } else {
            $input = strval($data);
            if (!$allowEmpty && $input === '') {
                throw new Crypt_GPG_NoDataException(
                    'No valid input data found.',
                    self::ERROR_NO_DATA
                );
            }
        }

        return $input;
    }

    /**
     * Prepares command output
     *
     * @param string|null $outputFile The name of the file in which the output
     *                                data should be stored. If null, the output
     *                                data is returned as a string.
     * @param resource    $input      The input resource, in case it would need
     *                                to be released (closed) on exception.
     *
     * @throws Crypt_GPG_FileException if the file is not writeable.
     *
     * @return string|resource The command output
     */
    protected function _prepareOutput($outputFile, $input = null)
    {
        if ($outputFile === null) {
            $output = '';
        } else {
            $output = @fopen($outputFile, 'wb');
            if ($output === false) {
                if (is_resource($input)) {
                    fclose($input);
                }
                throw new Crypt_GPG_FileException(
                    'Could not open output file "' . $outputFile . '"',
                    0,
                    $outputFile
                );
            }
        }

        return $output;
    }
}
PK}c�\����=?=?$pear/crypt_gpg/Crypt/GPGAbstract.phpnu�[���<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * Crypt_GPG is a package to use GPG from PHP
 *
 * This package provides an object oriented interface to GNU Privacy
 * Guard (GPG). It requires the GPG executable to be on the system.
 *
 * Though GPG can support symmetric-key cryptography, this package is intended
 * only to facilitate public-key cryptography.
 *
 * This file contains an abstract implementation of a user of the
 * {@link Crypt_GPG_Engine} class.
 *
 * LICENSE:
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see
 * <http://www.gnu.org/licenses/>
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Nathan Fredrickson <nathan@silverorange.com>
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2005-2013 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 * @link      http://pear.php.net/manual/en/package.encryption.crypt-gpg.php
 * @link      http://www.gnupg.org/
 */

/**
 * GPG key class
 */
require_once 'Crypt/GPG/Key.php';

/**
 * GPG sub-key class
 */
require_once 'Crypt/GPG/SubKey.php';

/**
 * GPG user id class
 */
require_once 'Crypt/GPG/UserId.php';

/**
 * GPG process and I/O engine class
 */
require_once 'Crypt/GPG/Engine.php';

/**
 * Base class for implementing a user of {@link Crypt_GPG_Engine}
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Nathan Fredrickson <nathan@silverorange.com>
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2005-2013 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 * @link      http://www.gnupg.org/
 */
abstract class Crypt_GPGAbstract
{
    /**
     * Error code returned when there is no error.
     */
    const ERROR_NONE = 0;

    /**
     * Error code returned when an unknown or unhandled error occurs.
     */
    const ERROR_UNKNOWN = 1;

    /**
     * Error code returned when a bad passphrase is used.
     */
    const ERROR_BAD_PASSPHRASE = 2;

    /**
     * Error code returned when a required passphrase is missing.
     */
    const ERROR_MISSING_PASSPHRASE = 3;

    /**
     * Error code returned when a key that is already in the keyring is
     * imported.
     */
    const ERROR_DUPLICATE_KEY = 4;

    /**
     * Error code returned the required data is missing for an operation.
     *
     * This could be missing key data, missing encrypted data or missing
     * signature data.
     */
    const ERROR_NO_DATA = 5;

    /**
     * Error code returned when an unsigned key is used.
     */
    const ERROR_UNSIGNED_KEY = 6;

    /**
     * Error code returned when a key that is not self-signed is used.
     */
    const ERROR_NOT_SELF_SIGNED = 7;

    /**
     * Error code returned when a public or private key that is not in the
     * keyring is used.
     */
    const ERROR_KEY_NOT_FOUND = 8;

    /**
     * Error code returned when an attempt to delete public key having a
     * private key is made.
     */
    const ERROR_DELETE_PRIVATE_KEY = 9;

    /**
     * Error code returned when one or more bad signatures are detected.
     */
    const ERROR_BAD_SIGNATURE = 10;

    /**
     * Error code returned when there is a problem reading GnuPG data files.
     */
    const ERROR_FILE_PERMISSIONS = 11;

    /**
     * Error code returned when a key could not be created.
     */
    const ERROR_KEY_NOT_CREATED = 12;

    /**
     * Error code returned when bad key parameters are used during key
     * generation.
     */
    const ERROR_BAD_KEY_PARAMS = 13;

    /**
     * URI at which package bugs may be reported.
     */
    const BUG_URI = 'http://pear.php.net/bugs/report.php?package=Crypt_GPG';

    /**
     * Engine used to control the GPG subprocess
     *
     * @var Crypt_GPG_Engine
     *
     * @see Crypt_GPGAbstract::setEngine()
     */
    protected $engine = null;

    /**
     * Creates a new GPG object
     *
     * Available options are:
     *
     * - <kbd>string homedir</kbd> - the directory where the GPG keyring files are
     *                      stored. If not specified, Crypt_GPG uses the default
     *                      of <kbd>~/.gnupg</kbd>.
     * - <kbd>string publicKeyring</kbd> - the file path of the public keyring.
     *                      Use this if the public keyring is not in the homedir,
     *                      or if the keyring is in a directory not writable
     *                      by the process invoking GPG (like Apache). Then you
     *                      can specify the path to the keyring with this option
     *                      (/foo/bar/pubring.gpg), and specify a writable directory
     *                      (like /tmp) using the <i>homedir</i> option.
     * - <kbd>string privateKeyring</kbd> - the file path of the private keyring.
     *                      Use this if the private keyring is not in the homedir,
     *                      or if the keyring is in a directory not writable
     *                      by the process invoking GPG (like Apache). Then
     *                      you can specify the path to the keyring with this option
     *                      (/foo/bar/secring.gpg), and specify a writable directory
     *                      (like /tmp) using the <i>homedir</i> option.
     * - <kbd>string trustDb</kbd> - the file path of the web-of-trust database.
     *                      Use this if the trust database is not in the homedir, or
     *                      if the database is in a directory not writable
     *                      by the process invoking GPG (like Apache). Then you can
     *                      specify the path to the trust database with this option
     *                      (/foo/bar/trustdb.gpg), and specify a writable directory
     *                      (like /tmp) using the <i>homedir</i> option.
     * - <kbd>string binary</kbd> - the location of the GPG binary.
     *                      If not specified, the driver attempts to auto-detect
     *                      the GPG binary location using a list of known default
     *                      locations for the current operating system. The option
     *                      <kbd>gpgBinary</kbd> is a deprecated alias.
     * - <kbd>string agent</kbd> - the location of the GnuPG agent binary.
     *                      The gpg-agent is only used for GnuPG 2.x. If not
     *                      specified, the engine attempts to auto-detect
     *                      the gpg-agent binary location using a list of
     *                      know default locations for the current operating system.
     * - <kbd>string|false gpgconf</kbd> - the location of the GnuPG conf binary.
     *                      The gpgconf is only used for GnuPG >= 2.1. If not
     *                      specified, the engine attempts to auto-detect
     *                      the location using a list of know default locations.
     *                      When set to FALSE `gpgconf --kill` will not be executed
     *                      via destructor.
     * - <kbd>string digest-algo</kbd> - Sets the message digest algorithm.
     * - <kbd>string cipher-algo</kbd> - Sets the symmetric cipher.
     * - <kbd>string compress-algo</kbd> - Sets the compression algorithm.
     * - <kbd>bool   strict</kbd> - In strict mode clock problems on subkeys
     *                      and signatures are not ignored (--ignore-time-conflict
     *                      and --ignore-valid-from options).
     * - <kbd>mixed debug</kbd> - whether or not to use debug mode.
     *                      When debug mode is on, all communication to and from
     *                      the GPG subprocess is logged. This can be useful to
     *                      diagnose errors when using Crypt_GPG.
     * - <kbd>array options</kbd> - additional per-command options to the GPG
     *                      command. Key of the array is a command (e.g.
     *                      gen-key, import, sign, encrypt, list-keys).
     *                      Value is a string containing command line arguments to be
     *                      added to the related command. For example:
     *                      array('sign' => '--emit-version').
     *
     * @param array $options optional. An array of options used to create the
     *                       GPG object. All options are optional and are
     *                       represented as key-value pairs.
     *
     * @throws Crypt_GPG_FileException if the <kbd>homedir</kbd> does not exist
     *         and cannot be created. This can happen if <kbd>homedir</kbd> is
     *         not specified, Crypt_GPG is run as the web user, and the web
     *         user has no home directory. This exception is also thrown if any
     *         of the options <kbd>publicKeyring</kbd>,
     *         <kbd>privateKeyring</kbd> or <kbd>trustDb</kbd> options are
     *         specified but the files do not exist or are are not readable.
     *         This can happen if the user running the Crypt_GPG process (for
     *         example, the Apache user) does not have permission to read the
     *         files.
     *
     * @throws PEAR_Exception if the provided <kbd>binary</kbd> is invalid, or
     *         if no <kbd>binary</kbd> is provided and no suitable binary could
     *         be found.
     *
     * @throws PEAR_Exception if the provided <kbd>agent</kbd> is invalid, or
     *         if no <kbd>agent</kbd> is provided and no suitable gpg-agent
     *         could be found.
     */
    public function __construct(array $options = array())
    {
        $this->setEngine(new Crypt_GPG_Engine($options));
    }

    /**
     * Sets the I/O engine to use for GnuPG operations
     *
     * Normally this method does not need to be used. It provides a means for
     * dependency injection.
     *
     * @param Crypt_GPG_Engine $engine the engine to use.
     *
     * @return Crypt_GPGAbstract the current object, for fluent interface.
     */
    public function setEngine(Crypt_GPG_Engine $engine)
    {
        $this->engine = $engine;
        return $this;
    }

    /**
     * Sets per-command additional arguments
     *
     * @param array $options Additional per-command options for GPG command.
     *                       Note: This will unset options set previously.
     *                       Key of the array is a command (e.g.
     *                       gen-key, import, sign, encrypt, list-keys).
     *                       Value is a string containing command line arguments to be
     *                       added to the related command. For example:
     *                       array('sign' => '--emit-version').
     *
     * @return Crypt_GPGAbstract the current object, for fluent interface.
     */
    public function setEngineOptions(array $options)
    {
        $this->engine->setOptions($options);
        return $this;
    }

    /**
     * Returns version of the engine (GnuPG) used for operation.
     *
     * @return string GnuPG version.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    public function getVersion()
    {
        return $this->engine->getVersion();
    }

    /**
     * Gets the available keys in the keyring
     *
     * Calls GPG with the <kbd>--list-keys</kbd> command and grabs keys. See
     * the first section of <b>doc/DETAILS</b> in the
     * {@link http://www.gnupg.org/download/ GPG package} for a detailed
     * description of how the GPG command output is parsed.
     *
     * @param string $keyId optional. Only keys with that match the specified
     *                      pattern are returned. The pattern may be part of
     *                      a user id, a key id or a key fingerprint. If not
     *                      specified, all keys are returned.
     *
     * @return array an array of {@link Crypt_GPG_Key} objects. If no keys
     *               match the specified <kbd>$keyId</kbd> an empty array is
     *               returned.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     *
     * @see Crypt_GPG_Key
     */
    protected function _getKeys($keyId = '')
    {
        // get private key fingerprints
        if ($keyId == '') {
            $operation = '--list-secret-keys';
        } else {
            $operation = '--utf8-strings --list-secret-keys -- ' . escapeshellarg($keyId);
        }

        // According to The file 'doc/DETAILS' in the GnuPG distribution, using
        // double '--with-fingerprint' also prints the fingerprint for subkeys.
        $arguments = array(
            '--with-colons',
            '--with-fingerprint',
            '--with-fingerprint',
            '--fixed-list-mode'
        );

        $output = '';

        $this->engine->reset();
        $this->engine->setOutput($output);
        $this->engine->setOperation($operation, $arguments);
        $this->engine->run();

        $privateKeyFingerprints = array();

        foreach (explode(PHP_EOL, $output) as $line) {
            $lineExp = explode(':', $line);
            if ($lineExp[0] == 'fpr') {
                $privateKeyFingerprints[] = $lineExp[9];
            }
        }

        // get public keys
        if ($keyId == '') {
            $operation = '--list-public-keys';
        } else {
            $operation = '--utf8-strings --list-public-keys -- ' . escapeshellarg($keyId);
        }

        $output = '';

        $this->engine->reset();
        $this->engine->setOutput($output);
        $this->engine->setOperation($operation, $arguments);
        $this->engine->run();

        $keys   = array();
        $key    = null; // current key
        $subKey = null; // current sub-key

        foreach (explode(PHP_EOL, $output) as $line) {
            $lineExp = explode(':', $line);

            if ($lineExp[0] == 'pub') {

                // new primary key means last key should be added to the array
                if ($key !== null) {
                    $keys[] = $key;
                }

                $key = new Crypt_GPG_Key();

                $subKey = Crypt_GPG_SubKey::parse($line);
                $key->addSubKey($subKey);

            } elseif ($lineExp[0] == 'sub') {

                $subKey = Crypt_GPG_SubKey::parse($line);
                $key->addSubKey($subKey);

            } elseif ($lineExp[0] == 'fpr') {

                $fingerprint = $lineExp[9];

                // set current sub-key fingerprint
                $subKey->setFingerprint($fingerprint);

                // if private key exists, set has private to true
                if (in_array($fingerprint, $privateKeyFingerprints)) {
                    $subKey->setHasPrivate(true);
                }

            } elseif ($lineExp[0] == 'uid') {

                $string = stripcslashes($lineExp[9]); // as per documentation
                $userId = new Crypt_GPG_UserId($string);

                if ($lineExp[1] == 'r') {
                    $userId->setRevoked(true);
                }

                $key->addUserId($userId);

            }
        }

        // add last key
        if ($key !== null) {
            $keys[] = $key;
        }

        return $keys;
    }
}
PK}c�\X��j�g�gpear/crypt_gpg/LICENSEnu�[���              GNU LESSER GENERAL PUBLIC LICENSE
                  Version 2.1, February 1999

 Copyright (C) 1991, 1999 Free Software Foundation, Inc.
     51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

[This is the first released version of the Lesser GPL.  It also counts
 as the successor of the GNU Library Public License, version 2, hence
 the version number 2.1.]

                           Preamble

  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.

  This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it.  You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.

  When we speak of free software, we are referring to freedom of use,
not price.  Our General Public Licenses are designed to make sure that
you have the freedom to distribute copies of free software (and charge
for this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.

  To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights.  These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.

  For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you.  You must make sure that they, too, receive or can get the source
code.  If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it.  And you must show them these terms so they know their rights.

  We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.

  To protect each distributor, we want to make it very clear that
there is no warranty for the free library.  Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.

  Finally, software patents pose a constant threat to the existence of
any free program.  We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder.  Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.

  Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License.  This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License.  We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.

  When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library.  The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom.  The Lesser General
Public License permits more lax criteria for linking other code with
the library.

  We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License.  It also provides other free software developers Less
of an advantage over competing non-free programs.  These disadvantages
are the reason we use the ordinary General Public License for many
libraries.  However, the Lesser license provides advantages in certain
special circumstances.

  For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard.  To achieve this, non-free programs must be
allowed to use the library.  A more frequent case is that a free
library does the same job as widely used non-free libraries.  In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.

  In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software.  For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.

  Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.

  The precise terms and conditions for copying, distribution and
modification follow.  Pay close attention to the difference between a
"work based on the library" and a "work that uses the library".  The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.

                GNU LESSER GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".

  A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.

  The "Library", below, refers to any such software library or work
which has been distributed under these terms.  A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language.  (Hereinafter, translation is
included without limitation in the term "modification".)

  "Source code" for a work means the preferred form of the work for
making modifications to it.  For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.

  Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it).  Whether that is true depends on what the Library does
and what the program that uses the Library does.

  1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.

  You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.

  2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:

    a) The modified work must itself be a software library.

    b) You must cause the files modified to carry prominent notices
    stating that you changed the files and the date of any change.

    c) You must cause the whole of the work to be licensed at no
    charge to all third parties under the terms of this License.

    d) If a facility in the modified Library refers to a function or a
    table of data to be supplied by an application program that uses
    the facility, other than as an argument passed when the facility
    is invoked, then you must make a good faith effort to ensure that,
    in the event an application does not supply such function or
    table, the facility still operates, and performs whatever part of
    its purpose remains meaningful.

    (For example, a function in a library to compute square roots has
    a purpose that is entirely well-defined independent of the
    application.  Therefore, Subsection 2d requires that any
    application-supplied function or table used by this function must
    be optional: if the application does not supply it, the square
    root function must still compute square roots.)

These requirements apply to the modified work as a whole.  If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works.  But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.

Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.

In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.

  3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library.  To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License.  (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.)  Do not make any other change in
these notices.

  Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.

  This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.

  4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.

  If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.

  5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library".  Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.

  However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library".  The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.

  When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library.  The
threshold for this to be true is not precisely defined by law.

  If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work.  (Executables containing this object code plus portions of the
Library will still fall under Section 6.)

  Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.

  6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.

  You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License.  You must supply a copy of this License.  If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License.  Also, you must do one
of these things:

    a) Accompany the work with the complete corresponding
    machine-readable source code for the Library including whatever
    changes were used in the work (which must be distributed under
    Sections 1 and 2 above); and, if the work is an executable linked
    with the Library, with the complete machine-readable "work that
    uses the Library", as object code and/or source code, so that the
    user can modify the Library and then relink to produce a modified
    executable containing the modified Library.  (It is understood
    that the user who changes the contents of definitions files in the
    Library will not necessarily be able to recompile the application
    to use the modified definitions.)

    b) Use a suitable shared library mechanism for linking with the
    Library.  A suitable mechanism is one that (1) uses at run time a
    copy of the library already present on the user's computer system,
    rather than copying library functions into the executable, and (2)
    will operate properly with a modified version of the library, if
    the user installs one, as long as the modified version is
    interface-compatible with the version that the work was made with.

    c) Accompany the work with a written offer, valid for at
    least three years, to give the same user the materials
    specified in Subsection 6a, above, for a charge no more
    than the cost of performing this distribution.

    d) If distribution of the work is made by offering access to copy
    from a designated place, offer equivalent access to copy the above
    specified materials from the same place.

    e) Verify that the user has already received a copy of these
    materials or that you have already sent this user a copy.

  For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it.  However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.

  It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system.  Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.

  7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:

    a) Accompany the combined library with a copy of the same work
    based on the Library, uncombined with any other library
    facilities.  This must be distributed under the terms of the
    Sections above.

    b) Give prominent notice with the combined library of the fact
    that part of it is a work based on the Library, and explaining
    where to find the accompanying uncombined form of the same work.

  8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License.  Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License.  However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.

  9. You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Library or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.

  10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.

  11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all.  For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.

If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.

It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.

This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.

  12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded.  In such case, this License incorporates the limitation as if
written in the body of this License.

  13. The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.

Each version is given a distinguishing version number.  If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation.  If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.

  14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission.  For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this.  Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.

                           NO WARRANTY

  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.

                    END OF TERMS AND CONDITIONS

           How to Apply These Terms to Your New Libraries

  If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change.  You can do so by permitting
redistribution under these terms (or, alternatively, under the terms of the
ordinary General Public License).

  To apply these terms, attach the following notices to the library.  It is
safest to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.

    <one line to give the library's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA

Also add information on how to contact you by electronic and paper mail.

You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the library, if
necessary.  Here is a sample; alter the names:

  Yoyodyne, Inc., hereby disclaims all copyright interest in the
  library `Frob' (a library for tweaking knobs) written by James Random Hacker.

  <signature of Ty Coon>, 1 April 1990
  Ty Coon, President of Vice

That's all there is to it!PK}c�\�}�2��$pear/crypt_gpg/data/pinentry-cli.xmlnu�[���<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<command>
	<description>Utility that emulates GnuPG 1.x passphrase handling over pipe-based IPC for GnuPG 2.x.</description>
	<version>@package-version@</version>
	<option name="log">
		<short_name>-l</short_name>
		<long_name>--log</long_name>
		<description>Optional location to log pinentry activity.</description>
		<action>StoreString</action>
	</option>
	<option name="verbose">
		<short_name>-v</short_name>
		<long_name>--verbose</long_name>
		<description>Sets verbosity level. Use multiples for more detail (e.g. "-vv").</description>
		<action>Counter</action>
		<default>0</default>
	</option>
</command>
PK}c�\��7�)pear/crypt_gpg/scripts/crypt-gpg-pinentrynu�[���#! /usr/bin/env php
<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

// Check if we're running directly from git repo or if we're running
// from a PEAR or Composer packaged version.
$ds    = DIRECTORY_SEPARATOR;
$root  = __DIR__ . $ds . '..' ;
$paths = array(
    '@php-dir@', // PEAR or Composer
    $root, // Git (or Composer with wrong @php-dir@)
    $root . $ds . '..' . $ds . 'Console_CommandLine', // Composer
    $root . $ds . '..' . $ds . 'console_commandline', // Composer
    // and composer-installed PEAR_Exception for Console_CommandLine (#21074)
    $root . $ds . '..' . $ds . '..' . $ds . 'pear' . $ds . 'pear_exception',
);

foreach ($paths as $idx => $path) {
    if (!is_dir($path)) {
        unset($paths[$idx]);
    }
}

// We depend on Console_CommandLine, so we append also the default include path
set_include_path(implode(PATH_SEPARATOR, $paths) . PATH_SEPARATOR . get_include_path());

require_once 'Crypt/GPG/PinEntry.php';

$pinentry = new Crypt_GPG_PinEntry();
$pinentry->__invoke();

?>
PK}c�\�C�k��pear/crypt_gpg/README.mdnu�[���# Crypt_GPG #
Crypt_GPG is a PHP package to interact with the [GNU Privacy Guard
(GnuPG)](https://www.gnupg.org/). GnuPG is a free and open-source
implementation of the [OpenPGP](https://www.ietf.org/rfc/rfc4880.txt)
protocol, providing key management, data encryption and data signing.
Crypt_GPG provides an object-oriented API for performing OpenPGP
actions using GnuPG.

## Documentation ##

### Quick Example
```php
<?php

require_once 'Crypt/GPG.php';

$gpg = new Crypt_GPG();
$gpg->addEncryptKey('test@example.com');
$data = $gpg->encrypt('my secret data');

?>
```

### Further Documentation ###
* [High-Level Documentation](https://pear.php.net/manual/en/package.encryption.crypt-gpg.intro.php)
* [Detailed API Documentation](https://pear.php.net/package/Crypt_GPG/docs/latest/)

## Bugs and Issues ##
Please report all new issues via the [PEAR bug tracker](https://pear.php.net/bugs/search.php?cmd=display&package_name[]=Crypt_GPG).

Please submit pull requests for your bug reports!

## Testing ##
To test, run either
`$ phpunit tests/`
  or
`$ pear run-tests -r`

## Building ##
To build, simply
`$ pear package`

## Installing ##
To install from scratch
`$ pear install package.xml`

To upgrade
`$ pear upgrade -f package.xml`
PK}c�\���zhh-ralouphie/getallheaders/src/getallheaders.phpnu�[���<?php

if (!function_exists('getallheaders')) {

    /**
     * Get all HTTP header key/values as an associative array for the current request.
     *
     * @return string[string] The HTTP header key/value pairs.
     */
    function getallheaders()
    {
        $headers = array();

        $copy_server = array(
            'CONTENT_TYPE'   => 'Content-Type',
            'CONTENT_LENGTH' => 'Content-Length',
            'CONTENT_MD5'    => 'Content-Md5',
        );

        foreach ($_SERVER as $key => $value) {
            if (substr($key, 0, 5) === 'HTTP_') {
                $key = substr($key, 5);
                if (!isset($copy_server[$key]) || !isset($_SERVER[$key])) {
                    $key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $key))));
                    $headers[$key] = $value;
                }
            } elseif (isset($copy_server[$key])) {
                $headers[$copy_server[$key]] = $value;
            }
        }

        if (!isset($headers['Authorization'])) {
            if (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) {
                $headers['Authorization'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
            } elseif (isset($_SERVER['PHP_AUTH_USER'])) {
                $basic_pass = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : '';
                $headers['Authorization'] = 'Basic ' . base64_encode($_SERVER['PHP_AUTH_USER'] . ':' . $basic_pass);
            } elseif (isset($_SERVER['PHP_AUTH_DIGEST'])) {
                $headers['Authorization'] = $_SERVER['PHP_AUTH_DIGEST'];
            }
        }

        return $headers;
    }

}
PK}c�\�G���%ralouphie/getallheaders/composer.jsonnu�[���{
	"name": "ralouphie/getallheaders",
	"description": "A polyfill for getallheaders.",
	"license": "MIT",
	"authors": [
		{
			"name": "Ralph Khattar",
			"email": "ralph.khattar@gmail.com"
		}
	],
	"require": {
		"php": ">=5.6"
	},
	"require-dev": {
		"phpunit/phpunit": "^5 || ^6.5",
		"php-coveralls/php-coveralls": "^2.1"
	},
	"autoload": {
		"files": ["src/getallheaders.php"]
	},
	"autoload-dev": {
		"psr-4": {
			"getallheaders\\Tests\\": "tests/"
		}
	}
}
PK}c�\Ka�88ralouphie/getallheaders/LICENSEnu�[���The MIT License (MIT)

Copyright (c) 2014 Ralph Khattar

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
PK}c�\��\�@@!ralouphie/getallheaders/README.mdnu�[���getallheaders
=============

PHP `getallheaders()` polyfill. Compatible with PHP >= 5.3.

[![Build Status](https://travis-ci.org/ralouphie/getallheaders.svg?branch=master)](https://travis-ci.org/ralouphie/getallheaders)
[![Coverage Status](https://coveralls.io/repos/ralouphie/getallheaders/badge.png?branch=master)](https://coveralls.io/r/ralouphie/getallheaders?branch=master)
[![Latest Stable Version](https://poser.pugx.org/ralouphie/getallheaders/v/stable.png)](https://packagist.org/packages/ralouphie/getallheaders)
[![Latest Unstable Version](https://poser.pugx.org/ralouphie/getallheaders/v/unstable.png)](https://packagist.org/packages/ralouphie/getallheaders)
[![License](https://poser.pugx.org/ralouphie/getallheaders/license.png)](https://packagist.org/packages/ralouphie/getallheaders)


This is a simple polyfill for [`getallheaders()`](http://www.php.net/manual/en/function.getallheaders.php).

## Install

For PHP version **`>= 5.6`**:

```
composer require ralouphie/getallheaders
```

For PHP version **`< 5.6`**:

```
composer require ralouphie/getallheaders "^2"
```
PK}c�\�(��b!b!masterminds/html5/src/HTML5.phpnu�[���<?php

namespace Masterminds;

use Masterminds\HTML5\Parser\DOMTreeBuilder;
use Masterminds\HTML5\Parser\Scanner;
use Masterminds\HTML5\Parser\Tokenizer;
use Masterminds\HTML5\Serializer\OutputRules;
use Masterminds\HTML5\Serializer\Traverser;

/**
 * This class offers convenience methods for parsing and serializing HTML5.
 * It is roughly designed to mirror the \DOMDocument native class.
 */
class HTML5
{
    /**
     * Global options for the parser and serializer.
     *
     * @var array
     */
    private $defaultOptions = array(
        // Whether the serializer should aggressively encode all characters as entities.
        'encode_entities' => false,

        // Prevents the parser from automatically assigning the HTML5 namespace to the DOM document.
        'disable_html_ns' => false,
    );

    protected $errors = array();

    public function __construct(array $defaultOptions = array())
    {
        $this->defaultOptions = array_merge($this->defaultOptions, $defaultOptions);
    }

    /**
     * Get the current default options.
     *
     * @return array
     */
    public function getOptions()
    {
        return $this->defaultOptions;
    }

    /**
     * Load and parse an HTML file.
     *
     * This will apply the HTML5 parser, which is tolerant of many
     * varieties of HTML, including XHTML 1, HTML 4, and well-formed HTML
     * 3. Note that in these cases, not all of the old data will be
     * preserved. For example, XHTML's XML declaration will be removed.
     *
     * The rules governing parsing are set out in the HTML 5 spec.
     *
     * @param string|resource $file    The path to the file to parse. If this is a resource, it is
     *                                 assumed to be an open stream whose pointer is set to the first
     *                                 byte of input.
     * @param array           $options Configuration options when parsing the HTML.
     *
     * @return \DOMDocument A DOM document. These object type is defined by the libxml
     *                      library, and should have been included with your version of PHP.
     */
    public function load($file, array $options = array())
    {
        // Handle the case where file is a resource.
        if (is_resource($file)) {
            return $this->parse(stream_get_contents($file), $options);
        }

        return $this->parse(file_get_contents($file), $options);
    }

    /**
     * Parse a HTML Document from a string.
     *
     * Take a string of HTML 5 (or earlier) and parse it into a
     * DOMDocument.
     *
     * @param string $string  A html5 document as a string.
     * @param array  $options Configuration options when parsing the HTML.
     *
     * @return \DOMDocument A DOM document. DOM is part of libxml, which is included with
     *                      almost all distribtions of PHP.
     */
    public function loadHTML($string, array $options = array())
    {
        return $this->parse($string, $options);
    }

    /**
     * Convenience function to load an HTML file.
     *
     * This is here to provide backwards compatibility with the
     * PHP DOM implementation. It simply calls load().
     *
     * @param string $file    The path to the file to parse. If this is a resource, it is
     *                        assumed to be an open stream whose pointer is set to the first
     *                        byte of input.
     * @param array  $options Configuration options when parsing the HTML.
     *
     * @return \DOMDocument A DOM document. These object type is defined by the libxml
     *                      library, and should have been included with your version of PHP.
     */
    public function loadHTMLFile($file, array $options = array())
    {
        return $this->load($file, $options);
    }

    /**
     * Parse a HTML fragment from a string.
     *
     * @param string $string  the HTML5 fragment as a string
     * @param array  $options Configuration options when parsing the HTML
     *
     * @return \DOMDocumentFragment A DOM fragment. The DOM is part of libxml, which is included with
     *                              almost all distributions of PHP.
     */
    public function loadHTMLFragment($string, array $options = array())
    {
        return $this->parseFragment($string, $options);
    }

    /**
     * Return all errors encountered into parsing phase.
     *
     * @return array
     */
    public function getErrors()
    {
        return $this->errors;
    }

    /**
     * Return true it some errors were encountered into parsing phase.
     *
     * @return bool
     */
    public function hasErrors()
    {
        return count($this->errors) > 0;
    }

    /**
     * Parse an input string.
     *
     * @param string $input
     * @param array  $options
     *
     * @return \DOMDocument
     */
    public function parse($input, array $options = array())
    {
        $this->errors = array();
        $options = array_merge($this->defaultOptions, $options);
        $events = new DOMTreeBuilder(false, $options);
        $scanner = new Scanner($input, !empty($options['encoding']) ? $options['encoding'] : 'UTF-8');
        $parser = new Tokenizer($scanner, $events, !empty($options['xmlNamespaces']) ? Tokenizer::CONFORMANT_XML : Tokenizer::CONFORMANT_HTML);

        $parser->parse();
        $this->errors = $events->getErrors();

        return $events->document();
    }

    /**
     * Parse an input stream where the stream is a fragment.
     *
     * Lower-level loading function. This requires an input stream instead
     * of a string, file, or resource.
     *
     * @param string $input   The input data to parse in the form of a string.
     * @param array  $options An array of options.
     *
     * @return \DOMDocumentFragment
     */
    public function parseFragment($input, array $options = array())
    {
        $options = array_merge($this->defaultOptions, $options);
        $events = new DOMTreeBuilder(true, $options);
        $scanner = new Scanner($input, !empty($options['encoding']) ? $options['encoding'] : 'UTF-8');
        $parser = new Tokenizer($scanner, $events, !empty($options['xmlNamespaces']) ? Tokenizer::CONFORMANT_XML : Tokenizer::CONFORMANT_HTML);

        $parser->parse();
        $this->errors = $events->getErrors();

        return $events->fragment();
    }

    /**
     * Save a DOM into a given file as HTML5.
     *
     * @param mixed           $dom     The DOM to be serialized.
     * @param string|resource $file    The filename to be written or resource to write to.
     * @param array           $options Configuration options when serializing the DOM. These include:
     *                                 - encode_entities: Text written to the output is escaped by default and not all
     *                                 entities are encoded. If this is set to true all entities will be encoded.
     *                                 Defaults to false.
     */
    public function save($dom, $file, $options = array())
    {
        $close = true;
        if (is_resource($file)) {
            $stream = $file;
            $close = false;
        } else {
            $stream = fopen($file, 'wb');
        }
        $options = array_merge($this->defaultOptions, $options);
        $rules = new OutputRules($stream, $options);
        $trav = new Traverser($dom, $stream, $rules, $options);

        $trav->walk();
        /*
         * release the traverser to avoid cyclic references and allow PHP to free memory without waiting for gc_collect_cycles
         */
        $rules->unsetTraverser();
        if ($close) {
            fclose($stream);
        }
    }

    /**
     * Convert a DOM into an HTML5 string.
     *
     * @param mixed $dom     The DOM to be serialized.
     * @param array $options Configuration options when serializing the DOM. These include:
     *                       - encode_entities: Text written to the output is escaped by default and not all
     *                       entities are encoded. If this is set to true all entities will be encoded.
     *                       Defaults to false.
     *
     * @return string A HTML5 documented generated from the DOM.
     */
    public function saveHTML($dom, $options = array())
    {
        $stream = fopen('php://temp', 'wb');
        $this->save($dom, $stream, array_merge($this->defaultOptions, $options));

        $html = stream_get_contents($stream, -1, 0);

        fclose($stream);

        return $html;
    }
}
PK}c�\{�7
7
9masterminds/html5/src/HTML5/Serializer/RulesInterface.phpnu�[���<?php
/**
 * @file
 * The interface definition for Rules to generate output.
 */

namespace Masterminds\HTML5\Serializer;

/**
 * To create a new rule set for writing output the RulesInterface needs to be implemented.
 * The resulting class can be specified in the options with the key of rules.
 *
 * For an example implementation see Serializer\OutputRules.
 */
interface RulesInterface
{
    /**
     * The class constructor.
     *
     * Note, before the rules can be used a traverser must be registered.
     *
     * @param mixed $output  The output stream to write output to.
     * @param array $options An array of options.
     */
    public function __construct($output, $options = array());

    /**
     * Register the traverser used in but the rules.
     *
     * Note, only one traverser can be used by the rules.
     *
     * @param Traverser $traverser The traverser used in the rules.
     *
     * @return RulesInterface $this for the current object.
     */
    public function setTraverser(Traverser $traverser);

    /**
     * Write a document element (\DOMDocument).
     *
     * Instead of returning the result write it to the output stream ($output)
     * that was passed into the constructor.
     *
     * @param \DOMDocument $dom
     */
    public function document($dom);

    /**
     * Write an element.
     *
     * Instead of returning the result write it to the output stream ($output)
     * that was passed into the constructor.
     *
     * @param mixed $ele
     */
    public function element($ele);

    /**
     * Write a text node.
     *
     * Instead of returning the result write it to the output stream ($output)
     * that was passed into the constructor.
     *
     * @param mixed $ele
     */
    public function text($ele);

    /**
     * Write a CDATA node.
     *
     * Instead of returning the result write it to the output stream ($output)
     * that was passed into the constructor.
     *
     * @param mixed $ele
     */
    public function cdata($ele);

    /**
     * Write a comment node.
     *
     * Instead of returning the result write it to the output stream ($output)
     * that was passed into the constructor.
     *
     * @param mixed $ele
     */
    public function comment($ele);

    /**
     * Write a processor instruction.
     *
     * To learn about processor instructions see InstructionProcessor
     *
     * Instead of returning the result write it to the output stream ($output)
     * that was passed into the constructor.
     *
     * @param mixed $ele
     */
    public function processorInstruction($ele);
}
PK}c�\)�%�@�@6masterminds/html5/src/HTML5/Serializer/OutputRules.phpnu�[���<?php
/**
 * @file
 * The rules for generating output in the serializer.
 *
 * These output rules are likely to generate output similar to the document that
 * was parsed. It is not intended to output exactly the document that was parsed.
 */

namespace Masterminds\HTML5\Serializer;

use Masterminds\HTML5\Elements;

/**
 * Generate the output html5 based on element rules.
 */
class OutputRules implements RulesInterface
{
    /**
     * Defined in http://www.w3.org/TR/html51/infrastructure.html#html-namespace-0.
     */
    const NAMESPACE_HTML = 'http://www.w3.org/1999/xhtml';

    const NAMESPACE_MATHML = 'http://www.w3.org/1998/Math/MathML';

    const NAMESPACE_SVG = 'http://www.w3.org/2000/svg';

    const NAMESPACE_XLINK = 'http://www.w3.org/1999/xlink';

    const NAMESPACE_XML = 'http://www.w3.org/XML/1998/namespace';

    const NAMESPACE_XMLNS = 'http://www.w3.org/2000/xmlns/';

    /**
     * Holds the HTML5 element names that causes a namespace switch.
     *
     * @var array
     */
    protected $implicitNamespaces = array(
        self::NAMESPACE_HTML,
        self::NAMESPACE_SVG,
        self::NAMESPACE_MATHML,
        self::NAMESPACE_XML,
        self::NAMESPACE_XMLNS,
    );

    const IM_IN_HTML = 1;

    const IM_IN_SVG = 2;

    const IM_IN_MATHML = 3;

    /**
     * Used as cache to detect if is available ENT_HTML5.
     *
     * @var bool
     */
    private $hasHTML5 = false;

    protected $traverser;

    protected $encode = false;

    protected $out;

    protected $outputMode;

    private $xpath;

    protected $nonBooleanAttributes = array(
        /*
        array(
            'nodeNamespace'=>'http://www.w3.org/1999/xhtml',
            'attrNamespace'=>'http://www.w3.org/1999/xhtml',

            'nodeName'=>'img', 'nodeName'=>array('img', 'a'),
            'attrName'=>'alt', 'attrName'=>array('title', 'alt'),
        ),
        */
        array(
            'nodeNamespace' => 'http://www.w3.org/1999/xhtml',
            'attrName' => array('href',
                'hreflang',
                'http-equiv',
                'icon',
                'id',
                'keytype',
                'kind',
                'label',
                'lang',
                'language',
                'list',
                'maxlength',
                'media',
                'method',
                'name',
                'placeholder',
                'rel',
                'rows',
                'rowspan',
                'sandbox',
                'spellcheck',
                'scope',
                'seamless',
                'shape',
                'size',
                'sizes',
                'span',
                'src',
                'srcdoc',
                'srclang',
                'srcset',
                'start',
                'step',
                'style',
                'summary',
                'tabindex',
                'target',
                'title',
                'type',
                'value',
                'width',
                'border',
                'charset',
                'cite',
                'class',
                'code',
                'codebase',
                'color',
                'cols',
                'colspan',
                'content',
                'coords',
                'data',
                'datetime',
                'default',
                'dir',
                'dirname',
                'enctype',
                'for',
                'form',
                'formaction',
                'headers',
                'height',
                'accept',
                'accept-charset',
                'accesskey',
                'action',
                'align',
                'alt',
                'bgcolor',
            ),
        ),
        array(
            'nodeNamespace' => 'http://www.w3.org/1999/xhtml',
            'xpath' => 'starts-with(local-name(), \'data-\')',
        ),
    );

    const DOCTYPE = '<!DOCTYPE html>';

    public function __construct($output, $options = array())
    {
        if (isset($options['encode_entities'])) {
            $this->encode = $options['encode_entities'];
        }

        $this->outputMode = static::IM_IN_HTML;
        $this->out = $output;
        $this->hasHTML5 = defined('ENT_HTML5');
    }

    public function addRule(array $rule)
    {
        $this->nonBooleanAttributes[] = $rule;
    }

    public function setTraverser(Traverser $traverser)
    {
        $this->traverser = $traverser;

        return $this;
    }

    public function unsetTraverser()
    {
        $this->traverser = null;

        return $this;
    }

    public function document($dom)
    {
        $this->doctype();
        if ($dom->documentElement) {
            foreach ($dom->childNodes as $node) {
                $this->traverser->node($node);
            }
            $this->nl();
        }
    }

    protected function doctype()
    {
        $this->wr(static::DOCTYPE);
        $this->nl();
    }

    public function element($ele)
    {
        $name = $ele->tagName;

        // Per spec:
        // If the element has a declared namespace in the HTML, MathML or
        // SVG namespaces, we use the lname instead of the tagName.
        if ($this->traverser->isLocalElement($ele)) {
            $name = $ele->localName;
        }

        // If we are in SVG or MathML there is special handling.
        // Using if/elseif instead of switch because it's faster in PHP.
        if ('svg' == $name) {
            $this->outputMode = static::IM_IN_SVG;
            $name = Elements::normalizeSvgElement($name);
        } elseif ('math' == $name) {
            $this->outputMode = static::IM_IN_MATHML;
        }

        $this->openTag($ele);
        if (Elements::isA($name, Elements::TEXT_RAW)) {
            foreach ($ele->childNodes as $child) {
                if ($child instanceof \DOMCharacterData) {
                    $this->wr($child->data);
                } elseif ($child instanceof \DOMElement) {
                    $this->element($child);
                }
            }
        } else {
            // Handle children.
            if ($ele->hasChildNodes()) {
                $this->traverser->children($ele->childNodes);
            }

            // Close out the SVG or MathML special handling.
            if ('svg' == $name || 'math' == $name) {
                $this->outputMode = static::IM_IN_HTML;
            }
        }

        // If not unary, add a closing tag.
        if (!Elements::isA($name, Elements::VOID_TAG)) {
            $this->closeTag($ele);
        }
    }

    /**
     * Write a text node.
     *
     * @param \DOMText $ele The text node to write.
     */
    public function text($ele)
    {
        if (isset($ele->parentNode) && isset($ele->parentNode->tagName) && Elements::isA($ele->parentNode->localName, Elements::TEXT_RAW)) {
            $this->wr($ele->data);

            return;
        }

        // FIXME: This probably needs some flags set.
        $this->wr($this->enc($ele->data));
    }

    public function cdata($ele)
    {
        // This encodes CDATA.
        $this->wr($ele->ownerDocument->saveXML($ele));
    }

    public function comment($ele)
    {
        // These produce identical output.
        // $this->wr('<!--')->wr($ele->data)->wr('-->');
        $this->wr($ele->ownerDocument->saveXML($ele));
    }

    public function processorInstruction($ele)
    {
        $this->wr('<?')
            ->wr($ele->target)
            ->wr(' ')
            ->wr($ele->data)
            ->wr('?>');
    }

    /**
     * Write the namespace attributes.
     *
     * @param \DOMNode $ele The element being written.
     */
    protected function namespaceAttrs($ele)
    {
        if (!$this->xpath || $this->xpath->document !== $ele->ownerDocument) {
            $this->xpath = new \DOMXPath($ele->ownerDocument);
        }

        foreach ($this->xpath->query('namespace::*[not(.=../../namespace::*)]', $ele) as $nsNode) {
            if (!in_array($nsNode->nodeValue, $this->implicitNamespaces)) {
                $this->wr(' ')->wr($nsNode->nodeName)->wr('="')->wr($nsNode->nodeValue)->wr('"');
            }
        }
    }

    /**
     * Write the opening tag.
     *
     * Tags for HTML, MathML, and SVG are in the local name. Otherwise, use the
     * qualified name (8.3).
     *
     * @param \DOMNode $ele The element being written.
     */
    protected function openTag($ele)
    {
        $this->wr('<')->wr($this->traverser->isLocalElement($ele) ? $ele->localName : $ele->tagName);

        $this->attrs($ele);
        $this->namespaceAttrs($ele);

        if ($this->outputMode == static::IM_IN_HTML) {
            $this->wr('>');
        }         // If we are not in html mode we are in SVG, MathML, or XML embedded content.
        else {
            if ($ele->hasChildNodes()) {
                $this->wr('>');
            }             // If there are no children this is self closing.
            else {
                $this->wr(' />');
            }
        }
    }

    protected function attrs($ele)
    {
        // FIXME: Needs support for xml, xmlns, xlink, and namespaced elements.
        if (!$ele->hasAttributes()) {
            return $this;
        }

        // TODO: Currently, this always writes name="value", and does not do
        // value-less attributes.
        $map = $ele->attributes;
        $len = $map->length;
        for ($i = 0; $i < $len; ++$i) {
            $node = $map->item($i);
            $val = $this->enc($node->value, true);

            // XXX: The spec says that we need to ensure that anything in
            // the XML, XMLNS, or XLink NS's should use the canonical
            // prefix. It seems that DOM does this for us already, but there
            // may be exceptions.
            $name = $node->nodeName;

            // Special handling for attributes in SVG and MathML.
            // Using if/elseif instead of switch because it's faster in PHP.
            if ($this->outputMode == static::IM_IN_SVG) {
                $name = Elements::normalizeSvgAttribute($name);
            } elseif ($this->outputMode == static::IM_IN_MATHML) {
                $name = Elements::normalizeMathMlAttribute($name);
            }

            $this->wr(' ')->wr($name);

            if ((isset($val) && '' !== $val) || $this->nonBooleanAttribute($node)) {
                $this->wr('="')->wr($val)->wr('"');
            }
        }
    }

    protected function nonBooleanAttribute(\DOMAttr $attr)
    {
        $ele = $attr->ownerElement;
        foreach ($this->nonBooleanAttributes as $rule) {
            if (isset($rule['nodeNamespace']) && $rule['nodeNamespace'] !== $ele->namespaceURI) {
                continue;
            }
            if (isset($rule['attNamespace']) && $rule['attNamespace'] !== $attr->namespaceURI) {
                continue;
            }
            if (isset($rule['nodeName']) && !is_array($rule['nodeName']) && $rule['nodeName'] !== $ele->localName) {
                continue;
            }
            if (isset($rule['nodeName']) && is_array($rule['nodeName']) && !in_array($ele->localName, $rule['nodeName'], true)) {
                continue;
            }
            if (isset($rule['attrName']) && !is_array($rule['attrName']) && $rule['attrName'] !== $attr->localName) {
                continue;
            }
            if (isset($rule['attrName']) && is_array($rule['attrName']) && !in_array($attr->localName, $rule['attrName'], true)) {
                continue;
            }
            if (isset($rule['xpath'])) {
                $xp = $this->getXPath($attr);
                if (isset($rule['prefixes'])) {
                    foreach ($rule['prefixes'] as $nsPrefix => $ns) {
                        $xp->registerNamespace($nsPrefix, $ns);
                    }
                }
                if (!$xp->evaluate($rule['xpath'], $attr)) {
                    continue;
                }
            }

            return true;
        }

        return false;
    }

    private function getXPath(\DOMNode $node)
    {
        if (!$this->xpath) {
            $this->xpath = new \DOMXPath($node->ownerDocument);
        }

        return $this->xpath;
    }

    /**
     * Write the closing tag.
     *
     * Tags for HTML, MathML, and SVG are in the local name. Otherwise, use the
     * qualified name (8.3).
     *
     * @param \DOMNode $ele The element being written.
     */
    protected function closeTag($ele)
    {
        if ($this->outputMode == static::IM_IN_HTML || $ele->hasChildNodes()) {
            $this->wr('</')->wr($this->traverser->isLocalElement($ele) ? $ele->localName : $ele->tagName)->wr('>');
        }
    }

    /**
     * Write to the output.
     *
     * @param string $text The string to put into the output
     *
     * @return $this
     */
    protected function wr($text)
    {
        fwrite($this->out, $text);

        return $this;
    }

    /**
     * Write a new line character.
     *
     * @return $this
     */
    protected function nl()
    {
        fwrite($this->out, PHP_EOL);

        return $this;
    }

    /**
     * Encode text.
     *
     * When encode is set to false, the default value, the text passed in is
     * escaped per section 8.3 of the html5 spec. For details on how text is
     * escaped see the escape() method.
     *
     * When encoding is set to true the text is converted to named character
     * references where appropriate. Section 8.1.4 Character references of the
     * html5 spec refers to using named character references. This is useful for
     * characters that can't otherwise legally be used in the text.
     *
     * The named character references are listed in section 8.5.
     *
     * @see http://www.w3.org/TR/2013/CR-html5-20130806/syntax.html#named-character-references True encoding will turn all named character references into their entities.
     *      This includes such characters as +.# and many other common ones. By default
     *      encoding here will just escape &'<>".
     *
     *      Note, PHP 5.4+ has better html5 encoding.
     *
     * @todo Use the Entities class in php 5.3 to have html5 entities.
     *
     * @param string $text      Text to encode.
     * @param bool   $attribute True if we are encoding an attrubute, false otherwise.
     *
     * @return string The encoded text.
     */
    protected function enc($text, $attribute = false)
    {
        // Escape the text rather than convert to named character references.
        if (!$this->encode) {
            return $this->escape($text, $attribute);
        }

        // If we are in PHP 5.4+ we can use the native html5 entity functionality to
        // convert the named character references.

        if ($this->hasHTML5) {
            return htmlentities($text, ENT_HTML5 | ENT_SUBSTITUTE | ENT_QUOTES, 'UTF-8', false);
        }         // If a version earlier than 5.4 html5 entities are not entirely handled.
        // This manually handles them.
        else {
            return strtr($text, HTML5Entities::$map);
        }
    }

    /**
     * Escape test.
     *
     * According to the html5 spec section 8.3 Serializing HTML fragments, text
     * within tags that are not style, script, xmp, iframe, noembed, and noframes
     * need to be properly escaped.
     *
     * The & should be converted to &amp;, no breaking space unicode characters
     * converted to &nbsp;, when in attribute mode the " should be converted to
     * &quot;, and when not in attribute mode the < and > should be converted to
     * &lt; and &gt;.
     *
     * @see http://www.w3.org/TR/2013/CR-html5-20130806/syntax.html#escapingString
     *
     * @param string $text      Text to escape.
     * @param bool   $attribute True if we are escaping an attrubute, false otherwise.
     */
    protected function escape($text, $attribute = false)
    {
        // Not using htmlspecialchars because, while it does escaping, it doesn't
        // match the requirements of section 8.5. For example, it doesn't handle
        // non-breaking spaces.
        if ($attribute) {
            $replace = array(
                '"' => '&quot;',
                '&' => '&amp;',
                "\xc2\xa0" => '&nbsp;',
            );
        } else {
            $replace = array(
                '<' => '&lt;',
                '>' => '&gt;',
                '&' => '&amp;',
                "\xc2\xa0" => '&nbsp;',
            );
        }

        return strtr($text, $replace);
    }
}
PK}c�\�l�E��4masterminds/html5/src/HTML5/Serializer/Traverser.phpnu�[���<?php

namespace Masterminds\HTML5\Serializer;

/**
 * Traverser for walking a DOM tree.
 *
 * This is a concrete traverser designed to convert a DOM tree into an
 * HTML5 document. It is not intended to be a generic DOMTreeWalker
 * implementation.
 *
 * @see http://www.w3.org/TR/2012/CR-html5-20121217/syntax.html#serializing-html-fragments
 */
class Traverser
{
    /**
     * Namespaces that should be treated as "local" to HTML5.
     */
    protected static $local_ns = array(
        'http://www.w3.org/1999/xhtml' => 'html',
        'http://www.w3.org/1998/Math/MathML' => 'math',
        'http://www.w3.org/2000/svg' => 'svg',
    );

    protected $dom;

    protected $options;

    protected $encode = false;

    protected $rules;

    protected $out;

    /**
     * Create a traverser.
     *
     * @param \DOMNode|\DOMNodeList $dom     The document or node to traverse.
     * @param resource              $out     A stream that allows writing. The traverser will output into this
     *                                       stream.
     * @param array                 $options An array of options for the traverser as key/value pairs. These include:
     *                                       - encode_entities: A bool to specify if full encding should happen for all named
     *                                       charachter references. Defaults to false which escapes &'<>".
     *                                       - output_rules: The path to the class handling the output rules.
     */
    public function __construct($dom, $out, RulesInterface $rules, $options = array())
    {
        $this->dom = $dom;
        $this->out = $out;
        $this->rules = $rules;
        $this->options = $options;

        $this->rules->setTraverser($this);
    }

    /**
     * Tell the traverser to walk the DOM.
     *
     * @return resource $out Returns the output stream.
     */
    public function walk()
    {
        if ($this->dom instanceof \DOMDocument) {
            $this->rules->document($this->dom);
        } elseif ($this->dom instanceof \DOMDocumentFragment) {
            // Document fragments are a special case. Only the children need to
            // be serialized.
            if ($this->dom->hasChildNodes()) {
                $this->children($this->dom->childNodes);
            }
        }        // If NodeList, loop
        elseif ($this->dom instanceof \DOMNodeList) {
            // If this is a NodeList of DOMDocuments this will not work.
            $this->children($this->dom);
        }         // Else assume this is a DOMNode-like datastructure.
        else {
            $this->node($this->dom);
        }

        return $this->out;
    }

    /**
     * Process a node in the DOM.
     *
     * @param mixed $node A node implementing \DOMNode.
     */
    public function node($node)
    {
        // A listing of types is at http://php.net/manual/en/dom.constants.php
        switch ($node->nodeType) {
            case XML_ELEMENT_NODE:
                $this->rules->element($node);
                break;
            case XML_TEXT_NODE:
                $this->rules->text($node);
                break;
            case XML_CDATA_SECTION_NODE:
                $this->rules->cdata($node);
                break;
            case XML_PI_NODE:
                $this->rules->processorInstruction($node);
                break;
            case XML_COMMENT_NODE:
                $this->rules->comment($node);
                break;
            // Currently we don't support embedding DTDs.
            default:
                //print '<!-- Skipped -->';
                break;
        }
    }

    /**
     * Walk through all the nodes on a node list.
     *
     * @param \DOMNodeList $nl A list of child elements to walk through.
     */
    public function children($nl)
    {
        foreach ($nl as $node) {
            $this->node($node);
        }
    }

    /**
     * Is an element local?
     *
     * @param mixed $ele An element that implement \DOMNode.
     *
     * @return bool true if local and false otherwise.
     */
    public function isLocalElement($ele)
    {
        $uri = $ele->namespaceURI;
        if (empty($uri)) {
            return false;
        }

        return isset(static::$local_ns[$uri]);
    }
}
PK}c�\	�n���8masterminds/html5/src/HTML5/Serializer/HTML5Entities.phpnu�[���<?php
/**
 * @file
 * This contains HTML5 entities to use with serializing.
 *
 * The list here is mildly different from the list at Entities because
 * that list was generated from the w3c. It contains some entities that are
 * not entirely proper such as &am; which maps to &. This list is meant to be
 * a fallback for PHP versions prior to PHP 5.4 when dealing with encoding.
 */

namespace Masterminds\HTML5\Serializer;

/**
 * A mapping of entities to their html5 representation.
 * Used for older PHP
 * versions that don't have the mapping.
 */
class HTML5Entities
{
    public static $map = array(
        '	' => '&Tab;',
        "\n" => '&NewLine;',
        '!' => '&excl;',
        '"' => '&quot;',
        '#' => '&num;',
        '$' => '&dollar;',
        '%' => '&percnt;',
        '&' => '&amp;',
        '\'' => '&apos;',
        '(' => '&lpar;',
        ')' => '&rpar;',
        '*' => '&ast;',
        '+' => '&plus;',
        ',' => '&comma;',
        '.' => '&period;',
        '/' => '&sol;',
        ':' => '&colon;',
        ';' => '&semi;',
        '<' => '&lt;',
        '<⃒' => '&nvlt',
        '=' => '&equals;',
        '=⃥' => '&bne',
        '>' => '&gt;',
        '>⃒' => '&nvgt',
        '?' => '&quest;',
        '@' => '&commat;',
        '[' => '&lbrack;',
        '\\' => '&bsol;',
        ']' => '&rsqb;',
        '^' => '&Hat;',
        '_' => '&lowbar;',
        '`' => '&grave;',
        'fj' => '&fjlig',
        '{' => '&lbrace;',
        '|' => '&vert;',
        '}' => '&rcub;',
        ' ' => '&nbsp;',
        '¡' => '&iexcl;',
        '¢' => '&cent;',
        '£' => '&pound;',
        '¤' => '&curren;',
        '¥' => '&yen;',
        '¦' => '&brvbar;',
        '§' => '&sect;',
        '¨' => '&DoubleDot;',
        '©' => '&copy;',
        'ª' => '&ordf;',
        '«' => '&laquo;',
        '¬' => '&not;',
        '­' => '&shy;',
        '®' => '&reg;',
        '¯' => '&macr;',
        '°' => '&deg;',
        '±' => '&plusmn;',
        '²' => '&sup2;',
        '³' => '&sup3;',
        '´' => '&DiacriticalAcute;',
        'µ' => '&micro;',
        '¶' => '&para;',
        '·' => '&CenterDot;',
        '¸' => '&Cedilla;',
        '¹' => '&sup1;',
        'º' => '&ordm;',
        '»' => '&raquo;',
        '¼' => '&frac14;',
        '½' => '&half;',
        '¾' => '&frac34;',
        '¿' => '&iquest;',
        'À' => '&Agrave;',
        'Á' => '&Aacute;',
        'Â' => '&Acirc;',
        'Ã' => '&Atilde;',
        'Ä' => '&Auml;',
        'Å' => '&Aring;',
        'Æ' => '&AElig;',
        'Ç' => '&Ccedil;',
        'È' => '&Egrave;',
        'É' => '&Eacute;',
        'Ê' => '&Ecirc;',
        'Ë' => '&Euml;',
        'Ì' => '&Igrave;',
        'Í' => '&Iacute;',
        'Î' => '&Icirc;',
        'Ï' => '&Iuml;',
        'Ð' => '&ETH;',
        'Ñ' => '&Ntilde;',
        'Ò' => '&Ograve;',
        'Ó' => '&Oacute;',
        'Ô' => '&Ocirc;',
        'Õ' => '&Otilde;',
        'Ö' => '&Ouml;',
        '×' => '&times;',
        'Ø' => '&Oslash;',
        'Ù' => '&Ugrave;',
        'Ú' => '&Uacute;',
        'Û' => '&Ucirc;',
        'Ü' => '&Uuml;',
        'Ý' => '&Yacute;',
        'Þ' => '&THORN;',
        'ß' => '&szlig;',
        'à' => '&agrave;',
        'á' => '&aacute;',
        'â' => '&acirc;',
        'ã' => '&atilde;',
        'ä' => '&auml;',
        'å' => '&aring;',
        'æ' => '&aelig;',
        'ç' => '&ccedil;',
        'è' => '&egrave;',
        'é' => '&eacute;',
        'ê' => '&ecirc;',
        'ë' => '&euml;',
        'ì' => '&igrave;',
        'í' => '&iacute;',
        'î' => '&icirc;',
        'ï' => '&iuml;',
        'ð' => '&eth;',
        'ñ' => '&ntilde;',
        'ò' => '&ograve;',
        'ó' => '&oacute;',
        'ô' => '&ocirc;',
        'õ' => '&otilde;',
        'ö' => '&ouml;',
        '÷' => '&divide;',
        'ø' => '&oslash;',
        'ù' => '&ugrave;',
        'ú' => '&uacute;',
        'û' => '&ucirc;',
        'ü' => '&uuml;',
        'ý' => '&yacute;',
        'þ' => '&thorn;',
        'ÿ' => '&yuml;',
        'Ā' => '&Amacr;',
        'ā' => '&amacr;',
        'Ă' => '&Abreve;',
        'ă' => '&abreve;',
        'Ą' => '&Aogon;',
        'ą' => '&aogon;',
        'Ć' => '&Cacute;',
        'ć' => '&cacute;',
        'Ĉ' => '&Ccirc;',
        'ĉ' => '&ccirc;',
        'Ċ' => '&Cdot;',
        'ċ' => '&cdot;',
        'Č' => '&Ccaron;',
        'č' => '&ccaron;',
        'Ď' => '&Dcaron;',
        'ď' => '&dcaron;',
        'Đ' => '&Dstrok;',
        'đ' => '&dstrok;',
        'Ē' => '&Emacr;',
        'ē' => '&emacr;',
        'Ė' => '&Edot;',
        'ė' => '&edot;',
        'Ę' => '&Eogon;',
        'ę' => '&eogon;',
        'Ě' => '&Ecaron;',
        'ě' => '&ecaron;',
        'Ĝ' => '&Gcirc;',
        'ĝ' => '&gcirc;',
        'Ğ' => '&Gbreve;',
        'ğ' => '&gbreve;',
        'Ġ' => '&Gdot;',
        'ġ' => '&gdot;',
        'Ģ' => '&Gcedil;',
        'Ĥ' => '&Hcirc;',
        'ĥ' => '&hcirc;',
        'Ħ' => '&Hstrok;',
        'ħ' => '&hstrok;',
        'Ĩ' => '&Itilde;',
        'ĩ' => '&itilde;',
        'Ī' => '&Imacr;',
        'ī' => '&imacr;',
        'Į' => '&Iogon;',
        'į' => '&iogon;',
        'İ' => '&Idot;',
        'ı' => '&inodot;',
        'IJ' => '&IJlig;',
        'ij' => '&ijlig;',
        'Ĵ' => '&Jcirc;',
        'ĵ' => '&jcirc;',
        'Ķ' => '&Kcedil;',
        'ķ' => '&kcedil;',
        'ĸ' => '&kgreen;',
        'Ĺ' => '&Lacute;',
        'ĺ' => '&lacute;',
        'Ļ' => '&Lcedil;',
        'ļ' => '&lcedil;',
        'Ľ' => '&Lcaron;',
        'ľ' => '&lcaron;',
        'Ŀ' => '&Lmidot;',
        'ŀ' => '&lmidot;',
        'Ł' => '&Lstrok;',
        'ł' => '&lstrok;',
        'Ń' => '&Nacute;',
        'ń' => '&nacute;',
        'Ņ' => '&Ncedil;',
        'ņ' => '&ncedil;',
        'Ň' => '&Ncaron;',
        'ň' => '&ncaron;',
        'ʼn' => '&napos;',
        'Ŋ' => '&ENG;',
        'ŋ' => '&eng;',
        'Ō' => '&Omacr;',
        'ō' => '&omacr;',
        'Ő' => '&Odblac;',
        'ő' => '&odblac;',
        'Œ' => '&OElig;',
        'œ' => '&oelig;',
        'Ŕ' => '&Racute;',
        'ŕ' => '&racute;',
        'Ŗ' => '&Rcedil;',
        'ŗ' => '&rcedil;',
        'Ř' => '&Rcaron;',
        'ř' => '&rcaron;',
        'Ś' => '&Sacute;',
        'ś' => '&sacute;',
        'Ŝ' => '&Scirc;',
        'ŝ' => '&scirc;',
        'Ş' => '&Scedil;',
        'ş' => '&scedil;',
        'Š' => '&Scaron;',
        'š' => '&scaron;',
        'Ţ' => '&Tcedil;',
        'ţ' => '&tcedil;',
        'Ť' => '&Tcaron;',
        'ť' => '&tcaron;',
        'Ŧ' => '&Tstrok;',
        'ŧ' => '&tstrok;',
        'Ũ' => '&Utilde;',
        'ũ' => '&utilde;',
        'Ū' => '&Umacr;',
        'ū' => '&umacr;',
        'Ŭ' => '&Ubreve;',
        'ŭ' => '&ubreve;',
        'Ů' => '&Uring;',
        'ů' => '&uring;',
        'Ű' => '&Udblac;',
        'ű' => '&udblac;',
        'Ų' => '&Uogon;',
        'ų' => '&uogon;',
        'Ŵ' => '&Wcirc;',
        'ŵ' => '&wcirc;',
        'Ŷ' => '&Ycirc;',
        'ŷ' => '&ycirc;',
        'Ÿ' => '&Yuml;',
        'Ź' => '&Zacute;',
        'ź' => '&zacute;',
        'Ż' => '&Zdot;',
        'ż' => '&zdot;',
        'Ž' => '&Zcaron;',
        'ž' => '&zcaron;',
        'ƒ' => '&fnof;',
        'Ƶ' => '&imped;',
        'ǵ' => '&gacute;',
        'ȷ' => '&jmath;',
        'ˆ' => '&circ;',
        'ˇ' => '&Hacek;',
        '˘' => '&Breve;',
        '˙' => '&dot;',
        '˚' => '&ring;',
        '˛' => '&ogon;',
        '˜' => '&DiacriticalTilde;',
        '˝' => '&DiacriticalDoubleAcute;',
        '̑' => '&DownBreve;',
        'Α' => '&Alpha;',
        'Β' => '&Beta;',
        'Γ' => '&Gamma;',
        'Δ' => '&Delta;',
        'Ε' => '&Epsilon;',
        'Ζ' => '&Zeta;',
        'Η' => '&Eta;',
        'Θ' => '&Theta;',
        'Ι' => '&Iota;',
        'Κ' => '&Kappa;',
        'Λ' => '&Lambda;',
        'Μ' => '&Mu;',
        'Ν' => '&Nu;',
        'Ξ' => '&Xi;',
        'Ο' => '&Omicron;',
        'Π' => '&Pi;',
        'Ρ' => '&Rho;',
        'Σ' => '&Sigma;',
        'Τ' => '&Tau;',
        'Υ' => '&Upsilon;',
        'Φ' => '&Phi;',
        'Χ' => '&Chi;',
        'Ψ' => '&Psi;',
        'Ω' => '&Omega;',
        'α' => '&alpha;',
        'β' => '&beta;',
        'γ' => '&gamma;',
        'δ' => '&delta;',
        'ε' => '&epsi;',
        'ζ' => '&zeta;',
        'η' => '&eta;',
        'θ' => '&theta;',
        'ι' => '&iota;',
        'κ' => '&kappa;',
        'λ' => '&lambda;',
        'μ' => '&mu;',
        'ν' => '&nu;',
        'ξ' => '&xi;',
        'ο' => '&omicron;',
        'π' => '&pi;',
        'ρ' => '&rho;',
        'ς' => '&sigmav;',
        'σ' => '&sigma;',
        'τ' => '&tau;',
        'υ' => '&upsi;',
        'φ' => '&phi;',
        'χ' => '&chi;',
        'ψ' => '&psi;',
        'ω' => '&omega;',
        'ϑ' => '&thetasym;',
        'ϒ' => '&upsih;',
        'ϕ' => '&straightphi;',
        'ϖ' => '&piv;',
        'Ϝ' => '&Gammad;',
        'ϝ' => '&gammad;',
        'ϰ' => '&varkappa;',
        'ϱ' => '&rhov;',
        'ϵ' => '&straightepsilon;',
        '϶' => '&backepsilon;',
        'Ё' => '&IOcy;',
        'Ђ' => '&DJcy;',
        'Ѓ' => '&GJcy;',
        'Є' => '&Jukcy;',
        'Ѕ' => '&DScy;',
        'І' => '&Iukcy;',
        'Ї' => '&YIcy;',
        'Ј' => '&Jsercy;',
        'Љ' => '&LJcy;',
        'Њ' => '&NJcy;',
        'Ћ' => '&TSHcy;',
        'Ќ' => '&KJcy;',
        'Ў' => '&Ubrcy;',
        'Џ' => '&DZcy;',
        'А' => '&Acy;',
        'Б' => '&Bcy;',
        'В' => '&Vcy;',
        'Г' => '&Gcy;',
        'Д' => '&Dcy;',
        'Е' => '&IEcy;',
        'Ж' => '&ZHcy;',
        'З' => '&Zcy;',
        'И' => '&Icy;',
        'Й' => '&Jcy;',
        'К' => '&Kcy;',
        'Л' => '&Lcy;',
        'М' => '&Mcy;',
        'Н' => '&Ncy;',
        'О' => '&Ocy;',
        'П' => '&Pcy;',
        'Р' => '&Rcy;',
        'С' => '&Scy;',
        'Т' => '&Tcy;',
        'У' => '&Ucy;',
        'Ф' => '&Fcy;',
        'Х' => '&KHcy;',
        'Ц' => '&TScy;',
        'Ч' => '&CHcy;',
        'Ш' => '&SHcy;',
        'Щ' => '&SHCHcy;',
        'Ъ' => '&HARDcy;',
        'Ы' => '&Ycy;',
        'Ь' => '&SOFTcy;',
        'Э' => '&Ecy;',
        'Ю' => '&YUcy;',
        'Я' => '&YAcy;',
        'а' => '&acy;',
        'б' => '&bcy;',
        'в' => '&vcy;',
        'г' => '&gcy;',
        'д' => '&dcy;',
        'е' => '&iecy;',
        'ж' => '&zhcy;',
        'з' => '&zcy;',
        'и' => '&icy;',
        'й' => '&jcy;',
        'к' => '&kcy;',
        'л' => '&lcy;',
        'м' => '&mcy;',
        'н' => '&ncy;',
        'о' => '&ocy;',
        'п' => '&pcy;',
        'р' => '&rcy;',
        'с' => '&scy;',
        'т' => '&tcy;',
        'у' => '&ucy;',
        'ф' => '&fcy;',
        'х' => '&khcy;',
        'ц' => '&tscy;',
        'ч' => '&chcy;',
        'ш' => '&shcy;',
        'щ' => '&shchcy;',
        'ъ' => '&hardcy;',
        'ы' => '&ycy;',
        'ь' => '&softcy;',
        'э' => '&ecy;',
        'ю' => '&yucy;',
        'я' => '&yacy;',
        'ё' => '&iocy;',
        'ђ' => '&djcy;',
        'ѓ' => '&gjcy;',
        'є' => '&jukcy;',
        'ѕ' => '&dscy;',
        'і' => '&iukcy;',
        'ї' => '&yicy;',
        'ј' => '&jsercy;',
        'љ' => '&ljcy;',
        'њ' => '&njcy;',
        'ћ' => '&tshcy;',
        'ќ' => '&kjcy;',
        'ў' => '&ubrcy;',
        'џ' => '&dzcy;',
        ' ' => '&ensp;',
        ' ' => '&emsp;',
        ' ' => '&emsp13;',
        ' ' => '&emsp14;',
        ' ' => '&numsp;',
        ' ' => '&puncsp;',
        ' ' => '&ThinSpace;',
        ' ' => '&hairsp;',
        '​' => '&ZeroWidthSpace;',
        '‌' => '&zwnj;',
        '‍' => '&zwj;',
        '‎' => '&lrm;',
        '‏' => '&rlm;',
        '‐' => '&hyphen;',
        '–' => '&ndash;',
        '—' => '&mdash;',
        '―' => '&horbar;',
        '‖' => '&Verbar;',
        '‘' => '&OpenCurlyQuote;',
        '’' => '&rsquo;',
        '‚' => '&sbquo;',
        '“' => '&OpenCurlyDoubleQuote;',
        '”' => '&rdquo;',
        '„' => '&bdquo;',
        '†' => '&dagger;',
        '‡' => '&Dagger;',
        '•' => '&bull;',
        '‥' => '&nldr;',
        '…' => '&hellip;',
        '‰' => '&permil;',
        '‱' => '&pertenk;',
        '′' => '&prime;',
        '″' => '&Prime;',
        '‴' => '&tprime;',
        '‵' => '&backprime;',
        '‹' => '&lsaquo;',
        '›' => '&rsaquo;',
        '‾' => '&oline;',
        '⁁' => '&caret;',
        '⁃' => '&hybull;',
        '⁄' => '&frasl;',
        '⁏' => '&bsemi;',
        '⁗' => '&qprime;',
        ' ' => '&MediumSpace;',
        '  ' => '&ThickSpace',
        '⁠' => '&NoBreak;',
        '⁡' => '&af;',
        '⁢' => '&InvisibleTimes;',
        '⁣' => '&ic;',
        '€' => '&euro;',
        '⃛' => '&TripleDot;',
        '⃜' => '&DotDot;',
        'ℂ' => '&complexes;',
        '℅' => '&incare;',
        'ℊ' => '&gscr;',
        'ℋ' => '&HilbertSpace;',
        'ℌ' => '&Hfr;',
        'ℍ' => '&Hopf;',
        'ℎ' => '&planckh;',
        'ℏ' => '&planck;',
        'ℐ' => '&imagline;',
        'ℑ' => '&Ifr;',
        'ℒ' => '&lagran;',
        'ℓ' => '&ell;',
        'ℕ' => '&naturals;',
        '№' => '&numero;',
        '℗' => '&copysr;',
        '℘' => '&wp;',
        'ℙ' => '&primes;',
        'ℚ' => '&rationals;',
        'ℛ' => '&realine;',
        'ℜ' => '&Rfr;',
        'ℝ' => '&Ropf;',
        '℞' => '&rx;',
        '™' => '&trade;',
        'ℤ' => '&Zopf;',
        '℧' => '&mho;',
        'ℨ' => '&Zfr;',
        '℩' => '&iiota;',
        'ℬ' => '&Bscr;',
        'ℭ' => '&Cfr;',
        'ℯ' => '&escr;',
        'ℰ' => '&expectation;',
        'ℱ' => '&Fouriertrf;',
        'ℳ' => '&Mellintrf;',
        'ℴ' => '&orderof;',
        'ℵ' => '&aleph;',
        'ℶ' => '&beth;',
        'ℷ' => '&gimel;',
        'ℸ' => '&daleth;',
        'ⅅ' => '&CapitalDifferentialD;',
        'ⅆ' => '&DifferentialD;',
        'ⅇ' => '&exponentiale;',
        'ⅈ' => '&ImaginaryI;',
        '⅓' => '&frac13;',
        '⅔' => '&frac23;',
        '⅕' => '&frac15;',
        '⅖' => '&frac25;',
        '⅗' => '&frac35;',
        '⅘' => '&frac45;',
        '⅙' => '&frac16;',
        '⅚' => '&frac56;',
        '⅛' => '&frac18;',
        '⅜' => '&frac38;',
        '⅝' => '&frac58;',
        '⅞' => '&frac78;',
        '←' => '&larr;',
        '↑' => '&uarr;',
        '→' => '&srarr;',
        '↓' => '&darr;',
        '↔' => '&harr;',
        '↕' => '&UpDownArrow;',
        '↖' => '&nwarrow;',
        '↗' => '&UpperRightArrow;',
        '↘' => '&LowerRightArrow;',
        '↙' => '&swarr;',
        '↚' => '&nleftarrow;',
        '↛' => '&nrarr;',
        '↝' => '&rarrw;',
        '↝̸' => '&nrarrw',
        '↞' => '&Larr;',
        '↟' => '&Uarr;',
        '↠' => '&twoheadrightarrow;',
        '↡' => '&Darr;',
        '↢' => '&larrtl;',
        '↣' => '&rarrtl;',
        '↤' => '&LeftTeeArrow;',
        '↥' => '&UpTeeArrow;',
        '↦' => '&map;',
        '↧' => '&DownTeeArrow;',
        '↩' => '&larrhk;',
        '↪' => '&rarrhk;',
        '↫' => '&larrlp;',
        '↬' => '&looparrowright;',
        '↭' => '&harrw;',
        '↮' => '&nleftrightarrow;',
        '↰' => '&Lsh;',
        '↱' => '&rsh;',
        '↲' => '&ldsh;',
        '↳' => '&rdsh;',
        '↵' => '&crarr;',
        '↶' => '&curvearrowleft;',
        '↷' => '&curarr;',
        '↺' => '&olarr;',
        '↻' => '&orarr;',
        '↼' => '&leftharpoonup;',
        '↽' => '&leftharpoondown;',
        '↾' => '&RightUpVector;',
        '↿' => '&uharl;',
        '⇀' => '&rharu;',
        '⇁' => '&rhard;',
        '⇂' => '&RightDownVector;',
        '⇃' => '&dharl;',
        '⇄' => '&rightleftarrows;',
        '⇅' => '&udarr;',
        '⇆' => '&lrarr;',
        '⇇' => '&llarr;',
        '⇈' => '&upuparrows;',
        '⇉' => '&rrarr;',
        '⇊' => '&downdownarrows;',
        '⇋' => '&leftrightharpoons;',
        '⇌' => '&rightleftharpoons;',
        '⇍' => '&nLeftarrow;',
        '⇎' => '&nhArr;',
        '⇏' => '&nrArr;',
        '⇐' => '&DoubleLeftArrow;',
        '⇑' => '&DoubleUpArrow;',
        '⇒' => '&Implies;',
        '⇓' => '&Downarrow;',
        '⇔' => '&hArr;',
        '⇕' => '&Updownarrow;',
        '⇖' => '&nwArr;',
        '⇗' => '&neArr;',
        '⇘' => '&seArr;',
        '⇙' => '&swArr;',
        '⇚' => '&lAarr;',
        '⇛' => '&rAarr;',
        '⇝' => '&zigrarr;',
        '⇤' => '&LeftArrowBar;',
        '⇥' => '&RightArrowBar;',
        '⇵' => '&DownArrowUpArrow;',
        '⇽' => '&loarr;',
        '⇾' => '&roarr;',
        '⇿' => '&hoarr;',
        '∀' => '&forall;',
        '∁' => '&comp;',
        '∂' => '&part;',
        '∂̸' => '&npart',
        '∃' => '&Exists;',
        '∄' => '&nexist;',
        '∅' => '&empty;',
        '∇' => '&nabla;',
        '∈' => '&isinv;',
        '∉' => '&notin;',
        '∋' => '&ReverseElement;',
        '∌' => '&notniva;',
        '∏' => '&prod;',
        '∐' => '&Coproduct;',
        '∑' => '&sum;',
        '−' => '&minus;',
        '∓' => '&MinusPlus;',
        '∔' => '&plusdo;',
        '∖' => '&ssetmn;',
        '∗' => '&lowast;',
        '∘' => '&compfn;',
        '√' => '&Sqrt;',
        '∝' => '&prop;',
        '∞' => '&infin;',
        '∟' => '&angrt;',
        '∠' => '&angle;',
        '∠⃒' => '&nang',
        '∡' => '&angmsd;',
        '∢' => '&angsph;',
        '∣' => '&mid;',
        '∤' => '&nshortmid;',
        '∥' => '&shortparallel;',
        '∦' => '&nparallel;',
        '∧' => '&and;',
        '∨' => '&or;',
        '∩' => '&cap;',
        '∩︀' => '&caps',
        '∪' => '&cup;',
        '∪︀' => '&cups',
        '∫' => '&Integral;',
        '∬' => '&Int;',
        '∭' => '&tint;',
        '∮' => '&ContourIntegral;',
        '∯' => '&DoubleContourIntegral;',
        '∰' => '&Cconint;',
        '∱' => '&cwint;',
        '∲' => '&cwconint;',
        '∳' => '&awconint;',
        '∴' => '&there4;',
        '∵' => '&Because;',
        '∶' => '&ratio;',
        '∷' => '&Colon;',
        '∸' => '&minusd;',
        '∺' => '&mDDot;',
        '∻' => '&homtht;',
        '∼' => '&sim;',
        '∼⃒' => '&nvsim',
        '∽' => '&bsim;',
        '∽̱' => '&race',
        '∾' => '&ac;',
        '∾̳' => '&acE',
        '∿' => '&acd;',
        '≀' => '&wr;',
        '≁' => '&NotTilde;',
        '≂' => '&esim;',
        '≂̸' => '&nesim',
        '≃' => '&simeq;',
        '≄' => '&nsime;',
        '≅' => '&TildeFullEqual;',
        '≆' => '&simne;',
        '≇' => '&ncong;',
        '≈' => '&approx;',
        '≉' => '&napprox;',
        '≊' => '&ape;',
        '≋' => '&apid;',
        '≋̸' => '&napid',
        '≌' => '&bcong;',
        '≍' => '&CupCap;',
        '≍⃒' => '&nvap',
        '≎' => '&bump;',
        '≎̸' => '&nbump',
        '≏' => '&HumpEqual;',
        '≏̸' => '&nbumpe',
        '≐' => '&esdot;',
        '≐̸' => '&nedot',
        '≑' => '&doteqdot;',
        '≒' => '&fallingdotseq;',
        '≓' => '&risingdotseq;',
        '≔' => '&coloneq;',
        '≕' => '&eqcolon;',
        '≖' => '&ecir;',
        '≗' => '&circeq;',
        '≙' => '&wedgeq;',
        '≚' => '&veeeq;',
        '≜' => '&triangleq;',
        '≟' => '&equest;',
        '≠' => '&NotEqual;',
        '≡' => '&Congruent;',
        '≡⃥' => '&bnequiv',
        '≢' => '&NotCongruent;',
        '≤' => '&leq;',
        '≤⃒' => '&nvle',
        '≥' => '&ge;',
        '≥⃒' => '&nvge',
        '≦' => '&lE;',
        '≦̸' => '&nlE',
        '≧' => '&geqq;',
        '≧̸' => '&NotGreaterFullEqual',
        '≨' => '&lneqq;',
        '≨︀' => '&lvertneqq',
        '≩' => '&gneqq;',
        '≩︀' => '&gvertneqq',
        '≪' => '&ll;',
        '≪̸' => '&nLtv',
        '≪⃒' => '&nLt',
        '≫' => '&gg;',
        '≫̸' => '&NotGreaterGreater',
        '≫⃒' => '&nGt',
        '≬' => '&between;',
        '≭' => '&NotCupCap;',
        '≮' => '&NotLess;',
        '≯' => '&ngtr;',
        '≰' => '&NotLessEqual;',
        '≱' => '&ngeq;',
        '≲' => '&LessTilde;',
        '≳' => '&GreaterTilde;',
        '≴' => '&nlsim;',
        '≵' => '&ngsim;',
        '≶' => '&lessgtr;',
        '≷' => '&gl;',
        '≸' => '&ntlg;',
        '≹' => '&NotGreaterLess;',
        '≺' => '&prec;',
        '≻' => '&succ;',
        '≼' => '&PrecedesSlantEqual;',
        '≽' => '&succcurlyeq;',
        '≾' => '&precsim;',
        '≿' => '&SucceedsTilde;',
        '≿̸' => '&NotSucceedsTilde',
        '⊀' => '&npr;',
        '⊁' => '&NotSucceeds;',
        '⊂' => '&sub;',
        '⊂⃒' => '&vnsub',
        '⊃' => '&sup;',
        '⊃⃒' => '&nsupset',
        '⊄' => '&nsub;',
        '⊅' => '&nsup;',
        '⊆' => '&SubsetEqual;',
        '⊇' => '&supe;',
        '⊈' => '&NotSubsetEqual;',
        '⊉' => '&NotSupersetEqual;',
        '⊊' => '&subsetneq;',
        '⊊︀' => '&vsubne',
        '⊋' => '&supsetneq;',
        '⊋︀' => '&vsupne',
        '⊍' => '&cupdot;',
        '⊎' => '&UnionPlus;',
        '⊏' => '&sqsub;',
        '⊏̸' => '&NotSquareSubset',
        '⊐' => '&sqsupset;',
        '⊐̸' => '&NotSquareSuperset',
        '⊑' => '&SquareSubsetEqual;',
        '⊒' => '&SquareSupersetEqual;',
        '⊓' => '&sqcap;',
        '⊓︀' => '&sqcaps',
        '⊔' => '&sqcup;',
        '⊔︀' => '&sqcups',
        '⊕' => '&CirclePlus;',
        '⊖' => '&ominus;',
        '⊗' => '&CircleTimes;',
        '⊘' => '&osol;',
        '⊙' => '&CircleDot;',
        '⊚' => '&ocir;',
        '⊛' => '&oast;',
        '⊝' => '&odash;',
        '⊞' => '&boxplus;',
        '⊟' => '&boxminus;',
        '⊠' => '&timesb;',
        '⊡' => '&sdotb;',
        '⊢' => '&vdash;',
        '⊣' => '&dashv;',
        '⊤' => '&DownTee;',
        '⊥' => '&perp;',
        '⊧' => '&models;',
        '⊨' => '&DoubleRightTee;',
        '⊩' => '&Vdash;',
        '⊪' => '&Vvdash;',
        '⊫' => '&VDash;',
        '⊬' => '&nvdash;',
        '⊭' => '&nvDash;',
        '⊮' => '&nVdash;',
        '⊯' => '&nVDash;',
        '⊰' => '&prurel;',
        '⊲' => '&vartriangleleft;',
        '⊳' => '&vrtri;',
        '⊴' => '&LeftTriangleEqual;',
        '⊴⃒' => '&nvltrie',
        '⊵' => '&RightTriangleEqual;',
        '⊵⃒' => '&nvrtrie',
        '⊶' => '&origof;',
        '⊷' => '&imof;',
        '⊸' => '&mumap;',
        '⊹' => '&hercon;',
        '⊺' => '&intcal;',
        '⊻' => '&veebar;',
        '⊽' => '&barvee;',
        '⊾' => '&angrtvb;',
        '⊿' => '&lrtri;',
        '⋀' => '&xwedge;',
        '⋁' => '&xvee;',
        '⋂' => '&bigcap;',
        '⋃' => '&bigcup;',
        '⋄' => '&diamond;',
        '⋅' => '&sdot;',
        '⋆' => '&Star;',
        '⋇' => '&divonx;',
        '⋈' => '&bowtie;',
        '⋉' => '&ltimes;',
        '⋊' => '&rtimes;',
        '⋋' => '&lthree;',
        '⋌' => '&rthree;',
        '⋍' => '&backsimeq;',
        '⋎' => '&curlyvee;',
        '⋏' => '&curlywedge;',
        '⋐' => '&Sub;',
        '⋑' => '&Supset;',
        '⋒' => '&Cap;',
        '⋓' => '&Cup;',
        '⋔' => '&pitchfork;',
        '⋕' => '&epar;',
        '⋖' => '&lessdot;',
        '⋗' => '&gtrdot;',
        '⋘' => '&Ll;',
        '⋘̸' => '&nLl',
        '⋙' => '&Gg;',
        '⋙̸' => '&nGg',
        '⋚' => '&lesseqgtr;',
        '⋚︀' => '&lesg',
        '⋛' => '&gtreqless;',
        '⋛︀' => '&gesl',
        '⋞' => '&curlyeqprec;',
        '⋟' => '&cuesc;',
        '⋠' => '&NotPrecedesSlantEqual;',
        '⋡' => '&NotSucceedsSlantEqual;',
        '⋢' => '&NotSquareSubsetEqual;',
        '⋣' => '&NotSquareSupersetEqual;',
        '⋦' => '&lnsim;',
        '⋧' => '&gnsim;',
        '⋨' => '&precnsim;',
        '⋩' => '&scnsim;',
        '⋪' => '&nltri;',
        '⋫' => '&ntriangleright;',
        '⋬' => '&nltrie;',
        '⋭' => '&NotRightTriangleEqual;',
        '⋮' => '&vellip;',
        '⋯' => '&ctdot;',
        '⋰' => '&utdot;',
        '⋱' => '&dtdot;',
        '⋲' => '&disin;',
        '⋳' => '&isinsv;',
        '⋴' => '&isins;',
        '⋵' => '&isindot;',
        '⋵̸' => '&notindot',
        '⋶' => '&notinvc;',
        '⋷' => '&notinvb;',
        '⋹' => '&isinE;',
        '⋹̸' => '&notinE',
        '⋺' => '&nisd;',
        '⋻' => '&xnis;',
        '⋼' => '&nis;',
        '⋽' => '&notnivc;',
        '⋾' => '&notnivb;',
        '⌅' => '&barwed;',
        '⌆' => '&doublebarwedge;',
        '⌈' => '&lceil;',
        '⌉' => '&RightCeiling;',
        '⌊' => '&LeftFloor;',
        '⌋' => '&RightFloor;',
        '⌌' => '&drcrop;',
        '⌍' => '&dlcrop;',
        '⌎' => '&urcrop;',
        '⌏' => '&ulcrop;',
        '⌐' => '&bnot;',
        '⌒' => '&profline;',
        '⌓' => '&profsurf;',
        '⌕' => '&telrec;',
        '⌖' => '&target;',
        '⌜' => '&ulcorner;',
        '⌝' => '&urcorner;',
        '⌞' => '&llcorner;',
        '⌟' => '&drcorn;',
        '⌢' => '&frown;',
        '⌣' => '&smile;',
        '⌭' => '&cylcty;',
        '⌮' => '&profalar;',
        '⌶' => '&topbot;',
        '⌽' => '&ovbar;',
        '⌿' => '&solbar;',
        '⍼' => '&angzarr;',
        '⎰' => '&lmoust;',
        '⎱' => '&rmoust;',
        '⎴' => '&OverBracket;',
        '⎵' => '&bbrk;',
        '⎶' => '&bbrktbrk;',
        '⏜' => '&OverParenthesis;',
        '⏝' => '&UnderParenthesis;',
        '⏞' => '&OverBrace;',
        '⏟' => '&UnderBrace;',
        '⏢' => '&trpezium;',
        '⏧' => '&elinters;',
        '␣' => '&blank;',
        'Ⓢ' => '&oS;',
        '─' => '&HorizontalLine;',
        '│' => '&boxv;',
        '┌' => '&boxdr;',
        '┐' => '&boxdl;',
        '└' => '&boxur;',
        '┘' => '&boxul;',
        '├' => '&boxvr;',
        '┤' => '&boxvl;',
        '┬' => '&boxhd;',
        '┴' => '&boxhu;',
        '┼' => '&boxvh;',
        '═' => '&boxH;',
        '║' => '&boxV;',
        '╒' => '&boxdR;',
        '╓' => '&boxDr;',
        '╔' => '&boxDR;',
        '╕' => '&boxdL;',
        '╖' => '&boxDl;',
        '╗' => '&boxDL;',
        '╘' => '&boxuR;',
        '╙' => '&boxUr;',
        '╚' => '&boxUR;',
        '╛' => '&boxuL;',
        '╜' => '&boxUl;',
        '╝' => '&boxUL;',
        '╞' => '&boxvR;',
        '╟' => '&boxVr;',
        '╠' => '&boxVR;',
        '╡' => '&boxvL;',
        '╢' => '&boxVl;',
        '╣' => '&boxVL;',
        '╤' => '&boxHd;',
        '╥' => '&boxhD;',
        '╦' => '&boxHD;',
        '╧' => '&boxHu;',
        '╨' => '&boxhU;',
        '╩' => '&boxHU;',
        '╪' => '&boxvH;',
        '╫' => '&boxVh;',
        '╬' => '&boxVH;',
        '▀' => '&uhblk;',
        '▄' => '&lhblk;',
        '█' => '&block;',
        '░' => '&blk14;',
        '▒' => '&blk12;',
        '▓' => '&blk34;',
        '□' => '&Square;',
        '▪' => '&squarf;',
        '▫' => '&EmptyVerySmallSquare;',
        '▭' => '&rect;',
        '▮' => '&marker;',
        '▱' => '&fltns;',
        '△' => '&bigtriangleup;',
        '▴' => '&blacktriangle;',
        '▵' => '&triangle;',
        '▸' => '&blacktriangleright;',
        '▹' => '&rtri;',
        '▽' => '&bigtriangledown;',
        '▾' => '&blacktriangledown;',
        '▿' => '&triangledown;',
        '◂' => '&blacktriangleleft;',
        '◃' => '&ltri;',
        '◊' => '&lozenge;',
        '○' => '&cir;',
        '◬' => '&tridot;',
        '◯' => '&bigcirc;',
        '◸' => '&ultri;',
        '◹' => '&urtri;',
        '◺' => '&lltri;',
        '◻' => '&EmptySmallSquare;',
        '◼' => '&FilledSmallSquare;',
        '★' => '&starf;',
        '☆' => '&star;',
        '☎' => '&phone;',
        '♀' => '&female;',
        '♂' => '&male;',
        '♠' => '&spadesuit;',
        '♣' => '&clubs;',
        '♥' => '&hearts;',
        '♦' => '&diamondsuit;',
        '♪' => '&sung;',
        '♭' => '&flat;',
        '♮' => '&natur;',
        '♯' => '&sharp;',
        '✓' => '&check;',
        '✗' => '&cross;',
        '✠' => '&maltese;',
        '✶' => '&sext;',
        '❘' => '&VerticalSeparator;',
        '❲' => '&lbbrk;',
        '❳' => '&rbbrk;',
        '⟈' => '&bsolhsub;',
        '⟉' => '&suphsol;',
        '⟦' => '&LeftDoubleBracket;',
        '⟧' => '&RightDoubleBracket;',
        '⟨' => '&langle;',
        '⟩' => '&RightAngleBracket;',
        '⟪' => '&Lang;',
        '⟫' => '&Rang;',
        '⟬' => '&loang;',
        '⟭' => '&roang;',
        '⟵' => '&longleftarrow;',
        '⟶' => '&LongRightArrow;',
        '⟷' => '&LongLeftRightArrow;',
        '⟸' => '&xlArr;',
        '⟹' => '&DoubleLongRightArrow;',
        '⟺' => '&xhArr;',
        '⟼' => '&xmap;',
        '⟿' => '&dzigrarr;',
        '⤂' => '&nvlArr;',
        '⤃' => '&nvrArr;',
        '⤄' => '&nvHarr;',
        '⤅' => '&Map;',
        '⤌' => '&lbarr;',
        '⤍' => '&bkarow;',
        '⤎' => '&lBarr;',
        '⤏' => '&dbkarow;',
        '⤐' => '&drbkarow;',
        '⤑' => '&DDotrahd;',
        '⤒' => '&UpArrowBar;',
        '⤓' => '&DownArrowBar;',
        '⤖' => '&Rarrtl;',
        '⤙' => '&latail;',
        '⤚' => '&ratail;',
        '⤛' => '&lAtail;',
        '⤜' => '&rAtail;',
        '⤝' => '&larrfs;',
        '⤞' => '&rarrfs;',
        '⤟' => '&larrbfs;',
        '⤠' => '&rarrbfs;',
        '⤣' => '&nwarhk;',
        '⤤' => '&nearhk;',
        '⤥' => '&searhk;',
        '⤦' => '&swarhk;',
        '⤧' => '&nwnear;',
        '⤨' => '&toea;',
        '⤩' => '&seswar;',
        '⤪' => '&swnwar;',
        '⤳' => '&rarrc;',
        '⤳̸' => '&nrarrc',
        '⤵' => '&cudarrr;',
        '⤶' => '&ldca;',
        '⤷' => '&rdca;',
        '⤸' => '&cudarrl;',
        '⤹' => '&larrpl;',
        '⤼' => '&curarrm;',
        '⤽' => '&cularrp;',
        '⥅' => '&rarrpl;',
        '⥈' => '&harrcir;',
        '⥉' => '&Uarrocir;',
        '⥊' => '&lurdshar;',
        '⥋' => '&ldrushar;',
        '⥎' => '&LeftRightVector;',
        '⥏' => '&RightUpDownVector;',
        '⥐' => '&DownLeftRightVector;',
        '⥑' => '&LeftUpDownVector;',
        '⥒' => '&LeftVectorBar;',
        '⥓' => '&RightVectorBar;',
        '⥔' => '&RightUpVectorBar;',
        '⥕' => '&RightDownVectorBar;',
        '⥖' => '&DownLeftVectorBar;',
        '⥗' => '&DownRightVectorBar;',
        '⥘' => '&LeftUpVectorBar;',
        '⥙' => '&LeftDownVectorBar;',
        '⥚' => '&LeftTeeVector;',
        '⥛' => '&RightTeeVector;',
        '⥜' => '&RightUpTeeVector;',
        '⥝' => '&RightDownTeeVector;',
        '⥞' => '&DownLeftTeeVector;',
        '⥟' => '&DownRightTeeVector;',
        '⥠' => '&LeftUpTeeVector;',
        '⥡' => '&LeftDownTeeVector;',
        '⥢' => '&lHar;',
        '⥣' => '&uHar;',
        '⥤' => '&rHar;',
        '⥥' => '&dHar;',
        '⥦' => '&luruhar;',
        '⥧' => '&ldrdhar;',
        '⥨' => '&ruluhar;',
        '⥩' => '&rdldhar;',
        '⥪' => '&lharul;',
        '⥫' => '&llhard;',
        '⥬' => '&rharul;',
        '⥭' => '&lrhard;',
        '⥮' => '&udhar;',
        '⥯' => '&ReverseUpEquilibrium;',
        '⥰' => '&RoundImplies;',
        '⥱' => '&erarr;',
        '⥲' => '&simrarr;',
        '⥳' => '&larrsim;',
        '⥴' => '&rarrsim;',
        '⥵' => '&rarrap;',
        '⥶' => '&ltlarr;',
        '⥸' => '&gtrarr;',
        '⥹' => '&subrarr;',
        '⥻' => '&suplarr;',
        '⥼' => '&lfisht;',
        '⥽' => '&rfisht;',
        '⥾' => '&ufisht;',
        '⥿' => '&dfisht;',
        '⦅' => '&lopar;',
        '⦆' => '&ropar;',
        '⦋' => '&lbrke;',
        '⦌' => '&rbrke;',
        '⦍' => '&lbrkslu;',
        '⦎' => '&rbrksld;',
        '⦏' => '&lbrksld;',
        '⦐' => '&rbrkslu;',
        '⦑' => '&langd;',
        '⦒' => '&rangd;',
        '⦓' => '&lparlt;',
        '⦔' => '&rpargt;',
        '⦕' => '&gtlPar;',
        '⦖' => '&ltrPar;',
        '⦚' => '&vzigzag;',
        '⦜' => '&vangrt;',
        '⦝' => '&angrtvbd;',
        '⦤' => '&ange;',
        '⦥' => '&range;',
        '⦦' => '&dwangle;',
        '⦧' => '&uwangle;',
        '⦨' => '&angmsdaa;',
        '⦩' => '&angmsdab;',
        '⦪' => '&angmsdac;',
        '⦫' => '&angmsdad;',
        '⦬' => '&angmsdae;',
        '⦭' => '&angmsdaf;',
        '⦮' => '&angmsdag;',
        '⦯' => '&angmsdah;',
        '⦰' => '&bemptyv;',
        '⦱' => '&demptyv;',
        '⦲' => '&cemptyv;',
        '⦳' => '&raemptyv;',
        '⦴' => '&laemptyv;',
        '⦵' => '&ohbar;',
        '⦶' => '&omid;',
        '⦷' => '&opar;',
        '⦹' => '&operp;',
        '⦻' => '&olcross;',
        '⦼' => '&odsold;',
        '⦾' => '&olcir;',
        '⦿' => '&ofcir;',
        '⧀' => '&olt;',
        '⧁' => '&ogt;',
        '⧂' => '&cirscir;',
        '⧃' => '&cirE;',
        '⧄' => '&solb;',
        '⧅' => '&bsolb;',
        '⧉' => '&boxbox;',
        '⧍' => '&trisb;',
        '⧎' => '&rtriltri;',
        '⧏' => '&LeftTriangleBar;',
        '⧏̸' => '&NotLeftTriangleBar',
        '⧐' => '&RightTriangleBar;',
        '⧐̸' => '&NotRightTriangleBar',
        '⧜' => '&iinfin;',
        '⧝' => '&infintie;',
        '⧞' => '&nvinfin;',
        '⧣' => '&eparsl;',
        '⧤' => '&smeparsl;',
        '⧥' => '&eqvparsl;',
        '⧫' => '&lozf;',
        '⧴' => '&RuleDelayed;',
        '⧶' => '&dsol;',
        '⨀' => '&xodot;',
        '⨁' => '&bigoplus;',
        '⨂' => '&bigotimes;',
        '⨄' => '&biguplus;',
        '⨆' => '&bigsqcup;',
        '⨌' => '&iiiint;',
        '⨍' => '&fpartint;',
        '⨐' => '&cirfnint;',
        '⨑' => '&awint;',
        '⨒' => '&rppolint;',
        '⨓' => '&scpolint;',
        '⨔' => '&npolint;',
        '⨕' => '&pointint;',
        '⨖' => '&quatint;',
        '⨗' => '&intlarhk;',
        '⨢' => '&pluscir;',
        '⨣' => '&plusacir;',
        '⨤' => '&simplus;',
        '⨥' => '&plusdu;',
        '⨦' => '&plussim;',
        '⨧' => '&plustwo;',
        '⨩' => '&mcomma;',
        '⨪' => '&minusdu;',
        '⨭' => '&loplus;',
        '⨮' => '&roplus;',
        '⨯' => '&Cross;',
        '⨰' => '&timesd;',
        '⨱' => '&timesbar;',
        '⨳' => '&smashp;',
        '⨴' => '&lotimes;',
        '⨵' => '&rotimes;',
        '⨶' => '&otimesas;',
        '⨷' => '&Otimes;',
        '⨸' => '&odiv;',
        '⨹' => '&triplus;',
        '⨺' => '&triminus;',
        '⨻' => '&tritime;',
        '⨼' => '&iprod;',
        '⨿' => '&amalg;',
        '⩀' => '&capdot;',
        '⩂' => '&ncup;',
        '⩃' => '&ncap;',
        '⩄' => '&capand;',
        '⩅' => '&cupor;',
        '⩆' => '&cupcap;',
        '⩇' => '&capcup;',
        '⩈' => '&cupbrcap;',
        '⩉' => '&capbrcup;',
        '⩊' => '&cupcup;',
        '⩋' => '&capcap;',
        '⩌' => '&ccups;',
        '⩍' => '&ccaps;',
        '⩐' => '&ccupssm;',
        '⩓' => '&And;',
        '⩔' => '&Or;',
        '⩕' => '&andand;',
        '⩖' => '&oror;',
        '⩗' => '&orslope;',
        '⩘' => '&andslope;',
        '⩚' => '&andv;',
        '⩛' => '&orv;',
        '⩜' => '&andd;',
        '⩝' => '&ord;',
        '⩟' => '&wedbar;',
        '⩦' => '&sdote;',
        '⩪' => '&simdot;',
        '⩭' => '&congdot;',
        '⩭̸' => '&ncongdot',
        '⩮' => '&easter;',
        '⩯' => '&apacir;',
        '⩰' => '&apE;',
        '⩰̸' => '&napE',
        '⩱' => '&eplus;',
        '⩲' => '&pluse;',
        '⩳' => '&Esim;',
        '⩴' => '&Colone;',
        '⩵' => '&Equal;',
        '⩷' => '&ddotseq;',
        '⩸' => '&equivDD;',
        '⩹' => '&ltcir;',
        '⩺' => '&gtcir;',
        '⩻' => '&ltquest;',
        '⩼' => '&gtquest;',
        '⩽' => '&les;',
        '⩽̸' => '&nles',
        '⩾' => '&ges;',
        '⩾̸' => '&nges',
        '⩿' => '&lesdot;',
        '⪀' => '&gesdot;',
        '⪁' => '&lesdoto;',
        '⪂' => '&gesdoto;',
        '⪃' => '&lesdotor;',
        '⪄' => '&gesdotol;',
        '⪅' => '&lap;',
        '⪆' => '&gap;',
        '⪇' => '&lne;',
        '⪈' => '&gne;',
        '⪉' => '&lnap;',
        '⪊' => '&gnap;',
        '⪋' => '&lesseqqgtr;',
        '⪌' => '&gEl;',
        '⪍' => '&lsime;',
        '⪎' => '&gsime;',
        '⪏' => '&lsimg;',
        '⪐' => '&gsiml;',
        '⪑' => '&lgE;',
        '⪒' => '&glE;',
        '⪓' => '&lesges;',
        '⪔' => '&gesles;',
        '⪕' => '&els;',
        '⪖' => '&egs;',
        '⪗' => '&elsdot;',
        '⪘' => '&egsdot;',
        '⪙' => '&el;',
        '⪚' => '&eg;',
        '⪝' => '&siml;',
        '⪞' => '&simg;',
        '⪟' => '&simlE;',
        '⪠' => '&simgE;',
        '⪡' => '&LessLess;',
        '⪡̸' => '&NotNestedLessLess',
        '⪢' => '&GreaterGreater;',
        '⪢̸' => '&NotNestedGreaterGreater',
        '⪤' => '&glj;',
        '⪥' => '&gla;',
        '⪦' => '&ltcc;',
        '⪧' => '&gtcc;',
        '⪨' => '&lescc;',
        '⪩' => '&gescc;',
        '⪪' => '&smt;',
        '⪫' => '&lat;',
        '⪬' => '&smte;',
        '⪬︀' => '&smtes',
        '⪭' => '&late;',
        '⪭︀' => '&lates',
        '⪮' => '&bumpE;',
        '⪯' => '&preceq;',
        '⪯̸' => '&NotPrecedesEqual',
        '⪰' => '&SucceedsEqual;',
        '⪰̸' => '&NotSucceedsEqual',
        '⪳' => '&prE;',
        '⪴' => '&scE;',
        '⪵' => '&precneqq;',
        '⪶' => '&scnE;',
        '⪷' => '&precapprox;',
        '⪸' => '&succapprox;',
        '⪹' => '&precnapprox;',
        '⪺' => '&succnapprox;',
        '⪻' => '&Pr;',
        '⪼' => '&Sc;',
        '⪽' => '&subdot;',
        '⪾' => '&supdot;',
        '⪿' => '&subplus;',
        '⫀' => '&supplus;',
        '⫁' => '&submult;',
        '⫂' => '&supmult;',
        '⫃' => '&subedot;',
        '⫄' => '&supedot;',
        '⫅' => '&subE;',
        '⫅̸' => '&nsubE',
        '⫆' => '&supseteqq;',
        '⫆̸' => '&nsupseteqq',
        '⫇' => '&subsim;',
        '⫈' => '&supsim;',
        '⫋' => '&subsetneqq;',
        '⫋︀' => '&vsubnE',
        '⫌' => '&supnE;',
        '⫌︀' => '&varsupsetneqq',
        '⫏' => '&csub;',
        '⫐' => '&csup;',
        '⫑' => '&csube;',
        '⫒' => '&csupe;',
        '⫓' => '&subsup;',
        '⫔' => '&supsub;',
        '⫕' => '&subsub;',
        '⫖' => '&supsup;',
        '⫗' => '&suphsub;',
        '⫘' => '&supdsub;',
        '⫙' => '&forkv;',
        '⫚' => '&topfork;',
        '⫛' => '&mlcp;',
        '⫤' => '&Dashv;',
        '⫦' => '&Vdashl;',
        '⫧' => '&Barv;',
        '⫨' => '&vBar;',
        '⫩' => '&vBarv;',
        '⫫' => '&Vbar;',
        '⫬' => '&Not;',
        '⫭' => '&bNot;',
        '⫮' => '&rnmid;',
        '⫯' => '&cirmid;',
        '⫰' => '&midcir;',
        '⫱' => '&topcir;',
        '⫲' => '&nhpar;',
        '⫳' => '&parsim;',
        '⫽︀' => '&varsupsetneqq',
        'ff' => '&fflig;',
        'fi' => '&filig;',
        'fl' => '&fllig;',
        'ffi' => '&ffilig;',
        'ffl' => '&ffllig;',
        '𝒜' => '&Ascr;',
        '𝒞' => '&Cscr;',
        '𝒟' => '&Dscr;',
        '𝒢' => '&Gscr;',
        '𝒥' => '&Jscr;',
        '𝒦' => '&Kscr;',
        '𝒩' => '&Nscr;',
        '𝒪' => '&Oscr;',
        '𝒫' => '&Pscr;',
        '𝒬' => '&Qscr;',
        '𝒮' => '&Sscr;',
        '𝒯' => '&Tscr;',
        '𝒰' => '&Uscr;',
        '𝒱' => '&Vscr;',
        '𝒲' => '&Wscr;',
        '𝒳' => '&Xscr;',
        '𝒴' => '&Yscr;',
        '𝒵' => '&Zscr;',
        '𝒶' => '&ascr;',
        '𝒷' => '&bscr;',
        '𝒸' => '&cscr;',
        '𝒹' => '&dscr;',
        '𝒻' => '&fscr;',
        '𝒽' => '&hscr;',
        '𝒾' => '&iscr;',
        '𝒿' => '&jscr;',
        '𝓀' => '&kscr;',
        '𝓁' => '&lscr;',
        '𝓂' => '&mscr;',
        '𝓃' => '&nscr;',
        '𝓅' => '&pscr;',
        '𝓆' => '&qscr;',
        '𝓇' => '&rscr;',
        '𝓈' => '&sscr;',
        '𝓉' => '&tscr;',
        '𝓊' => '&uscr;',
        '𝓋' => '&vscr;',
        '𝓌' => '&wscr;',
        '𝓍' => '&xscr;',
        '𝓎' => '&yscr;',
        '𝓏' => '&zscr;',
        '𝔄' => '&Afr;',
        '𝔅' => '&Bfr;',
        '𝔇' => '&Dfr;',
        '𝔈' => '&Efr;',
        '𝔉' => '&Ffr;',
        '𝔊' => '&Gfr;',
        '𝔍' => '&Jfr;',
        '𝔎' => '&Kfr;',
        '𝔏' => '&Lfr;',
        '𝔐' => '&Mfr;',
        '𝔑' => '&Nfr;',
        '𝔒' => '&Ofr;',
        '𝔓' => '&Pfr;',
        '𝔔' => '&Qfr;',
        '𝔖' => '&Sfr;',
        '𝔗' => '&Tfr;',
        '𝔘' => '&Ufr;',
        '𝔙' => '&Vfr;',
        '𝔚' => '&Wfr;',
        '𝔛' => '&Xfr;',
        '𝔜' => '&Yfr;',
        '𝔞' => '&afr;',
        '𝔟' => '&bfr;',
        '𝔠' => '&cfr;',
        '𝔡' => '&dfr;',
        '𝔢' => '&efr;',
        '𝔣' => '&ffr;',
        '𝔤' => '&gfr;',
        '𝔥' => '&hfr;',
        '𝔦' => '&ifr;',
        '𝔧' => '&jfr;',
        '𝔨' => '&kfr;',
        '𝔩' => '&lfr;',
        '𝔪' => '&mfr;',
        '𝔫' => '&nfr;',
        '𝔬' => '&ofr;',
        '𝔭' => '&pfr;',
        '𝔮' => '&qfr;',
        '𝔯' => '&rfr;',
        '𝔰' => '&sfr;',
        '𝔱' => '&tfr;',
        '𝔲' => '&ufr;',
        '𝔳' => '&vfr;',
        '𝔴' => '&wfr;',
        '𝔵' => '&xfr;',
        '𝔶' => '&yfr;',
        '𝔷' => '&zfr;',
        '𝔸' => '&Aopf;',
        '𝔹' => '&Bopf;',
        '𝔻' => '&Dopf;',
        '𝔼' => '&Eopf;',
        '𝔽' => '&Fopf;',
        '𝔾' => '&Gopf;',
        '𝕀' => '&Iopf;',
        '𝕁' => '&Jopf;',
        '𝕂' => '&Kopf;',
        '𝕃' => '&Lopf;',
        '𝕄' => '&Mopf;',
        '𝕆' => '&Oopf;',
        '𝕊' => '&Sopf;',
        '𝕋' => '&Topf;',
        '𝕌' => '&Uopf;',
        '𝕍' => '&Vopf;',
        '𝕎' => '&Wopf;',
        '𝕏' => '&Xopf;',
        '𝕐' => '&Yopf;',
        '𝕒' => '&aopf;',
        '𝕓' => '&bopf;',
        '𝕔' => '&copf;',
        '𝕕' => '&dopf;',
        '𝕖' => '&eopf;',
        '𝕗' => '&fopf;',
        '𝕘' => '&gopf;',
        '𝕙' => '&hopf;',
        '𝕚' => '&iopf;',
        '𝕛' => '&jopf;',
        '𝕜' => '&kopf;',
        '𝕝' => '&lopf;',
        '𝕞' => '&mopf;',
        '𝕟' => '&nopf;',
        '𝕠' => '&oopf;',
        '𝕡' => '&popf;',
        '𝕢' => '&qopf;',
        '𝕣' => '&ropf;',
        '𝕤' => '&sopf;',
        '𝕥' => '&topf;',
        '𝕦' => '&uopf;',
        '𝕧' => '&vopf;',
        '𝕨' => '&wopf;',
        '𝕩' => '&xopf;',
        '𝕪' => '&yopf;',
        '𝕫' => '&zopf;',
    );
}
PK}c�\e%ա�0masterminds/html5/src/HTML5/Serializer/README.mdnu�[���# The Serializer (Writer) Model

The serializer roughly follows sections _8.1 Writing HTML documents_ and section
_8.3 Serializing HTML fragments_ by converting DOMDocument, DOMDocumentFragment,
and DOMNodeList into HTML5.

       [ HTML5 ]   // Interface for saving.
          ||
     [ Traverser ]   // Walk the DOM
          ||
       [ Rules ]     // Convert DOM elements into strings.
          ||
       [ HTML5 ]     // HTML5 document or fragment in text.


## HTML5 Class

Provides the top level interface for saving.

## The Traverser

Walks the DOM finding each element and passing it off to the output rules to
convert to HTML5.

## Output Rules

The output rules are defined in the RulesInterface which can have multiple
implementations. Currently, the OutputRules is the default implementation that
converts a DOM as is into HTML5.

## HTML5 String

The output of the process it HTML5 as a string or saved to a file.PK}c�\��e��)masterminds/html5/src/HTML5/Exception.phpnu�[���<?php

namespace Masterminds\HTML5;

/**
 * The base exception for the HTML5 project.
 */
class Exception extends \Exception
{
}
PK}c�\P�k���4masterminds/html5/src/HTML5/InstructionProcessor.phpnu�[���<?php
/**
 * A handler for processor instructions.
 */

namespace Masterminds\HTML5;

/**
 * Provide an processor to handle embedded instructions.
 *
 * XML defines a mechanism for inserting instructions (like PHP) into a
 * document. These are called "Processor Instructions." The HTML5 parser
 * provides an opportunity to handle these processor instructions during
 * the tree-building phase (before the DOM is constructed), which makes
 * it possible to alter the document as it is being created.
 *
 * One could, for example, use this mechanism to execute well-formed PHP
 * code embedded inside of an HTML5 document.
 */
interface InstructionProcessor
{
    /**
     * Process an individual processing instruction.
     *
     * The process() function is responsible for doing the following:
     * - Determining whether $name is an instruction type it can handle.
     * - Determining what to do with the data passed in.
     * - Making any subsequent modifications to the DOM by modifying the
     * DOMElement or its attached DOM tree.
     *
     * @param \DOMElement $element The parent element for the current processing instruction.
     * @param string      $name    The instruction's name. E.g. `&lt;?php` has the name `php`.
     * @param string      $data    All of the data between the opening and closing PI marks.
     *
     * @return \DOMElement The element that should be considered "Current". This may just be
     *                     the element passed in, but if the processor added more elements,
     *                     it may choose to reset the current element to one of the elements
     *                     it created. (When in doubt, return the element passed in.)
     */
    public function process(\DOMElement $element, $name, $data);
}
PK}c�\�(#\M\M(masterminds/html5/src/HTML5/Elements.phpnu�[���<?php
/**
 * Provide general element functions.
 */

namespace Masterminds\HTML5;

/**
 * This class provides general information about HTML5 elements,
 * including syntactic and semantic issues.
 * Parsers and serializers can
 * use this class as a reference point for information about the rules
 * of various HTML5 elements.
 *
 * @todo consider using a bitmask table lookup. There is enough overlap in
 *       naming that this could significantly shrink the size and maybe make it
 *       faster. See the Go teams implementation at https://code.google.com/p/go/source/browse/html/atom.
 */
class Elements
{
    /**
     * Indicates an element is described in the specification.
     */
    const KNOWN_ELEMENT = 1;

    // From section 8.1.2: "script", "style"
    // From 8.2.5.4.7 ("in body" insertion mode): "noembed"
    // From 8.4 "style", "xmp", "iframe", "noembed", "noframes"
    /**
     * Indicates the contained text should be processed as raw text.
     */
    const TEXT_RAW = 2;

    // From section 8.1.2: "textarea", "title"
    /**
     * Indicates the contained text should be processed as RCDATA.
     */
    const TEXT_RCDATA = 4;

    /**
     * Indicates the tag cannot have content.
     */
    const VOID_TAG = 8;

    // "address", "article", "aside", "blockquote", "center", "details", "dialog", "dir", "div", "dl",
    // "fieldset", "figcaption", "figure", "footer", "header", "hgroup", "menu",
    // "nav", "ol", "p", "section", "summary", "ul"
    // "h1", "h2", "h3", "h4", "h5", "h6"
    // "pre", "listing"
    // "form"
    // "plaintext"
    /**
     * Indicates that if a previous event is for a P tag, that element
     * should be considered closed.
     */
    const AUTOCLOSE_P = 16;

    /**
     * Indicates that the text inside is plaintext (pre).
     */
    const TEXT_PLAINTEXT = 32;

    // See https://developer.mozilla.org/en-US/docs/HTML/Block-level_elements
    /**
     * Indicates that the tag is a block.
     */
    const BLOCK_TAG = 64;

    /**
     * Indicates that the tag allows only inline elements as child nodes.
     */
    const BLOCK_ONLY_INLINE = 128;

    /**
     * The HTML5 elements as defined in http://dev.w3.org/html5/markup/elements.html.
     *
     * @var array
     */
    public static $html5 = array(
        'a' => 1,
        'abbr' => 1,
        'address' => 65, // NORMAL | BLOCK_TAG
        'area' => 9, // NORMAL | VOID_TAG
        'article' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
        'aside' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
        'audio' => 1, // NORMAL
        'b' => 1,
        'base' => 9, // NORMAL | VOID_TAG
        'bdi' => 1,
        'bdo' => 1,
        'blockquote' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
        'body' => 1,
        'br' => 9, // NORMAL | VOID_TAG
        'button' => 1,
        'canvas' => 65, // NORMAL | BLOCK_TAG
        'caption' => 1,
        'cite' => 1,
        'code' => 1,
        'col' => 9, // NORMAL | VOID_TAG
        'colgroup' => 1,
        'command' => 9, // NORMAL | VOID_TAG
                        // "data" => 1, // This is highly experimental and only part of the whatwg spec (not w3c). See https://developer.mozilla.org/en-US/docs/HTML/Element/data
        'datalist' => 1,
        'dd' => 65, // NORMAL | BLOCK_TAG
        'del' => 1,
        'details' => 17, // NORMAL | AUTOCLOSE_P,
        'dfn' => 1,
        'dialog' => 17, // NORMAL | AUTOCLOSE_P,
        'div' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
        'dl' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
        'dt' => 1,
        'em' => 1,
        'embed' => 9, // NORMAL | VOID_TAG
        'fieldset' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
        'figcaption' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
        'figure' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
        'footer' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
        'form' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
        'h1' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
        'h2' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
        'h3' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
        'h4' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
        'h5' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
        'h6' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
        'head' => 1,
        'header' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
        'hgroup' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
        'hr' => 73, // NORMAL | VOID_TAG
        'html' => 1,
        'i' => 1,
        'iframe' => 3, // NORMAL | TEXT_RAW
        'img' => 9, // NORMAL | VOID_TAG
        'input' => 9, // NORMAL | VOID_TAG
        'kbd' => 1,
        'ins' => 1,
        'keygen' => 9, // NORMAL | VOID_TAG
        'label' => 1,
        'legend' => 1,
        'li' => 1,
        'link' => 9, // NORMAL | VOID_TAG
        'map' => 1,
        'mark' => 1,
        'menu' => 17, // NORMAL | AUTOCLOSE_P,
        'meta' => 9, // NORMAL | VOID_TAG
        'meter' => 1,
        'nav' => 17, // NORMAL | AUTOCLOSE_P,
        'noscript' => 65, // NORMAL | BLOCK_TAG
        'object' => 1,
        'ol' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
        'optgroup' => 1,
        'option' => 1,
        'output' => 65, // NORMAL | BLOCK_TAG
        'p' => 209, // NORMAL | AUTOCLOSE_P | BLOCK_TAG | BLOCK_ONLY_INLINE
        'param' => 9, // NORMAL | VOID_TAG
        'pre' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
        'progress' => 1,
        'q' => 1,
        'rp' => 1,
        'rt' => 1,
        'ruby' => 1,
        's' => 1,
        'samp' => 1,
        'script' => 3, // NORMAL | TEXT_RAW
        'section' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
        'select' => 1,
        'small' => 1,
        'source' => 9, // NORMAL | VOID_TAG
        'span' => 1,
        'strong' => 1,
        'style' => 3, // NORMAL | TEXT_RAW
        'sub' => 1,
        'summary' => 17, // NORMAL | AUTOCLOSE_P,
        'sup' => 1,
        'table' => 65, // NORMAL | BLOCK_TAG
        'tbody' => 1,
        'td' => 1,
        'textarea' => 5, // NORMAL | TEXT_RCDATA
        'tfoot' => 65, // NORMAL | BLOCK_TAG
        'th' => 1,
        'thead' => 1,
        'time' => 1,
        'title' => 5, // NORMAL | TEXT_RCDATA
        'tr' => 1,
        'track' => 9, // NORMAL | VOID_TAG
        'u' => 1,
        'ul' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
        'var' => 1,
        'video' => 65, // NORMAL | BLOCK_TAG
        'wbr' => 9, // NORMAL | VOID_TAG

        // Legacy?
        'basefont' => 8, // VOID_TAG
        'bgsound' => 8, // VOID_TAG
        'noframes' => 2, // RAW_TEXT
        'frame' => 9, // NORMAL | VOID_TAG
        'frameset' => 1,
        'center' => 16,
        'dir' => 16,
        'listing' => 16, // AUTOCLOSE_P
        'plaintext' => 48, // AUTOCLOSE_P | TEXT_PLAINTEXT
        'applet' => 0,
        'marquee' => 0,
        'isindex' => 8, // VOID_TAG
        'xmp' => 20, // AUTOCLOSE_P | VOID_TAG | RAW_TEXT
        'noembed' => 2, // RAW_TEXT
        );

    /**
     * The MathML elements.
     * See http://www.w3.org/wiki/MathML/Elements.
     *
     * In our case we are only concerned with presentation MathML and not content
     * MathML. There is a nice list of this subset at https://developer.mozilla.org/en-US/docs/MathML/Element.
     *
     * @var array
     */
    public static $mathml = array(
        'maction' => 1,
        'maligngroup' => 1,
        'malignmark' => 1,
        'math' => 1,
        'menclose' => 1,
        'merror' => 1,
        'mfenced' => 1,
        'mfrac' => 1,
        'mglyph' => 1,
        'mi' => 1,
        'mlabeledtr' => 1,
        'mlongdiv' => 1,
        'mmultiscripts' => 1,
        'mn' => 1,
        'mo' => 1,
        'mover' => 1,
        'mpadded' => 1,
        'mphantom' => 1,
        'mroot' => 1,
        'mrow' => 1,
        'ms' => 1,
        'mscarries' => 1,
        'mscarry' => 1,
        'msgroup' => 1,
        'msline' => 1,
        'mspace' => 1,
        'msqrt' => 1,
        'msrow' => 1,
        'mstack' => 1,
        'mstyle' => 1,
        'msub' => 1,
        'msup' => 1,
        'msubsup' => 1,
        'mtable' => 1,
        'mtd' => 1,
        'mtext' => 1,
        'mtr' => 1,
        'munder' => 1,
        'munderover' => 1,
    );

    /**
     * The svg elements.
     *
     * The Mozilla documentation has a good list at https://developer.mozilla.org/en-US/docs/SVG/Element.
     * The w3c list appears to be lacking in some areas like filter effect elements.
     * That list can be found at http://www.w3.org/wiki/SVG/Elements.
     *
     * Note, FireFox appears to do a better job rendering filter effects than chrome.
     * While they are in the spec I'm not sure how widely implemented they are.
     *
     * @var array
     */
    public static $svg = array(
        'a' => 1,
        'altGlyph' => 1,
        'altGlyphDef' => 1,
        'altGlyphItem' => 1,
        'animate' => 1,
        'animateColor' => 1,
        'animateMotion' => 1,
        'animateTransform' => 1,
        'circle' => 1,
        'clipPath' => 1,
        'color-profile' => 1,
        'cursor' => 1,
        'defs' => 1,
        'desc' => 1,
        'ellipse' => 1,
        'feBlend' => 1,
        'feColorMatrix' => 1,
        'feComponentTransfer' => 1,
        'feComposite' => 1,
        'feConvolveMatrix' => 1,
        'feDiffuseLighting' => 1,
        'feDisplacementMap' => 1,
        'feDistantLight' => 1,
        'feFlood' => 1,
        'feFuncA' => 1,
        'feFuncB' => 1,
        'feFuncG' => 1,
        'feFuncR' => 1,
        'feGaussianBlur' => 1,
        'feImage' => 1,
        'feMerge' => 1,
        'feMergeNode' => 1,
        'feMorphology' => 1,
        'feOffset' => 1,
        'fePointLight' => 1,
        'feSpecularLighting' => 1,
        'feSpotLight' => 1,
        'feTile' => 1,
        'feTurbulence' => 1,
        'filter' => 1,
        'font' => 1,
        'font-face' => 1,
        'font-face-format' => 1,
        'font-face-name' => 1,
        'font-face-src' => 1,
        'font-face-uri' => 1,
        'foreignObject' => 1,
        'g' => 1,
        'glyph' => 1,
        'glyphRef' => 1,
        'hkern' => 1,
        'image' => 1,
        'line' => 1,
        'linearGradient' => 1,
        'marker' => 1,
        'mask' => 1,
        'metadata' => 1,
        'missing-glyph' => 1,
        'mpath' => 1,
        'path' => 1,
        'pattern' => 1,
        'polygon' => 1,
        'polyline' => 1,
        'radialGradient' => 1,
        'rect' => 1,
        'script' => 3, // NORMAL | RAW_TEXT
        'set' => 1,
        'stop' => 1,
        'style' => 3, // NORMAL | RAW_TEXT
        'svg' => 1,
        'switch' => 1,
        'symbol' => 1,
        'text' => 1,
        'textPath' => 1,
        'title' => 1,
        'tref' => 1,
        'tspan' => 1,
        'use' => 1,
        'view' => 1,
        'vkern' => 1,
    );

    /**
     * Some attributes in SVG are case sensitive.
     *
     * This map contains key/value pairs with the key as the lowercase attribute
     * name and the value with the correct casing.
     */
    public static $svgCaseSensitiveAttributeMap = array(
        'attributename' => 'attributeName',
        'attributetype' => 'attributeType',
        'basefrequency' => 'baseFrequency',
        'baseprofile' => 'baseProfile',
        'calcmode' => 'calcMode',
        'clippathunits' => 'clipPathUnits',
        'contentscripttype' => 'contentScriptType',
        'contentstyletype' => 'contentStyleType',
        'diffuseconstant' => 'diffuseConstant',
        'edgemode' => 'edgeMode',
        'externalresourcesrequired' => 'externalResourcesRequired',
        'filterres' => 'filterRes',
        'filterunits' => 'filterUnits',
        'glyphref' => 'glyphRef',
        'gradienttransform' => 'gradientTransform',
        'gradientunits' => 'gradientUnits',
        'kernelmatrix' => 'kernelMatrix',
        'kernelunitlength' => 'kernelUnitLength',
        'keypoints' => 'keyPoints',
        'keysplines' => 'keySplines',
        'keytimes' => 'keyTimes',
        'lengthadjust' => 'lengthAdjust',
        'limitingconeangle' => 'limitingConeAngle',
        'markerheight' => 'markerHeight',
        'markerunits' => 'markerUnits',
        'markerwidth' => 'markerWidth',
        'maskcontentunits' => 'maskContentUnits',
        'maskunits' => 'maskUnits',
        'numoctaves' => 'numOctaves',
        'pathlength' => 'pathLength',
        'patterncontentunits' => 'patternContentUnits',
        'patterntransform' => 'patternTransform',
        'patternunits' => 'patternUnits',
        'pointsatx' => 'pointsAtX',
        'pointsaty' => 'pointsAtY',
        'pointsatz' => 'pointsAtZ',
        'preservealpha' => 'preserveAlpha',
        'preserveaspectratio' => 'preserveAspectRatio',
        'primitiveunits' => 'primitiveUnits',
        'refx' => 'refX',
        'refy' => 'refY',
        'repeatcount' => 'repeatCount',
        'repeatdur' => 'repeatDur',
        'requiredextensions' => 'requiredExtensions',
        'requiredfeatures' => 'requiredFeatures',
        'specularconstant' => 'specularConstant',
        'specularexponent' => 'specularExponent',
        'spreadmethod' => 'spreadMethod',
        'startoffset' => 'startOffset',
        'stddeviation' => 'stdDeviation',
        'stitchtiles' => 'stitchTiles',
        'surfacescale' => 'surfaceScale',
        'systemlanguage' => 'systemLanguage',
        'tablevalues' => 'tableValues',
        'targetx' => 'targetX',
        'targety' => 'targetY',
        'textlength' => 'textLength',
        'viewbox' => 'viewBox',
        'viewtarget' => 'viewTarget',
        'xchannelselector' => 'xChannelSelector',
        'ychannelselector' => 'yChannelSelector',
        'zoomandpan' => 'zoomAndPan',
    );

    /**
     * Some SVG elements are case sensitive.
     * This map contains these.
     *
     * The map contains key/value store of the name is lowercase as the keys and
     * the correct casing as the value.
     */
    public static $svgCaseSensitiveElementMap = array(
        'altglyph' => 'altGlyph',
        'altglyphdef' => 'altGlyphDef',
        'altglyphitem' => 'altGlyphItem',
        'animatecolor' => 'animateColor',
        'animatemotion' => 'animateMotion',
        'animatetransform' => 'animateTransform',
        'clippath' => 'clipPath',
        'feblend' => 'feBlend',
        'fecolormatrix' => 'feColorMatrix',
        'fecomponenttransfer' => 'feComponentTransfer',
        'fecomposite' => 'feComposite',
        'feconvolvematrix' => 'feConvolveMatrix',
        'fediffuselighting' => 'feDiffuseLighting',
        'fedisplacementmap' => 'feDisplacementMap',
        'fedistantlight' => 'feDistantLight',
        'feflood' => 'feFlood',
        'fefunca' => 'feFuncA',
        'fefuncb' => 'feFuncB',
        'fefuncg' => 'feFuncG',
        'fefuncr' => 'feFuncR',
        'fegaussianblur' => 'feGaussianBlur',
        'feimage' => 'feImage',
        'femerge' => 'feMerge',
        'femergenode' => 'feMergeNode',
        'femorphology' => 'feMorphology',
        'feoffset' => 'feOffset',
        'fepointlight' => 'fePointLight',
        'fespecularlighting' => 'feSpecularLighting',
        'fespotlight' => 'feSpotLight',
        'fetile' => 'feTile',
        'feturbulence' => 'feTurbulence',
        'foreignobject' => 'foreignObject',
        'glyphref' => 'glyphRef',
        'lineargradient' => 'linearGradient',
        'radialgradient' => 'radialGradient',
        'textpath' => 'textPath',
    );

    /**
     * Check whether the given element meets the given criterion.
     *
     * Example:
     *
     * Elements::isA('script', Elements::TEXT_RAW); // Returns true.
     *
     * Elements::isA('script', Elements::TEXT_RCDATA); // Returns false.
     *
     * @param string $name The element name.
     * @param int    $mask One of the constants on this class.
     *
     * @return bool true if the element matches the mask, false otherwise.
     */
    public static function isA($name, $mask)
    {
        return (static::element($name) & $mask) === $mask;
    }

    /**
     * Test if an element is a valid html5 element.
     *
     * @param string $name The name of the element.
     *
     * @return bool true if a html5 element and false otherwise.
     */
    public static function isHtml5Element($name)
    {
        // html5 element names are case insensitive. Forcing lowercase for the check.
        // Do we need this check or will all data passed here already be lowercase?
        return isset(static::$html5[strtolower($name)]);
    }

    /**
     * Test if an element name is a valid MathML presentation element.
     *
     * @param string $name The name of the element.
     *
     * @return bool true if a MathML name and false otherwise.
     */
    public static function isMathMLElement($name)
    {
        // MathML is case-sensitive unlike html5 elements.
        return isset(static::$mathml[$name]);
    }

    /**
     * Test if an element is a valid SVG element.
     *
     * @param string $name The name of the element.
     *
     * @return bool true if a SVG element and false otherise.
     */
    public static function isSvgElement($name)
    {
        // SVG is case-sensitive unlike html5 elements.
        return isset(static::$svg[$name]);
    }

    /**
     * Is an element name valid in an html5 document.
     * This includes html5 elements along with other allowed embedded content
     * such as svg and mathml.
     *
     * @param string $name The name of the element.
     *
     * @return bool true if valid and false otherwise.
     */
    public static function isElement($name)
    {
        return static::isHtml5Element($name) || static::isMathMLElement($name) || static::isSvgElement($name);
    }

    /**
     * Get the element mask for the given element name.
     *
     * @param string $name The name of the element.
     *
     * @return int the element mask.
     */
    public static function element($name)
    {
        if (isset(static::$html5[$name])) {
            return static::$html5[$name];
        }
        if (isset(static::$svg[$name])) {
            return static::$svg[$name];
        }
        if (isset(static::$mathml[$name])) {
            return static::$mathml[$name];
        }

        return 0;
    }

    /**
     * Normalize a SVG element name to its proper case and form.
     *
     * @param string $name The name of the element.
     *
     * @return string the normalized form of the element name.
     */
    public static function normalizeSvgElement($name)
    {
        $name = strtolower($name);
        if (isset(static::$svgCaseSensitiveElementMap[$name])) {
            $name = static::$svgCaseSensitiveElementMap[$name];
        }

        return $name;
    }

    /**
     * Normalize a SVG attribute name to its proper case and form.
     *
     * @param string $name The name of the attribute.
     *
     * @return string The normalized form of the attribute name.
     */
    public static function normalizeSvgAttribute($name)
    {
        $name = strtolower($name);
        if (isset(static::$svgCaseSensitiveAttributeMap[$name])) {
            $name = static::$svgCaseSensitiveAttributeMap[$name];
        }

        return $name;
    }

    /**
     * Normalize a MathML attribute name to its proper case and form.
     * Note, all MathML element names are lowercase.
     *
     * @param string $name The name of the attribute.
     *
     * @return string The normalized form of the attribute name.
     */
    public static function normalizeMathMlAttribute($name)
    {
        $name = strtolower($name);

        // Only one attribute has a mixed case form for MathML.
        if ('definitionurl' === $name) {
            $name = 'definitionURL';
        }

        return $name;
    }
}
PK}c�\ȍRx��(masterminds/html5/src/HTML5/Entities.phpnu�[���<?php

namespace Masterminds\HTML5;

/**
 * Entity lookup tables.
 * This class is automatically generated.
 */
class Entities
{
    public static $byName = array(
        'Aacute' => 'Á',
        'Aacut' => 'Á',
        'aacute' => 'á',
        'aacut' => 'á',
        'Abreve' => 'Ă',
        'abreve' => 'ă',
        'ac' => '∾',
        'acd' => '∿',
        'acE' => '∾̳',
        'Acirc' => 'Â',
        'Acir' => 'Â',
        'acirc' => 'â',
        'acir' => 'â',
        'acute' => '´',
        'acut' => '´',
        'Acy' => 'А',
        'acy' => 'а',
        'AElig' => 'Æ',
        'AEli' => 'Æ',
        'aelig' => 'æ',
        'aeli' => 'æ',
        'af' => '⁡',
        'Afr' => '𝔄',
        'afr' => '𝔞',
        'Agrave' => 'À',
        'Agrav' => 'À',
        'agrave' => 'à',
        'agrav' => 'à',
        'alefsym' => 'ℵ',
        'aleph' => 'ℵ',
        'Alpha' => 'Α',
        'alpha' => 'α',
        'Amacr' => 'Ā',
        'amacr' => 'ā',
        'amalg' => '⨿',
        'AMP' => '&',
        'AM' => '&',
        'amp' => '&',
        'am' => '&',
        'And' => '⩓',
        'and' => '∧',
        'andand' => '⩕',
        'andd' => '⩜',
        'andslope' => '⩘',
        'andv' => '⩚',
        'ang' => '∠',
        'ange' => '⦤',
        'angle' => '∠',
        'angmsd' => '∡',
        'angmsdaa' => '⦨',
        'angmsdab' => '⦩',
        'angmsdac' => '⦪',
        'angmsdad' => '⦫',
        'angmsdae' => '⦬',
        'angmsdaf' => '⦭',
        'angmsdag' => '⦮',
        'angmsdah' => '⦯',
        'angrt' => '∟',
        'angrtvb' => '⊾',
        'angrtvbd' => '⦝',
        'angsph' => '∢',
        'angst' => 'Å',
        'angzarr' => '⍼',
        'Aogon' => 'Ą',
        'aogon' => 'ą',
        'Aopf' => '𝔸',
        'aopf' => '𝕒',
        'ap' => '≈',
        'apacir' => '⩯',
        'apE' => '⩰',
        'ape' => '≊',
        'apid' => '≋',
        'apos' => '\'',
        'ApplyFunction' => '⁡',
        'approx' => '≈',
        'approxeq' => '≊',
        'Aring' => 'Å',
        'Arin' => 'Å',
        'aring' => 'å',
        'arin' => 'å',
        'Ascr' => '𝒜',
        'ascr' => '𝒶',
        'Assign' => '≔',
        'ast' => '*',
        'asymp' => '≈',
        'asympeq' => '≍',
        'Atilde' => 'Ã',
        'Atild' => 'Ã',
        'atilde' => 'ã',
        'atild' => 'ã',
        'Auml' => 'Ä',
        'Aum' => 'Ä',
        'auml' => 'ä',
        'aum' => 'ä',
        'awconint' => '∳',
        'awint' => '⨑',
        'backcong' => '≌',
        'backepsilon' => '϶',
        'backprime' => '‵',
        'backsim' => '∽',
        'backsimeq' => '⋍',
        'Backslash' => '∖',
        'Barv' => '⫧',
        'barvee' => '⊽',
        'Barwed' => '⌆',
        'barwed' => '⌅',
        'barwedge' => '⌅',
        'bbrk' => '⎵',
        'bbrktbrk' => '⎶',
        'bcong' => '≌',
        'Bcy' => 'Б',
        'bcy' => 'б',
        'bdquo' => '„',
        'becaus' => '∵',
        'Because' => '∵',
        'because' => '∵',
        'bemptyv' => '⦰',
        'bepsi' => '϶',
        'bernou' => 'ℬ',
        'Bernoullis' => 'ℬ',
        'Beta' => 'Β',
        'beta' => 'β',
        'beth' => 'ℶ',
        'between' => '≬',
        'Bfr' => '𝔅',
        'bfr' => '𝔟',
        'bigcap' => '⋂',
        'bigcirc' => '◯',
        'bigcup' => '⋃',
        'bigodot' => '⨀',
        'bigoplus' => '⨁',
        'bigotimes' => '⨂',
        'bigsqcup' => '⨆',
        'bigstar' => '★',
        'bigtriangledown' => '▽',
        'bigtriangleup' => '△',
        'biguplus' => '⨄',
        'bigvee' => '⋁',
        'bigwedge' => '⋀',
        'bkarow' => '⤍',
        'blacklozenge' => '⧫',
        'blacksquare' => '▪',
        'blacktriangle' => '▴',
        'blacktriangledown' => '▾',
        'blacktriangleleft' => '◂',
        'blacktriangleright' => '▸',
        'blank' => '␣',
        'blk12' => '▒',
        'blk14' => '░',
        'blk34' => '▓',
        'block' => '█',
        'bne' => '=⃥',
        'bnequiv' => '≡⃥',
        'bNot' => '⫭',
        'bnot' => '⌐',
        'Bopf' => '𝔹',
        'bopf' => '𝕓',
        'bot' => '⊥',
        'bottom' => '⊥',
        'bowtie' => '⋈',
        'boxbox' => '⧉',
        'boxDL' => '╗',
        'boxDl' => '╖',
        'boxdL' => '╕',
        'boxdl' => '┐',
        'boxDR' => '╔',
        'boxDr' => '╓',
        'boxdR' => '╒',
        'boxdr' => '┌',
        'boxH' => '═',
        'boxh' => '─',
        'boxHD' => '╦',
        'boxHd' => '╤',
        'boxhD' => '╥',
        'boxhd' => '┬',
        'boxHU' => '╩',
        'boxHu' => '╧',
        'boxhU' => '╨',
        'boxhu' => '┴',
        'boxminus' => '⊟',
        'boxplus' => '⊞',
        'boxtimes' => '⊠',
        'boxUL' => '╝',
        'boxUl' => '╜',
        'boxuL' => '╛',
        'boxul' => '┘',
        'boxUR' => '╚',
        'boxUr' => '╙',
        'boxuR' => '╘',
        'boxur' => '└',
        'boxV' => '║',
        'boxv' => '│',
        'boxVH' => '╬',
        'boxVh' => '╫',
        'boxvH' => '╪',
        'boxvh' => '┼',
        'boxVL' => '╣',
        'boxVl' => '╢',
        'boxvL' => '╡',
        'boxvl' => '┤',
        'boxVR' => '╠',
        'boxVr' => '╟',
        'boxvR' => '╞',
        'boxvr' => '├',
        'bprime' => '‵',
        'Breve' => '˘',
        'breve' => '˘',
        'brvbar' => '¦',
        'brvba' => '¦',
        'Bscr' => 'ℬ',
        'bscr' => '𝒷',
        'bsemi' => '⁏',
        'bsim' => '∽',
        'bsime' => '⋍',
        'bsol' => '\\',
        'bsolb' => '⧅',
        'bsolhsub' => '⟈',
        'bull' => '•',
        'bullet' => '•',
        'bump' => '≎',
        'bumpE' => '⪮',
        'bumpe' => '≏',
        'Bumpeq' => '≎',
        'bumpeq' => '≏',
        'Cacute' => 'Ć',
        'cacute' => 'ć',
        'Cap' => '⋒',
        'cap' => '∩',
        'capand' => '⩄',
        'capbrcup' => '⩉',
        'capcap' => '⩋',
        'capcup' => '⩇',
        'capdot' => '⩀',
        'CapitalDifferentialD' => 'ⅅ',
        'caps' => '∩︀',
        'caret' => '⁁',
        'caron' => 'ˇ',
        'Cayleys' => 'ℭ',
        'ccaps' => '⩍',
        'Ccaron' => 'Č',
        'ccaron' => 'č',
        'Ccedil' => 'Ç',
        'Ccedi' => 'Ç',
        'ccedil' => 'ç',
        'ccedi' => 'ç',
        'Ccirc' => 'Ĉ',
        'ccirc' => 'ĉ',
        'Cconint' => '∰',
        'ccups' => '⩌',
        'ccupssm' => '⩐',
        'Cdot' => 'Ċ',
        'cdot' => 'ċ',
        'cedil' => '¸',
        'cedi' => '¸',
        'Cedilla' => '¸',
        'cemptyv' => '⦲',
        'cent' => '¢',
        'cen' => '¢',
        'CenterDot' => '·',
        'centerdot' => '·',
        'Cfr' => 'ℭ',
        'cfr' => '𝔠',
        'CHcy' => 'Ч',
        'chcy' => 'ч',
        'check' => '✓',
        'checkmark' => '✓',
        'Chi' => 'Χ',
        'chi' => 'χ',
        'cir' => '○',
        'circ' => 'ˆ',
        'circeq' => '≗',
        'circlearrowleft' => '↺',
        'circlearrowright' => '↻',
        'circledast' => '⊛',
        'circledcirc' => '⊚',
        'circleddash' => '⊝',
        'CircleDot' => '⊙',
        'circledR' => '®',
        'circledS' => 'Ⓢ',
        'CircleMinus' => '⊖',
        'CirclePlus' => '⊕',
        'CircleTimes' => '⊗',
        'cirE' => '⧃',
        'cire' => '≗',
        'cirfnint' => '⨐',
        'cirmid' => '⫯',
        'cirscir' => '⧂',
        'ClockwiseContourIntegral' => '∲',
        'CloseCurlyDoubleQuote' => '”',
        'CloseCurlyQuote' => '’',
        'clubs' => '♣',
        'clubsuit' => '♣',
        'Colon' => '∷',
        'colon' => ':',
        'Colone' => '⩴',
        'colone' => '≔',
        'coloneq' => '≔',
        'comma' => ',',
        'commat' => '@',
        'comp' => '∁',
        'compfn' => '∘',
        'complement' => '∁',
        'complexes' => 'ℂ',
        'cong' => '≅',
        'congdot' => '⩭',
        'Congruent' => '≡',
        'Conint' => '∯',
        'conint' => '∮',
        'ContourIntegral' => '∮',
        'Copf' => 'ℂ',
        'copf' => '𝕔',
        'coprod' => '∐',
        'Coproduct' => '∐',
        'COPY' => '©',
        'COP' => '©',
        'copy' => '©',
        'cop' => '©',
        'copysr' => '℗',
        'CounterClockwiseContourIntegral' => '∳',
        'crarr' => '↵',
        'Cross' => '⨯',
        'cross' => '✗',
        'Cscr' => '𝒞',
        'cscr' => '𝒸',
        'csub' => '⫏',
        'csube' => '⫑',
        'csup' => '⫐',
        'csupe' => '⫒',
        'ctdot' => '⋯',
        'cudarrl' => '⤸',
        'cudarrr' => '⤵',
        'cuepr' => '⋞',
        'cuesc' => '⋟',
        'cularr' => '↶',
        'cularrp' => '⤽',
        'Cup' => '⋓',
        'cup' => '∪',
        'cupbrcap' => '⩈',
        'CupCap' => '≍',
        'cupcap' => '⩆',
        'cupcup' => '⩊',
        'cupdot' => '⊍',
        'cupor' => '⩅',
        'cups' => '∪︀',
        'curarr' => '↷',
        'curarrm' => '⤼',
        'curlyeqprec' => '⋞',
        'curlyeqsucc' => '⋟',
        'curlyvee' => '⋎',
        'curlywedge' => '⋏',
        'curren' => '¤',
        'curre' => '¤',
        'curvearrowleft' => '↶',
        'curvearrowright' => '↷',
        'cuvee' => '⋎',
        'cuwed' => '⋏',
        'cwconint' => '∲',
        'cwint' => '∱',
        'cylcty' => '⌭',
        'Dagger' => '‡',
        'dagger' => '†',
        'daleth' => 'ℸ',
        'Darr' => '↡',
        'dArr' => '⇓',
        'darr' => '↓',
        'dash' => '‐',
        'Dashv' => '⫤',
        'dashv' => '⊣',
        'dbkarow' => '⤏',
        'dblac' => '˝',
        'Dcaron' => 'Ď',
        'dcaron' => 'ď',
        'Dcy' => 'Д',
        'dcy' => 'д',
        'DD' => 'ⅅ',
        'dd' => 'ⅆ',
        'ddagger' => '‡',
        'ddarr' => '⇊',
        'DDotrahd' => '⤑',
        'ddotseq' => '⩷',
        'deg' => '°',
        'de' => '°',
        'Del' => '∇',
        'Delta' => 'Δ',
        'delta' => 'δ',
        'demptyv' => '⦱',
        'dfisht' => '⥿',
        'Dfr' => '𝔇',
        'dfr' => '𝔡',
        'dHar' => '⥥',
        'dharl' => '⇃',
        'dharr' => '⇂',
        'DiacriticalAcute' => '´',
        'DiacriticalDot' => '˙',
        'DiacriticalDoubleAcute' => '˝',
        'DiacriticalGrave' => '`',
        'DiacriticalTilde' => '˜',
        'diam' => '⋄',
        'Diamond' => '⋄',
        'diamond' => '⋄',
        'diamondsuit' => '♦',
        'diams' => '♦',
        'die' => '¨',
        'DifferentialD' => 'ⅆ',
        'digamma' => 'ϝ',
        'disin' => '⋲',
        'div' => '÷',
        'divide' => '÷',
        'divid' => '÷',
        'divideontimes' => '⋇',
        'divonx' => '⋇',
        'DJcy' => 'Ђ',
        'djcy' => 'ђ',
        'dlcorn' => '⌞',
        'dlcrop' => '⌍',
        'dollar' => '$',
        'Dopf' => '𝔻',
        'dopf' => '𝕕',
        'Dot' => '¨',
        'dot' => '˙',
        'DotDot' => '⃜',
        'doteq' => '≐',
        'doteqdot' => '≑',
        'DotEqual' => '≐',
        'dotminus' => '∸',
        'dotplus' => '∔',
        'dotsquare' => '⊡',
        'doublebarwedge' => '⌆',
        'DoubleContourIntegral' => '∯',
        'DoubleDot' => '¨',
        'DoubleDownArrow' => '⇓',
        'DoubleLeftArrow' => '⇐',
        'DoubleLeftRightArrow' => '⇔',
        'DoubleLeftTee' => '⫤',
        'DoubleLongLeftArrow' => '⟸',
        'DoubleLongLeftRightArrow' => '⟺',
        'DoubleLongRightArrow' => '⟹',
        'DoubleRightArrow' => '⇒',
        'DoubleRightTee' => '⊨',
        'DoubleUpArrow' => '⇑',
        'DoubleUpDownArrow' => '⇕',
        'DoubleVerticalBar' => '∥',
        'DownArrow' => '↓',
        'Downarrow' => '⇓',
        'downarrow' => '↓',
        'DownArrowBar' => '⤓',
        'DownArrowUpArrow' => '⇵',
        'DownBreve' => '̑',
        'downdownarrows' => '⇊',
        'downharpoonleft' => '⇃',
        'downharpoonright' => '⇂',
        'DownLeftRightVector' => '⥐',
        'DownLeftTeeVector' => '⥞',
        'DownLeftVector' => '↽',
        'DownLeftVectorBar' => '⥖',
        'DownRightTeeVector' => '⥟',
        'DownRightVector' => '⇁',
        'DownRightVectorBar' => '⥗',
        'DownTee' => '⊤',
        'DownTeeArrow' => '↧',
        'drbkarow' => '⤐',
        'drcorn' => '⌟',
        'drcrop' => '⌌',
        'Dscr' => '𝒟',
        'dscr' => '𝒹',
        'DScy' => 'Ѕ',
        'dscy' => 'ѕ',
        'dsol' => '⧶',
        'Dstrok' => 'Đ',
        'dstrok' => 'đ',
        'dtdot' => '⋱',
        'dtri' => '▿',
        'dtrif' => '▾',
        'duarr' => '⇵',
        'duhar' => '⥯',
        'dwangle' => '⦦',
        'DZcy' => 'Џ',
        'dzcy' => 'џ',
        'dzigrarr' => '⟿',
        'Eacute' => 'É',
        'Eacut' => 'É',
        'eacute' => 'é',
        'eacut' => 'é',
        'easter' => '⩮',
        'Ecaron' => 'Ě',
        'ecaron' => 'ě',
        'ecir' => 'ê',
        'Ecirc' => 'Ê',
        'Ecir' => 'Ê',
        'ecirc' => 'ê',
        'ecolon' => '≕',
        'Ecy' => 'Э',
        'ecy' => 'э',
        'eDDot' => '⩷',
        'Edot' => 'Ė',
        'eDot' => '≑',
        'edot' => 'ė',
        'ee' => 'ⅇ',
        'efDot' => '≒',
        'Efr' => '𝔈',
        'efr' => '𝔢',
        'eg' => '⪚',
        'Egrave' => 'È',
        'Egrav' => 'È',
        'egrave' => 'è',
        'egrav' => 'è',
        'egs' => '⪖',
        'egsdot' => '⪘',
        'el' => '⪙',
        'Element' => '∈',
        'elinters' => '⏧',
        'ell' => 'ℓ',
        'els' => '⪕',
        'elsdot' => '⪗',
        'Emacr' => 'Ē',
        'emacr' => 'ē',
        'empty' => '∅',
        'emptyset' => '∅',
        'EmptySmallSquare' => '◻',
        'emptyv' => '∅',
        'EmptyVerySmallSquare' => '▫',
        'emsp' => ' ',
        'emsp13' => ' ',
        'emsp14' => ' ',
        'ENG' => 'Ŋ',
        'eng' => 'ŋ',
        'ensp' => ' ',
        'Eogon' => 'Ę',
        'eogon' => 'ę',
        'Eopf' => '𝔼',
        'eopf' => '𝕖',
        'epar' => '⋕',
        'eparsl' => '⧣',
        'eplus' => '⩱',
        'epsi' => 'ε',
        'Epsilon' => 'Ε',
        'epsilon' => 'ε',
        'epsiv' => 'ϵ',
        'eqcirc' => '≖',
        'eqcolon' => '≕',
        'eqsim' => '≂',
        'eqslantgtr' => '⪖',
        'eqslantless' => '⪕',
        'Equal' => '⩵',
        'equals' => '=',
        'EqualTilde' => '≂',
        'equest' => '≟',
        'Equilibrium' => '⇌',
        'equiv' => '≡',
        'equivDD' => '⩸',
        'eqvparsl' => '⧥',
        'erarr' => '⥱',
        'erDot' => '≓',
        'Escr' => 'ℰ',
        'escr' => 'ℯ',
        'esdot' => '≐',
        'Esim' => '⩳',
        'esim' => '≂',
        'Eta' => 'Η',
        'eta' => 'η',
        'ETH' => 'Ð',
        'ET' => 'Ð',
        'eth' => 'ð',
        'et' => 'ð',
        'Euml' => 'Ë',
        'Eum' => 'Ë',
        'euml' => 'ë',
        'eum' => 'ë',
        'euro' => '€',
        'excl' => '!',
        'exist' => '∃',
        'Exists' => '∃',
        'expectation' => 'ℰ',
        'ExponentialE' => 'ⅇ',
        'exponentiale' => 'ⅇ',
        'fallingdotseq' => '≒',
        'Fcy' => 'Ф',
        'fcy' => 'ф',
        'female' => '♀',
        'ffilig' => 'ffi',
        'fflig' => 'ff',
        'ffllig' => 'ffl',
        'Ffr' => '𝔉',
        'ffr' => '𝔣',
        'filig' => 'fi',
        'FilledSmallSquare' => '◼',
        'FilledVerySmallSquare' => '▪',
        'fjlig' => 'fj',
        'flat' => '♭',
        'fllig' => 'fl',
        'fltns' => '▱',
        'fnof' => 'ƒ',
        'Fopf' => '𝔽',
        'fopf' => '𝕗',
        'ForAll' => '∀',
        'forall' => '∀',
        'fork' => '⋔',
        'forkv' => '⫙',
        'Fouriertrf' => 'ℱ',
        'fpartint' => '⨍',
        'frac12' => '½',
        'frac1' => '¼',
        'frac13' => '⅓',
        'frac14' => '¼',
        'frac15' => '⅕',
        'frac16' => '⅙',
        'frac18' => '⅛',
        'frac23' => '⅔',
        'frac25' => '⅖',
        'frac34' => '¾',
        'frac3' => '¾',
        'frac35' => '⅗',
        'frac38' => '⅜',
        'frac45' => '⅘',
        'frac56' => '⅚',
        'frac58' => '⅝',
        'frac78' => '⅞',
        'frasl' => '⁄',
        'frown' => '⌢',
        'Fscr' => 'ℱ',
        'fscr' => '𝒻',
        'gacute' => 'ǵ',
        'Gamma' => 'Γ',
        'gamma' => 'γ',
        'Gammad' => 'Ϝ',
        'gammad' => 'ϝ',
        'gap' => '⪆',
        'Gbreve' => 'Ğ',
        'gbreve' => 'ğ',
        'Gcedil' => 'Ģ',
        'Gcirc' => 'Ĝ',
        'gcirc' => 'ĝ',
        'Gcy' => 'Г',
        'gcy' => 'г',
        'Gdot' => 'Ġ',
        'gdot' => 'ġ',
        'gE' => '≧',
        'ge' => '≥',
        'gEl' => '⪌',
        'gel' => '⋛',
        'geq' => '≥',
        'geqq' => '≧',
        'geqslant' => '⩾',
        'ges' => '⩾',
        'gescc' => '⪩',
        'gesdot' => '⪀',
        'gesdoto' => '⪂',
        'gesdotol' => '⪄',
        'gesl' => '⋛︀',
        'gesles' => '⪔',
        'Gfr' => '𝔊',
        'gfr' => '𝔤',
        'Gg' => '⋙',
        'gg' => '≫',
        'ggg' => '⋙',
        'gimel' => 'ℷ',
        'GJcy' => 'Ѓ',
        'gjcy' => 'ѓ',
        'gl' => '≷',
        'gla' => '⪥',
        'glE' => '⪒',
        'glj' => '⪤',
        'gnap' => '⪊',
        'gnapprox' => '⪊',
        'gnE' => '≩',
        'gne' => '⪈',
        'gneq' => '⪈',
        'gneqq' => '≩',
        'gnsim' => '⋧',
        'Gopf' => '𝔾',
        'gopf' => '𝕘',
        'grave' => '`',
        'GreaterEqual' => '≥',
        'GreaterEqualLess' => '⋛',
        'GreaterFullEqual' => '≧',
        'GreaterGreater' => '⪢',
        'GreaterLess' => '≷',
        'GreaterSlantEqual' => '⩾',
        'GreaterTilde' => '≳',
        'Gscr' => '𝒢',
        'gscr' => 'ℊ',
        'gsim' => '≳',
        'gsime' => '⪎',
        'gsiml' => '⪐',
        'GT' => '>',
        'G' => '>',
        'Gt' => '≫',
        'gt' => '>',
        'g' => '>',
        'gtcc' => '⪧',
        'gtcir' => '⩺',
        'gtdot' => '⋗',
        'gtlPar' => '⦕',
        'gtquest' => '⩼',
        'gtrapprox' => '⪆',
        'gtrarr' => '⥸',
        'gtrdot' => '⋗',
        'gtreqless' => '⋛',
        'gtreqqless' => '⪌',
        'gtrless' => '≷',
        'gtrsim' => '≳',
        'gvertneqq' => '≩︀',
        'gvnE' => '≩︀',
        'Hacek' => 'ˇ',
        'hairsp' => ' ',
        'half' => '½',
        'hamilt' => 'ℋ',
        'HARDcy' => 'Ъ',
        'hardcy' => 'ъ',
        'hArr' => '⇔',
        'harr' => '↔',
        'harrcir' => '⥈',
        'harrw' => '↭',
        'Hat' => '^',
        'hbar' => 'ℏ',
        'Hcirc' => 'Ĥ',
        'hcirc' => 'ĥ',
        'hearts' => '♥',
        'heartsuit' => '♥',
        'hellip' => '…',
        'hercon' => '⊹',
        'Hfr' => 'ℌ',
        'hfr' => '𝔥',
        'HilbertSpace' => 'ℋ',
        'hksearow' => '⤥',
        'hkswarow' => '⤦',
        'hoarr' => '⇿',
        'homtht' => '∻',
        'hookleftarrow' => '↩',
        'hookrightarrow' => '↪',
        'Hopf' => 'ℍ',
        'hopf' => '𝕙',
        'horbar' => '―',
        'HorizontalLine' => '─',
        'Hscr' => 'ℋ',
        'hscr' => '𝒽',
        'hslash' => 'ℏ',
        'Hstrok' => 'Ħ',
        'hstrok' => 'ħ',
        'HumpDownHump' => '≎',
        'HumpEqual' => '≏',
        'hybull' => '⁃',
        'hyphen' => '‐',
        'Iacute' => 'Í',
        'Iacut' => 'Í',
        'iacute' => 'í',
        'iacut' => 'í',
        'ic' => '⁣',
        'Icirc' => 'Î',
        'Icir' => 'Î',
        'icirc' => 'î',
        'icir' => 'î',
        'Icy' => 'И',
        'icy' => 'и',
        'Idot' => 'İ',
        'IEcy' => 'Е',
        'iecy' => 'е',
        'iexcl' => '¡',
        'iexc' => '¡',
        'iff' => '⇔',
        'Ifr' => 'ℑ',
        'ifr' => '𝔦',
        'Igrave' => 'Ì',
        'Igrav' => 'Ì',
        'igrave' => 'ì',
        'igrav' => 'ì',
        'ii' => 'ⅈ',
        'iiiint' => '⨌',
        'iiint' => '∭',
        'iinfin' => '⧜',
        'iiota' => '℩',
        'IJlig' => 'IJ',
        'ijlig' => 'ij',
        'Im' => 'ℑ',
        'Imacr' => 'Ī',
        'imacr' => 'ī',
        'image' => 'ℑ',
        'ImaginaryI' => 'ⅈ',
        'imagline' => 'ℐ',
        'imagpart' => 'ℑ',
        'imath' => 'ı',
        'imof' => '⊷',
        'imped' => 'Ƶ',
        'Implies' => '⇒',
        'in' => '∈',
        'incare' => '℅',
        'infin' => '∞',
        'infintie' => '⧝',
        'inodot' => 'ı',
        'Int' => '∬',
        'int' => '∫',
        'intcal' => '⊺',
        'integers' => 'ℤ',
        'Integral' => '∫',
        'intercal' => '⊺',
        'Intersection' => '⋂',
        'intlarhk' => '⨗',
        'intprod' => '⨼',
        'InvisibleComma' => '⁣',
        'InvisibleTimes' => '⁢',
        'IOcy' => 'Ё',
        'iocy' => 'ё',
        'Iogon' => 'Į',
        'iogon' => 'į',
        'Iopf' => '𝕀',
        'iopf' => '𝕚',
        'Iota' => 'Ι',
        'iota' => 'ι',
        'iprod' => '⨼',
        'iquest' => '¿',
        'iques' => '¿',
        'Iscr' => 'ℐ',
        'iscr' => '𝒾',
        'isin' => '∈',
        'isindot' => '⋵',
        'isinE' => '⋹',
        'isins' => '⋴',
        'isinsv' => '⋳',
        'isinv' => '∈',
        'it' => '⁢',
        'Itilde' => 'Ĩ',
        'itilde' => 'ĩ',
        'Iukcy' => 'І',
        'iukcy' => 'і',
        'Iuml' => 'Ï',
        'Ium' => 'Ï',
        'iuml' => 'ï',
        'ium' => 'ï',
        'Jcirc' => 'Ĵ',
        'jcirc' => 'ĵ',
        'Jcy' => 'Й',
        'jcy' => 'й',
        'Jfr' => '𝔍',
        'jfr' => '𝔧',
        'jmath' => 'ȷ',
        'Jopf' => '𝕁',
        'jopf' => '𝕛',
        'Jscr' => '𝒥',
        'jscr' => '𝒿',
        'Jsercy' => 'Ј',
        'jsercy' => 'ј',
        'Jukcy' => 'Є',
        'jukcy' => 'є',
        'Kappa' => 'Κ',
        'kappa' => 'κ',
        'kappav' => 'ϰ',
        'Kcedil' => 'Ķ',
        'kcedil' => 'ķ',
        'Kcy' => 'К',
        'kcy' => 'к',
        'Kfr' => '𝔎',
        'kfr' => '𝔨',
        'kgreen' => 'ĸ',
        'KHcy' => 'Х',
        'khcy' => 'х',
        'KJcy' => 'Ќ',
        'kjcy' => 'ќ',
        'Kopf' => '𝕂',
        'kopf' => '𝕜',
        'Kscr' => '𝒦',
        'kscr' => '𝓀',
        'lAarr' => '⇚',
        'Lacute' => 'Ĺ',
        'lacute' => 'ĺ',
        'laemptyv' => '⦴',
        'lagran' => 'ℒ',
        'Lambda' => 'Λ',
        'lambda' => 'λ',
        'Lang' => '⟪',
        'lang' => '⟨',
        'langd' => '⦑',
        'langle' => '⟨',
        'lap' => '⪅',
        'Laplacetrf' => 'ℒ',
        'laquo' => '«',
        'laqu' => '«',
        'Larr' => '↞',
        'lArr' => '⇐',
        'larr' => '←',
        'larrb' => '⇤',
        'larrbfs' => '⤟',
        'larrfs' => '⤝',
        'larrhk' => '↩',
        'larrlp' => '↫',
        'larrpl' => '⤹',
        'larrsim' => '⥳',
        'larrtl' => '↢',
        'lat' => '⪫',
        'lAtail' => '⤛',
        'latail' => '⤙',
        'late' => '⪭',
        'lates' => '⪭︀',
        'lBarr' => '⤎',
        'lbarr' => '⤌',
        'lbbrk' => '❲',
        'lbrace' => '{',
        'lbrack' => '[',
        'lbrke' => '⦋',
        'lbrksld' => '⦏',
        'lbrkslu' => '⦍',
        'Lcaron' => 'Ľ',
        'lcaron' => 'ľ',
        'Lcedil' => 'Ļ',
        'lcedil' => 'ļ',
        'lceil' => '⌈',
        'lcub' => '{',
        'Lcy' => 'Л',
        'lcy' => 'л',
        'ldca' => '⤶',
        'ldquo' => '“',
        'ldquor' => '„',
        'ldrdhar' => '⥧',
        'ldrushar' => '⥋',
        'ldsh' => '↲',
        'lE' => '≦',
        'le' => '≤',
        'LeftAngleBracket' => '⟨',
        'LeftArrow' => '←',
        'Leftarrow' => '⇐',
        'leftarrow' => '←',
        'LeftArrowBar' => '⇤',
        'LeftArrowRightArrow' => '⇆',
        'leftarrowtail' => '↢',
        'LeftCeiling' => '⌈',
        'LeftDoubleBracket' => '⟦',
        'LeftDownTeeVector' => '⥡',
        'LeftDownVector' => '⇃',
        'LeftDownVectorBar' => '⥙',
        'LeftFloor' => '⌊',
        'leftharpoondown' => '↽',
        'leftharpoonup' => '↼',
        'leftleftarrows' => '⇇',
        'LeftRightArrow' => '↔',
        'Leftrightarrow' => '⇔',
        'leftrightarrow' => '↔',
        'leftrightarrows' => '⇆',
        'leftrightharpoons' => '⇋',
        'leftrightsquigarrow' => '↭',
        'LeftRightVector' => '⥎',
        'LeftTee' => '⊣',
        'LeftTeeArrow' => '↤',
        'LeftTeeVector' => '⥚',
        'leftthreetimes' => '⋋',
        'LeftTriangle' => '⊲',
        'LeftTriangleBar' => '⧏',
        'LeftTriangleEqual' => '⊴',
        'LeftUpDownVector' => '⥑',
        'LeftUpTeeVector' => '⥠',
        'LeftUpVector' => '↿',
        'LeftUpVectorBar' => '⥘',
        'LeftVector' => '↼',
        'LeftVectorBar' => '⥒',
        'lEg' => '⪋',
        'leg' => '⋚',
        'leq' => '≤',
        'leqq' => '≦',
        'leqslant' => '⩽',
        'les' => '⩽',
        'lescc' => '⪨',
        'lesdot' => '⩿',
        'lesdoto' => '⪁',
        'lesdotor' => '⪃',
        'lesg' => '⋚︀',
        'lesges' => '⪓',
        'lessapprox' => '⪅',
        'lessdot' => '⋖',
        'lesseqgtr' => '⋚',
        'lesseqqgtr' => '⪋',
        'LessEqualGreater' => '⋚',
        'LessFullEqual' => '≦',
        'LessGreater' => '≶',
        'lessgtr' => '≶',
        'LessLess' => '⪡',
        'lesssim' => '≲',
        'LessSlantEqual' => '⩽',
        'LessTilde' => '≲',
        'lfisht' => '⥼',
        'lfloor' => '⌊',
        'Lfr' => '𝔏',
        'lfr' => '𝔩',
        'lg' => '≶',
        'lgE' => '⪑',
        'lHar' => '⥢',
        'lhard' => '↽',
        'lharu' => '↼',
        'lharul' => '⥪',
        'lhblk' => '▄',
        'LJcy' => 'Љ',
        'ljcy' => 'љ',
        'Ll' => '⋘',
        'll' => '≪',
        'llarr' => '⇇',
        'llcorner' => '⌞',
        'Lleftarrow' => '⇚',
        'llhard' => '⥫',
        'lltri' => '◺',
        'Lmidot' => 'Ŀ',
        'lmidot' => 'ŀ',
        'lmoust' => '⎰',
        'lmoustache' => '⎰',
        'lnap' => '⪉',
        'lnapprox' => '⪉',
        'lnE' => '≨',
        'lne' => '⪇',
        'lneq' => '⪇',
        'lneqq' => '≨',
        'lnsim' => '⋦',
        'loang' => '⟬',
        'loarr' => '⇽',
        'lobrk' => '⟦',
        'LongLeftArrow' => '⟵',
        'Longleftarrow' => '⟸',
        'longleftarrow' => '⟵',
        'LongLeftRightArrow' => '⟷',
        'Longleftrightarrow' => '⟺',
        'longleftrightarrow' => '⟷',
        'longmapsto' => '⟼',
        'LongRightArrow' => '⟶',
        'Longrightarrow' => '⟹',
        'longrightarrow' => '⟶',
        'looparrowleft' => '↫',
        'looparrowright' => '↬',
        'lopar' => '⦅',
        'Lopf' => '𝕃',
        'lopf' => '𝕝',
        'loplus' => '⨭',
        'lotimes' => '⨴',
        'lowast' => '∗',
        'lowbar' => '_',
        'LowerLeftArrow' => '↙',
        'LowerRightArrow' => '↘',
        'loz' => '◊',
        'lozenge' => '◊',
        'lozf' => '⧫',
        'lpar' => '(',
        'lparlt' => '⦓',
        'lrarr' => '⇆',
        'lrcorner' => '⌟',
        'lrhar' => '⇋',
        'lrhard' => '⥭',
        'lrm' => '‎',
        'lrtri' => '⊿',
        'lsaquo' => '‹',
        'Lscr' => 'ℒ',
        'lscr' => '𝓁',
        'Lsh' => '↰',
        'lsh' => '↰',
        'lsim' => '≲',
        'lsime' => '⪍',
        'lsimg' => '⪏',
        'lsqb' => '[',
        'lsquo' => '‘',
        'lsquor' => '‚',
        'Lstrok' => 'Ł',
        'lstrok' => 'ł',
        'LT' => '<',
        'L' => '<',
        'Lt' => '≪',
        'lt' => '<',
        'l' => '<',
        'ltcc' => '⪦',
        'ltcir' => '⩹',
        'ltdot' => '⋖',
        'lthree' => '⋋',
        'ltimes' => '⋉',
        'ltlarr' => '⥶',
        'ltquest' => '⩻',
        'ltri' => '◃',
        'ltrie' => '⊴',
        'ltrif' => '◂',
        'ltrPar' => '⦖',
        'lurdshar' => '⥊',
        'luruhar' => '⥦',
        'lvertneqq' => '≨︀',
        'lvnE' => '≨︀',
        'macr' => '¯',
        'mac' => '¯',
        'male' => '♂',
        'malt' => '✠',
        'maltese' => '✠',
        'Map' => '⤅',
        'map' => '↦',
        'mapsto' => '↦',
        'mapstodown' => '↧',
        'mapstoleft' => '↤',
        'mapstoup' => '↥',
        'marker' => '▮',
        'mcomma' => '⨩',
        'Mcy' => 'М',
        'mcy' => 'м',
        'mdash' => '—',
        'mDDot' => '∺',
        'measuredangle' => '∡',
        'MediumSpace' => ' ',
        'Mellintrf' => 'ℳ',
        'Mfr' => '𝔐',
        'mfr' => '𝔪',
        'mho' => '℧',
        'micro' => 'µ',
        'micr' => 'µ',
        'mid' => '∣',
        'midast' => '*',
        'midcir' => '⫰',
        'middot' => '·',
        'middo' => '·',
        'minus' => '−',
        'minusb' => '⊟',
        'minusd' => '∸',
        'minusdu' => '⨪',
        'MinusPlus' => '∓',
        'mlcp' => '⫛',
        'mldr' => '…',
        'mnplus' => '∓',
        'models' => '⊧',
        'Mopf' => '𝕄',
        'mopf' => '𝕞',
        'mp' => '∓',
        'Mscr' => 'ℳ',
        'mscr' => '𝓂',
        'mstpos' => '∾',
        'Mu' => 'Μ',
        'mu' => 'μ',
        'multimap' => '⊸',
        'mumap' => '⊸',
        'nabla' => '∇',
        'Nacute' => 'Ń',
        'nacute' => 'ń',
        'nang' => '∠⃒',
        'nap' => '≉',
        'napE' => '⩰̸',
        'napid' => '≋̸',
        'napos' => 'ʼn',
        'napprox' => '≉',
        'natur' => '♮',
        'natural' => '♮',
        'naturals' => 'ℕ',
        'nbsp' => ' ',
        'nbs' => ' ',
        'nbump' => '≎̸',
        'nbumpe' => '≏̸',
        'ncap' => '⩃',
        'Ncaron' => 'Ň',
        'ncaron' => 'ň',
        'Ncedil' => 'Ņ',
        'ncedil' => 'ņ',
        'ncong' => '≇',
        'ncongdot' => '⩭̸',
        'ncup' => '⩂',
        'Ncy' => 'Н',
        'ncy' => 'н',
        'ndash' => '–',
        'ne' => '≠',
        'nearhk' => '⤤',
        'neArr' => '⇗',
        'nearr' => '↗',
        'nearrow' => '↗',
        'nedot' => '≐̸',
        'NegativeMediumSpace' => '​',
        'NegativeThickSpace' => '​',
        'NegativeThinSpace' => '​',
        'NegativeVeryThinSpace' => '​',
        'nequiv' => '≢',
        'nesear' => '⤨',
        'nesim' => '≂̸',
        'NestedGreaterGreater' => '≫',
        'NestedLessLess' => '≪',
        'NewLine' => '
',
        'nexist' => '∄',
        'nexists' => '∄',
        'Nfr' => '𝔑',
        'nfr' => '𝔫',
        'ngE' => '≧̸',
        'nge' => '≱',
        'ngeq' => '≱',
        'ngeqq' => '≧̸',
        'ngeqslant' => '⩾̸',
        'nges' => '⩾̸',
        'nGg' => '⋙̸',
        'ngsim' => '≵',
        'nGt' => '≫⃒',
        'ngt' => '≯',
        'ngtr' => '≯',
        'nGtv' => '≫̸',
        'nhArr' => '⇎',
        'nharr' => '↮',
        'nhpar' => '⫲',
        'ni' => '∋',
        'nis' => '⋼',
        'nisd' => '⋺',
        'niv' => '∋',
        'NJcy' => 'Њ',
        'njcy' => 'њ',
        'nlArr' => '⇍',
        'nlarr' => '↚',
        'nldr' => '‥',
        'nlE' => '≦̸',
        'nle' => '≰',
        'nLeftarrow' => '⇍',
        'nleftarrow' => '↚',
        'nLeftrightarrow' => '⇎',
        'nleftrightarrow' => '↮',
        'nleq' => '≰',
        'nleqq' => '≦̸',
        'nleqslant' => '⩽̸',
        'nles' => '⩽̸',
        'nless' => '≮',
        'nLl' => '⋘̸',
        'nlsim' => '≴',
        'nLt' => '≪⃒',
        'nlt' => '≮',
        'nltri' => '⋪',
        'nltrie' => '⋬',
        'nLtv' => '≪̸',
        'nmid' => '∤',
        'NoBreak' => '⁠',
        'NonBreakingSpace' => ' ',
        'Nopf' => 'ℕ',
        'nopf' => '𝕟',
        'Not' => '⫬',
        'not' => '¬',
        'no' => '¬',
        'NotCongruent' => '≢',
        'NotCupCap' => '≭',
        'NotDoubleVerticalBar' => '∦',
        'NotElement' => '∉',
        'NotEqual' => '≠',
        'NotEqualTilde' => '≂̸',
        'NotExists' => '∄',
        'NotGreater' => '≯',
        'NotGreaterEqual' => '≱',
        'NotGreaterFullEqual' => '≧̸',
        'NotGreaterGreater' => '≫̸',
        'NotGreaterLess' => '≹',
        'NotGreaterSlantEqual' => '⩾̸',
        'NotGreaterTilde' => '≵',
        'NotHumpDownHump' => '≎̸',
        'NotHumpEqual' => '≏̸',
        'notin' => '∉',
        'notindot' => '⋵̸',
        'notinE' => '⋹̸',
        'notinva' => '∉',
        'notinvb' => '⋷',
        'notinvc' => '⋶',
        'NotLeftTriangle' => '⋪',
        'NotLeftTriangleBar' => '⧏̸',
        'NotLeftTriangleEqual' => '⋬',
        'NotLess' => '≮',
        'NotLessEqual' => '≰',
        'NotLessGreater' => '≸',
        'NotLessLess' => '≪̸',
        'NotLessSlantEqual' => '⩽̸',
        'NotLessTilde' => '≴',
        'NotNestedGreaterGreater' => '⪢̸',
        'NotNestedLessLess' => '⪡̸',
        'notni' => '∌',
        'notniva' => '∌',
        'notnivb' => '⋾',
        'notnivc' => '⋽',
        'NotPrecedes' => '⊀',
        'NotPrecedesEqual' => '⪯̸',
        'NotPrecedesSlantEqual' => '⋠',
        'NotReverseElement' => '∌',
        'NotRightTriangle' => '⋫',
        'NotRightTriangleBar' => '⧐̸',
        'NotRightTriangleEqual' => '⋭',
        'NotSquareSubset' => '⊏̸',
        'NotSquareSubsetEqual' => '⋢',
        'NotSquareSuperset' => '⊐̸',
        'NotSquareSupersetEqual' => '⋣',
        'NotSubset' => '⊂⃒',
        'NotSubsetEqual' => '⊈',
        'NotSucceeds' => '⊁',
        'NotSucceedsEqual' => '⪰̸',
        'NotSucceedsSlantEqual' => '⋡',
        'NotSucceedsTilde' => '≿̸',
        'NotSuperset' => '⊃⃒',
        'NotSupersetEqual' => '⊉',
        'NotTilde' => '≁',
        'NotTildeEqual' => '≄',
        'NotTildeFullEqual' => '≇',
        'NotTildeTilde' => '≉',
        'NotVerticalBar' => '∤',
        'npar' => '∦',
        'nparallel' => '∦',
        'nparsl' => '⫽⃥',
        'npart' => '∂̸',
        'npolint' => '⨔',
        'npr' => '⊀',
        'nprcue' => '⋠',
        'npre' => '⪯̸',
        'nprec' => '⊀',
        'npreceq' => '⪯̸',
        'nrArr' => '⇏',
        'nrarr' => '↛',
        'nrarrc' => '⤳̸',
        'nrarrw' => '↝̸',
        'nRightarrow' => '⇏',
        'nrightarrow' => '↛',
        'nrtri' => '⋫',
        'nrtrie' => '⋭',
        'nsc' => '⊁',
        'nsccue' => '⋡',
        'nsce' => '⪰̸',
        'Nscr' => '𝒩',
        'nscr' => '𝓃',
        'nshortmid' => '∤',
        'nshortparallel' => '∦',
        'nsim' => '≁',
        'nsime' => '≄',
        'nsimeq' => '≄',
        'nsmid' => '∤',
        'nspar' => '∦',
        'nsqsube' => '⋢',
        'nsqsupe' => '⋣',
        'nsub' => '⊄',
        'nsubE' => '⫅̸',
        'nsube' => '⊈',
        'nsubset' => '⊂⃒',
        'nsubseteq' => '⊈',
        'nsubseteqq' => '⫅̸',
        'nsucc' => '⊁',
        'nsucceq' => '⪰̸',
        'nsup' => '⊅',
        'nsupE' => '⫆̸',
        'nsupe' => '⊉',
        'nsupset' => '⊃⃒',
        'nsupseteq' => '⊉',
        'nsupseteqq' => '⫆̸',
        'ntgl' => '≹',
        'Ntilde' => 'Ñ',
        'Ntild' => 'Ñ',
        'ntilde' => 'ñ',
        'ntild' => 'ñ',
        'ntlg' => '≸',
        'ntriangleleft' => '⋪',
        'ntrianglelefteq' => '⋬',
        'ntriangleright' => '⋫',
        'ntrianglerighteq' => '⋭',
        'Nu' => 'Ν',
        'nu' => 'ν',
        'num' => '#',
        'numero' => '№',
        'numsp' => ' ',
        'nvap' => '≍⃒',
        'nVDash' => '⊯',
        'nVdash' => '⊮',
        'nvDash' => '⊭',
        'nvdash' => '⊬',
        'nvge' => '≥⃒',
        'nvgt' => '>⃒',
        'nvHarr' => '⤄',
        'nvinfin' => '⧞',
        'nvlArr' => '⤂',
        'nvle' => '≤⃒',
        'nvlt' => '<⃒',
        'nvltrie' => '⊴⃒',
        'nvrArr' => '⤃',
        'nvrtrie' => '⊵⃒',
        'nvsim' => '∼⃒',
        'nwarhk' => '⤣',
        'nwArr' => '⇖',
        'nwarr' => '↖',
        'nwarrow' => '↖',
        'nwnear' => '⤧',
        'Oacute' => 'Ó',
        'Oacut' => 'Ó',
        'oacute' => 'ó',
        'oacut' => 'ó',
        'oast' => '⊛',
        'ocir' => 'ô',
        'Ocirc' => 'Ô',
        'Ocir' => 'Ô',
        'ocirc' => 'ô',
        'Ocy' => 'О',
        'ocy' => 'о',
        'odash' => '⊝',
        'Odblac' => 'Ő',
        'odblac' => 'ő',
        'odiv' => '⨸',
        'odot' => '⊙',
        'odsold' => '⦼',
        'OElig' => 'Œ',
        'oelig' => 'œ',
        'ofcir' => '⦿',
        'Ofr' => '𝔒',
        'ofr' => '𝔬',
        'ogon' => '˛',
        'Ograve' => 'Ò',
        'Ograv' => 'Ò',
        'ograve' => 'ò',
        'ograv' => 'ò',
        'ogt' => '⧁',
        'ohbar' => '⦵',
        'ohm' => 'Ω',
        'oint' => '∮',
        'olarr' => '↺',
        'olcir' => '⦾',
        'olcross' => '⦻',
        'oline' => '‾',
        'olt' => '⧀',
        'Omacr' => 'Ō',
        'omacr' => 'ō',
        'Omega' => 'Ω',
        'omega' => 'ω',
        'Omicron' => 'Ο',
        'omicron' => 'ο',
        'omid' => '⦶',
        'ominus' => '⊖',
        'Oopf' => '𝕆',
        'oopf' => '𝕠',
        'opar' => '⦷',
        'OpenCurlyDoubleQuote' => '“',
        'OpenCurlyQuote' => '‘',
        'operp' => '⦹',
        'oplus' => '⊕',
        'Or' => '⩔',
        'or' => '∨',
        'orarr' => '↻',
        'ord' => 'º',
        'order' => 'ℴ',
        'orderof' => 'ℴ',
        'ordf' => 'ª',
        'ordm' => 'º',
        'origof' => '⊶',
        'oror' => '⩖',
        'orslope' => '⩗',
        'orv' => '⩛',
        'oS' => 'Ⓢ',
        'Oscr' => '𝒪',
        'oscr' => 'ℴ',
        'Oslash' => 'Ø',
        'Oslas' => 'Ø',
        'oslash' => 'ø',
        'oslas' => 'ø',
        'osol' => '⊘',
        'Otilde' => 'Õ',
        'Otild' => 'Õ',
        'otilde' => 'õ',
        'otild' => 'õ',
        'Otimes' => '⨷',
        'otimes' => '⊗',
        'otimesas' => '⨶',
        'Ouml' => 'Ö',
        'Oum' => 'Ö',
        'ouml' => 'ö',
        'oum' => 'ö',
        'ovbar' => '⌽',
        'OverBar' => '‾',
        'OverBrace' => '⏞',
        'OverBracket' => '⎴',
        'OverParenthesis' => '⏜',
        'par' => '¶',
        'para' => '¶',
        'parallel' => '∥',
        'parsim' => '⫳',
        'parsl' => '⫽',
        'part' => '∂',
        'PartialD' => '∂',
        'Pcy' => 'П',
        'pcy' => 'п',
        'percnt' => '%',
        'period' => '.',
        'permil' => '‰',
        'perp' => '⊥',
        'pertenk' => '‱',
        'Pfr' => '𝔓',
        'pfr' => '𝔭',
        'Phi' => 'Φ',
        'phi' => 'φ',
        'phiv' => 'ϕ',
        'phmmat' => 'ℳ',
        'phone' => '☎',
        'Pi' => 'Π',
        'pi' => 'π',
        'pitchfork' => '⋔',
        'piv' => 'ϖ',
        'planck' => 'ℏ',
        'planckh' => 'ℎ',
        'plankv' => 'ℏ',
        'plus' => '+',
        'plusacir' => '⨣',
        'plusb' => '⊞',
        'pluscir' => '⨢',
        'plusdo' => '∔',
        'plusdu' => '⨥',
        'pluse' => '⩲',
        'PlusMinus' => '±',
        'plusmn' => '±',
        'plusm' => '±',
        'plussim' => '⨦',
        'plustwo' => '⨧',
        'pm' => '±',
        'Poincareplane' => 'ℌ',
        'pointint' => '⨕',
        'Popf' => 'ℙ',
        'popf' => '𝕡',
        'pound' => '£',
        'poun' => '£',
        'Pr' => '⪻',
        'pr' => '≺',
        'prap' => '⪷',
        'prcue' => '≼',
        'prE' => '⪳',
        'pre' => '⪯',
        'prec' => '≺',
        'precapprox' => '⪷',
        'preccurlyeq' => '≼',
        'Precedes' => '≺',
        'PrecedesEqual' => '⪯',
        'PrecedesSlantEqual' => '≼',
        'PrecedesTilde' => '≾',
        'preceq' => '⪯',
        'precnapprox' => '⪹',
        'precneqq' => '⪵',
        'precnsim' => '⋨',
        'precsim' => '≾',
        'Prime' => '″',
        'prime' => '′',
        'primes' => 'ℙ',
        'prnap' => '⪹',
        'prnE' => '⪵',
        'prnsim' => '⋨',
        'prod' => '∏',
        'Product' => '∏',
        'profalar' => '⌮',
        'profline' => '⌒',
        'profsurf' => '⌓',
        'prop' => '∝',
        'Proportion' => '∷',
        'Proportional' => '∝',
        'propto' => '∝',
        'prsim' => '≾',
        'prurel' => '⊰',
        'Pscr' => '𝒫',
        'pscr' => '𝓅',
        'Psi' => 'Ψ',
        'psi' => 'ψ',
        'puncsp' => ' ',
        'Qfr' => '𝔔',
        'qfr' => '𝔮',
        'qint' => '⨌',
        'Qopf' => 'ℚ',
        'qopf' => '𝕢',
        'qprime' => '⁗',
        'Qscr' => '𝒬',
        'qscr' => '𝓆',
        'quaternions' => 'ℍ',
        'quatint' => '⨖',
        'quest' => '?',
        'questeq' => '≟',
        'QUOT' => '"',
        'QUO' => '"',
        'quot' => '"',
        'quo' => '"',
        'rAarr' => '⇛',
        'race' => '∽̱',
        'Racute' => 'Ŕ',
        'racute' => 'ŕ',
        'radic' => '√',
        'raemptyv' => '⦳',
        'Rang' => '⟫',
        'rang' => '⟩',
        'rangd' => '⦒',
        'range' => '⦥',
        'rangle' => '⟩',
        'raquo' => '»',
        'raqu' => '»',
        'Rarr' => '↠',
        'rArr' => '⇒',
        'rarr' => '→',
        'rarrap' => '⥵',
        'rarrb' => '⇥',
        'rarrbfs' => '⤠',
        'rarrc' => '⤳',
        'rarrfs' => '⤞',
        'rarrhk' => '↪',
        'rarrlp' => '↬',
        'rarrpl' => '⥅',
        'rarrsim' => '⥴',
        'Rarrtl' => '⤖',
        'rarrtl' => '↣',
        'rarrw' => '↝',
        'rAtail' => '⤜',
        'ratail' => '⤚',
        'ratio' => '∶',
        'rationals' => 'ℚ',
        'RBarr' => '⤐',
        'rBarr' => '⤏',
        'rbarr' => '⤍',
        'rbbrk' => '❳',
        'rbrace' => '}',
        'rbrack' => ']',
        'rbrke' => '⦌',
        'rbrksld' => '⦎',
        'rbrkslu' => '⦐',
        'Rcaron' => 'Ř',
        'rcaron' => 'ř',
        'Rcedil' => 'Ŗ',
        'rcedil' => 'ŗ',
        'rceil' => '⌉',
        'rcub' => '}',
        'Rcy' => 'Р',
        'rcy' => 'р',
        'rdca' => '⤷',
        'rdldhar' => '⥩',
        'rdquo' => '”',
        'rdquor' => '”',
        'rdsh' => '↳',
        'Re' => 'ℜ',
        'real' => 'ℜ',
        'realine' => 'ℛ',
        'realpart' => 'ℜ',
        'reals' => 'ℝ',
        'rect' => '▭',
        'REG' => '®',
        'RE' => '®',
        'reg' => '®',
        're' => '®',
        'ReverseElement' => '∋',
        'ReverseEquilibrium' => '⇋',
        'ReverseUpEquilibrium' => '⥯',
        'rfisht' => '⥽',
        'rfloor' => '⌋',
        'Rfr' => 'ℜ',
        'rfr' => '𝔯',
        'rHar' => '⥤',
        'rhard' => '⇁',
        'rharu' => '⇀',
        'rharul' => '⥬',
        'Rho' => 'Ρ',
        'rho' => 'ρ',
        'rhov' => 'ϱ',
        'RightAngleBracket' => '⟩',
        'RightArrow' => '→',
        'Rightarrow' => '⇒',
        'rightarrow' => '→',
        'RightArrowBar' => '⇥',
        'RightArrowLeftArrow' => '⇄',
        'rightarrowtail' => '↣',
        'RightCeiling' => '⌉',
        'RightDoubleBracket' => '⟧',
        'RightDownTeeVector' => '⥝',
        'RightDownVector' => '⇂',
        'RightDownVectorBar' => '⥕',
        'RightFloor' => '⌋',
        'rightharpoondown' => '⇁',
        'rightharpoonup' => '⇀',
        'rightleftarrows' => '⇄',
        'rightleftharpoons' => '⇌',
        'rightrightarrows' => '⇉',
        'rightsquigarrow' => '↝',
        'RightTee' => '⊢',
        'RightTeeArrow' => '↦',
        'RightTeeVector' => '⥛',
        'rightthreetimes' => '⋌',
        'RightTriangle' => '⊳',
        'RightTriangleBar' => '⧐',
        'RightTriangleEqual' => '⊵',
        'RightUpDownVector' => '⥏',
        'RightUpTeeVector' => '⥜',
        'RightUpVector' => '↾',
        'RightUpVectorBar' => '⥔',
        'RightVector' => '⇀',
        'RightVectorBar' => '⥓',
        'ring' => '˚',
        'risingdotseq' => '≓',
        'rlarr' => '⇄',
        'rlhar' => '⇌',
        'rlm' => '‏',
        'rmoust' => '⎱',
        'rmoustache' => '⎱',
        'rnmid' => '⫮',
        'roang' => '⟭',
        'roarr' => '⇾',
        'robrk' => '⟧',
        'ropar' => '⦆',
        'Ropf' => 'ℝ',
        'ropf' => '𝕣',
        'roplus' => '⨮',
        'rotimes' => '⨵',
        'RoundImplies' => '⥰',
        'rpar' => ')',
        'rpargt' => '⦔',
        'rppolint' => '⨒',
        'rrarr' => '⇉',
        'Rrightarrow' => '⇛',
        'rsaquo' => '›',
        'Rscr' => 'ℛ',
        'rscr' => '𝓇',
        'Rsh' => '↱',
        'rsh' => '↱',
        'rsqb' => ']',
        'rsquo' => '’',
        'rsquor' => '’',
        'rthree' => '⋌',
        'rtimes' => '⋊',
        'rtri' => '▹',
        'rtrie' => '⊵',
        'rtrif' => '▸',
        'rtriltri' => '⧎',
        'RuleDelayed' => '⧴',
        'ruluhar' => '⥨',
        'rx' => '℞',
        'Sacute' => 'Ś',
        'sacute' => 'ś',
        'sbquo' => '‚',
        'Sc' => '⪼',
        'sc' => '≻',
        'scap' => '⪸',
        'Scaron' => 'Š',
        'scaron' => 'š',
        'sccue' => '≽',
        'scE' => '⪴',
        'sce' => '⪰',
        'Scedil' => 'Ş',
        'scedil' => 'ş',
        'Scirc' => 'Ŝ',
        'scirc' => 'ŝ',
        'scnap' => '⪺',
        'scnE' => '⪶',
        'scnsim' => '⋩',
        'scpolint' => '⨓',
        'scsim' => '≿',
        'Scy' => 'С',
        'scy' => 'с',
        'sdot' => '⋅',
        'sdotb' => '⊡',
        'sdote' => '⩦',
        'searhk' => '⤥',
        'seArr' => '⇘',
        'searr' => '↘',
        'searrow' => '↘',
        'sect' => '§',
        'sec' => '§',
        'semi' => ';',
        'seswar' => '⤩',
        'setminus' => '∖',
        'setmn' => '∖',
        'sext' => '✶',
        'Sfr' => '𝔖',
        'sfr' => '𝔰',
        'sfrown' => '⌢',
        'sharp' => '♯',
        'SHCHcy' => 'Щ',
        'shchcy' => 'щ',
        'SHcy' => 'Ш',
        'shcy' => 'ш',
        'ShortDownArrow' => '↓',
        'ShortLeftArrow' => '←',
        'shortmid' => '∣',
        'shortparallel' => '∥',
        'ShortRightArrow' => '→',
        'ShortUpArrow' => '↑',
        'shy' => '­',
        'sh' => '­',
        'Sigma' => 'Σ',
        'sigma' => 'σ',
        'sigmaf' => 'ς',
        'sigmav' => 'ς',
        'sim' => '∼',
        'simdot' => '⩪',
        'sime' => '≃',
        'simeq' => '≃',
        'simg' => '⪞',
        'simgE' => '⪠',
        'siml' => '⪝',
        'simlE' => '⪟',
        'simne' => '≆',
        'simplus' => '⨤',
        'simrarr' => '⥲',
        'slarr' => '←',
        'SmallCircle' => '∘',
        'smallsetminus' => '∖',
        'smashp' => '⨳',
        'smeparsl' => '⧤',
        'smid' => '∣',
        'smile' => '⌣',
        'smt' => '⪪',
        'smte' => '⪬',
        'smtes' => '⪬︀',
        'SOFTcy' => 'Ь',
        'softcy' => 'ь',
        'sol' => '/',
        'solb' => '⧄',
        'solbar' => '⌿',
        'Sopf' => '𝕊',
        'sopf' => '𝕤',
        'spades' => '♠',
        'spadesuit' => '♠',
        'spar' => '∥',
        'sqcap' => '⊓',
        'sqcaps' => '⊓︀',
        'sqcup' => '⊔',
        'sqcups' => '⊔︀',
        'Sqrt' => '√',
        'sqsub' => '⊏',
        'sqsube' => '⊑',
        'sqsubset' => '⊏',
        'sqsubseteq' => '⊑',
        'sqsup' => '⊐',
        'sqsupe' => '⊒',
        'sqsupset' => '⊐',
        'sqsupseteq' => '⊒',
        'squ' => '□',
        'Square' => '□',
        'square' => '□',
        'SquareIntersection' => '⊓',
        'SquareSubset' => '⊏',
        'SquareSubsetEqual' => '⊑',
        'SquareSuperset' => '⊐',
        'SquareSupersetEqual' => '⊒',
        'SquareUnion' => '⊔',
        'squarf' => '▪',
        'squf' => '▪',
        'srarr' => '→',
        'Sscr' => '𝒮',
        'sscr' => '𝓈',
        'ssetmn' => '∖',
        'ssmile' => '⌣',
        'sstarf' => '⋆',
        'Star' => '⋆',
        'star' => '☆',
        'starf' => '★',
        'straightepsilon' => 'ϵ',
        'straightphi' => 'ϕ',
        'strns' => '¯',
        'Sub' => '⋐',
        'sub' => '⊂',
        'subdot' => '⪽',
        'subE' => '⫅',
        'sube' => '⊆',
        'subedot' => '⫃',
        'submult' => '⫁',
        'subnE' => '⫋',
        'subne' => '⊊',
        'subplus' => '⪿',
        'subrarr' => '⥹',
        'Subset' => '⋐',
        'subset' => '⊂',
        'subseteq' => '⊆',
        'subseteqq' => '⫅',
        'SubsetEqual' => '⊆',
        'subsetneq' => '⊊',
        'subsetneqq' => '⫋',
        'subsim' => '⫇',
        'subsub' => '⫕',
        'subsup' => '⫓',
        'succ' => '≻',
        'succapprox' => '⪸',
        'succcurlyeq' => '≽',
        'Succeeds' => '≻',
        'SucceedsEqual' => '⪰',
        'SucceedsSlantEqual' => '≽',
        'SucceedsTilde' => '≿',
        'succeq' => '⪰',
        'succnapprox' => '⪺',
        'succneqq' => '⪶',
        'succnsim' => '⋩',
        'succsim' => '≿',
        'SuchThat' => '∋',
        'Sum' => '∑',
        'sum' => '∑',
        'sung' => '♪',
        'Sup' => '⋑',
        'sup' => '³',
        'sup1' => '¹',
        'sup2' => '²',
        'sup3' => '³',
        'supdot' => '⪾',
        'supdsub' => '⫘',
        'supE' => '⫆',
        'supe' => '⊇',
        'supedot' => '⫄',
        'Superset' => '⊃',
        'SupersetEqual' => '⊇',
        'suphsol' => '⟉',
        'suphsub' => '⫗',
        'suplarr' => '⥻',
        'supmult' => '⫂',
        'supnE' => '⫌',
        'supne' => '⊋',
        'supplus' => '⫀',
        'Supset' => '⋑',
        'supset' => '⊃',
        'supseteq' => '⊇',
        'supseteqq' => '⫆',
        'supsetneq' => '⊋',
        'supsetneqq' => '⫌',
        'supsim' => '⫈',
        'supsub' => '⫔',
        'supsup' => '⫖',
        'swarhk' => '⤦',
        'swArr' => '⇙',
        'swarr' => '↙',
        'swarrow' => '↙',
        'swnwar' => '⤪',
        'szlig' => 'ß',
        'szli' => 'ß',
        'Tab' => '	',
        'target' => '⌖',
        'Tau' => 'Τ',
        'tau' => 'τ',
        'tbrk' => '⎴',
        'Tcaron' => 'Ť',
        'tcaron' => 'ť',
        'Tcedil' => 'Ţ',
        'tcedil' => 'ţ',
        'Tcy' => 'Т',
        'tcy' => 'т',
        'tdot' => '⃛',
        'telrec' => '⌕',
        'Tfr' => '𝔗',
        'tfr' => '𝔱',
        'there4' => '∴',
        'Therefore' => '∴',
        'therefore' => '∴',
        'Theta' => 'Θ',
        'theta' => 'θ',
        'thetasym' => 'ϑ',
        'thetav' => 'ϑ',
        'thickapprox' => '≈',
        'thicksim' => '∼',
        'ThickSpace' => '  ',
        'thinsp' => ' ',
        'ThinSpace' => ' ',
        'thkap' => '≈',
        'thksim' => '∼',
        'THORN' => 'Þ',
        'THOR' => 'Þ',
        'thorn' => 'þ',
        'thor' => 'þ',
        'Tilde' => '∼',
        'tilde' => '˜',
        'TildeEqual' => '≃',
        'TildeFullEqual' => '≅',
        'TildeTilde' => '≈',
        'times' => '×',
        'time' => '×',
        'timesb' => '⊠',
        'timesbar' => '⨱',
        'timesd' => '⨰',
        'tint' => '∭',
        'toea' => '⤨',
        'top' => '⊤',
        'topbot' => '⌶',
        'topcir' => '⫱',
        'Topf' => '𝕋',
        'topf' => '𝕥',
        'topfork' => '⫚',
        'tosa' => '⤩',
        'tprime' => '‴',
        'TRADE' => '™',
        'trade' => '™',
        'triangle' => '▵',
        'triangledown' => '▿',
        'triangleleft' => '◃',
        'trianglelefteq' => '⊴',
        'triangleq' => '≜',
        'triangleright' => '▹',
        'trianglerighteq' => '⊵',
        'tridot' => '◬',
        'trie' => '≜',
        'triminus' => '⨺',
        'TripleDot' => '⃛',
        'triplus' => '⨹',
        'trisb' => '⧍',
        'tritime' => '⨻',
        'trpezium' => '⏢',
        'Tscr' => '𝒯',
        'tscr' => '𝓉',
        'TScy' => 'Ц',
        'tscy' => 'ц',
        'TSHcy' => 'Ћ',
        'tshcy' => 'ћ',
        'Tstrok' => 'Ŧ',
        'tstrok' => 'ŧ',
        'twixt' => '≬',
        'twoheadleftarrow' => '↞',
        'twoheadrightarrow' => '↠',
        'Uacute' => 'Ú',
        'Uacut' => 'Ú',
        'uacute' => 'ú',
        'uacut' => 'ú',
        'Uarr' => '↟',
        'uArr' => '⇑',
        'uarr' => '↑',
        'Uarrocir' => '⥉',
        'Ubrcy' => 'Ў',
        'ubrcy' => 'ў',
        'Ubreve' => 'Ŭ',
        'ubreve' => 'ŭ',
        'Ucirc' => 'Û',
        'Ucir' => 'Û',
        'ucirc' => 'û',
        'ucir' => 'û',
        'Ucy' => 'У',
        'ucy' => 'у',
        'udarr' => '⇅',
        'Udblac' => 'Ű',
        'udblac' => 'ű',
        'udhar' => '⥮',
        'ufisht' => '⥾',
        'Ufr' => '𝔘',
        'ufr' => '𝔲',
        'Ugrave' => 'Ù',
        'Ugrav' => 'Ù',
        'ugrave' => 'ù',
        'ugrav' => 'ù',
        'uHar' => '⥣',
        'uharl' => '↿',
        'uharr' => '↾',
        'uhblk' => '▀',
        'ulcorn' => '⌜',
        'ulcorner' => '⌜',
        'ulcrop' => '⌏',
        'ultri' => '◸',
        'Umacr' => 'Ū',
        'umacr' => 'ū',
        'uml' => '¨',
        'um' => '¨',
        'UnderBar' => '_',
        'UnderBrace' => '⏟',
        'UnderBracket' => '⎵',
        'UnderParenthesis' => '⏝',
        'Union' => '⋃',
        'UnionPlus' => '⊎',
        'Uogon' => 'Ų',
        'uogon' => 'ų',
        'Uopf' => '𝕌',
        'uopf' => '𝕦',
        'UpArrow' => '↑',
        'Uparrow' => '⇑',
        'uparrow' => '↑',
        'UpArrowBar' => '⤒',
        'UpArrowDownArrow' => '⇅',
        'UpDownArrow' => '↕',
        'Updownarrow' => '⇕',
        'updownarrow' => '↕',
        'UpEquilibrium' => '⥮',
        'upharpoonleft' => '↿',
        'upharpoonright' => '↾',
        'uplus' => '⊎',
        'UpperLeftArrow' => '↖',
        'UpperRightArrow' => '↗',
        'Upsi' => 'ϒ',
        'upsi' => 'υ',
        'upsih' => 'ϒ',
        'Upsilon' => 'Υ',
        'upsilon' => 'υ',
        'UpTee' => '⊥',
        'UpTeeArrow' => '↥',
        'upuparrows' => '⇈',
        'urcorn' => '⌝',
        'urcorner' => '⌝',
        'urcrop' => '⌎',
        'Uring' => 'Ů',
        'uring' => 'ů',
        'urtri' => '◹',
        'Uscr' => '𝒰',
        'uscr' => '𝓊',
        'utdot' => '⋰',
        'Utilde' => 'Ũ',
        'utilde' => 'ũ',
        'utri' => '▵',
        'utrif' => '▴',
        'uuarr' => '⇈',
        'Uuml' => 'Ü',
        'Uum' => 'Ü',
        'uuml' => 'ü',
        'uum' => 'ü',
        'uwangle' => '⦧',
        'vangrt' => '⦜',
        'varepsilon' => 'ϵ',
        'varkappa' => 'ϰ',
        'varnothing' => '∅',
        'varphi' => 'ϕ',
        'varpi' => 'ϖ',
        'varpropto' => '∝',
        'vArr' => '⇕',
        'varr' => '↕',
        'varrho' => 'ϱ',
        'varsigma' => 'ς',
        'varsubsetneq' => '⊊︀',
        'varsubsetneqq' => '⫋︀',
        'varsupsetneq' => '⊋︀',
        'varsupsetneqq' => '⫌︀',
        'vartheta' => 'ϑ',
        'vartriangleleft' => '⊲',
        'vartriangleright' => '⊳',
        'Vbar' => '⫫',
        'vBar' => '⫨',
        'vBarv' => '⫩',
        'Vcy' => 'В',
        'vcy' => 'в',
        'VDash' => '⊫',
        'Vdash' => '⊩',
        'vDash' => '⊨',
        'vdash' => '⊢',
        'Vdashl' => '⫦',
        'Vee' => '⋁',
        'vee' => '∨',
        'veebar' => '⊻',
        'veeeq' => '≚',
        'vellip' => '⋮',
        'Verbar' => '‖',
        'verbar' => '|',
        'Vert' => '‖',
        'vert' => '|',
        'VerticalBar' => '∣',
        'VerticalLine' => '|',
        'VerticalSeparator' => '❘',
        'VerticalTilde' => '≀',
        'VeryThinSpace' => ' ',
        'Vfr' => '𝔙',
        'vfr' => '𝔳',
        'vltri' => '⊲',
        'vnsub' => '⊂⃒',
        'vnsup' => '⊃⃒',
        'Vopf' => '𝕍',
        'vopf' => '𝕧',
        'vprop' => '∝',
        'vrtri' => '⊳',
        'Vscr' => '𝒱',
        'vscr' => '𝓋',
        'vsubnE' => '⫋︀',
        'vsubne' => '⊊︀',
        'vsupnE' => '⫌︀',
        'vsupne' => '⊋︀',
        'Vvdash' => '⊪',
        'vzigzag' => '⦚',
        'Wcirc' => 'Ŵ',
        'wcirc' => 'ŵ',
        'wedbar' => '⩟',
        'Wedge' => '⋀',
        'wedge' => '∧',
        'wedgeq' => '≙',
        'weierp' => '℘',
        'Wfr' => '𝔚',
        'wfr' => '𝔴',
        'Wopf' => '𝕎',
        'wopf' => '𝕨',
        'wp' => '℘',
        'wr' => '≀',
        'wreath' => '≀',
        'Wscr' => '𝒲',
        'wscr' => '𝓌',
        'xcap' => '⋂',
        'xcirc' => '◯',
        'xcup' => '⋃',
        'xdtri' => '▽',
        'Xfr' => '𝔛',
        'xfr' => '𝔵',
        'xhArr' => '⟺',
        'xharr' => '⟷',
        'Xi' => 'Ξ',
        'xi' => 'ξ',
        'xlArr' => '⟸',
        'xlarr' => '⟵',
        'xmap' => '⟼',
        'xnis' => '⋻',
        'xodot' => '⨀',
        'Xopf' => '𝕏',
        'xopf' => '𝕩',
        'xoplus' => '⨁',
        'xotime' => '⨂',
        'xrArr' => '⟹',
        'xrarr' => '⟶',
        'Xscr' => '𝒳',
        'xscr' => '𝓍',
        'xsqcup' => '⨆',
        'xuplus' => '⨄',
        'xutri' => '△',
        'xvee' => '⋁',
        'xwedge' => '⋀',
        'Yacute' => 'Ý',
        'Yacut' => 'Ý',
        'yacute' => 'ý',
        'yacut' => 'ý',
        'YAcy' => 'Я',
        'yacy' => 'я',
        'Ycirc' => 'Ŷ',
        'ycirc' => 'ŷ',
        'Ycy' => 'Ы',
        'ycy' => 'ы',
        'yen' => '¥',
        'ye' => '¥',
        'Yfr' => '𝔜',
        'yfr' => '𝔶',
        'YIcy' => 'Ї',
        'yicy' => 'ї',
        'Yopf' => '𝕐',
        'yopf' => '𝕪',
        'Yscr' => '𝒴',
        'yscr' => '𝓎',
        'YUcy' => 'Ю',
        'yucy' => 'ю',
        'Yuml' => 'Ÿ',
        'yuml' => 'ÿ',
        'yum' => 'ÿ',
        'Zacute' => 'Ź',
        'zacute' => 'ź',
        'Zcaron' => 'Ž',
        'zcaron' => 'ž',
        'Zcy' => 'З',
        'zcy' => 'з',
        'Zdot' => 'Ż',
        'zdot' => 'ż',
        'zeetrf' => 'ℨ',
        'ZeroWidthSpace' => '​',
        'Zeta' => 'Ζ',
        'zeta' => 'ζ',
        'Zfr' => 'ℨ',
        'zfr' => '𝔷',
        'ZHcy' => 'Ж',
        'zhcy' => 'ж',
        'zigrarr' => '⇝',
        'Zopf' => 'ℤ',
        'zopf' => '𝕫',
        'Zscr' => '𝒵',
        'zscr' => '𝓏',
        'zwj' => '‍',
        'zwnj' => '‌',
    );
}
PK}c�\���̹���0masterminds/html5/src/HTML5/Parser/Tokenizer.phpnu�[���<?php

namespace Masterminds\HTML5\Parser;

use Masterminds\HTML5\Elements;

/**
 * The HTML5 tokenizer.
 *
 * The tokenizer's role is reading data from the scanner and gathering it into
 * semantic units. From the tokenizer, data is emitted to an event handler,
 * which may (for example) create a DOM tree.
 *
 * The HTML5 specification has a detailed explanation of tokenizing HTML5. We
 * follow that specification to the maximum extent that we can. If you find
 * a discrepancy that is not documented, please file a bug and/or submit a
 * patch.
 *
 * This tokenizer is implemented as a recursive descent parser.
 *
 * Within the API documentation, you may see references to the specific section
 * of the HTML5 spec that the code attempts to reproduce. Example: 8.2.4.1.
 * This refers to section 8.2.4.1 of the HTML5 CR specification.
 *
 * @see http://www.w3.org/TR/2012/CR-html5-20121217/
 */
class Tokenizer
{
    protected $scanner;

    protected $events;

    protected $tok;

    /**
     * Buffer for text.
     */
    protected $text = '';

    // When this goes to false, the parser stops.
    protected $carryOn = true;

    protected $textMode = 0; // TEXTMODE_NORMAL;
    protected $untilTag = null;

    const CONFORMANT_XML = 'xml';
    const CONFORMANT_HTML = 'html';
    protected $mode = self::CONFORMANT_HTML;

    /**
     * Create a new tokenizer.
     *
     * Typically, parsing a document involves creating a new tokenizer, giving
     * it a scanner (input) and an event handler (output), and then calling
     * the Tokenizer::parse() method.`
     *
     * @param Scanner      $scanner      A scanner initialized with an input stream.
     * @param EventHandler $eventHandler An event handler, initialized and ready to receive events.
     * @param string       $mode
     */
    public function __construct($scanner, $eventHandler, $mode = self::CONFORMANT_HTML)
    {
        $this->scanner = $scanner;
        $this->events = $eventHandler;
        $this->mode = $mode;
    }

    /**
     * Begin parsing.
     *
     * This will begin scanning the document, tokenizing as it goes.
     * Tokens are emitted into the event handler.
     *
     * Tokenizing will continue until the document is completely
     * read. Errors are emitted into the event handler, but
     * the parser will attempt to continue parsing until the
     * entire input stream is read.
     */
    public function parse()
    {
        do {
            $this->consumeData();
            // FIXME: Add infinite loop protection.
        } while ($this->carryOn);
    }

    /**
     * Set the text mode for the character data reader.
     *
     * HTML5 defines three different modes for reading text:
     * - Normal: Read until a tag is encountered.
     * - RCDATA: Read until a tag is encountered, but skip a few otherwise-
     * special characters.
     * - Raw: Read until a special closing tag is encountered (viz. pre, script)
     *
     * This allows those modes to be set.
     *
     * Normally, setting is done by the event handler via a special return code on
     * startTag(), but it can also be set manually using this function.
     *
     * @param int    $textmode One of Elements::TEXT_*.
     * @param string $untilTag The tag that should stop RAW or RCDATA mode. Normal mode does not
     *                         use this indicator.
     */
    public function setTextMode($textmode, $untilTag = null)
    {
        $this->textMode = $textmode & (Elements::TEXT_RAW | Elements::TEXT_RCDATA);
        $this->untilTag = $untilTag;
    }

    /**
     * Consume a character and make a move.
     * HTML5 8.2.4.1.
     */
    protected function consumeData()
    {
        $tok = $this->scanner->current();

        if ('&' === $tok) {
            // Character reference
            $ref = $this->decodeCharacterReference();
            $this->buffer($ref);

            $tok = $this->scanner->current();
        }

        // Parse tag
        if ('<' === $tok) {
            // Any buffered text data can go out now.
            $this->flushBuffer();

            $tok = $this->scanner->next();

            if ('!' === $tok) {
                $this->markupDeclaration();
            } elseif ('/' === $tok) {
                $this->endTag();
            } elseif ('?' === $tok) {
                $this->processingInstruction();
            } elseif (ctype_alpha($tok)) {
                $this->tagName();
            } else {
                $this->parseError('Illegal tag opening');
                // TODO is this necessary ?
                $this->characterData();
            }

            $tok = $this->scanner->current();
        }

        if (false === $tok) {
            // Handle end of document
            $this->eof();
        } else {
            // Parse character
            switch ($this->textMode) {
                case Elements::TEXT_RAW:
                    $this->rawText($tok);
                    break;

                case Elements::TEXT_RCDATA:
                    $this->rcdata($tok);
                    break;

                default:
                    if ('<' === $tok || '&' === $tok) {
                        break;
                    }

                    // NULL character
                    if ("\00" === $tok) {
                        $this->parseError('Received null character.');

                        $this->text .= $tok;
                        $this->scanner->consume();

                        break;
                    }

                    $this->text .= $this->scanner->charsUntil("<&\0");
            }
        }

        return $this->carryOn;
    }

    /**
     * Parse anything that looks like character data.
     *
     * Different rules apply based on the current text mode.
     *
     * @see Elements::TEXT_RAW Elements::TEXT_RCDATA.
     */
    protected function characterData()
    {
        $tok = $this->scanner->current();
        if (false === $tok) {
            return false;
        }
        switch ($this->textMode) {
            case Elements::TEXT_RAW:
                return $this->rawText($tok);
            case Elements::TEXT_RCDATA:
                return $this->rcdata($tok);
            default:
                if ('<' === $tok || '&' === $tok) {
                    return false;
                }

                return $this->text($tok);
        }
    }

    /**
     * This buffers the current token as character data.
     *
     * @param string $tok The current token.
     *
     * @return bool
     */
    protected function text($tok)
    {
        // This should never happen...
        if (false === $tok) {
            return false;
        }

        // NULL character
        if ("\00" === $tok) {
            $this->parseError('Received null character.');
        }

        $this->buffer($tok);
        $this->scanner->consume();

        return true;
    }

    /**
     * Read text in RAW mode.
     *
     * @param string $tok The current token.
     *
     * @return bool
     */
    protected function rawText($tok)
    {
        if (is_null($this->untilTag)) {
            return $this->text($tok);
        }

        $sequence = '</' . $this->untilTag . '>';
        $txt = $this->readUntilSequence($sequence);
        $this->events->text($txt);
        $this->setTextMode(0);

        return $this->endTag();
    }

    /**
     * Read text in RCDATA mode.
     *
     * @param string $tok The current token.
     *
     * @return bool
     */
    protected function rcdata($tok)
    {
        if (is_null($this->untilTag)) {
            return $this->text($tok);
        }

        $sequence = '</' . $this->untilTag;
        $txt = '';

        $caseSensitive = !Elements::isHtml5Element($this->untilTag);
        while (false !== $tok && !('<' == $tok && ($this->scanner->sequenceMatches($sequence, $caseSensitive)))) {
            if ('&' == $tok) {
                $txt .= $this->decodeCharacterReference();
                $tok = $this->scanner->current();
            } else {
                $txt .= $tok;
                $tok = $this->scanner->next();
            }
        }
        $len = strlen($sequence);
        $this->scanner->consume($len);
        $len += $this->scanner->whitespace();
        if ('>' !== $this->scanner->current()) {
            $this->parseError('Unclosed RCDATA end tag');
        }

        $this->scanner->unconsume($len);
        $this->events->text($txt);
        $this->setTextMode(0);

        return $this->endTag();
    }

    /**
     * If the document is read, emit an EOF event.
     */
    protected function eof()
    {
        // fprintf(STDOUT, "EOF");
        $this->flushBuffer();
        $this->events->eof();
        $this->carryOn = false;
    }

    /**
     * Look for markup.
     */
    protected function markupDeclaration()
    {
        $tok = $this->scanner->next();

        // Comment:
        if ('-' == $tok && '-' == $this->scanner->peek()) {
            $this->scanner->consume(2);

            return $this->comment();
        } elseif ('D' == $tok || 'd' == $tok) { // Doctype
            return $this->doctype();
        } elseif ('[' == $tok) { // CDATA section
            return $this->cdataSection();
        }

        // FINISH
        $this->parseError('Expected <!--, <![CDATA[, or <!DOCTYPE. Got <!%s', $tok);
        $this->bogusComment('<!');

        return true;
    }

    /**
     * Consume an end tag. See section 8.2.4.9.
     */
    protected function endTag()
    {
        if ('/' != $this->scanner->current()) {
            return false;
        }
        $tok = $this->scanner->next();

        // a-zA-Z -> tagname
        // > -> parse error
        // EOF -> parse error
        // -> parse error
        if (!ctype_alpha($tok)) {
            $this->parseError("Expected tag name, got '%s'", $tok);
            if ("\0" == $tok || false === $tok) {
                return false;
            }

            return $this->bogusComment('</');
        }

        $name = $this->scanner->charsUntil("\n\f \t>");
        $name = self::CONFORMANT_XML === $this->mode ? $name : strtolower($name);
        // Trash whitespace.
        $this->scanner->whitespace();

        $tok = $this->scanner->current();
        if ('>' != $tok) {
            $this->parseError("Expected >, got '%s'", $tok);
            // We just trash stuff until we get to the next tag close.
            $this->scanner->charsUntil('>');
        }

        $this->events->endTag($name);
        $this->scanner->consume();

        return true;
    }

    /**
     * Consume a tag name and body. See section 8.2.4.10.
     */
    protected function tagName()
    {
        // We know this is at least one char.
        $name = $this->scanner->charsWhile(':_-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz');
        $name = self::CONFORMANT_XML === $this->mode ? $name : strtolower($name);
        $attributes = array();
        $selfClose = false;

        // Handle attribute parse exceptions here so that we can
        // react by trying to build a sensible parse tree.
        try {
            do {
                $this->scanner->whitespace();
                $this->attribute($attributes);
            } while (!$this->isTagEnd($selfClose));
        } catch (ParseError $e) {
            $selfClose = false;
        }

        $mode = $this->events->startTag($name, $attributes, $selfClose);

        if (is_int($mode)) {
            $this->setTextMode($mode, $name);
        }

        $this->scanner->consume();

        return true;
    }

    /**
     * Check if the scanner has reached the end of a tag.
     */
    protected function isTagEnd(&$selfClose)
    {
        $tok = $this->scanner->current();
        if ('/' == $tok) {
            $this->scanner->consume();
            $this->scanner->whitespace();
            $tok = $this->scanner->current();

            if ('>' == $tok) {
                $selfClose = true;

                return true;
            }
            if (false === $tok) {
                $this->parseError('Unexpected EOF inside of tag.');

                return true;
            }
            // Basically, we skip the / token and go on.
            // See 8.2.4.43.
            $this->parseError("Unexpected '%s' inside of a tag.", $tok);

            return false;
        }

        if ('>' == $tok) {
            return true;
        }
        if (false === $tok) {
            $this->parseError('Unexpected EOF inside of tag.');

            return true;
        }

        return false;
    }

    /**
     * Parse attributes from inside of a tag.
     *
     * @param string[] $attributes
     *
     * @return bool
     *
     * @throws ParseError
     */
    protected function attribute(&$attributes)
    {
        $tok = $this->scanner->current();
        if ('/' == $tok || '>' == $tok || false === $tok) {
            return false;
        }

        if ('<' == $tok) {
            $this->parseError("Unexpected '<' inside of attributes list.");
            // Push the < back onto the stack.
            $this->scanner->unconsume();
            // Let the caller figure out how to handle this.
            throw new ParseError('Start tag inside of attribute.');
        }

        $name = strtolower($this->scanner->charsUntil("/>=\n\f\t "));

        if (0 == strlen($name)) {
            $tok = $this->scanner->current();
            $this->parseError('Expected an attribute name, got %s.', $tok);
            // Really, only '=' can be the char here. Everything else gets absorbed
            // under one rule or another.
            $name = $tok;
            $this->scanner->consume();
        }

        $isValidAttribute = true;
        // Attribute names can contain most Unicode characters for HTML5.
        // But method "DOMElement::setAttribute" is throwing exception
        // because of it's own internal restriction so these have to be filtered.
        // see issue #23: https://github.com/Masterminds/html5-php/issues/23
        // and http://www.w3.org/TR/2011/WD-html5-20110525/syntax.html#syntax-attribute-name
        if (preg_match("/[\x1-\x2C\\/\x3B-\x40\x5B-\x5E\x60\x7B-\x7F]/u", $name)) {
            $this->parseError('Unexpected characters in attribute name: %s', $name);
            $isValidAttribute = false;
        }         // There is no limitation for 1st character in HTML5.
        // But method "DOMElement::setAttribute" is throwing exception for the
        // characters below so they have to be filtered.
        // see issue #23: https://github.com/Masterminds/html5-php/issues/23
        // and http://www.w3.org/TR/2011/WD-html5-20110525/syntax.html#syntax-attribute-name
        elseif (preg_match('/^[0-9.-]/u', $name)) {
            $this->parseError('Unexpected character at the begining of attribute name: %s', $name);
            $isValidAttribute = false;
        }
        // 8.1.2.3
        $this->scanner->whitespace();

        $val = $this->attributeValue();
        if ($isValidAttribute) {
            $attributes[$name] = $val;
        }

        return true;
    }

    /**
     * Consume an attribute value. See section 8.2.4.37 and after.
     *
     * @return string|null
     */
    protected function attributeValue()
    {
        if ('=' != $this->scanner->current()) {
            return null;
        }
        $this->scanner->consume();
        // 8.1.2.3
        $this->scanner->whitespace();

        $tok = $this->scanner->current();
        switch ($tok) {
            case "\n":
            case "\f":
            case ' ':
            case "\t":
                // Whitespace here indicates an empty value.
                return null;
            case '"':
            case "'":
                $this->scanner->consume();

                return $this->quotedAttributeValue($tok);
            case '>':
                // case '/': // 8.2.4.37 seems to allow foo=/ as a valid attr.
                $this->parseError('Expected attribute value, got tag end.');

                return null;
            case '=':
            case '`':
                $this->parseError('Expecting quotes, got %s.', $tok);

                return $this->unquotedAttributeValue();
            default:
                return $this->unquotedAttributeValue();
        }
    }

    /**
     * Get an attribute value string.
     *
     * @param string $quote IMPORTANT: This is a series of chars! Any one of which will be considered
     *                      termination of an attribute's value. E.g. "\"'" will stop at either
     *                      ' or ".
     *
     * @return string The attribute value.
     */
    protected function quotedAttributeValue($quote)
    {
        $stoplist = "\f" . $quote;
        $val = '';

        while (true) {
            $tokens = $this->scanner->charsUntil($stoplist . '&');
            if (false !== $tokens) {
                $val .= $tokens;
            } else {
                break;
            }

            $tok = $this->scanner->current();
            if ('&' == $tok) {
                $val .= $this->decodeCharacterReference(true);
                continue;
            }
            break;
        }
        $this->scanner->consume();

        return $val;
    }

    protected function unquotedAttributeValue()
    {
        $val = '';
        $tok = $this->scanner->current();
        while (false !== $tok) {
            switch ($tok) {
                case "\n":
                case "\f":
                case ' ':
                case "\t":
                case '>':
                    break 2;

                case '&':
                    $val .= $this->decodeCharacterReference(true);
                    $tok = $this->scanner->current();

                    break;

                case "'":
                case '"':
                case '<':
                case '=':
                case '`':
                    $this->parseError('Unexpected chars in unquoted attribute value %s', $tok);
                    $val .= $tok;
                    $tok = $this->scanner->next();
                    break;

                default:
                    $val .= $this->scanner->charsUntil("\t\n\f >&\"'<=`");

                    $tok = $this->scanner->current();
            }
        }

        return $val;
    }

    /**
     * Consume malformed markup as if it were a comment.
     * 8.2.4.44.
     *
     * The spec requires that the ENTIRE tag-like thing be enclosed inside of
     * the comment. So this will generate comments like:
     *
     * &lt;!--&lt/+foo&gt;--&gt;
     *
     * @param string $leading Prepend any leading characters. This essentially
     *                        negates the need to backtrack, but it's sort of a hack.
     *
     * @return bool
     */
    protected function bogusComment($leading = '')
    {
        $comment = $leading;
        $tokens = $this->scanner->charsUntil('>');
        if (false !== $tokens) {
            $comment .= $tokens;
        }
        $tok = $this->scanner->current();
        if (false !== $tok) {
            $comment .= $tok;
        }

        $this->flushBuffer();
        $this->events->comment($comment);
        $this->scanner->consume();

        return true;
    }

    /**
     * Read a comment.
     * Expects the first tok to be inside of the comment.
     *
     * @return bool
     */
    protected function comment()
    {
        $tok = $this->scanner->current();
        $comment = '';

        // <!-->. Emit an empty comment because 8.2.4.46 says to.
        if ('>' == $tok) {
            // Parse error. Emit the comment token.
            $this->parseError("Expected comment data, got '>'");
            $this->events->comment('');
            $this->scanner->consume();

            return true;
        }

        // Replace NULL with the replacement char.
        if ("\0" == $tok) {
            $tok = UTF8Utils::FFFD;
        }
        while (!$this->isCommentEnd()) {
            $comment .= $tok;
            $tok = $this->scanner->next();
        }

        $this->events->comment($comment);
        $this->scanner->consume();

        return true;
    }

    /**
     * Check if the scanner has reached the end of a comment.
     *
     * @return bool
     */
    protected function isCommentEnd()
    {
        $tok = $this->scanner->current();

        // EOF
        if (false === $tok) {
            // Hit the end.
            $this->parseError('Unexpected EOF in a comment.');

            return true;
        }

        // If next two tokens are not '--', not the end.
        if ('-' != $tok || '-' != $this->scanner->peek()) {
            return false;
        }

        $this->scanner->consume(2); // Consume '-' and one of '!' or '>'

        // Test for '>'
        if ('>' == $this->scanner->current()) {
            return true;
        }
        // Test for '!>'
        if ('!' == $this->scanner->current() && '>' == $this->scanner->peek()) {
            $this->scanner->consume(); // Consume the last '>'
            return true;
        }
        // Unread '-' and one of '!' or '>';
        $this->scanner->unconsume(2);

        return false;
    }

    /**
     * Parse a DOCTYPE.
     *
     * Parse a DOCTYPE declaration. This method has strong bearing on whether or
     * not Quirksmode is enabled on the event handler.
     *
     * @todo This method is a little long. Should probably refactor.
     *
     * @return bool
     */
    protected function doctype()
    {
        // Check that string is DOCTYPE.
        if ($this->scanner->sequenceMatches('DOCTYPE', false)) {
            $this->scanner->consume(7);
        } else {
            $chars = $this->scanner->charsWhile('DOCTYPEdoctype');
            $this->parseError('Expected DOCTYPE, got %s', $chars);

            return $this->bogusComment('<!' . $chars);
        }

        $this->scanner->whitespace();
        $tok = $this->scanner->current();

        // EOF: die.
        if (false === $tok) {
            $this->events->doctype('html5', EventHandler::DOCTYPE_NONE, '', true);
            $this->eof();

            return true;
        }

        // NULL char: convert.
        if ("\0" === $tok) {
            $this->parseError('Unexpected null character in DOCTYPE.');
        }

        $stop = " \n\f>";
        $doctypeName = $this->scanner->charsUntil($stop);
        // Lowercase ASCII, replace \0 with FFFD
        $doctypeName = strtolower(strtr($doctypeName, "\0", UTF8Utils::FFFD));

        $tok = $this->scanner->current();

        // If false, emit a parse error, DOCTYPE, and return.
        if (false === $tok) {
            $this->parseError('Unexpected EOF in DOCTYPE declaration.');
            $this->events->doctype($doctypeName, EventHandler::DOCTYPE_NONE, null, true);

            return true;
        }

        // Short DOCTYPE, like <!DOCTYPE html>
        if ('>' == $tok) {
            // DOCTYPE without a name.
            if (0 == strlen($doctypeName)) {
                $this->parseError('Expected a DOCTYPE name. Got nothing.');
                $this->events->doctype($doctypeName, 0, null, true);
                $this->scanner->consume();

                return true;
            }
            $this->events->doctype($doctypeName);
            $this->scanner->consume();

            return true;
        }
        $this->scanner->whitespace();

        $pub = strtoupper($this->scanner->getAsciiAlpha());
        $white = $this->scanner->whitespace();

        // Get ID, and flag it as pub or system.
        if (('PUBLIC' == $pub || 'SYSTEM' == $pub) && $white > 0) {
            // Get the sys ID.
            $type = 'PUBLIC' == $pub ? EventHandler::DOCTYPE_PUBLIC : EventHandler::DOCTYPE_SYSTEM;
            $id = $this->quotedString("\0>");
            if (false === $id) {
                $this->events->doctype($doctypeName, $type, $pub, false);

                return true;
            }

            // Premature EOF.
            if (false === $this->scanner->current()) {
                $this->parseError('Unexpected EOF in DOCTYPE');
                $this->events->doctype($doctypeName, $type, $id, true);

                return true;
            }

            // Well-formed complete DOCTYPE.
            $this->scanner->whitespace();
            if ('>' == $this->scanner->current()) {
                $this->events->doctype($doctypeName, $type, $id, false);
                $this->scanner->consume();

                return true;
            }

            // If we get here, we have <!DOCTYPE foo PUBLIC "bar" SOME_JUNK
            // Throw away the junk, parse error, quirks mode, return true.
            $this->scanner->charsUntil('>');
            $this->parseError('Malformed DOCTYPE.');
            $this->events->doctype($doctypeName, $type, $id, true);
            $this->scanner->consume();

            return true;
        }

        // Else it's a bogus DOCTYPE.
        // Consume to > and trash.
        $this->scanner->charsUntil('>');

        $this->parseError('Expected PUBLIC or SYSTEM. Got %s.', $pub);
        $this->events->doctype($doctypeName, 0, null, true);
        $this->scanner->consume();

        return true;
    }

    /**
     * Utility for reading a quoted string.
     *
     * @param string $stopchars Characters (in addition to a close-quote) that should stop the string.
     *                          E.g. sometimes '>' is higher precedence than '"' or "'".
     *
     * @return mixed String if one is found (quotations omitted).
     */
    protected function quotedString($stopchars)
    {
        $tok = $this->scanner->current();
        if ('"' == $tok || "'" == $tok) {
            $this->scanner->consume();
            $ret = $this->scanner->charsUntil($tok . $stopchars);
            if ($this->scanner->current() == $tok) {
                $this->scanner->consume();
            } else {
                // Parse error because no close quote.
                $this->parseError('Expected %s, got %s', $tok, $this->scanner->current());
            }

            return $ret;
        }

        return false;
    }

    /**
     * Handle a CDATA section.
     *
     * @return bool
     */
    protected function cdataSection()
    {
        $cdata = '';
        $this->scanner->consume();

        $chars = $this->scanner->charsWhile('CDAT');
        if ('CDATA' != $chars || '[' != $this->scanner->current()) {
            $this->parseError('Expected [CDATA[, got %s', $chars);

            return $this->bogusComment('<![' . $chars);
        }

        $tok = $this->scanner->next();
        do {
            if (false === $tok) {
                $this->parseError('Unexpected EOF inside CDATA.');
                $this->bogusComment('<![CDATA[' . $cdata);

                return true;
            }
            $cdata .= $tok;
            $tok = $this->scanner->next();
        } while (!$this->scanner->sequenceMatches(']]>'));

        // Consume ]]>
        $this->scanner->consume(3);

        $this->events->cdata($cdata);

        return true;
    }

    // ================================================================
    // Non-HTML5
    // ================================================================

    /**
     * Handle a processing instruction.
     *
     * XML processing instructions are supposed to be ignored in HTML5,
     * treated as "bogus comments". However, since we're not a user
     * agent, we allow them. We consume until ?> and then issue a
     * EventListener::processingInstruction() event.
     *
     * @return bool
     */
    protected function processingInstruction()
    {
        if ('?' != $this->scanner->current()) {
            return false;
        }

        $tok = $this->scanner->next();
        $procName = $this->scanner->getAsciiAlpha();
        $white = $this->scanner->whitespace();

        // If not a PI, send to bogusComment.
        if (0 == strlen($procName) || 0 == $white || false == $this->scanner->current()) {
            $this->parseError("Expected processing instruction name, got $tok");
            $this->bogusComment('<?' . $tok . $procName);

            return true;
        }

        $data = '';
        // As long as it's not the case that the next two chars are ? and >.
        while (!('?' == $this->scanner->current() && '>' == $this->scanner->peek())) {
            $data .= $this->scanner->current();

            $tok = $this->scanner->next();
            if (false === $tok) {
                $this->parseError('Unexpected EOF in processing instruction.');
                $this->events->processingInstruction($procName, $data);

                return true;
            }
        }

        $this->scanner->consume(2); // Consume the closing tag
        $this->events->processingInstruction($procName, $data);

        return true;
    }

    // ================================================================
    // UTILITY FUNCTIONS
    // ================================================================

    /**
     * Read from the input stream until we get to the desired sequene
     * or hit the end of the input stream.
     *
     * @param string $sequence
     *
     * @return string
     */
    protected function readUntilSequence($sequence)
    {
        $buffer = '';

        // Optimization for reading larger blocks faster.
        $first = substr($sequence, 0, 1);
        while (false !== $this->scanner->current()) {
            $buffer .= $this->scanner->charsUntil($first);

            // Stop as soon as we hit the stopping condition.
            if ($this->scanner->sequenceMatches($sequence, false)) {
                return $buffer;
            }
            $buffer .= $this->scanner->current();
            $this->scanner->consume();
        }

        // If we get here, we hit the EOF.
        $this->parseError('Unexpected EOF during text read.');

        return $buffer;
    }

    /**
     * Check if upcomming chars match the given sequence.
     *
     * This will read the stream for the $sequence. If it's
     * found, this will return true. If not, return false.
     * Since this unconsumes any chars it reads, the caller
     * will still need to read the next sequence, even if
     * this returns true.
     *
     * Example: $this->scanner->sequenceMatches('</script>') will
     * see if the input stream is at the start of a
     * '</script>' string.
     *
     * @param string $sequence
     * @param bool   $caseSensitive
     *
     * @return bool
     */
    protected function sequenceMatches($sequence, $caseSensitive = true)
    {
        @trigger_error(__METHOD__ . ' method is deprecated since version 2.4 and will be removed in 3.0. Use Scanner::sequenceMatches() instead.', E_USER_DEPRECATED);

        return $this->scanner->sequenceMatches($sequence, $caseSensitive);
    }

    /**
     * Send a TEXT event with the contents of the text buffer.
     *
     * This emits an EventHandler::text() event with the current contents of the
     * temporary text buffer. (The buffer is used to group as much PCDATA
     * as we can instead of emitting lots and lots of TEXT events.)
     */
    protected function flushBuffer()
    {
        if ('' === $this->text) {
            return;
        }
        $this->events->text($this->text);
        $this->text = '';
    }

    /**
     * Add text to the temporary buffer.
     *
     * @see flushBuffer()
     *
     * @param string $str
     */
    protected function buffer($str)
    {
        $this->text .= $str;
    }

    /**
     * Emit a parse error.
     *
     * A parse error always returns false because it never consumes any
     * characters.
     *
     * @param string $msg
     *
     * @return string
     */
    protected function parseError($msg)
    {
        $args = func_get_args();

        if (count($args) > 1) {
            array_shift($args);
            $msg = vsprintf($msg, $args);
        }

        $line = $this->scanner->currentLine();
        $col = $this->scanner->columnOffset();
        $this->events->parseError($msg, $line, $col);

        return false;
    }

    /**
     * Decode a character reference and return the string.
     *
     * If $inAttribute is set to true, a bare & will be returned as-is.
     *
     * @param bool $inAttribute Set to true if the text is inside of an attribute value.
     *                          false otherwise.
     *
     * @return string
     */
    protected function decodeCharacterReference($inAttribute = false)
    {
        // Next char after &.
        $tok = $this->scanner->next();
        $start = $this->scanner->position();

        if (false === $tok) {
            return '&';
        }

        // These indicate not an entity. We return just
        // the &.
        if ("\t" === $tok || "\n" === $tok || "\f" === $tok || ' ' === $tok || '&' === $tok || '<' === $tok) {
            // $this->scanner->next();
            return '&';
        }

        // Numeric entity
        if ('#' === $tok) {
            $tok = $this->scanner->next();

            if (false === $tok) {
                $this->parseError('Expected &#DEC; &#HEX;, got EOF');
                $this->scanner->unconsume(1);

                return '&';
            }

            // Hexidecimal encoding.
            // X[0-9a-fA-F]+;
            // x[0-9a-fA-F]+;
            if ('x' === $tok || 'X' === $tok) {
                $tok = $this->scanner->next(); // Consume x

                // Convert from hex code to char.
                $hex = $this->scanner->getHex();
                if (empty($hex)) {
                    $this->parseError('Expected &#xHEX;, got &#x%s', $tok);
                    // We unconsume because we don't know what parser rules might
                    // be in effect for the remaining chars. For example. '&#>'
                    // might result in a specific parsing rule inside of tag
                    // contexts, while not inside of pcdata context.
                    $this->scanner->unconsume(2);

                    return '&';
                }
                $entity = CharacterReference::lookupHex($hex);
            }             // Decimal encoding.
            // [0-9]+;
            else {
                // Convert from decimal to char.
                $numeric = $this->scanner->getNumeric();
                if (false === $numeric) {
                    $this->parseError('Expected &#DIGITS;, got &#%s', $tok);
                    $this->scanner->unconsume(2);

                    return '&';
                }
                $entity = CharacterReference::lookupDecimal($numeric);
            }
        } elseif ('=' === $tok && $inAttribute) {
            return '&';
        } else { // String entity.
            // Attempt to consume a string up to a ';'.
            // [a-zA-Z0-9]+;
            $cname = $this->scanner->getAsciiAlphaNum();
            $entity = CharacterReference::lookupName($cname);

            // When no entity is found provide the name of the unmatched string
            // and continue on as the & is not part of an entity. The & will
            // be converted to &amp; elsewhere.
            if (null === $entity) {
                if (!$inAttribute || '' === $cname) {
                    $this->parseError("No match in entity table for '%s'", $cname);
                }
                $this->scanner->unconsume($this->scanner->position() - $start);

                return '&';
            }
        }

        // The scanner has advanced the cursor for us.
        $tok = $this->scanner->current();

        // We have an entity. We're done here.
        if (';' === $tok) {
            $this->scanner->consume();

            return $entity;
        }

        // Failing to match ; means unconsume the entire string.
        $this->scanner->unconsume($this->scanner->position() - $start);

        $this->parseError('Expected &ENTITY;, got &ENTITY%s (no trailing ;) ', $tok);

        return '&';
    }
}
PK}c�\r5u��8masterminds/html5/src/HTML5/Parser/TreeBuildingRules.phpnu�[���<?php

namespace Masterminds\HTML5\Parser;

/**
 * Handles special-case rules for the DOM tree builder.
 *
 * Many tags have special rules that need to be accomodated on an
 * individual basis. This class handles those rules.
 *
 * See section 8.1.2.4 of the spec.
 *
 * @todo - colgroup and col special behaviors
 *       - body and head special behaviors
 */
class TreeBuildingRules
{
    protected static $tags = array(
        'li' => 1,
        'dd' => 1,
        'dt' => 1,
        'rt' => 1,
        'rp' => 1,
        'tr' => 1,
        'th' => 1,
        'td' => 1,
        'thead' => 1,
        'tfoot' => 1,
        'tbody' => 1,
        'table' => 1,
        'optgroup' => 1,
        'option' => 1,
    );

    /**
     * Returns true if the given tagname has special processing rules.
     */
    public function hasRules($tagname)
    {
        return isset(static::$tags[$tagname]);
    }

    /**
     * Evaluate the rule for the current tag name.
     *
     * This may modify the existing DOM.
     *
     * @return \DOMElement The new Current DOM element.
     */
    public function evaluate($new, $current)
    {
        switch ($new->tagName) {
            case 'li':
                return $this->handleLI($new, $current);
            case 'dt':
            case 'dd':
                return $this->handleDT($new, $current);
            case 'rt':
            case 'rp':
                return $this->handleRT($new, $current);
            case 'optgroup':
                return $this->closeIfCurrentMatches($new, $current, array(
                    'optgroup',
                ));
            case 'option':
                return $this->closeIfCurrentMatches($new, $current, array(
                    'option',
                ));
            case 'tr':
                return $this->closeIfCurrentMatches($new, $current, array(
                    'tr',
                ));
            case 'td':
            case 'th':
                return $this->closeIfCurrentMatches($new, $current, array(
                    'th',
                    'td',
                ));
            case 'tbody':
            case 'thead':
            case 'tfoot':
            case 'table': // Spec isn't explicit about this, but it's necessary.

                return $this->closeIfCurrentMatches($new, $current, array(
                    'thead',
                    'tfoot',
                    'tbody',
                ));
        }

        return $current;
    }

    protected function handleLI($ele, $current)
    {
        return $this->closeIfCurrentMatches($ele, $current, array(
            'li',
        ));
    }

    protected function handleDT($ele, $current)
    {
        return $this->closeIfCurrentMatches($ele, $current, array(
            'dt',
            'dd',
        ));
    }

    protected function handleRT($ele, $current)
    {
        return $this->closeIfCurrentMatches($ele, $current, array(
            'rt',
            'rp',
        ));
    }

    protected function closeIfCurrentMatches($ele, $current, $match)
    {
        if (in_array($current->tagName, $match, true)) {
            $current->parentNode->appendChild($ele);
        } else {
            $current->appendChild($ele);
        }

        return $ele;
    }
}
PK�c�\���i[[0masterminds/html5/src/HTML5/Parser/UTF8Utils.phpnu�[���<?php

namespace Masterminds\HTML5\Parser;

/*
Portions based on code from html5lib files with the following copyright:

Copyright 2009 Geoffrey Sneddon <http://gsnedders.com/>

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
    "Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

use Masterminds\HTML5\Exception;

class UTF8Utils
{
    /**
     * The Unicode replacement character.
     */
    const FFFD = "\xEF\xBF\xBD";

    /**
     * Count the number of characters in a string.
     * UTF-8 aware. This will try (in order) iconv, MB, libxml, and finally a custom counter.
     *
     * @param string $string
     *
     * @return int
     */
    public static function countChars($string)
    {
        // Get the length for the string we need.
        if (function_exists('mb_strlen')) {
            return mb_strlen($string, 'utf-8');
        }

        if (function_exists('iconv_strlen')) {
            return iconv_strlen($string, 'utf-8');
        }

        if (function_exists('utf8_decode')) {
            // MPB: Will this work? Won't certain decodes lead to two chars
            // extrapolated out of 2-byte chars?
            return strlen(utf8_decode($string));
        }

        $count = count_chars($string);

        // 0x80 = 0x7F - 0 + 1 (one added to get inclusive range)
        // 0x33 = 0xF4 - 0x2C + 1 (one added to get inclusive range)
        return array_sum(array_slice($count, 0, 0x80)) + array_sum(array_slice($count, 0xC2, 0x33));
    }

    /**
     * Convert data from the given encoding to UTF-8.
     *
     * This has not yet been tested with charactersets other than UTF-8.
     * It should work with ISO-8859-1/-13 and standard Latin Win charsets.
     *
     * @param string $data     The data to convert
     * @param string $encoding A valid encoding. Examples: http://www.php.net/manual/en/mbstring.supported-encodings.php
     *
     * @return string
     */
    public static function convertToUTF8($data, $encoding = 'UTF-8')
    {
        /*
         * From the HTML5 spec: Given an encoding, the bytes in the input stream must be converted
         * to Unicode characters for the tokeniser, as described by the rules for that encoding,
         * except that the leading U+FEFF BYTE ORDER MARK character, if any, must not be stripped
         * by the encoding layer (it is stripped by the rule below). Bytes or sequences of bytes
         * in the original byte stream that could not be converted to Unicode characters must be
         * converted to U+FFFD REPLACEMENT CHARACTER code points.
         */

        // mb_convert_encoding is chosen over iconv because of a bug. The best
        // details for the bug are on http://us1.php.net/manual/en/function.iconv.php#108643
        // which contains links to the actual but reports as well as work around
        // details.
        if (function_exists('mb_convert_encoding')) {
            // mb library has the following behaviors:
            // - UTF-16 surrogates result in false.
            // - Overlongs and outside Plane 16 result in empty strings.

            // Before we run mb_convert_encoding we need to tell it what to do with
            // characters it does not know. This could be different than the parent
            // application executing this library so we store the value, change it
            // to our needs, and then change it back when we are done. This feels
            // a little excessive and it would be great if there was a better way.
            $save = mb_substitute_character();
            mb_substitute_character('none');
            $data = mb_convert_encoding($data, 'UTF-8', $encoding);
            mb_substitute_character($save);
        }
        // @todo Get iconv running in at least some environments if that is possible.
        elseif (function_exists('iconv') && 'auto' !== $encoding) {
            // fprintf(STDOUT, "iconv found\n");
            // iconv has the following behaviors:
            // - Overlong representations are ignored.
            // - Beyond Plane 16 is replaced with a lower char.
            // - Incomplete sequences generate a warning.
            $data = @iconv($encoding, 'UTF-8//IGNORE', $data);
        } else {
            throw new Exception('Not implemented, please install mbstring or iconv');
        }

        /*
         * One leading U+FEFF BYTE ORDER MARK character must be ignored if any are present.
         */
        if ("\xEF\xBB\xBF" === substr($data, 0, 3)) {
            $data = substr($data, 3);
        }

        return $data;
    }

    /**
     * Checks for Unicode code points that are not valid in a document.
     *
     * @param string $data A string to analyze
     *
     * @return array An array of (string) error messages produced by the scanning
     */
    public static function checkForIllegalCodepoints($data)
    {
        // Vestigal error handling.
        $errors = array();

        /*
         * All U+0000 null characters in the input must be replaced by U+FFFD REPLACEMENT CHARACTERs.
         * Any occurrences of such characters is a parse error.
         */
        for ($i = 0, $count = substr_count($data, "\0"); $i < $count; ++$i) {
            $errors[] = 'null-character';
        }

        /*
         * Any occurrences of any characters in the ranges U+0001 to U+0008, U+000B, U+000E to U+001F, U+007F
         * to U+009F, U+D800 to U+DFFF , U+FDD0 to U+FDEF, and characters U+FFFE, U+FFFF, U+1FFFE, U+1FFFF,
         * U+2FFFE, U+2FFFF, U+3FFFE, U+3FFFF, U+4FFFE, U+4FFFF, U+5FFFE, U+5FFFF, U+6FFFE, U+6FFFF, U+7FFFE,
         * U+7FFFF, U+8FFFE, U+8FFFF, U+9FFFE, U+9FFFF, U+AFFFE, U+AFFFF, U+BFFFE, U+BFFFF, U+CFFFE, U+CFFFF,
         * U+DFFFE, U+DFFFF, U+EFFFE, U+EFFFF, U+FFFFE, U+FFFFF, U+10FFFE, and U+10FFFF are parse errors.
         * (These are all control characters or permanently undefined Unicode characters.)
         */
        // Check PCRE is loaded.
        $count = preg_match_all(
            '/(?:
        [\x01-\x08\x0B\x0E-\x1F\x7F] # U+0001 to U+0008, U+000B,  U+000E to U+001F and U+007F
      |
        \xC2[\x80-\x9F] # U+0080 to U+009F
      |
        \xED(?:\xA0[\x80-\xFF]|[\xA1-\xBE][\x00-\xFF]|\xBF[\x00-\xBF]) # U+D800 to U+DFFFF
      |
        \xEF\xB7[\x90-\xAF] # U+FDD0 to U+FDEF
      |
        \xEF\xBF[\xBE\xBF] # U+FFFE and U+FFFF
      |
        [\xF0-\xF4][\x8F-\xBF]\xBF[\xBE\xBF] # U+nFFFE and U+nFFFF (1 <= n <= 10_{16})
      )/x', $data, $matches);
        for ($i = 0; $i < $count; ++$i) {
            $errors[] = 'invalid-codepoint';
        }

        return $errors;
    }
}
PK�c�\�w"YY5masterminds/html5/src/HTML5/Parser/DOMTreeBuilder.phpnu�[���<?php

namespace Masterminds\HTML5\Parser;

use Masterminds\HTML5\Elements;
use Masterminds\HTML5\InstructionProcessor;

/**
 * Create an HTML5 DOM tree from events.
 *
 * This attempts to create a DOM from events emitted by a parser. This
 * attempts (but does not guarantee) to up-convert older HTML documents
 * to HTML5. It does this by applying HTML5's rules, but it will not
 * change the architecture of the document itself.
 *
 * Many of the error correction and quirks features suggested in the specification
 * are implemented herein; however, not all of them are. Since we do not
 * assume a graphical user agent, no presentation-specific logic is conducted
 * during tree building.
 *
 * FIXME: The present tree builder does not exactly follow the state machine rules
 * for insert modes as outlined in the HTML5 spec. The processor needs to be
 * re-written to accomodate this. See, for example, the Go language HTML5
 * parser.
 */
class DOMTreeBuilder implements EventHandler
{
    /**
     * Defined in http://www.w3.org/TR/html51/infrastructure.html#html-namespace-0.
     */
    const NAMESPACE_HTML = 'http://www.w3.org/1999/xhtml';

    const NAMESPACE_MATHML = 'http://www.w3.org/1998/Math/MathML';

    const NAMESPACE_SVG = 'http://www.w3.org/2000/svg';

    const NAMESPACE_XLINK = 'http://www.w3.org/1999/xlink';

    const NAMESPACE_XML = 'http://www.w3.org/XML/1998/namespace';

    const NAMESPACE_XMLNS = 'http://www.w3.org/2000/xmlns/';

    const OPT_DISABLE_HTML_NS = 'disable_html_ns';

    const OPT_TARGET_DOC = 'target_document';

    const OPT_IMPLICIT_NS = 'implicit_namespaces';

    /**
     * Holds the HTML5 element names that causes a namespace switch.
     *
     * @var array
     */
    protected $nsRoots = array(
        'html' => self::NAMESPACE_HTML,
        'svg' => self::NAMESPACE_SVG,
        'math' => self::NAMESPACE_MATHML,
    );

    /**
     * Holds the always available namespaces (which does not require the XMLNS declaration).
     *
     * @var array
     */
    protected $implicitNamespaces = array(
        'xml' => self::NAMESPACE_XML,
        'xmlns' => self::NAMESPACE_XMLNS,
        'xlink' => self::NAMESPACE_XLINK,
    );

    /**
     * Holds a stack of currently active namespaces.
     *
     * @var array
     */
    protected $nsStack = array();

    /**
     * Holds the number of namespaces declared by a node.
     *
     * @var array
     */
    protected $pushes = array();

    /**
     * Defined in 8.2.5.
     */
    const IM_INITIAL = 0;

    const IM_BEFORE_HTML = 1;

    const IM_BEFORE_HEAD = 2;

    const IM_IN_HEAD = 3;

    const IM_IN_HEAD_NOSCRIPT = 4;

    const IM_AFTER_HEAD = 5;

    const IM_IN_BODY = 6;

    const IM_TEXT = 7;

    const IM_IN_TABLE = 8;

    const IM_IN_TABLE_TEXT = 9;

    const IM_IN_CAPTION = 10;

    const IM_IN_COLUMN_GROUP = 11;

    const IM_IN_TABLE_BODY = 12;

    const IM_IN_ROW = 13;

    const IM_IN_CELL = 14;

    const IM_IN_SELECT = 15;

    const IM_IN_SELECT_IN_TABLE = 16;

    const IM_AFTER_BODY = 17;

    const IM_IN_FRAMESET = 18;

    const IM_AFTER_FRAMESET = 19;

    const IM_AFTER_AFTER_BODY = 20;

    const IM_AFTER_AFTER_FRAMESET = 21;

    const IM_IN_SVG = 22;

    const IM_IN_MATHML = 23;

    protected $options = array();

    protected $stack = array();

    protected $current; // Pointer in the tag hierarchy.
    protected $rules;
    protected $doc;

    protected $frag;

    protected $processor;

    protected $insertMode = 0;

    /**
     * Track if we are in an element that allows only inline child nodes.
     *
     * @var string|null
     */
    protected $onlyInline;

    /**
     * Quirks mode is enabled by default.
     * Any document that is missing the DT will be considered to be in quirks mode.
     */
    protected $quirks = true;

    protected $errors = array();

    public function __construct($isFragment = false, array $options = array())
    {
        $this->options = $options;

        if (isset($options[self::OPT_TARGET_DOC])) {
            $this->doc = $options[self::OPT_TARGET_DOC];
        } else {
            $impl = new \DOMImplementation();
            // XXX:
            // Create the doctype. For now, we are always creating HTML5
            // documents, and attempting to up-convert any older DTDs to HTML5.
            $dt = $impl->createDocumentType('html');
            // $this->doc = \DOMImplementation::createDocument(NULL, 'html', $dt);
            $this->doc = $impl->createDocument(null, '', $dt);
            $this->doc->encoding = !empty($options['encoding']) ? $options['encoding'] : 'UTF-8';
        }

        $this->errors = array();

        $this->current = $this->doc; // ->documentElement;

        // Create a rules engine for tags.
        $this->rules = new TreeBuildingRules();

        $implicitNS = array();
        if (isset($this->options[self::OPT_IMPLICIT_NS])) {
            $implicitNS = $this->options[self::OPT_IMPLICIT_NS];
        } elseif (isset($this->options['implicitNamespaces'])) {
            $implicitNS = $this->options['implicitNamespaces'];
        }

        // Fill $nsStack with the defalut HTML5 namespaces, plus the "implicitNamespaces" array taken form $options
        array_unshift($this->nsStack, $implicitNS + array('' => self::NAMESPACE_HTML) + $this->implicitNamespaces);

        if ($isFragment) {
            $this->insertMode = static::IM_IN_BODY;
            $this->frag = $this->doc->createDocumentFragment();
            $this->current = $this->frag;
        }
    }

    /**
     * Get the document.
     */
    public function document()
    {
        return $this->doc;
    }

    /**
     * Get the DOM fragment for the body.
     *
     * This returns a DOMNodeList because a fragment may have zero or more
     * DOMNodes at its root.
     *
     * @see http://www.w3.org/TR/2012/CR-html5-20121217/syntax.html#concept-frag-parse-context
     *
     * @return \DOMDocumentFragment
     */
    public function fragment()
    {
        return $this->frag;
    }

    /**
     * Provide an instruction processor.
     *
     * This is used for handling Processor Instructions as they are
     * inserted. If omitted, PI's are inserted directly into the DOM tree.
     *
     * @param InstructionProcessor $proc
     */
    public function setInstructionProcessor(InstructionProcessor $proc)
    {
        $this->processor = $proc;
    }

    public function doctype($name, $idType = 0, $id = null, $quirks = false)
    {
        // This is used solely for setting quirks mode. Currently we don't
        // try to preserve the inbound DT. We convert it to HTML5.
        $this->quirks = $quirks;

        if ($this->insertMode > static::IM_INITIAL) {
            $this->parseError('Illegal placement of DOCTYPE tag. Ignoring: ' . $name);

            return;
        }

        $this->insertMode = static::IM_BEFORE_HTML;
    }

    /**
     * Process the start tag.
     *
     * @todo - XMLNS namespace handling (we need to parse, even if it's not valid)
     *       - XLink, MathML and SVG namespace handling
     *       - Omission rules: 8.1.2.4 Optional tags
     *
     * @param string $name
     * @param array  $attributes
     * @param bool   $selfClosing
     *
     * @return int
     */
    public function startTag($name, $attributes = array(), $selfClosing = false)
    {
        $lname = $this->normalizeTagName($name);

        // Make sure we have an html element.
        if (!$this->doc->documentElement && 'html' !== $name && !$this->frag) {
            $this->startTag('html');
        }

        // Set quirks mode if we're at IM_INITIAL with no doctype.
        if ($this->insertMode === static::IM_INITIAL) {
            $this->quirks = true;
            $this->parseError('No DOCTYPE specified.');
        }

        // SPECIAL TAG HANDLING:
        // Spec says do this, and "don't ask."
        // find the spec where this is defined... looks problematic
        if ('image' === $name && !($this->insertMode === static::IM_IN_SVG || $this->insertMode === static::IM_IN_MATHML)) {
            $name = 'img';
        }

        // Autoclose p tags where appropriate.
        if ($this->insertMode >= static::IM_IN_BODY && Elements::isA($name, Elements::AUTOCLOSE_P)) {
            $this->autoclose('p');
        }

        // Set insert mode:
        switch ($name) {
            case 'html':
                $this->insertMode = static::IM_BEFORE_HEAD;
                break;
            case 'head':
                if ($this->insertMode > static::IM_BEFORE_HEAD) {
                    $this->parseError('Unexpected head tag outside of head context.');
                } else {
                    $this->insertMode = static::IM_IN_HEAD;
                }
                break;
            case 'body':
                $this->insertMode = static::IM_IN_BODY;
                break;
            case 'svg':
                $this->insertMode = static::IM_IN_SVG;
                break;
            case 'math':
                $this->insertMode = static::IM_IN_MATHML;
                break;
            case 'noscript':
                if ($this->insertMode === static::IM_IN_HEAD) {
                    $this->insertMode = static::IM_IN_HEAD_NOSCRIPT;
                }
                break;
        }

        // Special case handling for SVG.
        if ($this->insertMode === static::IM_IN_SVG) {
            $lname = Elements::normalizeSvgElement($lname);
        }

        $pushes = 0;
        // when we found a tag thats appears inside $nsRoots, we have to switch the defalut namespace
        if (isset($this->nsRoots[$lname]) && $this->nsStack[0][''] !== $this->nsRoots[$lname]) {
            array_unshift($this->nsStack, array(
                '' => $this->nsRoots[$lname],
            ) + $this->nsStack[0]);
            ++$pushes;
        }
        $needsWorkaround = false;
        if (isset($this->options['xmlNamespaces']) && $this->options['xmlNamespaces']) {
            // when xmlNamespaces is true a and we found a 'xmlns' or 'xmlns:*' attribute, we should add a new item to the $nsStack
            foreach ($attributes as $aName => $aVal) {
                if ('xmlns' === $aName) {
                    $needsWorkaround = $aVal;
                    array_unshift($this->nsStack, array(
                        '' => $aVal,
                    ) + $this->nsStack[0]);
                    ++$pushes;
                } elseif ('xmlns' === (($pos = strpos($aName, ':')) ? substr($aName, 0, $pos) : '')) {
                    array_unshift($this->nsStack, array(
                        substr($aName, $pos + 1) => $aVal,
                    ) + $this->nsStack[0]);
                    ++$pushes;
                }
            }
        }

        if ($this->onlyInline && Elements::isA($lname, Elements::BLOCK_TAG)) {
            $this->autoclose($this->onlyInline);
            $this->onlyInline = null;
        }

        try {
            $prefix = ($pos = strpos($lname, ':')) ? substr($lname, 0, $pos) : '';

            if (false !== $needsWorkaround) {
                $xml = "<$lname xmlns=\"$needsWorkaround\" " . (strlen($prefix) && isset($this->nsStack[0][$prefix]) ? ("xmlns:$prefix=\"" . $this->nsStack[0][$prefix] . '"') : '') . '/>';

                $frag = new \DOMDocument('1.0', 'UTF-8');
                $frag->loadXML($xml);

                $ele = $this->doc->importNode($frag->documentElement, true);
            } else {
                if (!isset($this->nsStack[0][$prefix]) || ('' === $prefix && isset($this->options[self::OPT_DISABLE_HTML_NS]) && $this->options[self::OPT_DISABLE_HTML_NS])) {
                    $ele = $this->doc->createElement($lname);
                } else {
                    $ele = $this->doc->createElementNS($this->nsStack[0][$prefix], $lname);
                }
            }
        } catch (\DOMException $e) {
            $this->parseError("Illegal tag name: <$lname>. Replaced with <invalid>.");
            $ele = $this->doc->createElement('invalid');
        }

        if (Elements::isA($lname, Elements::BLOCK_ONLY_INLINE)) {
            $this->onlyInline = $lname;
        }

        // When we add some namespacess, we have to track them. Later, when "endElement" is invoked, we have to remove them.
        // When we are on a void tag, we do not need to care about namesapce nesting.
        if ($pushes > 0 && !Elements::isA($name, Elements::VOID_TAG)) {
            // PHP tends to free the memory used by DOM,
            // to avoid spl_object_hash collisions whe have to avoid garbage collection of $ele storing it into $pushes
            // see https://bugs.php.net/bug.php?id=67459
            $this->pushes[spl_object_hash($ele)] = array($pushes, $ele);
        }

        foreach ($attributes as $aName => $aVal) {
            // xmlns attributes can't be set
            if ('xmlns' === $aName) {
                continue;
            }

            if ($this->insertMode === static::IM_IN_SVG) {
                $aName = Elements::normalizeSvgAttribute($aName);
            } elseif ($this->insertMode === static::IM_IN_MATHML) {
                $aName = Elements::normalizeMathMlAttribute($aName);
            }

            $aVal = (string) $aVal;

            try {
                $prefix = ($pos = strpos($aName, ':')) ? substr($aName, 0, $pos) : false;

                if ('xmlns' === $prefix) {
                    $ele->setAttributeNS(self::NAMESPACE_XMLNS, $aName, $aVal);
                } elseif (false !== $prefix && isset($this->nsStack[0][$prefix])) {
                    $ele->setAttributeNS($this->nsStack[0][$prefix], $aName, $aVal);
                } else {
                    $ele->setAttribute($aName, $aVal);
                }
            } catch (\DOMException $e) {
                $this->parseError("Illegal attribute name for tag $name. Ignoring: $aName");
                continue;
            }

            // This is necessary on a non-DTD schema, like HTML5.
            if ('id' === $aName) {
                $ele->setIdAttribute('id', true);
            }
        }

        if ($this->frag !== $this->current && $this->rules->hasRules($name)) {
            // Some elements have special processing rules. Handle those separately.
            $this->current = $this->rules->evaluate($ele, $this->current);
        } else {
            // Otherwise, it's a standard element.
            $this->current->appendChild($ele);

            if (!Elements::isA($name, Elements::VOID_TAG)) {
                $this->current = $ele;
            }

            // Self-closing tags should only be respected on foreign elements
            // (and are implied on void elements)
            // See: https://www.w3.org/TR/html5/syntax.html#start-tags
            if (Elements::isHtml5Element($name)) {
                $selfClosing = false;
            }
        }

        // This is sort of a last-ditch attempt to correct for cases where no head/body
        // elements are provided.
        if ($this->insertMode <= static::IM_BEFORE_HEAD && 'head' !== $name && 'html' !== $name) {
            $this->insertMode = static::IM_IN_BODY;
        }

        // When we are on a void tag, we do not need to care about namesapce nesting,
        // but we have to remove the namespaces pushed to $nsStack.
        if ($pushes > 0 && Elements::isA($name, Elements::VOID_TAG)) {
            // remove the namespaced definded by current node
            for ($i = 0; $i < $pushes; ++$i) {
                array_shift($this->nsStack);
            }
        }

        if ($selfClosing) {
            $this->endTag($name);
        }

        // Return the element mask, which the tokenizer can then use to set
        // various processing rules.
        return Elements::element($name);
    }

    public function endTag($name)
    {
        $lname = $this->normalizeTagName($name);

        // Special case within 12.2.6.4.7: An end tag whose tag name is "br" should be treated as an opening tag
        if ('br' === $name) {
            $this->parseError('Closing tag encountered for void element br.');

            $this->startTag('br');
        }
        // Ignore closing tags for other unary elements.
        elseif (Elements::isA($name, Elements::VOID_TAG)) {
            return;
        }

        if ($this->insertMode <= static::IM_BEFORE_HTML) {
            // 8.2.5.4.2
            if (in_array($name, array(
                'html',
                'br',
                'head',
                'title',
            ))) {
                $this->startTag('html');
                $this->endTag($name);
                $this->insertMode = static::IM_BEFORE_HEAD;

                return;
            }

            // Ignore the tag.
            $this->parseError('Illegal closing tag at global scope.');

            return;
        }

        // Special case handling for SVG.
        if ($this->insertMode === static::IM_IN_SVG) {
            $lname = Elements::normalizeSvgElement($lname);
        }

        $cid = spl_object_hash($this->current);

        // XXX: HTML has no parent. What do we do, though,
        // if this element appears in the wrong place?
        if ('html' === $lname) {
            return;
        }

        // remove the namespaced definded by current node
        if (isset($this->pushes[$cid])) {
            for ($i = 0; $i < $this->pushes[$cid][0]; ++$i) {
                array_shift($this->nsStack);
            }
            unset($this->pushes[$cid]);
        }

        if (!$this->autoclose($lname)) {
            $this->parseError('Could not find closing tag for ' . $lname);
        }

        switch ($lname) {
            case 'head':
                $this->insertMode = static::IM_AFTER_HEAD;
                break;
            case 'body':
                $this->insertMode = static::IM_AFTER_BODY;
                break;
            case 'svg':
            case 'mathml':
                $this->insertMode = static::IM_IN_BODY;
                break;
        }
    }

    public function comment($cdata)
    {
        // TODO: Need to handle case where comment appears outside of the HTML tag.
        $node = $this->doc->createComment($cdata);
        $this->current->appendChild($node);
    }

    public function text($data)
    {
        // XXX: Hmmm.... should we really be this strict?
        if ($this->insertMode < static::IM_IN_HEAD) {
            // Per '8.2.5.4.3 The "before head" insertion mode' the characters
            // " \t\n\r\f" should be ignored but no mention of a parse error. This is
            // practical as most documents contain these characters. Other text is not
            // expected here so recording a parse error is necessary.
            $dataTmp = trim($data, " \t\n\r\f");
            if (!empty($dataTmp)) {
                // fprintf(STDOUT, "Unexpected insert mode: %d", $this->insertMode);
                $this->parseError('Unexpected text. Ignoring: ' . $dataTmp);
            }

            return;
        }
        // fprintf(STDOUT, "Appending text %s.", $data);
        $node = $this->doc->createTextNode($data);
        $this->current->appendChild($node);
    }

    public function eof()
    {
        // If the $current isn't the $root, do we need to do anything?
    }

    public function parseError($msg, $line = 0, $col = 0)
    {
        $this->errors[] = sprintf('Line %d, Col %d: %s', $line, $col, $msg);
    }

    public function getErrors()
    {
        return $this->errors;
    }

    public function cdata($data)
    {
        $node = $this->doc->createCDATASection($data);
        $this->current->appendChild($node);
    }

    public function processingInstruction($name, $data = null)
    {
        // XXX: Ignore initial XML declaration, per the spec.
        if ($this->insertMode === static::IM_INITIAL && 'xml' === strtolower($name)) {
            return;
        }

        // Important: The processor may modify the current DOM tree however it sees fit.
        if ($this->processor instanceof InstructionProcessor) {
            $res = $this->processor->process($this->current, $name, $data);
            if (!empty($res)) {
                $this->current = $res;
            }

            return;
        }

        // Otherwise, this is just a dumb PI element.
        $node = $this->doc->createProcessingInstruction($name, $data);

        $this->current->appendChild($node);
    }

    // ==========================================================================
    // UTILITIES
    // ==========================================================================

    /**
     * Apply normalization rules to a tag name.
     * See sections 2.9 and 8.1.2.
     *
     * @param string $tagName
     *
     * @return string The normalized tag name.
     */
    protected function normalizeTagName($tagName)
    {
        /*
         * Section 2.9 suggests that we should not do this. if (strpos($name, ':') !== false) { // We know from the grammar that there must be at least one other // char besides :, since : is not a legal tag start. $parts = explode(':', $name); return array_pop($parts); }
         */
        return $tagName;
    }

    protected function quirksTreeResolver($name)
    {
        throw new \Exception('Not implemented.');
    }

    /**
     * Automatically climb the tree and close the closest node with the matching $tag.
     *
     * @param string $tagName
     *
     * @return bool
     */
    protected function autoclose($tagName)
    {
        $working = $this->current;
        do {
            if (XML_ELEMENT_NODE !== $working->nodeType) {
                return false;
            }
            if ($working->tagName === $tagName) {
                $this->current = $working->parentNode;

                return true;
            }
        } while ($working = $working->parentNode);

        return false;
    }

    /**
     * Checks if the given tagname is an ancestor of the present candidate.
     *
     * If $this->current or anything above $this->current matches the given tag
     * name, this returns true.
     *
     * @param string $tagName
     *
     * @return bool
     */
    protected function isAncestor($tagName)
    {
        $candidate = $this->current;
        while (XML_ELEMENT_NODE === $candidate->nodeType) {
            if ($candidate->tagName === $tagName) {
                return true;
            }
            $candidate = $candidate->parentNode;
        }

        return false;
    }

    /**
     * Returns true if the immediate parent element is of the given tagname.
     *
     * @param string $tagName
     *
     * @return bool
     */
    protected function isParent($tagName)
    {
        return $this->current->tagName === $tagName;
    }
}
PK�c�\�w�[��6masterminds/html5/src/HTML5/Parser/FileInputStream.phpnu�[���<?php

namespace Masterminds\HTML5\Parser;

/**
 * The FileInputStream loads a file to be parsed.
 *
 * So right now we read files into strings and then process the
 * string. We chose to do this largely for the sake of expediency of
 * development, and also because we could optimize toward processing
 * arbitrarily large chunks of the input. But in the future, we'd
 * really like to rewrite this class to efficiently handle lower level
 * stream reads (and thus efficiently handle large documents).
 *
 * @deprecated since 2.4, to remove in 3.0. Use a string in the scanner instead.
 */
class FileInputStream extends StringInputStream implements InputStream
{
    /**
     * Load a file input stream.
     *
     * @param string $data     The file or url path to load.
     * @param string $encoding The encoding to use for the data.
     * @param string $debug    A fprintf format to use to echo the data on stdout.
     */
    public function __construct($data, $encoding = 'UTF-8', $debug = '')
    {
        // Get the contents of the file.
        $content = file_get_contents($data);

        parent::__construct($content, $encoding, $debug);
    }
}
PK�c�\�����,�,.masterminds/html5/src/HTML5/Parser/Scanner.phpnu�[���<?php

namespace Masterminds\HTML5\Parser;

use Masterminds\HTML5\Exception;

/**
 * The scanner scans over a given data input to react appropriately to characters.
 */
class Scanner
{
    const CHARS_HEX = 'abcdefABCDEF01234567890';
    const CHARS_ALNUM = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890';
    const CHARS_ALPHA = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';

    /**
     * The string data we're parsing.
     */
    private $data;

    /**
     * The current integer byte position we are in $data.
     */
    private $char;

    /**
     * Length of $data; when $char === $data, we are at the end-of-file.
     */
    private $EOF;

    /**
     * Parse errors.
     */
    public $errors = array();

    /**
     * Create a new Scanner.
     *
     * @param string $data     Data to parse.
     * @param string $encoding The encoding to use for the data.
     *
     * @throws Exception If the given data cannot be encoded to UTF-8.
     */
    public function __construct($data, $encoding = 'UTF-8')
    {
        if ($data instanceof InputStream) {
            @trigger_error('InputStream objects are deprecated since version 2.4 and will be removed in 3.0. Use strings instead.', E_USER_DEPRECATED);
            $data = (string) $data;
        }

        $data = UTF8Utils::convertToUTF8($data, $encoding);

        // There is good reason to question whether it makes sense to
        // do this here, since most of these checks are done during
        // parsing, and since this check doesn't actually *do* anything.
        $this->errors = UTF8Utils::checkForIllegalCodepoints($data);

        $data = $this->replaceLinefeeds($data);

        $this->data = $data;
        $this->char = 0;
        $this->EOF = strlen($data);
    }

    /**
     * Check if upcomming chars match the given sequence.
     *
     * This will read the stream for the $sequence. If it's
     * found, this will return true. If not, return false.
     * Since this unconsumes any chars it reads, the caller
     * will still need to read the next sequence, even if
     * this returns true.
     *
     * Example: $this->scanner->sequenceMatches('</script>') will
     * see if the input stream is at the start of a
     * '</script>' string.
     *
     * @param string $sequence
     * @param bool   $caseSensitive
     *
     * @return bool
     */
    public function sequenceMatches($sequence, $caseSensitive = true)
    {
        $portion = substr($this->data, $this->char, strlen($sequence));

        return $caseSensitive ? $portion === $sequence : 0 === strcasecmp($portion, $sequence);
    }

    /**
     * Get the current position.
     *
     * @return int The current intiger byte position.
     */
    public function position()
    {
        return $this->char;
    }

    /**
     * Take a peek at the next character in the data.
     *
     * @return string The next character.
     */
    public function peek()
    {
        if (($this->char + 1) < $this->EOF) {
            return $this->data[$this->char + 1];
        }

        return false;
    }

    /**
     * Get the next character.
     * Note: This advances the pointer.
     *
     * @return string The next character.
     */
    public function next()
    {
        ++$this->char;

        if ($this->char < $this->EOF) {
            return $this->data[$this->char];
        }

        return false;
    }

    /**
     * Get the current character.
     * Note, this does not advance the pointer.
     *
     * @return string The current character.
     */
    public function current()
    {
        if ($this->char < $this->EOF) {
            return $this->data[$this->char];
        }

        return false;
    }

    /**
     * Silently consume N chars.
     *
     * @param int $count
     */
    public function consume($count = 1)
    {
        $this->char += $count;
    }

    /**
     * Unconsume some of the data.
     * This moves the data pointer backwards.
     *
     * @param int $howMany The number of characters to move the pointer back.
     */
    public function unconsume($howMany = 1)
    {
        if (($this->char - $howMany) >= 0) {
            $this->char -= $howMany;
        }
    }

    /**
     * Get the next group of that contains hex characters.
     * Note, along with getting the characters the pointer in the data will be
     * moved as well.
     *
     * @return string The next group that is hex characters.
     */
    public function getHex()
    {
        return $this->doCharsWhile(static::CHARS_HEX);
    }

    /**
     * Get the next group of characters that are ASCII Alpha characters.
     * Note, along with getting the characters the pointer in the data will be
     * moved as well.
     *
     * @return string The next group of ASCII alpha characters.
     */
    public function getAsciiAlpha()
    {
        return $this->doCharsWhile(static::CHARS_ALPHA);
    }

    /**
     * Get the next group of characters that are ASCII Alpha characters and numbers.
     * Note, along with getting the characters the pointer in the data will be
     * moved as well.
     *
     * @return string The next group of ASCII alpha characters and numbers.
     */
    public function getAsciiAlphaNum()
    {
        return $this->doCharsWhile(static::CHARS_ALNUM);
    }

    /**
     * Get the next group of numbers.
     * Note, along with getting the characters the pointer in the data will be
     * moved as well.
     *
     * @return string The next group of numbers.
     */
    public function getNumeric()
    {
        return $this->doCharsWhile('0123456789');
    }

    /**
     * Consume whitespace.
     * Whitespace in HTML5 is: formfeed, tab, newline, space.
     *
     * @return int The length of the matched whitespaces.
     */
    public function whitespace()
    {
        if ($this->char >= $this->EOF) {
            return false;
        }

        $len = strspn($this->data, "\n\t\f ", $this->char);

        $this->char += $len;

        return $len;
    }

    /**
     * Returns the current line that is being consumed.
     *
     * @return int The current line number.
     */
    public function currentLine()
    {
        if (empty($this->EOF) || 0 === $this->char) {
            return 1;
        }

        // Add one to $this->char because we want the number for the next
        // byte to be processed.
        return substr_count($this->data, "\n", 0, min($this->char, $this->EOF)) + 1;
    }

    /**
     * Read chars until something in the mask is encountered.
     *
     * @param string $mask
     *
     * @return mixed
     */
    public function charsUntil($mask)
    {
        return $this->doCharsUntil($mask);
    }

    /**
     * Read chars as long as the mask matches.
     *
     * @param string $mask
     *
     * @return int
     */
    public function charsWhile($mask)
    {
        return $this->doCharsWhile($mask);
    }

    /**
     * Returns the current column of the current line that the tokenizer is at.
     *
     * Newlines are column 0. The first char after a newline is column 1.
     *
     * @return int The column number.
     */
    public function columnOffset()
    {
        // Short circuit for the first char.
        if (0 === $this->char) {
            return 0;
        }

        // strrpos is weird, and the offset needs to be negative for what we
        // want (i.e., the last \n before $this->char). This needs to not have
        // one (to make it point to the next character, the one we want the
        // position of) added to it because strrpos's behaviour includes the
        // final offset byte.
        $backwardFrom = $this->char - 1 - strlen($this->data);
        $lastLine = strrpos($this->data, "\n", $backwardFrom);

        // However, for here we want the length up until the next byte to be
        // processed, so add one to the current byte ($this->char).
        if (false !== $lastLine) {
            $findLengthOf = substr($this->data, $lastLine + 1, $this->char - 1 - $lastLine);
        } else {
            // After a newline.
            $findLengthOf = substr($this->data, 0, $this->char);
        }

        return UTF8Utils::countChars($findLengthOf);
    }

    /**
     * Get all characters until EOF.
     *
     * This consumes characters until the EOF.
     *
     * @return int The number of characters remaining.
     */
    public function remainingChars()
    {
        if ($this->char < $this->EOF) {
            $data = substr($this->data, $this->char);
            $this->char = $this->EOF;

            return $data;
        }

        return ''; // false;
    }

    /**
     * Replace linefeed characters according to the spec.
     *
     * @param $data
     *
     * @return string
     */
    private function replaceLinefeeds($data)
    {
        /*
         * U+000D CARRIAGE RETURN (CR) characters and U+000A LINE FEED (LF) characters are treated specially.
         * Any CR characters that are followed by LF characters must be removed, and any CR characters not
         * followed by LF characters must be converted to LF characters. Thus, newlines in HTML DOMs are
         * represented by LF characters, and there are never any CR characters in the input to the tokenization
         * stage.
         */
        $crlfTable = array(
            "\0" => "\xEF\xBF\xBD",
            "\r\n" => "\n",
            "\r" => "\n",
        );

        return strtr($data, $crlfTable);
    }

    /**
     * Read to a particular match (or until $max bytes are consumed).
     *
     * This operates on byte sequences, not characters.
     *
     * Matches as far as possible until we reach a certain set of bytes
     * and returns the matched substring.
     *
     * @param string $bytes Bytes to match.
     * @param int    $max   Maximum number of bytes to scan.
     *
     * @return mixed Index or false if no match is found. You should use strong
     *               equality when checking the result, since index could be 0.
     */
    private function doCharsUntil($bytes, $max = null)
    {
        if ($this->char >= $this->EOF) {
            return false;
        }

        if (0 === $max || $max) {
            $len = strcspn($this->data, $bytes, $this->char, $max);
        } else {
            $len = strcspn($this->data, $bytes, $this->char);
        }

        $string = (string) substr($this->data, $this->char, $len);
        $this->char += $len;

        return $string;
    }

    /**
     * Returns the string so long as $bytes matches.
     *
     * Matches as far as possible with a certain set of bytes
     * and returns the matched substring.
     *
     * @param string $bytes A mask of bytes to match. If ANY byte in this mask matches the
     *                      current char, the pointer advances and the char is part of the
     *                      substring.
     * @param int    $max   The max number of chars to read.
     *
     * @return string
     */
    private function doCharsWhile($bytes, $max = null)
    {
        if ($this->char >= $this->EOF) {
            return false;
        }

        if (0 === $max || $max) {
            $len = strspn($this->data, $bytes, $this->char, $max);
        } else {
            $len = strspn($this->data, $bytes, $this->char);
        }

        $string = (string) substr($this->data, $this->char, $len);
        $this->char += $len;

        return $string;
    }
}
PK�c�\��ز:%:%8masterminds/html5/src/HTML5/Parser/StringInputStream.phpnu�[���<?php
/**
 * Loads a string to be parsed.
 */

namespace Masterminds\HTML5\Parser;

/*
 *
* Based on code from html5lib:

Copyright 2009 Geoffrey Sneddon <http://gsnedders.com/>

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
    "Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

*/

// Some conventions:
// - /* */ indicates verbatim text from the HTML 5 specification
//   MPB: Not sure which version of the spec. Moving from HTML5lib to
//   HTML5-PHP, I have been using this version:
//   http://www.w3.org/TR/2012/CR-html5-20121217/Overview.html#contents
//
// - // indicates regular comments

/**
 * @deprecated since 2.4, to remove in 3.0. Use a string in the scanner instead.
 */
class StringInputStream implements InputStream
{
    /**
     * The string data we're parsing.
     */
    private $data;

    /**
     * The current integer byte position we are in $data.
     */
    private $char;

    /**
     * Length of $data; when $char === $data, we are at the end-of-file.
     */
    private $EOF;

    /**
     * Parse errors.
     */
    public $errors = array();

    /**
     * Create a new InputStream wrapper.
     *
     * @param string $data     Data to parse.
     * @param string $encoding The encoding to use for the data.
     * @param string $debug    A fprintf format to use to echo the data on stdout.
     */
    public function __construct($data, $encoding = 'UTF-8', $debug = '')
    {
        $data = UTF8Utils::convertToUTF8($data, $encoding);
        if ($debug) {
            fprintf(STDOUT, $debug, $data, strlen($data));
        }

        // There is good reason to question whether it makes sense to
        // do this here, since most of these checks are done during
        // parsing, and since this check doesn't actually *do* anything.
        $this->errors = UTF8Utils::checkForIllegalCodepoints($data);

        $data = $this->replaceLinefeeds($data);

        $this->data = $data;
        $this->char = 0;
        $this->EOF = strlen($data);
    }

    public function __toString()
    {
        return $this->data;
    }

    /**
     * Replace linefeed characters according to the spec.
     */
    protected function replaceLinefeeds($data)
    {
        /*
         * U+000D CARRIAGE RETURN (CR) characters and U+000A LINE FEED (LF) characters are treated specially.
         * Any CR characters that are followed by LF characters must be removed, and any CR characters not
         * followed by LF characters must be converted to LF characters. Thus, newlines in HTML DOMs are
         * represented by LF characters, and there are never any CR characters in the input to the tokenization
         * stage.
         */
        $crlfTable = array(
            "\0" => "\xEF\xBF\xBD",
            "\r\n" => "\n",
            "\r" => "\n",
        );

        return strtr($data, $crlfTable);
    }

    /**
     * Returns the current line that the tokenizer is at.
     */
    public function currentLine()
    {
        if (empty($this->EOF) || 0 === $this->char) {
            return 1;
        }
        // Add one to $this->char because we want the number for the next
        // byte to be processed.
        return substr_count($this->data, "\n", 0, min($this->char, $this->EOF)) + 1;
    }

    /**
     * @deprecated
     */
    public function getCurrentLine()
    {
        return $this->currentLine();
    }

    /**
     * Returns the current column of the current line that the tokenizer is at.
     * Newlines are column 0. The first char after a newline is column 1.
     *
     * @return int The column number.
     */
    public function columnOffset()
    {
        // Short circuit for the first char.
        if (0 === $this->char) {
            return 0;
        }
        // strrpos is weird, and the offset needs to be negative for what we
        // want (i.e., the last \n before $this->char). This needs to not have
        // one (to make it point to the next character, the one we want the
        // position of) added to it because strrpos's behaviour includes the
        // final offset byte.
        $backwardFrom = $this->char - 1 - strlen($this->data);
        $lastLine = strrpos($this->data, "\n", $backwardFrom);

        // However, for here we want the length up until the next byte to be
        // processed, so add one to the current byte ($this->char).
        if (false !== $lastLine) {
            $findLengthOf = substr($this->data, $lastLine + 1, $this->char - 1 - $lastLine);
        } else {
            // After a newline.
            $findLengthOf = substr($this->data, 0, $this->char);
        }

        return UTF8Utils::countChars($findLengthOf);
    }

    /**
     * @deprecated
     */
    public function getColumnOffset()
    {
        return $this->columnOffset();
    }

    /**
     * Get the current character.
     *
     * @return string The current character.
     */
    public function current()
    {
        return $this->data[$this->char];
    }

    /**
     * Advance the pointer.
     * This is part of the Iterator interface.
     */
    public function next()
    {
        ++$this->char;
    }

    /**
     * Rewind to the start of the string.
     */
    public function rewind()
    {
        $this->char = 0;
    }

    /**
     * Is the current pointer location valid.
     *
     * @return bool Whether the current pointer location is valid.
     */
    public function valid()
    {
        return $this->char < $this->EOF;
    }

    /**
     * Get all characters until EOF.
     *
     * This reads to the end of the file, and sets the read marker at the
     * end of the file.
     *
     * Note this performs bounds checking.
     *
     * @return string Returns the remaining text. If called when the InputStream is
     *                already exhausted, it returns an empty string.
     */
    public function remainingChars()
    {
        if ($this->char < $this->EOF) {
            $data = substr($this->data, $this->char);
            $this->char = $this->EOF;

            return $data;
        }

        return ''; // false;
    }

    /**
     * Read to a particular match (or until $max bytes are consumed).
     *
     * This operates on byte sequences, not characters.
     *
     * Matches as far as possible until we reach a certain set of bytes
     * and returns the matched substring.
     *
     * @param string $bytes Bytes to match.
     * @param int    $max   Maximum number of bytes to scan.
     *
     * @return mixed Index or false if no match is found. You should use strong
     *               equality when checking the result, since index could be 0.
     */
    public function charsUntil($bytes, $max = null)
    {
        if ($this->char >= $this->EOF) {
            return false;
        }

        if (0 === $max || $max) {
            $len = strcspn($this->data, $bytes, $this->char, $max);
        } else {
            $len = strcspn($this->data, $bytes, $this->char);
        }

        $string = (string) substr($this->data, $this->char, $len);
        $this->char += $len;

        return $string;
    }

    /**
     * Returns the string so long as $bytes matches.
     *
     * Matches as far as possible with a certain set of bytes
     * and returns the matched substring.
     *
     * @param string $bytes A mask of bytes to match. If ANY byte in this mask matches the
     *                      current char, the pointer advances and the char is part of the
     *                      substring.
     * @param int    $max   The max number of chars to read.
     *
     * @return string
     */
    public function charsWhile($bytes, $max = null)
    {
        if ($this->char >= $this->EOF) {
            return false;
        }

        if (0 === $max || $max) {
            $len = strspn($this->data, $bytes, $this->char, $max);
        } else {
            $len = strspn($this->data, $bytes, $this->char);
        }
        $string = (string) substr($this->data, $this->char, $len);
        $this->char += $len;

        return $string;
    }

    /**
     * Unconsume characters.
     *
     * @param int $howMany The number of characters to unconsume.
     */
    public function unconsume($howMany = 1)
    {
        if (($this->char - $howMany) >= 0) {
            $this->char -= $howMany;
        }
    }

    /**
     * Look ahead without moving cursor.
     */
    public function peek()
    {
        if (($this->char + 1) <= $this->EOF) {
            return $this->data[$this->char + 1];
        }

        return false;
    }

    public function key()
    {
        return $this->char;
    }
}
PK�c�\�:�X��1masterminds/html5/src/HTML5/Parser/ParseError.phpnu�[���<?php

namespace Masterminds\HTML5\Parser;

/**
 * Emit when the parser has an error.
 */
class ParseError extends \Exception
{
}
PK�c�\����	�	2masterminds/html5/src/HTML5/Parser/InputStream.phpnu�[���<?php

namespace Masterminds\HTML5\Parser;

/**
 * Interface for stream readers.
 *
 * The parser only reads from streams. Various input sources can write
 * an adapater to this InputStream.
 *
 * Currently provided InputStream implementations include
 * FileInputStream and StringInputStream.
 *
 * @deprecated since 2.4, to remove in 3.0. Use a string in the scanner instead.
 */
interface InputStream extends \Iterator
{
    /**
     * Returns the current line that is being consumed.
     *
     * TODO: Move this to the scanner.
     */
    public function currentLine();

    /**
     * Returns the current column of the current line that the tokenizer is at.
     *
     * Newlines are column 0. The first char after a newline is column 1.
     *
     * @TODO Move this to the scanner.
     *
     * @return int The column number.
     */
    public function columnOffset();

    /**
     * Get all characters until EOF.
     *
     * This consumes characters until the EOF.
     */
    public function remainingChars();

    /**
     * Read to a particular match (or until $max bytes are consumed).
     *
     * This operates on byte sequences, not characters.
     *
     * Matches as far as possible until we reach a certain set of bytes
     * and returns the matched substring.
     *
     * @see strcspn
     *
     * @param string $bytes Bytes to match.
     * @param int    $max   Maximum number of bytes to scan.
     *
     * @return mixed Index or false if no match is found. You should use strong
     *               equality when checking the result, since index could be 0.
     */
    public function charsUntil($bytes, $max = null);

    /**
     * Returns the string so long as $bytes matches.
     *
     * Matches as far as possible with a certain set of bytes
     * and returns the matched substring.
     *
     * @see strspn
     *
     * @param string $bytes A mask of bytes to match. If ANY byte in this mask matches the
     *                      current char, the pointer advances and the char is part of the
     *                      substring.
     * @param int    $max   The max number of chars to read.
     */
    public function charsWhile($bytes, $max = null);

    /**
     * Unconsume one character.
     *
     * @param int $howMany The number of characters to move the pointer back.
     */
    public function unconsume($howMany = 1);

    /**
     * Retrieve the next character without advancing the pointer.
     */
    public function peek();
}
PK�c�\	3sg\\3masterminds/html5/src/HTML5/Parser/EventHandler.phpnu�[���<?php

namespace Masterminds\HTML5\Parser;

/**
 * Standard events for HTML5.
 *
 * This is roughly analogous to a SAX2 or expat-style interface.
 * However, it is tuned specifically for HTML5, according to section 8
 * of the HTML5 specification.
 *
 * An event handler receives parser events. For a concrete
 * implementation, see DOMTreeBuilder.
 *
 * Quirks support in the parser is limited to close-in syntax (malformed
 * tags or attributes). Higher order syntax and semantic issues with a
 * document (e.g. mismatched tags, illegal nesting, etc.) are the
 * responsibility of the event handler implementation.
 *
 * See HTML5 spec section 8.2.4
 */
interface EventHandler
{
    const DOCTYPE_NONE = 0;

    const DOCTYPE_PUBLIC = 1;

    const DOCTYPE_SYSTEM = 2;

    /**
     * A doctype declaration.
     *
     * @param string $name   The name of the root element.
     * @param int    $idType One of DOCTYPE_NONE, DOCTYPE_PUBLIC, or DOCTYPE_SYSTEM
     * @param string $id     The identifier. For DOCTYPE_PUBLIC, this is the public ID. If DOCTYPE_SYSTEM,
     *                       then this is a system ID.
     * @param bool   $quirks Indicates whether the builder should enter quirks mode.
     */
    public function doctype($name, $idType = 0, $id = null, $quirks = false);

    /**
     * A start tag.
     *
     * IMPORTANT: The parser watches the return value of this event. If this returns
     * an integer, the parser will switch TEXTMODE patters according to the int.
     *
     * This is how the Tree Builder can tell the Tokenizer when a certain tag should
     * cause the parser to go into RAW text mode.
     *
     * The HTML5 standard requires that the builder is the one that initiates this
     * step, and this is the only way short of a circular reference that we can
     * do that.
     *
     * Example: if a startTag even for a `script` name is fired, and the startTag()
     * implementation returns Tokenizer::TEXTMODE_RAW, then the tokenizer will
     * switch into RAW text mode and consume data until it reaches a closing
     * `script` tag.
     *
     * The textmode is automatically reset to Tokenizer::TEXTMODE_NORMAL when the
     * closing tag is encounter. **This behavior may change.**
     *
     * @param string $name        The tag name.
     * @param array  $attributes  An array with all of the tag's attributes.
     * @param bool   $selfClosing An indicator of whether or not this tag is self-closing (<foo/>).
     *
     * @return int one of the Tokenizer::TEXTMODE_* constants
     */
    public function startTag($name, $attributes = array(), $selfClosing = false);

    /**
     * An end-tag.
     */
    public function endTag($name);

    /**
     * A comment section (unparsed character data).
     */
    public function comment($cdata);

    /**
     * A unit of parsed character data.
     *
     * Entities in this text are *already decoded*.
     */
    public function text($cdata);

    /**
     * Indicates that the document has been entirely processed.
     */
    public function eof();

    /**
     * Emitted when the parser encounters an error condition.
     */
    public function parseError($msg, $line, $col);

    /**
     * A CDATA section.
     *
     * @param string $data
     *                     The unparsed character data
     */
    public function cdata($data);

    /**
     * This is a holdover from the XML spec.
     *
     * While user agents don't get PIs, server-side does.
     *
     * @param string $name The name of the processor (e.g. 'php').
     * @param string $data The unparsed data.
     */
    public function processingInstruction($name, $data = null);
}
PK�c�\���G��9masterminds/html5/src/HTML5/Parser/CharacterReference.phpnu�[���<?php

namespace Masterminds\HTML5\Parser;

use Masterminds\HTML5\Entities;

/**
 * Manage entity references.
 *
 * This is a simple resolver for HTML5 character reference entitites. See Entities for the list of supported entities.
 */
class CharacterReference
{
    protected static $numeric_mask = array(
        0x0,
        0x2FFFF,
        0,
        0xFFFF,
    );

    /**
     * Given a name (e.g. 'amp'), lookup the UTF-8 character ('&').
     *
     * @param string $name The name to look up.
     *
     * @return string The character sequence. In UTF-8 this may be more than one byte.
     */
    public static function lookupName($name)
    {
        // Do we really want to return NULL here? or FFFD
        return isset(Entities::$byName[$name]) ? Entities::$byName[$name] : null;
    }

    /**
     * Given a decimal number, return the UTF-8 character.
     *
     * @param $int
     *
     * @return false|string|string[]|null
     */
    public static function lookupDecimal($int)
    {
        $entity = '&#' . $int . ';';

        // UNTESTED: This may fail on some planes. Couldn't find full documentation
        // on the value of the mask array.
        return mb_decode_numericentity($entity, static::$numeric_mask, 'utf-8');
    }

    /**
     * Given a hexidecimal number, return the UTF-8 character.
     *
     * @param $hexdec
     *
     * @return false|string|string[]|null
     */
    public static function lookupHex($hexdec)
    {
        return static::lookupDecimal(hexdec($hexdec));
    }
}
PK�c�\�%Ʉ��,masterminds/html5/src/HTML5/Parser/README.mdnu�[���# The Parser Model

The parser model here follows the model in section
[8.2.1](http://www.w3.org/TR/2012/CR-html5-20121217/syntax.html#parsing)
of the HTML5 specification, though we do not assume a networking layer.

     [ InputStream ]    // Generic support for reading input.
           ||
      [ Scanner ]       // Breaks down the stream into characters.
           ||
     [ Tokenizer ]      // Groups characters into syntactic
           ||
    [ Tree Builder ]    // Organizes units into a tree of objects
           ||
     [ DOM Document ]     // The final state of the parsed document.


## InputStream

This is an interface with at least two concrete implementations:

- StringInputStream: Reads an HTML5 string.
- FileInputStream: Reads an HTML5 file.

## Scanner

This is a mechanical piece of the parser.

## Tokenizer

This follows section 8.4 of the HTML5 spec. It is (roughly) a recursive
descent parser. (Though there are plenty of optimizations that are less
than purely functional.

## EventHandler and DOMTree

EventHandler is the interface for tree builders. Since not all
implementations will necessarily build trees, we've chosen a more
generic name.

The event handler emits tokens during tokenization.

The DOMTree is an event handler that builds a DOM tree. The output of
the DOMTree builder is a DOMDocument.

## DOMDocument

PHP has a DOMDocument class built-in (technically, it's part of libxml.)
We use that, thus rendering the output of this process compatible with
SimpleXML, QueryPath, and many other XML/HTML processing tools.

For cases where the HTML5 is a fragment of a HTML5 document a
DOMDocumentFragment is returned instead. This is another built-in class.
PK�c�\�����masterminds/html5/RELEASE.mdnu�[���# Release Notes

2.7.6  (2021-08-18)

- #218: Address comment handling issues 

2.7.5  (2021-07-01)

- #204: Travis: Enable tests on PHP 8.0 
- #207: Fix PHP 8.1 deprecations 

2.7.4  (2020-10-01)

- #191: Fix travisci build 
- #195: Add .gitattributes file with export-ignore rules 
- #194: Fix query parameter parsed as character entity

2.7.3 (2020-07-05)

- #190: mitigate cyclic reference between output rules and the traverser objects 

2.7.2 (2020-07-01)

- #187: Fixed memory leak in HTML5::saveHTML() 
- #186: Add special case for end tag </br>

2.7.1 (2020-06-14)

- #171: add PHP 7.4 job 
- #178: Prevent infinite loop on un-terminated entity declaration at EOF 

2.7.0 (2019-07-25)

- #164: Drop HHVM support
- #168: Set default encoding in the DOMDocument object

2.6.0 (2019-03-10)

- #163: Allow to pass a charset to the Scanner

2.5.0 (2018-12-27)

- #162, #161, #155, #154, #153, #151: big performance improvements
- #156: fixed typos
- #160: adopt and enforce code style
- #159: remove deprecated php unit base test case
- #150: backport changes from old master branch 

2.4.0 (2018-11-17)

- #148: Improve performance by moving sequence matching 
- #147: Improve the Tokenizer performance 
- #146: Improve performance by relying on a native string instead of InputStream 
- #144: Add DOM extension in composer.json
- #145: Add more extensions on composer.json, improve phpdocs and remove dead code 
- #143: Remove experimental comment 

2.3.1 (2018-10-18)

- #121: Audio is not a block tag (fixed by #141)
- #136: Handle illegal self-closing according to spec (fixed by #137)
- #141: Minor fixes in the README

2.3.0 (2017-09-04)

- #129: image within inline svg breaks system (fixed by #133) 
- #131: &sup2; does not work (fixed by #132)
- #134: Improve tokenizer performance by 20% (alternative version of #130 thanks to @MichaelHeerklotz)
- #135: Raw & in attributes

2.2.2 (2016-09-22)

- #116: In XML mode, tags are case sensitive
- #115: Fix PHP Notice in OutputRules
- #112: fix parsing of options of an optgroup
- #111: Adding test for the address tag

2.2.1 (2016-05-10)

- #109: Fixed issue where address tag could be written without closing tag (thanks sylus)

2.2.0 (2016-04-11)

- #105: Enable composer cache (for CI/CD)
- #100: Use mb_substitute_character inset of ini_set for environments where ini_set is disable (e.g., shared hosting)
- #98: Allow link, meta, style tags in noscript tags
- #96: Fixed xml:href on svgs that use the "use" breaking
- #94: Counting UTF8 characters performance improvement
- #93: Use newer version of coveralls package
- #90: Remove duplicate test
- #87: Allow multiple root nodes

2.1.2 (2015-06-07)
- #82: Support for PHP7
- #84: Improved boolean attribute handling

2.1.1 (2015-03-23)
- #78: Fixes bug where unmatched entity like string drops everything after &.

2.1.0 (2015-02-01)
- #74: Added `disable_html_ns` and `target_doc` dom parsing options
- Unified option names
- #73: Fixed alphabet, &szlig; now can be detected
- #75 and #76: Allow whitespace in RCDATA tags
- #77: Fixed parsing blunder for json embeds
- #72: Add options to HTML methods

2.0.2 (2014-12-17)
- #50: empty document handling
- #63: tags with strange capitalization
- #65: dashes and underscores as allowed characters in tag names
- #68: Fixed issue with non-inline elements inside inline containers

2.0.1 (2014-09-23)
- #59: Fixed issue parsing some fragments.
- #56: Incorrectly saw 0 as empty string
- Sami as new documentation generator

2.0.0 (2014-07-28)
- #53: Improved boolean attributes handling
- #52: Facebook HHVM compatibility
- #48: Adopted PSR-2 as coding standard
- #47: Moved everything to Masterminds namespace
- #45: Added custom namespaces
- #44: Added support to XML-style namespaces
- #37: Refactored HTML5 class removing static methods

1.0.5 (2014-06-10)
- #38: Set the dev-master branch as the 1.0.x branch for composer (goetas)
- #34: Tests use PSR-4 for autoloading. (goetas)
- #40, #41: Fix entity handling in RCDATA sections. (KitaitiMakoto)
- #32: Fixed issue where wharacter references were being incorrectly encoded in style tags.

1.0.4 (2014-04-29)
- #30/#31 Don't throw an exception for invalid tag names.

1.0.3 (2014-02-28)
- #23 and #29: Ignore attributes with illegal chars in name for the PHP DOM.

1.0.2 (2014-02-12)
- #23: Handle missing tag close in attribute list.
- #25: Fixed text escaping in the serializer (HTML% 8.3).
- #27: Fixed tests on Windows: changed "\n" -> PHP_EOL.
- #28: Fixed infinite loop for char "&" in unquoted attribute in parser.
- #26: Updated tag name case handling to deal with uppercase usage.
- #24: Newlines and tabs are allowed inside quoted attributes (HTML5 8.2.4).
- Fixed Travis CI testing.

1.0.1 (2013-11-07)
- CDATA encoding is improved. (Non-standard; Issue #19)
- Some parser rules were not returning the new current element. (Issue #20)
- Added, to the README, details on code test coverage and to packagist version.
- Fixed processor instructions.
- Improved test coverage and documentation coverage.

1.0.0 (2013-10-02)
- Initial release.
PK�c�\��
���masterminds/html5/LICENSE.txtnu�[���## HTML5-PHP License

Copyright (c) 2013 The Authors of HTML5-PHP

Matt Butcher - mattbutcher@google.com
Matt Farina - matt@mattfarina.com
Asmir Mustafic - goetas@gmail.com

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in 
the Software without restriction, including without limitation the rights to 
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

## HTML5Lib License

Portions of this are based on html5lib's PHP version, which was a
sub-project of html5lib. The following is the list of contributors from
html5lib:

html5lib:

Copyright (c) 2006-2009 The Authors

Contributors:
James Graham - jg307@cam.ac.uk
Anne van Kesteren - annevankesteren@gmail.com
Lachlan Hunt - lachlan.hunt@lachy.id.au
Matt McDonald - kanashii@kanashii.ca
Sam Ruby - rubys@intertwingly.net
Ian Hickson (Google) - ian@hixie.ch
Thomas Broyer - t.broyer@ltgt.net
Jacques Distler - distler@golem.ph.utexas.edu
Henri Sivonen - hsivonen@iki.fi
Adam Barth - abarth@webkit.org
Eric Seidel - eric@webkit.org
The Mozilla Foundation (contributions from Henri Sivonen since 2008)
David Flanagan (Mozilla) - dflanagan@mozilla.com

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in 
the Software without restriction, including without limitation the rights to 
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
PK�c�\]�.�22masterminds/html5/composer.jsonnu�[���{
    "name": "masterminds/html5",
    "description": "An HTML5 parser and serializer.",
    "type": "library",
    "homepage": "http://masterminds.github.io/html5-php",
    "license": "MIT",
    "keywords": ["xml", "html", "html5", "dom", "parser", "serializer", "querypath"],
    "authors": [
        {
            "name": "Matt Butcher",
            "email": "technosophos@gmail.com"
        },
        {
            "name": "Matt Farina",
            "email": "matt@mattfarina.com"
        },
        {
            "name": "Asmir Mustafic",
            "email": "goetas@gmail.com"
        }
    ],
    "require" : {
        "ext-ctype": "*",
        "ext-dom": "*",
        "ext-libxml" : "*",
        "php" : ">=5.3.0"
    },
    "require-dev": {
        "phpunit/phpunit" : "^4.8.35 || ^5.7.21 || ^6 || ^7"
    },
    "autoload": {
        "psr-4": {"Masterminds\\": "src"}
    },
    "autoload-dev": {
        "psr-4": {"Masterminds\\HTML5\\Tests\\": "test/HTML5"}
    },
    "extra": {
        "branch-alias": {
            "dev-master": "2.7-dev"
        }
    }
}
PK�c�\t��s��masterminds/html5/CREDITSnu�[���Matt Butcher [technosophos] <technosophos@gmail.com> (lead)
Matt Farina  [mattfarina] <matt@mattfarina.com> (lead)
Asmir Mustafic [goetas] <goetas@lignano.it> (contributor)
Edward Z. Yang [ezyang] <ezyang@mit.edu> (contributor)
Geoffrey Sneddon [gsnedders] <geoffers@gmail.com> (contributor)
Kukhar Vasily [ngreduce] <ngreduce@gmail.com> (contributor)
Rune Christensen [MrElectronic] <mrelectronic@example.com> (contributor)
Mišo Belica [miso-belica] <miso-belica@example.com> (contributor)
Asmir Mustafic [goetas] <goetas@example.com> (contributor)
KITAITI Makoto [KitaitiMakoto] <KitaitiMakoto@example.com> (contributor) 
Jacob Floyd [cognifloyd] <cognifloyd@gmail.com> (contributor)
PK�c�\�H~~"masterminds/html5/bin/entities.phpnu�[���<?php
/**
 * Fetch the entities.json file and convert to PHP datastructure.
 */

// The URL to the official entities JSON file.
$ENTITIES_URL = 'http://www.w3.org/TR/2012/CR-html5-20121217/entities.json';

$payload = file_get_contents($ENTITIES_URL);
$json = json_decode($payload);

$table = array();
foreach ($json as $name => $obj) {
    $sname = substr($name, 1, -1);
    $table[$sname] = $obj->characters;
}

echo '<?php
namespace Masterminds\\HTML5;
/** Entity lookup tables. This class is automatically generated. */
class Entities {
  public static $byName = ';
var_export($table);
echo ';
}' . PHP_EOL;
//print serialize($table);
PK�c�\�)����masterminds/html5/UPGRADING.mdnu�[���From 1.x to 2.x
=================

- All classes uses `Masterminds` namespace.
- All public static methods has been removed from `HTML5` class and the general API to access the HTML5 functionalities has changed. 

    Before:
    
        $dom = \HTML5::loadHTML('<html>....');
        \HTML5::saveHTML($dom);
        
    After:

        use Masterminds\HTML5;
        
        $html5 = new HTML5();
        
        $dom = $html5->loadHTML('<html>....');
        echo $html5->saveHTML($dom);


PK�c�\��lk`&`&masterminds/html5/README.mdnu�[���> # UKRAINE NEEDS YOUR HELP NOW!
>
> On 24 February 2022, Russian [President Vladimir Putin ordered an invasion of Ukraine by Russian Armed Forces](https://www.bbc.com/news/world-europe-60504334).
>
> Your support is urgently needed.
>
> - Donate to the volunteers. Here is the volunteer fund helping the Ukrainian army to provide all the necessary equipment:
>  https://bank.gov.ua/en/news/all/natsionalniy-bank-vidkriv-spetsrahunok-dlya-zboru-koshtiv-na-potrebi-armiyi or https://savelife.in.ua/en/donate/
> - Triple-check social media sources. Russian disinformation is attempting to coverup and distort the reality in Ukraine.
> - Help Ukrainian refugees who are fleeing Russian attacks and shellings: https://www.globalcitizen.org/en/content/ways-to-help-ukraine-conflict/
> -  Put pressure on your political representatives to provide help to Ukraine.
> -  Believe in the Ukrainian people, they will not surrender, they don't have another Ukraine.
>
> THANK YOU!
----

# HTML5-PHP

HTML5 is a standards-compliant HTML5 parser and writer written entirely in PHP.
It is stable and used in many production websites, and has
well over [five million downloads](https://packagist.org/packages/masterminds/html5).

HTML5 provides the following features.

- An HTML5 serializer
- Support for PHP namespaces
- Composer support
- Event-based (SAX-like) parser
- A DOM tree builder
- Interoperability with [QueryPath](https://github.com/technosophos/querypath)
- Runs on **PHP** 5.3.0 or newer

[![Build Status](https://travis-ci.org/Masterminds/html5-php.png?branch=master)](https://travis-ci.org/Masterminds/html5-php)
[![Latest Stable Version](https://poser.pugx.org/masterminds/html5/v/stable.png)](https://packagist.org/packages/masterminds/html5)
[![Code Coverage](https://scrutinizer-ci.com/g/Masterminds/html5-php/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/Masterminds/html5-php/?branch=master)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/Masterminds/html5-php/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/Masterminds/html5-php/?branch=master)
[![Stability: Sustained](https://masterminds.github.io/stability/sustained.svg)](https://masterminds.github.io/stability/sustained.html)

## Installation

Install HTML5-PHP using [composer](http://getcomposer.org/).

By adding the `masterminds/html5` dependency to your `composer.json` file:

```json
{
  "require" : {
    "masterminds/html5": "^2.0"
  },
}
```

By invoking require command via composer executable:

```bash
composer require masterminds/html5
```

## Basic Usage

HTML5-PHP has a high-level API and a low-level API.

Here is how you use the high-level `HTML5` library API:

```php
<?php
// Assuming you installed from Composer:
require "vendor/autoload.php";

use Masterminds\HTML5;

// An example HTML document:
$html = <<< 'HERE'
  <html>
  <head>
    <title>TEST</title>
  </head>
  <body id='foo'>
    <h1>Hello World</h1>
    <p>This is a test of the HTML5 parser.</p>
  </body>
  </html>
HERE;

// Parse the document. $dom is a DOMDocument.
$html5 = new HTML5();
$dom = $html5->loadHTML($html);

// Render it as HTML5:
print $html5->saveHTML($dom);

// Or save it to a file:
$html5->save($dom, 'out.html');
```

The `$dom` created by the parser is a full `DOMDocument` object. And the
`save()` and `saveHTML()` methods will take any DOMDocument.

### Options

It is possible to pass in an array of configuration options when loading
an HTML5 document.

```php
// An associative array of options
$options = array(
  'option_name' => 'option_value',
);

// Provide the options to the constructor
$html5 = new HTML5($options);

$dom = $html5->loadHTML($html);
```

The following options are supported:

* `encode_entities` (boolean): Indicates that the serializer should aggressively
  encode characters as entities. Without this, it only encodes the bare
  minimum.
* `disable_html_ns` (boolean): Prevents the parser from automatically
  assigning the HTML5 namespace to the DOM document. This is for
  non-namespace aware DOM tools.
* `target_document` (\DOMDocument): A DOM document that will be used as the
  destination for the parsed nodes.
* `implicit_namespaces` (array): An assoc array of namespaces that should be
  used by the parser. Name is tag prefix, value is NS URI.

## The Low-Level API

This library provides the following low-level APIs that you can use to
create more customized HTML5 tools:

- A SAX-like event-based parser that you can hook into for special kinds
of parsing.
- A flexible error-reporting mechanism that can be tuned to document
syntax checking.
- A DOM implementation that uses PHP's built-in DOM library.

The unit tests exercise each piece of the API, and every public function
is well-documented.

### Parser Design

The parser is designed as follows:

- The `Scanner` handles scanning on behalf of the parser.
- The `Tokenizer` requests data off of the scanner, parses it, clasifies
it, and sends it to an `EventHandler`. It is a *recursive descent parser.*
- The `EventHandler` receives notifications and data for each specific
semantic event that occurs during tokenization.
- The `DOMBuilder` is an `EventHandler` that listens for tokenizing
events and builds a document tree (`DOMDocument`) based on the events.

### Serializer Design

The serializer takes a data structure (the `DOMDocument`) and transforms
it into a character representation -- an HTML5 document.

The serializer is broken into three parts:

- The `OutputRules` contain the rules to turn DOM elements into strings. The
rules are an implementation of the interface `RulesInterface` allowing for
different rule sets to be used.
- The `Traverser`, which is a special-purpose tree walker. It visits
each node node in the tree and uses the `OutputRules` to transform the node
into a string.
- `HTML5` manages the `Traverser` and stores the resultant data
in the correct place.

The serializer (`save()`, `saveHTML()`) follows the
[section 8.9 of the HTML 5.0 spec](http://www.w3.org/TR/2012/CR-html5-20121217/syntax.html#serializing-html-fragments).
So tags are serialized according to these rules:

- A tag with children: &lt;foo&gt;CHILDREN&lt;/foo&gt;
- A tag that cannot have content: &lt;foo&gt; (no closing tag)
- A tag that could have content, but doesn't: &lt;foo&gt;&lt;/foo&gt;

## Known Issues (Or, Things We Designed Against the Spec)

Please check the issue queue for a full list, but the following are
issues known issues that are not presently on the roadmap:

- Namespaces: HTML5 only [supports a selected list of namespaces](http://www.w3.org/TR/html5/infrastructure.html#namespaces)
  and they do not operate in the same way as XML namespaces. A `:` has no special
  meaning.
  By default the parser does not support XML style namespaces via `:`;
  to enable the XML namespaces see the  [XML Namespaces section](#xml-namespaces)
- Scripts: This parser does not contain a JavaScript or a CSS
  interpreter. While one may be supplied, not all features will be
  supported.
- Rentrance: The current parser is not re-entrant. (Thus you can't pause
  the parser to modify the HTML string mid-parse.)
- Validation: The current tree builder is **not** a validating parser.
  While it will correct some HTML, it does not check that the HTML
  conforms to the standard. (Should you wish, you can build a validating
  parser by extending DOMTree or building your own EventHandler
  implementation.)
  * There is limited support for insertion modes.
  * Some autocorrection is done automatically.
  * Per the spec, many legacy tags are admitted and correctly handled,
    even though they are technically not part of HTML5.
- Attribute names and values: Due to the implementation details of the
  PHP implementation of DOM, attribute names that do not follow the
  XML 1.0 standard are not inserted into the DOM. (Effectively, they
  are ignored.) If you've got a clever fix for this, jump in!
- Processor Instructions: The HTML5 spec does not allow processor
  instructions. We do. Since this is a server-side library, we think
  this is useful. And that means, dear reader, that in some cases you
  can parse the HTML from a mixed PHP/HTML document. This, however,
  is an incidental feature, not a core feature.
- HTML manifests: Unsupported.
- PLAINTEXT: Unsupported.
- Adoption Agency Algorithm: Not yet implemented. (8.2.5.4.7)

## XML Namespaces

To use XML style namespaces you have to configure well the main `HTML5` instance.

```php
use Masterminds\HTML5;
$html = new HTML5(array(
    "xmlNamespaces" => true
));

$dom = $html->loadHTML('<t:tag xmlns:t="http://www.example.com"/>');

$dom->documentElement->namespaceURI; // http://www.example.com

```

You can also add some default prefixes that will not require the namespace declaration,
but its elements will be namespaced.

```php
use Masterminds\HTML5;
$html = new HTML5(array(
    "implicitNamespaces"=>array(
        "t"=>"http://www.example.com"
    )
));

$dom = $html->loadHTML('<t:tag/>');

$dom->documentElement->namespaceURI; // http://www.example.com

```

## Thanks to...

The dedicated (and patient) contributors of patches small and large,
who have already made this library better.See the CREDITS file for
a list of contributors.

We owe a huge debt of gratitude to the original authors of html5lib.

While not much of the original parser remains, we learned a lot from
reading the html5lib library. And some pieces remain here. In
particular, much of the UTF-8 and Unicode handling is derived from the
html5lib project.

## License

This software is released under the MIT license. The original html5lib
library was also released under the MIT license.

See LICENSE.txt

Certain files contain copyright assertions by specific individuals
involved with html5lib. Those have been retained where appropriate.
PK�c�\�&Q��2roundcube/plugin-installer/src/PluginInstaller.phpnu�[���<?php

namespace Roundcube\Composer;

class PluginInstaller extends ExtensionInstaller
{
    protected $composer_type = 'roundcube-plugin';

    #[\Override]
    public function getVendorDir()
    {
        return $this->getRoundcubemailInstallPath() . \DIRECTORY_SEPARATOR . 'plugins';
    }

    #[\Override]
    protected function confirmInstall($package_name)
    {
        $config = $this->composer->getConfig()->get('roundcube');

        if (isset($config['enable-plugin'])) {
            $answer = $config['enable-plugin'];
        } else {
            $answer = $this->io->askConfirmation("Do you want to activate the plugin {$package_name}? [Y|n] ", true);
        }

        return $answer;
    }

    #[\Override]
    protected function getConfig($package_name, $config, $add)
    {
        $cur_config = !empty($config['plugins'])
            ? ((array) $config['plugins'])
            : [];
        $new_config = $cur_config;

        if ($add && !in_array($package_name, $new_config, true)) {
            $new_config[] = $package_name;
        } elseif (!$add && ($i = array_search($package_name, $new_config, true)) !== false) {
            unset($new_config[$i]);
        }

        if ($new_config !== $cur_config) {
            $config_val = count($new_config) > 0
                ? "[\n\t'" . implode("',\n\t'", $new_config) . "',\n];"
                : '[];';
            $result = ['plugins', $config_val];
        } else {
            $result = false;
        }

        return $result;
    }
}
PK�c�\Kp��JJEroundcube/plugin-installer/src/Roundcube/Composer/PluginInstaller.phpnu�[���<?php

namespace Roundcube\Composer;

/**
 * @category Plugins
 * @package  PluginInstaller
 * @author   Philip Weir <roundcube@tehinterweb.co.uk>
 * @license  GPL-3.0+
 * @version  GIT: <git_id>
 * @link     http://github.com/roundcube/plugin-installer
 */
class PluginInstaller extends ExtensionInstaller
{
    protected $composer_type = 'roundcube-plugin';

    public function getVendorDir()
    {
        return getcwd() . DIRECTORY_SEPARATOR . 'plugins';
    }

    protected function confirmInstall($package_name)
    {
        $config = $this->composer->getConfig()->get('roundcube');

        if (!empty($config['enable-plugin'])) {
            $answer = $config['enable-plugin'];
        }
        else {
            $answer = $this->io->askConfirmation("Do you want to activate the plugin $package_name? [Y|n] ", true);
        }

        return $answer;
    }

    protected function getConfig($package_name, $config, $add )
    {
        $cur_config = !empty($config['plugins']) ? ((array) $config['plugins']) : [];
        $new_config = $cur_config;

        if ($add && !in_array($package_name, $new_config)) {
            $new_config[] = $package_name;
        }
        elseif (!$add && ($i = array_search($package_name, $new_config)) !== false) {
            unset($new_config[$i]);
        }

        if ($new_config != $cur_config) {
            $config_val = count($new_config) > 0 ? "[\n\t'" . join("',\n\t'", $new_config) . "',\n];" : "[];";
            $result = ['plugins', $config_val];
        }
        else {
            $result = false;
        }

        return $result;
    }
}
PK�c�\�����Hroundcube/plugin-installer/src/Roundcube/Composer/RoundcubeInstaller.phpnu�[���<?php

namespace Roundcube\Composer;

use Composer\Composer;
use Composer\IO\IOInterface;
use Composer\Plugin\PluginInterface;

class RoundcubeInstaller implements PluginInterface
{
    private $extentions = ['\Roundcube\Composer\PluginInstaller', '\Roundcube\Composer\SkinInstaller'];
    private $installers = [];

    public function activate(Composer $composer, IOInterface $io)
    {
        foreach ($this->extentions as $extension) {
            $installer = new $extension($io, $composer);
            $composer->getInstallationManager()->addInstaller($installer);
            $this->installers[] = $installer;
        }
    }

    public function deactivate(Composer $composer, IOInterface $io)
    {
        foreach ($this->installers as $installer) {
            $composer->getInstallationManager()->removeInstaller($installer);
        }
    }

    public function uninstall(Composer $composer, IOInterface $io)
    {
    }
}
PK�c�\�ULs�<�<Hroundcube/plugin-installer/src/Roundcube/Composer/ExtensionInstaller.phpnu�[���<?php

namespace Roundcube\Composer;

use Composer\Installer\LibraryInstaller;
use Composer\Package\Version\VersionParser;
use Composer\Package\PackageInterface;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Util\ProcessExecutor;
use Composer\Util\Filesystem;
use React\Promise\PromiseInterface;

/**
 * @category Plugins
 * @package  PluginInstaller
 * @author   Till Klampaeckel <till@php.net>
 * @author   Thomas Bruederli <thomas@roundcube.net>
 * @author   Philip Weir <roundcube@tehinterweb.co.uk>
 * @license  GPL-3.0+
 * @version  GIT: <git_id>
 * @link     http://github.com/roundcube/plugin-installer
 */
class ExtensionInstaller extends LibraryInstaller
{
    protected $composer_type;

    /**
     * {@inheritDoc}
     */
    public function getInstallPath(PackageInterface $package)
    {
        static $vendorDir;
        if ($vendorDir === null) {
            $vendorDir = $this->getVendorDir();
        }

        return sprintf('%s/%s', $vendorDir, $this->getPackageName($package));
    }

    /**
     * {@inheritDoc}
     */
    public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        // initialize Roundcube environment
        if (!defined('INSTALL_PATH')) {
            define('INSTALL_PATH', getcwd() . '/');
        }
        include_once(INSTALL_PATH . 'program/include/clisetup.php');

        $this->rcubeVersionCheck($package);

        $self = $this;
        $postInstall = function() use ($self, $package) {
            $config_file  = $self->rcubeConfigFile();
            $package_name = $self->getPackageName($package);
            $package_dir  = $self->getVendorDir() . DIRECTORY_SEPARATOR . $package_name;
            $extra        = $package->getExtra();

            if (is_writeable($config_file) && php_sapi_name() == 'cli' && $this->confirmInstall($package_name)) {
                $self->rcubeAlterConfig($package_name);
            }

            // copy config.inc.php.dist -> config.inc.php
            if (is_file($package_dir . DIRECTORY_SEPARATOR . 'config.inc.php.dist')) {
                $config_exists   = false;
                $alt_config_file = $self->rcubeConfigFile($package_name . '.inc.php');

                if (is_file($package_dir . DIRECTORY_SEPARATOR . 'config.inc.php')) {
                    $config_exists = true;
                }
                elseif (is_file($alt_config_file)) {
                    $config_exists = true;
                }

                if (!$config_exists && is_writeable($package_dir)) {
                    $self->io->write("<info>Creating package config file</info>");
                    copy($package_dir . DIRECTORY_SEPARATOR . 'config.inc.php.dist', $package_dir . DIRECTORY_SEPARATOR . 'config.inc.php');
                }
            }

            // initialize database schema
            if (!empty($extra['roundcube']['sql-dir'])) {
                if ($sqldir = realpath($package_dir . DIRECTORY_SEPARATOR . $extra['roundcube']['sql-dir'])) {
                    $self->io->write("<info>Running database initialization script for $package_name</info>");

                    $roundcube_version = self::versionNormalize(RCMAIL_VERSION);
                    if (self::versionCompare($roundcube_version, '1.2.0', '>=')) {
                        \rcmail_utils::db_init($sqldir);
                    }
                    else {
                        throw new \Exception("Database initialization failed. Roundcube 1.2.0 or above required.");
                    }
                }
            }

            // run post-install script
            if (!empty($extra['roundcube']['post-install-script'])) {
                $self->rcubeRunScript($extra['roundcube']['post-install-script'], $package);
            }
        };

        $promise = parent::install($repo, $package);

        // Composer v2 might return a promise here
        if ($promise instanceof PromiseInterface) {
            return $promise->then($postInstall);
        }

        // If not, execute the code right away (composer v1, or v2 without async)
        $postInstall();
    }

    /**
     * {@inheritDoc}
     */
    public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
    {
        // initialize Roundcube environment
        if (!defined('INSTALL_PATH')) {
            define('INSTALL_PATH', getcwd() . '/');
        }
        include_once(INSTALL_PATH . 'program/include/clisetup.php');

        $this->rcubeVersionCheck($target);

        $self  = $this;
        $extra = $target->getExtra();
        $fs    = new Filesystem();

        // backup persistent files e.g. config.inc.php
        $package_name     = $self->getPackageName($initial);
        $package_dir      = $self->getVendorDir() . DIRECTORY_SEPARATOR . $package_name;
        $temp_dir         = $package_dir . '-' . sprintf('%010d%010d', mt_rand(), mt_rand());

        // make a backup of existing files (for restoring persistent files)
        $fs->copy($package_dir, $temp_dir);

        $postUpdate = function() use ($self, $target, $extra, $fs, $temp_dir) {
            $package_name = $self->getPackageName($target);
            $package_dir  = $self->getVendorDir() . DIRECTORY_SEPARATOR . $package_name;

            // restore persistent files
            $persistent_files = !empty($extra['roundcube']['persistent-files']) ? $extra['roundcube']['persistent-files'] : ['config.inc.php'];
            foreach ($persistent_files as $file) {
                $path = $temp_dir . DIRECTORY_SEPARATOR . $file;
                if (is_readable($path)) {
                    if ($fs->copy($path, $package_dir . DIRECTORY_SEPARATOR . $file)) {
                        $self->io->write("<info>Restored $package_name/$file</info>");
                    }
                    else {
                        throw new \Exception("Restoring " . $file . " failed.");
                    }
                }
            }
            // remove backup folder
            $fs->remove($temp_dir);

            // update database schema
            if (!empty($extra['roundcube']['sql-dir'])) {
                if ($sqldir = realpath($package_dir . DIRECTORY_SEPARATOR . $extra['roundcube']['sql-dir'])) {
                    $self->io->write("<info>Updating database schema for $package_name</info>");

                    $roundcube_version = self::versionNormalize(RCMAIL_VERSION);
                    if (self::versionCompare($roundcube_version, '1.2.0', '>=')) {
                        \rcmail_utils::db_update($sqldir, $package_name);
                    }
                    else {
                        throw new \Exception("Database update failed. Roundcube 1.2.0 or above required.");
                    }
                }
            }

            // run post-update script
            if (!empty($extra['roundcube']['post-update-script'])) {
                $self->rcubeRunScript($extra['roundcube']['post-update-script'], $target);
            }
        };

        $promise = parent::update($repo, $initial, $target);

        // Composer v2 might return a promise here
        if ($promise instanceof PromiseInterface) {
            return $promise->then($postUpdate);
        }

        // If not, execute the code right away (composer v1, or v2 without async)
        $postUpdate();
    }

    /**
     * {@inheritDoc}
     */
    public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        // initialize Roundcube environment
        if (!defined('INSTALL_PATH')) {
            define('INSTALL_PATH', getcwd() . '/');
        }
        include_once(INSTALL_PATH . 'program/include/clisetup.php');

        $self   = $this;
        $config = $self->composer->getConfig()->get('roundcube');

        $postUninstall = function() use ($self, $package, $config) {
            // post-uninstall: deactivate package
            $package_name = $self->getPackageName($package);
            $package_dir  = $self->getVendorDir() . DIRECTORY_SEPARATOR . $package_name;

            $self->rcubeAlterConfig($package_name, false);

            // run post-uninstall script
            $extra = $package->getExtra();
            if (!empty($extra['roundcube']['post-uninstall-script'])) {
                $self->rcubeRunScript($extra['roundcube']['post-uninstall-script'], $package);
            }

            // remove package folder
            if (!empty($config['uninstall-remove-folder'])) {
                $fs = new Filesystem();
                $fs->remove($package_dir);
                $self->io->write("<info>Removed $package_name files</info>");
            }
        };

        $promise = parent::uninstall($repo, $package);

        // Composer v2 might return a promise here
        if ($promise instanceof PromiseInterface) {
            return $promise->then($postUninstall);
        }

        // If not, execute the code right away (composer v1, or v2 without async)
        $postUninstall();
    }

    /**
     * {@inheritDoc}
     */
    public function supports($packageType)
    {
        return $packageType === $this->composer_type;
    }

    /**
     * Setup vendor directory to one of these two:
     *
     * @return string
     */
    public function getVendorDir()
    {
        return getcwd();
    }

    /**
     * Extract the (valid) package name from the package object
     */
    protected function getPackageName(PackageInterface $package)
    {
        @list($vendor, $packageName) = explode('/', $package->getPrettyName());

        return strtr($packageName, '-', '_');
    }

    /**
     * Check version requirements from the "extra" block of a package
     * against the local Roundcube version
     */
    private function rcubeVersionCheck($package)
    {
        // read rcube version from iniset
        $rcubeVersion = self::versionNormalize(RCMAIL_VERSION);

        if (empty($rcubeVersion)) {
            throw new \Exception("Unable to find a Roundcube installation in $rootdir");
        }

        $extra = $package->getExtra();

        if (!empty($extra['roundcube'])) {
            foreach (['min-version' => '>=', 'max-version' => '<='] as $key => $operator) {
                if (!empty($extra['roundcube'][$key])) {
                    $version = self::versionNormalize($extra['roundcube'][$key]);
                    if (!self::versionCompare($rcubeVersion, $version, $operator)) {
                        throw new \Exception("Version check failed! " . $package->getName() . " requires Roundcube version $operator $version, $rcubeVersion was detected.");
                    }
                }
            }
        }
    }

    /**
     * Add or remove the given package to the Roundcube config.
     */
    private function rcubeAlterConfig($package_name, $add = true)
    {
        $config_file = $this->rcubeConfigFile();
        @include($config_file);
        $success = false;
        $varname = '$config';

        if (empty($config) && !empty($rcmail_config)) {
            $config  = $rcmail_config;
            $varname = '$rcmail_config';
        }

        if (!empty($config) && is_writeable($config_file)) {
            $config_template = @file_get_contents($config_file) ?: '';

            if ($config = $this->getConfig($package_name, $config, $add)) {
                list($config_name, $config_val) = $config;
                $count = 0;

                if (empty($config_val)) {
                    $new_config = preg_replace(
                        "/(\\$varname\['$config_name'\])\s+=\s+(.+);/Uims",
                        "",
                        $config_template, -1, $count);
                }
                else {
                    $new_config = preg_replace(
                        "/(\\$varname\['$config_name'\])\s+=\s+(.+);/Uims",
                        "\\1 = " . $config_val,
                        $config_template, -1, $count);
                }

                // config option does not exist yet, add it...
                if (!$count) {
                    $var_txt    = "\n{$varname}['$config_name'] = $config_val\n";
                    $new_config = str_replace('?>', $var_txt . '?>', $config_template, $count);

                    if (!$count) {
                        $new_config = $config_template . $var_txt;
                    }
                }

                $success = file_put_contents($config_file, $new_config);
            }
        }

        if ($success && php_sapi_name() == 'cli') {
            $this->io->write("<info>Updated local config at $config_file</info>");
        }

        return $success;
    }

    /**
    * Ask the user to confirm installation
    */
    protected function confirmInstall($package_name)
    {
        return false;
    }

    /**
    * Generate Roundcube config entry
    */
    protected function getConfig($package_name, $cur_config, $add)
    {
        return false;
    }

    /**
     * Helper method to get an absolute path to the local Roundcube config file
     */
    private function rcubeConfigFile($file = 'config.inc.php')
    {
        $config = new \rcube_config();
        $paths  = $config->resolve_paths($file);
        $path   = getcwd() . '/config/' . $file;

        foreach ($paths as $fpath) {
            if ($fpath && is_file($fpath) && is_readable($fpath)) {
                $path = $fpath;
                break;
            }
        }

        return realpath($path);
    }

    /**
     * Run the given script file
     */
    private function rcubeRunScript($script, PackageInterface $package)
    {
        $package_name = $this->getPackageName($package);
        $package_type = $package->getType();
        $package_dir  = $this->getVendorDir() . DIRECTORY_SEPARATOR . $package_name;

        // check for executable shell script
        if (($scriptfile = realpath($package_dir . DIRECTORY_SEPARATOR . $script)) && is_executable($scriptfile)) {
            $script = $scriptfile;
        }

        // run PHP script in Roundcube context
        if ($scriptfile && preg_match('/\.php$/', $scriptfile)) {
            $incdir = realpath(getcwd() . '/program/include');
            include_once($incdir . '/iniset.php');
            include($scriptfile);
        }
        // attempt to execute the given string as shell commands
        else {
            $process = new ProcessExecutor($this->io);
            $exitCode = $process->execute($script, $output, $package_dir);
            if ($exitCode !== 0) {
                throw new \RuntimeException('Error executing script: '. $process->getErrorOutput(), $exitCode);
            }
        }
    }

    /**
     * normalize Roundcube version string
     */
    private static function versionNormalize($version)
    {
        $parser = new VersionParser;

        return $parser->normalize(str_replace('-git', '.999', $version));
    }

    /**
     * version_compare() wrapper, originally from composer/semver
     */
    private static function versionCompare($a, $b, $operator, $compareBranches = false)
    {
        $aIsBranch = 'dev-' === substr($a, 0, 4);
        $bIsBranch = 'dev-' === substr($b, 0, 4);

        if ($aIsBranch && $bIsBranch) {
            return $operator === '==' && $a === $b;
        }

        // when branches are not comparable, we make sure dev branches never match anything
        if (!$compareBranches && ($aIsBranch || $bIsBranch)) {
            return false;
        }

        return version_compare($a, $b, $operator);
    }
}
PK�c�\9���Croundcube/plugin-installer/src/Roundcube/Composer/SkinInstaller.phpnu�[���<?php

namespace Roundcube\Composer;

/**
 * @category Plugins
 * @package  PluginInstaller
 * @author   Philip Weir <roundcube@tehinterweb.co.uk>
 * @license  GPL-3.0+
 * @version  GIT: <git_id>
 * @link     http://github.com/roundcube/plugin-installer
 */
class SkinInstaller extends ExtensionInstaller
{
    protected $composer_type = 'roundcube-skin';

    public function getVendorDir()
    {
        return getcwd() . DIRECTORY_SEPARATOR . 'skins';
    }

    protected function confirmInstall($package_name)
    {
        $config = $this->composer->getConfig()->get('roundcube');

        if (!empty($config['enable-skin'])) {
            $answer = $config['enable-skin'];
        }
        else {
            $answer = $this->io->askConfirmation("Do you want to activate the skin $package_name? [Y|n] ", true);
        }

        return $answer;
    }

    protected function getConfig($package_name, $config, $add)
    {
        $cur_config = !empty($config['skin']) ? $config['skin'] : null;
        $new_config = $cur_config;

        if ($add && $new_config != $package_name) {
            $new_config = $package_name;
        }
        elseif (!$add && $new_config == $package_name) {
            $new_config = null;
        }

        if ($new_config != $cur_config) {
            $config_val = !empty($new_config) ? "'$new_config';" : null;
            $result = ['skin', $config_val];
        }
        else {
            $result = false;
        }

        return $result;
    }
}
PK�c�\o
^��5roundcube/plugin-installer/src/RoundcubeInstaller.phpnu�[���<?php

namespace Roundcube\Composer;

use Composer\Composer;
use Composer\IO\IOInterface;
use Composer\Plugin\PluginInterface;

class RoundcubeInstaller implements PluginInterface
{
    private $extentions = [PluginInstaller::class, SkinInstaller::class];
    private $installers = [];

    #[\Override]
    public function activate(Composer $composer, IOInterface $io)
    {
        foreach ($this->extentions as $extension) {
            $installer = new $extension($io, $composer);
            $composer->getInstallationManager()->addInstaller($installer);
            $this->installers[] = $installer;
        }
    }

    #[\Override]
    public function deactivate(Composer $composer, IOInterface $io)
    {
        foreach ($this->installers as $installer) {
            $composer->getInstallationManager()->removeInstaller($installer);
        }
    }

    #[\Override]
    public function uninstall(Composer $composer, IOInterface $io) {}
}
PK�c�\�@;�9B9B5roundcube/plugin-installer/src/ExtensionInstaller.phpnu�[���<?php

namespace Roundcube\Composer;

use Composer\Installer\InstallationManager;
use Composer\Installer\LibraryInstaller;
use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionParser;
use Composer\Repository\InstalledRepository;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Repository\RootPackageRepository;
use Composer\Util\Filesystem;
use Composer\Util\ProcessExecutor;
use React\Promise\PromiseInterface;

abstract class ExtensionInstaller extends LibraryInstaller
{
    /** @var string|null */
    private $roundcubemailInstallPath;

    /** @var string */
    protected $composer_type;

    protected function setRoundcubemailInstallPath(InstalledRepositoryInterface $installedRepo): void
    {
        // https://github.com/composer/composer/discussions/11927#discussioncomment-9116893
        $rootPackage = clone $this->composer->getPackage();
        $installedRepo = new InstalledRepository([
            $installedRepo,
            new RootPackageRepository($rootPackage),
        ]);

        $roundcubemailPackages = $installedRepo->findPackagesWithReplacersAndProviders('roundcube/roundcubemail');
        assert(count($roundcubemailPackages) === 1);
        $roundcubemailPackage = $roundcubemailPackages[0];

        if ($roundcubemailPackage === $rootPackage) { // $this->getInstallPath($package) does not work for root package
            $this->initializeVendorDir();
            $installPath = dirname($this->vendorDir);
        } else {
            $installPath = $this->getInstallPath($roundcubemailPackage);
        }

        if ($this->roundcubemailInstallPath === null) {
            $this->roundcubemailInstallPath = $installPath;
        } elseif ($this->roundcubemailInstallPath !== $installPath) {
            throw new \Exception('Install path of "roundcube/roundcubemail" package has unexpectedly changed');
        }
    }

    protected function getRoundcubemailInstallPath(): string
    {
        // install path is not set at composer download phase
        // never assume any path, but for this known composer behaviour get it from backtrace instead
        if ($this->roundcubemailInstallPath === null) {
            $backtrace = debug_backtrace();
            foreach ($backtrace as $frame) {
                // relies on https://github.com/composer/composer/blob/2.7.4/src/Composer/Installer/InstallationManager.php#L243
                if (($frame['object'] ?? null) instanceof InstallationManager
                    && $frame['function'] === 'downloadAndExecuteBatch'
                ) {
                    $this->setRoundcubemailInstallPath($frame['args'][0]);
                }
            }
        }

        return $this->roundcubemailInstallPath;
    }

    #[\Override]
    public function getInstallPath(PackageInterface $package)
    {
        if (!$this->supports($package->getType())) {
            return parent::getInstallPath($package);
        }

        $vendorDir = $this->getVendorDir();

        return $vendorDir . \DIRECTORY_SEPARATOR
            . str_replace('/', \DIRECTORY_SEPARATOR, $this->getPackageName($package));
    }

    private function initializeRoundcubemailEnvironment(): void
    {
        if (!defined('INSTALL_PATH')) {
            define('INSTALL_PATH', $this->getRoundcubemailInstallPath() . '/');
        }
        require_once INSTALL_PATH . 'program/include/iniset.php';
    }

    #[\Override]
    public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        $this->setRoundcubemailInstallPath($repo);

        return parent::isInstalled($repo, $package);
    }

    #[\Override]
    public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        $this->setRoundcubemailInstallPath($repo);
        $this->initializeRoundcubemailEnvironment();
        $this->rcubeVersionCheck($package);

        $postInstall = function () use ($package) {
            $config_file = $this->rcubeConfigFile();
            $package_name = $this->getPackageName($package);
            $package_dir = $this->getInstallPath($package);
            $extra = $package->getExtra();

            if (is_writable($config_file) && \PHP_SAPI === 'cli' && $this->confirmInstall($package_name)) {
                $this->rcubeAlterConfig($package_name);
            }

            // copy config.inc.php.dist -> config.inc.php
            if (is_file($package_dir . \DIRECTORY_SEPARATOR . 'config.inc.php.dist')) {
                $config_exists = false;
                $alt_config_file = $this->rcubeConfigFile($package_name . '.inc.php');

                if (is_file($package_dir . \DIRECTORY_SEPARATOR . 'config.inc.php')) {
                    $config_exists = true;
                } elseif (is_file($alt_config_file)) {
                    $config_exists = true;
                }

                if (!$config_exists && is_writable($package_dir)) {
                    $this->io->write('<info>Creating package config file</info>');
                    copy($package_dir . \DIRECTORY_SEPARATOR . 'config.inc.php.dist', $package_dir . \DIRECTORY_SEPARATOR . 'config.inc.php');
                }
            }

            // initialize database schema
            if (!empty($extra['roundcube']['sql-dir'])) {
                if ($sqldir = realpath($package_dir . \DIRECTORY_SEPARATOR . $extra['roundcube']['sql-dir'])) {
                    $this->io->write("<info>Running database initialization script for {$package_name}</info>");

                    \rcmail_utils::db_init($sqldir);
                }
            }

            // run post-install script
            if (!empty($extra['roundcube']['post-install-script'])) {
                $this->rcubeRunScript($extra['roundcube']['post-install-script'], $package);
            }
        };

        $promise = parent::install($repo, $package);

        // Composer v2 might return a promise here
        if ($promise instanceof PromiseInterface) {
            return $promise->then($postInstall);
        }

        // If not, execute the code right away (composer v1, or v2 without async)
        $postInstall();

        return null;
    }

    #[\Override]
    public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
    {
        $this->setRoundcubemailInstallPath($repo);
        $this->initializeRoundcubemailEnvironment();
        $this->rcubeVersionCheck($target);

        $extra = $target->getExtra();
        $fs = new Filesystem();

        // backup persistent files e.g. config.inc.php
        $package_dir = $this->getInstallPath($initial);
        $temp_dir = $package_dir . '-' . sprintf('%010d%010d', mt_rand(), mt_rand());

        // make a backup of existing files (for restoring persistent files)
        $fs->copy($package_dir, $temp_dir);

        $postUpdate = function () use ($target, $extra, $fs, $temp_dir) {
            $package_name = $this->getPackageName($target);
            $package_dir = $this->getInstallPath($target);

            // restore persistent files
            $persistent_files = !empty($extra['roundcube']['persistent-files'])
                ? $extra['roundcube']['persistent-files']
                : ['config.inc.php'];
            foreach ($persistent_files as $file) {
                $path = $temp_dir . \DIRECTORY_SEPARATOR . $file;
                if (is_readable($path)) {
                    if ($fs->copy($path, $package_dir . \DIRECTORY_SEPARATOR . $file)) {
                        $this->io->write("<info>Restored {$package_name}/{$file}</info>");
                    } else {
                        throw new \Exception('Restoring ' . $file . ' failed.');
                    }
                }
            }
            // remove backup folder
            $fs->remove($temp_dir);

            // update database schema
            if (!empty($extra['roundcube']['sql-dir'])) {
                if ($sqldir = realpath($package_dir . \DIRECTORY_SEPARATOR . $extra['roundcube']['sql-dir'])) {
                    $this->io->write("<info>Updating database schema for {$package_name}</info>");

                    \rcmail_utils::db_update($sqldir, $package_name);
                }
            }

            // run post-update script
            if (!empty($extra['roundcube']['post-update-script'])) {
                $this->rcubeRunScript($extra['roundcube']['post-update-script'], $target);
            }
        };

        $promise = parent::update($repo, $initial, $target);

        // Composer v2 might return a promise here
        if ($promise instanceof PromiseInterface) {
            return $promise->then($postUpdate);
        }

        // If not, execute the code right away (composer v1, or v2 without async)
        $postUpdate();

        return null;
    }

    #[\Override]
    public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        $this->setRoundcubemailInstallPath($repo);
        $this->initializeRoundcubemailEnvironment();

        $config = $this->composer->getConfig()->get('roundcube');

        $postUninstall = function () use ($package, $config) {
            // post-uninstall: deactivate package
            $package_name = $this->getPackageName($package);
            $package_dir = $this->getInstallPath($package);

            $this->rcubeAlterConfig($package_name, false);

            // run post-uninstall script
            $extra = $package->getExtra();
            if (!empty($extra['roundcube']['post-uninstall-script'])) {
                $this->rcubeRunScript($extra['roundcube']['post-uninstall-script'], $package);
            }

            // remove package folder
            if (!empty($config['uninstall-remove-folder'])) {
                $fs = new Filesystem();
                $fs->remove($package_dir);
                $this->io->write("<info>Removed {$package_name} files</info>");
            }
        };

        $promise = parent::uninstall($repo, $package);

        // Composer v2 might return a promise here
        if ($promise instanceof PromiseInterface) {
            return $promise->then($postUninstall);
        }

        // If not, execute the code right away (composer v1, or v2 without async)
        $postUninstall();

        return null;
    }

    #[\Override]
    public function supports($packageType)
    {
        return $packageType === $this->composer_type;
    }

    /**
     * Setup vendor directory to one of these two:
     *
     * @return string
     */
    abstract public function getVendorDir();

    /**
     * Extract the (valid) package name from the package object.
     */
    protected function getPackageName(PackageInterface $package)
    {
        @[$vendor, $packageName] = explode('/', $package->getPrettyName());

        return strtr($packageName, '-', '_');
    }

    /**
     * Check version requirements from the "extra" block of a package
     * against the local Roundcube version.
     */
    private function rcubeVersionCheck($package)
    {
        // read rcube version from iniset
        $rcubeVersion = self::versionNormalize(RCMAIL_VERSION);

        $extra = $package->getExtra();

        if (!empty($extra['roundcube'])) {
            foreach (['min-version' => '>=', 'max-version' => '<='] as $key => $operator) {
                if (!empty($extra['roundcube'][$key])) {
                    $version = self::versionNormalize($extra['roundcube'][$key]);
                    if (!self::versionCompare($rcubeVersion, $version, $operator)) {
                        throw new \Exception('Version check failed! ' . $package->getName() . " requires Roundcube version {$operator} {$version}, {$rcubeVersion} was detected.");
                    }
                }
            }
        }
    }

    /**
     * Add or remove the given package to the Roundcube config.
     */
    private function rcubeAlterConfig($package_name, $add = true)
    {
        $config_file = $this->rcubeConfigFile();
        @include $config_file;
        $success = false;
        $varname = '$config';

        if (empty($config) && !empty($rcmail_config)) {
            $config = $rcmail_config;
            $varname = '$rcmail_config';
        }

        if (!empty($config) && is_writable($config_file)) {
            $config_template = @file_get_contents($config_file);
            if ($config_template === false) {
                $config_template = '';
            }

            if ($config = $this->getConfig($package_name, $config, $add)) {
                [$config_name, $config_val] = $config;
                $count = 0;

                if (empty($config_val)) {
                    $new_config = preg_replace(
                        "/(\\{$varname}\\['{$config_name}'\\])\\s+=\\s+(.+);/Uims",
                        '',
                        $config_template,
                        -1,
                        $count
                    );
                } else {
                    $new_config = preg_replace(
                        "/(\\{$varname}\\['{$config_name}'\\])\\s+=\\s+(.+);/Uims",
                        '\\1 = ' . $config_val,
                        $config_template,
                        -1,
                        $count
                    );
                }

                // config option does not exist yet, add it...
                if (!$count) {
                    $var_txt = "\n{$varname}['{$config_name}'] = {$config_val}\n";
                    $new_config = str_replace('?>', $var_txt . '?>', $config_template, $count);

                    if (!$count) {
                        $new_config = $config_template . $var_txt;
                    }
                }

                $success = file_put_contents($config_file, $new_config);
            }
        }

        if ($success && \PHP_SAPI === 'cli') {
            $this->io->write("<info>Updated local config at {$config_file}</info>");
        }

        return $success;
    }

    /**
     * Ask the user to confirm installation.
     */
    protected function confirmInstall($package_name)
    {
        return false;
    }

    /**
     * Generate Roundcube config entry.
     */
    protected function getConfig($package_name, $cur_config, $add)
    {
        return false;
    }

    /**
     * Helper method to get an absolute path to the local Roundcube config file.
     */
    private function rcubeConfigFile($file = 'config.inc.php')
    {
        $config = new \rcube_config();
        $paths = $config->resolve_paths($file);
        $path = $this->getRoundcubemailInstallPath() . '/config/' . $file;

        foreach ($paths as $fpath) {
            if ($fpath && is_file($fpath) && is_readable($fpath)) {
                $path = $fpath;

                break;
            }
        }

        return realpath($path);
    }

    /**
     * Run the given script file.
     */
    private function rcubeRunScript($script, PackageInterface $package)
    {
        $package_name = $this->getPackageName($package);
        $package_type = $package->getType();
        $package_dir = $this->getInstallPath($package);

        // check for executable shell script
        if (($scriptfile = realpath($package_dir . \DIRECTORY_SEPARATOR . $script)) && is_executable($scriptfile)) {
            $script = $scriptfile;
        }

        // run PHP script in Roundcube context
        if ($scriptfile && preg_match('/\.php$/', $scriptfile)) {
            $incdir = realpath($this->getRoundcubemailInstallPath() . '/program/include');
            include_once $incdir . '/iniset.php';
            include $scriptfile;
        }
        // attempt to execute the given string as shell commands
        else {
            $process = new ProcessExecutor($this->io);
            $exitCode = $process->execute($script, $output, $package_dir);
            if ($exitCode !== 0) {
                throw new \RuntimeException('Error executing script: ' . $process->getErrorOutput(), $exitCode);
            }
        }
    }

    /**
     * Normalize Roundcube version string.
     */
    private static function versionNormalize(string $version): string
    {
        $parser = new VersionParser();

        return $parser->normalize(str_replace('-git', '.999', $version));
    }

    /**
     * version_compare() wrapper, originally from composer/semver.
     */
    private static function versionCompare($a, $b, $operator, $compareBranches = false)
    {
        $aIsBranch = substr($a, 0, 4) === 'dev-';
        $bIsBranch = substr($b, 0, 4) === 'dev-';

        if ($aIsBranch && $bIsBranch) {
            return $operator === '==' && $a === $b;
        }

        // when branches are not comparable, we make sure dev branches never match anything
        if (!$compareBranches && ($aIsBranch || $bIsBranch)) {
            return false;
        }

        return version_compare($a, $b, $operator);
    }
}
PK�c�\	]��tt0roundcube/plugin-installer/src/SkinInstaller.phpnu�[���<?php

namespace Roundcube\Composer;

class SkinInstaller extends ExtensionInstaller
{
    protected $composer_type = 'roundcube-skin';

    #[\Override]
    public function getVendorDir()
    {
        return $this->getRoundcubemailInstallPath() . \DIRECTORY_SEPARATOR . 'skins';
    }

    #[\Override]
    protected function confirmInstall($package_name)
    {
        $config = $this->composer->getConfig()->get('roundcube');

        if (isset($config['enable-skin'])) {
            $answer = $config['enable-skin'];
        } else {
            $answer = $this->io->askConfirmation("Do you want to activate the skin {$package_name}? [Y|n] ", true);
        }

        return $answer;
    }

    #[\Override]
    protected function getConfig($package_name, $config, $add)
    {
        $cur_config = !empty($config['skin'])
            ? $config['skin']
            : null;
        $new_config = $cur_config;

        if ($add && $new_config !== $package_name) {
            $new_config = $package_name;
        } elseif (!$add && $new_config === $package_name) {
            $new_config = null;
        }

        if ($new_config !== $cur_config) {
            $config_val = !empty($new_config)
                ? "'{$new_config}';"
                : null;
            $result = ['skin', $config_val];
        } else {
            $result = false;
        }

        return $result;
    }
}
PK�c�\�?�(roundcube/plugin-installer/composer.jsonnu�[���{
    "name": "roundcube/plugin-installer",
    "description": "A composer-installer for Roundcube plugins and skins.",
    "license": "GPL-3.0-or-later",
    "type": "composer-plugin",
    "authors": [
        {
            "name": "Thomas Bruederli",
            "email": "thomas@roundcube.net"
        },
        {
            "name": "Till Klampaeckel",
            "email": "till@php.net"
        },
        {
            "name": "Philip Weir",
            "email": "roundcube@tehinterweb.co.uk"
        }
    ],
    "require": {
        "php": ">=7.3 <8.4",
        "composer-plugin-api": "^2.1",
        "roundcube/roundcubemail": "*"
    },
    "require-dev": {
        "composer/composer": "^2.1",
        "ergebnis/composer-normalize": "^2.13",
        "friendsofphp/php-cs-fixer": "^3.0",
        "phpstan/extension-installer": "^1.1",
        "phpstan/phpstan": "^1.2",
        "phpstan/phpstan-deprecation-rules": "^1.0",
        "phpstan/phpstan-strict-rules": "^1.3"
    },
    "repositories": [
        {
            "type": "git",
            "url": "https://github.com/roundcube/roundcubemail.git"
        }
    ],
    "minimum-stability": "dev",
    "prefer-stable": true,
    "autoload": {
        "psr-4": {
            "Roundcube\\Composer\\": "src/"
        }
    },
    "config": {
        "allow-plugins": {
            "ergebnis/composer-normalize": true,
            "phpstan/extension-installer": true
        }
    },
    "extra": {
        "class": [
            "Roundcube\\Composer\\RoundcubeInstaller"
        ]
    }
}
PK�c�\�R(�%%1roundcube/plugin-installer/.php-cs-fixer.dist.phpnu�[���<?php

declare(strict_types=1);

use PhpCsFixer\Config;
use PhpCsFixer\Finder;

$finder = Finder::create()
    ->in([__DIR__])
    ->exclude(['vendor'])
    ->ignoreDotFiles(false);

return (new Config())
    ->setRiskyAllowed(true)
    ->setRules([
        '@PhpCsFixer' => true,
        '@PhpCsFixer:risky' => true,
        '@PHP74Migration' => true,
        '@PHP74Migration:risky' => true,

        // required by PSR-12
        'concat_space' => [
            'spacing' => 'one',
        ],

        // disable some too strict rules
        'phpdoc_types_order' => [
            'null_adjustment' => 'always_last',
            'sort_algorithm' => 'none',
        ],
        'single_line_throw' => false,
        'yoda_style' => [
            'equal' => false,
            'identical' => false,
        ],
        'native_constant_invocation' => true,
        'native_function_invocation' => false,
        'void_return' => false,
        'blank_line_before_statement' => [
            'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'exit'],
        ],
        'final_internal_class' => false,
        'combine_consecutive_issets' => false,
        'combine_consecutive_unsets' => false,
        'multiline_whitespace_before_semicolons' => false,
        'no_superfluous_elseif' => false,
        'ordered_class_elements' => false,
        'php_unit_internal_class' => false,
        'php_unit_test_class_requires_covers' => false,
        'phpdoc_add_missing_param_annotation' => false,
        'return_assignment' => false,
        'comment_to_phpdoc' => false,
        'general_phpdoc_annotation_remove' => [
            'annotations' => ['author', 'copyright', 'throws'],
        ],

        // fn => without curly brackets is less readable,
        // also prevent bounding of unwanted variables for GC
        'use_arrow_functions' => false,

        // disable too destructive formating for now
        'declare_strict_types' => false,
    ])
    ->setFinder($finder)
    ->setCacheFile(sys_get_temp_dir() . '/php-cs-fixer.' . md5(__DIR__) . '.cache');
PK�c�\�7����6roundcube/plugin-installer/test-composer/composer.jsonnu�[���{
    "name": "roundcube/plugin-installer-test",
    "require": {
        "roundcube/carddav": "^4 || ^5"
    },
    "repositories": [
        {
            "type": "git",
            "url": "https://github.com/roundcube/roundcubemail.git"
        },
        {
            "type": "path",
            "url": ".."
        }
    ],
    "minimum-stability": "dev",
    "prefer-stable": true,
    "config": {
        "allow-plugins": {
            "roundcube/plugin-installer": true
        }
    }
}
PK�c�\O��##,roundcube/plugin-installer/phpstan.neon.distnu�[���includes:
    - phar://phpstan.phar/conf/bleedingEdge.neon

parameters:
    level: 4
    checkMissingOverrideMethodAttribute: true
    paths:
        - .
    excludePaths:
        - vendor

    ignoreErrors:
        # relax strict rules
        - '~^Only booleans are allowed in .+, .+ given( on the (left|right) side)?\.~'
        - '~^Construct empty\(\) is not allowed\. Use more strict comparison\.~'

        -
            message: '~^Constant RCMAIL_VERSION not found\.$~'
            path: 'src/ExtensionInstaller.php'
            count: 1
PK�c�\���P�	�	$roundcube/plugin-installer/README.mdnu�[���# Plugin Installer for Roundcube

This installer ensures that plugins and skins end up in the correct directory:

 * Plugins - `<roundcube-root>/plugins/plugin-name`
 * Skins - `<roundcube-root>/skins/skin-name`

## Minimum setup

 * create a `composer.json` file in your plugin's repository
 * add the following contents

### sample composer.json for plugins

    {
        "name": "<your-vendor-name>/<plugin-name>",
        "type": "roundcube-plugin",
        "license": "GPL-3.0-or-later",
        "require": {
            "roundcube/plugin-installer": ">=0.3.0"
        }
    }

### sample composer.json for skins

    {
        "name": "<your-vendor-name>/<skin-name>",
        "type": "roundcube-skin",
        "license": "GPL-3.0-or-later",
        "require": {
            "roundcube/plugin-installer": ">=0.3.0"
        }
    }

## Roundcube specific composer.json params

For both plugins and skins you can, optionally, add the following section to your `composer.json` file. All properties are optional and provided below with example values.
`persistent-files` defines a list of files which should be maintained across updates. By default only `config.inc.php` is maintained. The array should contain paths relative to the root of your plugin.

    "extra": {
        "roundcube": {
            "min-version": "1.4.0",
            "sql-dir": "./SQL",
            "post-install-script": "./bin/install.sh",
            "post-update-script": "./bin/update.sh",
            "persistent-files": ["config.inc.php", "skins/elastic/_custom.less"]
        }
    }

## Configuration

This installer will ask if you want to enable each plugin or skin as it is installed. To always enable all plugins or skins add `enable-plugin`/`enable-skin` to the `config` section in the `composer.json` in the root of your Roundcube directory.
When uninstalling packages Composer will not remove the folder. To remove the folder set `uninstall-remove-folder` in your config.

    "config": {
        "roundcube": {
            "enable-plugin": true,
            "enable-skin": true,
            "uninstall-remove-folder": true
        }
    }

## Repository

Submit your plugin or skin to [Packagist](https://packagist.org/).

## Installation

 * clone Roundcube
 * `cp composer.json-dist composer.json`
 * add your plugin in the `require` section of composer.json
 * `composer.phar install`

Read the whole story at [plugins.roundcube.net](http://plugins.roundcube.net/#/about/).
PK�c�\��o�,�,'roundcube/rtf-html-php/src/Document.phpnu�[���<?php 

namespace RtfHtmlPhp;

class Document
{
    /** @var ?string Current character in an RTF stream */
    protected $char;
    /** @var string RTF string being parsed */
    protected $rtf;
    /** @var int Current position in RTF string */
    protected $pos;
    /** @var int Length of RTF string */
    protected $len;
    /** @var ?Group Current RTF group */
    protected $group;
    /** @var array */
    protected $uc = [];

    /** @var ?Group Root group */
    public $root = null;

    /**
     * Object contructor
     *
     * @param string $rtf The RTF content
     */
    public function __construct($rtf)
    {
        $this->parse($rtf);
    }

    /**
     * Position on the next character from the RTF stream.
     * Parsing is aborted when reading beyond end of input string.
     *
     * @return void
     */
    protected function getChar()
    {
        $this->char = null;

        if ($this->pos < strlen($this->rtf)) {
            $this->char = $this->rtf[$this->pos++];
        } else {
            $err = "Parse error: Tried to read past end of input; RTF is probably truncated.";
            throw new \Exception($err);
        }
    }

    /**
     * (Helper method) Is the current character a letter?
     *
     * @return bool
     */
    protected function isLetter()
    {
        if (ord($this->char) >= 65 && ord($this->char) <= 90) {
            return true;
        }

        if (ord($this->char) >= 97 && ord($this->char) <= 122) {
            return true;
        }

        return false;
    }

    /**
     * (Helper method) Is the current character a digit?
     *
     * @return bool
     */
    protected function isDigit()
    {
        return (ord($this->char) >= 48 && ord($this->char) <= 57);
    }

    /**
     * (Helper method) Is the current character end-of-line (EOL)?
     *
     * @return bool
     */
    protected function isEndOfLine()
    {
        if ($this->char == "\r" || $this->char == "\n") {
            // Checks for a Windows/Acron type EOL
            if ($this->rtf[$this->pos] == "\n" || $this->rtf[$this->pos] == "\r") {
                $this->getChar();
            }

            return true;
        }

        return false;
    }

    /**
     * (Helper method) Is the current character for a space delimiter?
     *
     * @return bool
     */
    protected function isSpaceDelimiter()
    {
        return ($this->char == " " || $this->isEndOfLine());
    }

    /**
     * Store state of document on stack.
     *
     * @return void
     */
    protected function parseStartGroup()
    {
        $group = new Group();

        if ($this->group) {
            // Make the new group a child of the current group
            $group->parent = $this->group;

            array_push($this->group->children, $group);
            array_push($this->uc, end($this->uc));
        } else {
            // If there is no parent group, then set this group
            // as the root group.
            $this->root = $group;
            // Create uc stack and insert the first default value
            $this->uc = [1];
        }

        // Set the new group as the current group:
        $this->group = $group;
    }

    /**
     * Retrieve state of document from stack.
     *
     * @return void
     */
    protected function parseEndGroup()
    {
        $this->group = $this->group->parent;
        // Retrieve last uc value from stack
        array_pop($this->uc);
    }

    /**
     * Parse ControlWord element
     *
     * @return void
     */
    protected function parseControlWord()
    {
        // Read letters until a non-letter is reached.
        $word = '';
        $this->getChar();

        while ($this->isLetter()) {
            $word .= $this->char;
            $this->getChar();
        }

        // Read parameter (if any) consisting of digits.
        // Parameter may be negative, i.e., starting with a '-'
        $parameter = null;
        $negative = false;

        if ($this->char == '-') {
            $this->getChar();
            $negative = true;
        }

        while ($this->isDigit()) {
            if ($parameter === null) {
                $parameter = 0;
            }
            $parameter = $parameter * 10 + (int) $this->char;
            $this->getChar();
        }

        // If no parameter present, assume control word's default (usually 1)
        // If no default then assign 0 to the parameter
        if ($parameter === null) {
            $parameter = 1;
        }

        // Convert parameter to a negative number when applicable
        if ($negative) {
            $parameter = -$parameter;
        }

        // Update uc value
        if ($word == "uc") {
            array_pop($this->uc);
            $this->uc[] = $parameter;
        }

        // Skip space delimiter
        if (!$this->isSpaceDelimiter()) {
            $this->pos--;
        }

        // If this is \u, then the parameter will be followed 
        // by {$this->uc} characters.
        if ($word == "u") {
            // Convert parameter to unsigned decimal unicode
            if ($negative) {
                $parameter = 65536 + $parameter;
            }

            // Will ignore replacement characters $uc times
            $uc = end($this->uc);

            while ($uc > 0) {
                $this->getChar();
                // If the replacement character is encoded as
                // hexadecimal value \'hh then jump over it
                if ($this->char == "\\" && $this->rtf[$this->pos] == '\'') {
                    $this->pos = $this->pos + 3;
                } elseif ($this->char == '{' || $this->char == '{') {
                    // Break if it's an RTF scope delimiter
                    break;
                }

                // - To include an RTF delimiter in skippable data, it must be
                //   represented using the appropriate control symbol (that is,
                //   escaped with a backslash,) as in plain text.
                //
                // - Any RTF control word or symbol is considered a single character
                //   for the purposes of counting skippable characters. For this reason
                //   it's more appropriate to create a $skip flag and let the Parse()
                //   function take care of the skippable characters.
                $uc--;
            }
        }

        // Add new RTF word as a child to the current group.
        $rtfword = new ControlWord();
        $rtfword->word = $word;
        $rtfword->parameter = $parameter;
        array_push($this->group->children, $rtfword);
    }

    /**
     * Parse ControlSymbol element
     *
     * @return void
     */
    protected function parseControlSymbol()
    {
        // Read symbol (one character only).
        $this->getChar();
        $symbol = $this->char;

        // Exceptional case:
        // Treat EOL symbols as \par control word
        if ($this->isEndOfLine()) {
            $rtfword = new ControlWord();
            $rtfword->word = 'par';
            $rtfword->parameter = 0;
            array_push($this->group->children, $rtfword);
            return;
        }

        // Symbols ordinarily have no parameter. However,
        // if this is \' (a single quote), then it is
        // followed by a 2-digit hex-code:
        $parameter = 0;
        if ($symbol == '\'') {
            $this->getChar();
            $parameter = $this->char;
            $this->getChar();
            $parameter = hexdec($parameter . $this->char);
        }

        // Add new control symbol as a child to the current group:
        $rtfsymbol = new ControlSymbol();
        $rtfsymbol->symbol = $symbol;
        $rtfsymbol->parameter = $parameter;
        array_push($this->group->children, $rtfsymbol);
    }

    /**
     * Parse Control element
     *
     * @return void
     */
    protected function parseControl()
    {
        // Beginning of an RTF control word or control symbol.
        // Look ahead by one character to see if it starts with
        // a letter (control world) or another symbol (control symbol):
        $this->GetChar();
        $this->pos--; // (go back after look-ahead)

        if ($this->isLetter()) {
            $this->parseControlWord();
        } else {
            $this->parseControlSymbol();
        }
    }

    /**
     * Parse Text element
     *
     * @return void
     */
    protected function parseText()
    {
        // Parse plain text up to backslash or brace,
        // unless escaped.
        $text = '';
        $terminate = false;

        do {
            // Ignore EOL characters
            if ($this->char == "\r" || $this->char == "\n") {
                $this->getChar();
                continue;
            }
            // Is this an escape?
            if ($this->char == "\\") {
                // Perform lookahead to see if this
                // is really an escape sequence.
                $this->getChar();
                switch ($this->char) {
                case "\\":
                    break;
                case '{':
                    break;
                case '}':
                    break;
                default:
                    // Not an escape. Roll back.
                    $this->pos = $this->pos - 2;
                    $terminate = true;
                    break;
                }
            } elseif ($this->char == '{' || $this->char == '}') {
                $this->pos--;
                $terminate = true;
            }

            if (!$terminate) {
                // Save plain text
                $text .= $this->char;
                $this->getChar();
            }
        } while (!$terminate && $this->pos < $this->len);

        // Create new Text element:
        $text = new Text($text);

        // If there is no current group, then this is not a valid RTF file.
        // Throw an exception.
        if (!$this->group) {
            throw new \Exception("Parse error: RTF text outside of group.");
        }

        // Add text as a child to the current group:
        array_push($this->group->children, $text);
    }

    /**
     * Attempt to parse an RTF string.
     *
     * @param string $rtf RTF content
     *
     * @return void
     */
    protected function parse($rtf)
    {
        $this->rtf = $rtf;
        $this->pos = 0;
        $this->len = strlen($this->rtf);
        $this->group = null;
        $this->root = null;

        while ($this->pos < $this->len-1) {
            // Read next character:
            $this->getChar();

            // Ignore \r and \n
            if ($this->char == "\n" || $this->char == "\r") {
                continue;
            }

            // What type of character is this?
            switch ($this->char) {
            case '{':
                $this->parseStartGroup();
                break;
            case '}':
                $this->parseEndGroup();
                break;
            case "\\":
                $this->parseControl();
                break;
            default:
                $this->parseText();
                break;
            }
        }
    }

    /**
     * Returns string representation of the document for debug purposes.
     *
     * @return string
     */
    public function __toString()
    {
        if (!$this->root) {
            return "No root group";
        }

        return $this->root->toString();
    }
}
PK�c�\<mN���*roundcube/rtf-html-php/src/ControlWord.phpnu�[���<?php

namespace RtfHtmlPhp;

class ControlWord extends Element
{
    public $word;
    public $parameter;

    /**
     * Returns string representation of the object for debug purposes
     *
     * @param int $level Indentation level
     *
     * @return string
     */
    public function toString($level)
    {
        return str_repeat("  ", $level) . "WORD {$this->word} ({$this->parameter})\n";
    }
}
PK�c�\�ۗ>K
K
$roundcube/rtf-html-php/src/Group.phpnu�[���<?php

namespace RtfHtmlPhp;

class Group extends Element
{
    public $parent;
    public $children;

    /**
     * Create a new Group, with no parent and no children.
     */
    public function __construct()
    {
        $this->parent = null;
        $this->children = [];
    }

    /**
     * Returns group type
     *
     * @return string|null
     */
    public function getType()
    {
        // No children? Then the group type is null.
        if (count($this->children) == 0) {
            return null;
        }

        $child = $this->children[0];

        // If the first child is a control word, then
        // the group type is the word.
        if ($child instanceof ControlWord) {
            return $child->word;
        }

        // If the first child is a control symbol, then
        // the group type is * for a special symbol, or null.
        if ($child instanceof ControlSymbol) {
            return ($child->symbol == '*') ? '*' : null;
        }

        // If first child is neither word nor symbol, then
        // group type is null.
        return null;
    }

    /**
     * If a group contains a '*' symbol as its first child,
     * then it is a destination group.
     *
     * @return bool|null Group destination
     */
    public function isDestination()
    {
        // If group has no children, then destination is null.
        if (count($this->children) == 0) {
            return null;
        }

        $child = $this->children[0];

        // First child not a control symbol?
        if (!$child instanceof ControlSymbol) {
            return null;
        }

        return $child->symbol == '*';
    }

    /**
     * Returns string representation of the object for debug purposes
     *
     * @param int $level Indentation level
     *
     * @return string
     */
    public function toString($level = 0)
    {
        $str = str_repeat("  ", $level) .  "{\n";

        foreach ($this->children as $child) {
            /*
            // Skip some group types:
            if ($child instanceof Group) {
                if ($child->GetType() == "fonttbl") continue;
                if ($child->GetType() == "colortbl") continue;
                if ($child->GetType() == "stylesheet") continue;
                if ($child->GetType() == "info") continue;
                // Skip any pictures:
                if (substr($child->GetType(), 0, 4) == "pict") continue;
                if ($child->IsDestination()) continue;
            }
            */
            $str .= $child->toString($level + 1);
        }

        return $str . str_repeat("  ", $level) . "}\n";
    }
}
PK�c�\��wS�b�b1roundcube/rtf-html-php/src/Html/HtmlFormatter.phpnu�[���<?php

namespace RtfHtmlPhp\Html;

use RtfHtmlPhp\Document;

class HtmlFormatter
{
    protected $encoding;
    protected $defaultFont;
    protected $fromhtml = false;
    protected $openedTags = [];
    protected $output = '';
    protected $previousState;
    protected $rtfEncoding;
    protected $state;
    protected $states = [];

    /**
     * Object constructor.
     *
     * By default, HtmlFormatter uses HTML_ENTITIES for code conversion.
     * You can optionally support a different endoing when creating
     * the HtmlFormatter instance.
     *
     * @param string $encoding Output encoding
     */
    public function __construct($encoding = 'HTML-ENTITIES')
    {
        if (!extension_loaded('mbstring')) {
            throw new \Exception("PHP mbstring extension not enabled");
        }

        if ($encoding != 'HTML-ENTITIES') {
            // Check if the encoding is reconized by mbstring extension
            if (!in_array($encoding, mb_list_encodings())) {
                throw new \Exception("Unsupported encoding: $encoding");
            }
        }

        $this->encoding = $encoding;
    }

    /**
     * Generates HTML output for the document
     *
     * @param Document $document The document
     *
     * @return string HTML content
     */
    public function format(Document $document)
    {
        // Clear current output
        $this->output = '';

        // Keep track of style modifications
        $this->previousState = null;

        // and create a stack of states
        $this->states = [];

        // Put an initial standard state onto the stack
        $this->state = new State();
        array_push($this->states, $this->state);

        // Keep track of opened html tags
        $this->openedTags = ['span' => false, 'p' => null];

        // Begin format
        $this->processGroup($document->root);

        // Instead of removing opened tags, we close them
        $this->output .= $this->openedTags['span'] ? '</span>' : ''; // @phpstan-ignore-line
        $this->output .= $this->openedTags['p'] ? '</p>' : ''; // @phpstan-ignore-line

        // Remove extra empty paragraph at the end
        // TODO: Find the real reason it's there and fix it
        $this->output = preg_replace('|<p></p>$|', '', $this->output);

        return $this->output;
    }

    /**
     * Registers a font definition.
     *
     * @param \RtfHtmlPhp\Group $fontGroup A group element with a font definition
     *
     * @return void
     */
    protected function loadFont(\RtfHtmlPhp\Group $fontGroup)
    {
        $fontNumber = 0;
        $font = new Font();

        // Loop through children of the font group. The font group
        // contains control words with the font number and charset,
        // and a control text with the font name.
        foreach ($fontGroup->children as $child) {
            // Control word
            if ($child instanceof \RtfHtmlPhp\ControlWord) {
                switch ($child->word) {
                case 'f':
                    $fontNumber = $child->parameter;
                    break;

                // Font family names
                case 'froman':
                    $font->family = "serif";
                    break;
                case 'fswiss':
                    $font->family = "sans-serif";
                    break;
                case 'fmodern':
                    $font->family = "monospace";
                    break;
                case 'fscript':
                    $font->family = "cursive";
                    break;
                case 'fdecor':
                    $font->family = "fantasy";
                    break;

                // case 'fnil': break; // default font
                // case 'ftech': break; // symbol
                // case 'fbidi': break; // bidirectional font

                case 'fcharset': // charset
                    $font->charset = $this->getEncodingFromCharset($child->parameter);
                    break;
                case 'cpg': // code page
                    $font->codepage = $this->getEncodingFromCodepage($child->parameter);
                    break;
                case 'fprq': // Font pitch
                    $font->fprq = $child->parameter;
                    break;
                }
            }

            // Control text contains the font name, if any:
            if ($child instanceof \RtfHtmlPhp\Text) {
                // Store font name (except ; delimiter at end)
                $font->name = substr($child->text, 0, -1);
            }

            /*
            elseif ($child instanceof \RtfHtmlPhp\Group) {
                // possible subgroups:
                // '{\*' \falt #PCDATA '}' = alternate font name
                // '{\*' \fontemb <fonttype> <fontfname>? <data>? '}'
                // '{\*' \fontfile <codepage>? #PCDATA '}'
                // '{\*' \panose <data> '}'
                continue;
            } elseif ($child instanceof \RtfHtmlPhp\ControlSymbol) {
                // the only authorized symbol here is '*':
                // \*\fname = non tagged file name (only WordPad uses it)
                continue;
            }
            */
        }

        State::setFont($fontNumber, $font);
    }

    protected function extractFontTable($fontTblGrp)
    {
        // {' \fonttbl (<fontinfo> | ('{' <fontinfo> '}'))+ '}'
        // <fontnum><fontfamily><fcharset>?<fprq>?<panose>?
        // <nontaggedname>?<fontemb>?<codepage>? <fontname><fontaltname>? ';'

        // The Font Table group contains the control word "fonttbl" and some
        // subgroups. Go through the subgroups, ignoring the "fonttbl"
        // identifier.
        foreach ($fontTblGrp->children as $child) {
            // Ignore non-group, which should be the fonttbl identified word.
            if (!($child instanceof \RtfHtmlPhp\Group)) {
                continue;
            }

            // Load the font specification in the subgroup:
            $this->loadFont($child);
        }
    }

    protected function extractColorTable($colorTblGrp)
    {
        // {\colortbl;\red0\green0\blue0;}
        // Index 0 of the RTF color table  is the 'auto' color
        $colortbl = [];
        $c = count($colorTblGrp);
        $color = '';

        for ($i=1; $i<$c; $i++) { // Iterate through colors
            if ($colorTblGrp[$i] instanceof \RtfHtmlPhp\ControlWord) {
                // Extract RGB color and convert it to hex string
                $color = sprintf(
                    '#%02x%02x%02x', // hex string format
                    $colorTblGrp[$i]->parameter, // red
                    $colorTblGrp[$i+1]->parameter, // green
                    $colorTblGrp[$i+2]->parameter // blue
                );
                $i+=2;
            } elseif ($colorTblGrp[$i] instanceof \RtfHtmlPhp\Text) {
                // This is a delimiter ';' so
                if ($i != 1) { // Store the already extracted color
                    $colortbl[] = $color;
                } else { // This is the 'auto' color
                    $colortbl[] = 0;
                }
            }
        }

        State::$colortbl = $colortbl;
    }

    protected function extractImage($pictGrp)
    {
        $image = new Image();

        foreach ($pictGrp as $child) {
            if ($child instanceof \RtfHtmlPhp\ControlWord) {
                switch ($child->word) {
                // Picture Format
                case "emfblip":
                    $image->format = 'emf';
                    break;
                case "pngblip":
                    $image->format = 'png';
                    break;
                case "jpegblip":
                    $image->format = 'jpeg';
                    break;
                case "macpict":
                    $image->format = 'pict';
                    break;
                // case "wmetafile": $Image->format = 'bmp'; break;

                // Picture size and scaling
                case "picw":
                    $image->width = $child->parameter;
                    break;
                case "pich":
                    $image->height = $child->parameter;
                    break;
                case "picwgoal":
                    $image->goalWidth = $child->parameter;
                    break;
                case "pichgoal":
                    $image->goalHeight = $child->parameter;
                    break;
                case "picscalex":
                    $image->pcScaleX = $child->parameter;
                    break;
                case "picscaley":
                    $image->pcScaleY = $child->parameter;
                    break;

                // Binary or Hexadecimal Data ?
                case "bin":
                    $image->binarySize = $child->parameter;
                    break;
                }
            } elseif ($child instanceof \RtfHtmlPhp\Text) {
                // store Data
                $image->imageData = $child->text;
            }
        }

        // output Image
        $this->output .= $image->printImage();
    }

    protected function processGroup($group)
    {
        // Special group processing:
        switch ($group->getType()) {
        case "fonttbl": // Extract font table
            $this->extractFontTable($group);
            return;
        case "colortbl": // Extract color table
            $this->extractColorTable($group->children);
            return;
        case "stylesheet":
            // Stylesheet extraction not yet supported
            return;
        case "info":
            // Ignore Document information
            return;
        case "pict":
            $this->extractImage($group->children);
            return;
        case "nonshppict":
            // Ignore alternative images
            return;
        case "*": // Process destination
            $this->processDestination($group->children);
            return;
        }

        // Pictures extraction not yet supported
        // if (substr($group->GetType(), 0, 4) == "pict") { return; }

        // Push a new state onto the stack:
        $this->state = clone $this->state;
        array_push($this->states, $this->state);

        foreach ($group->children as $child) {
            $this->formatEntry($child);
        }

        // Pop state from stack
        array_pop($this->states);
        $this->state = $this->states[count($this->states) - 1];
    }

    protected function processDestination($dest)
    {
        if (!$dest[1] instanceof \RtfHtmlPhp\ControlWord) {
            return;
        }

        // Check if this is a Word 97 picture
        if ($dest[1]->word == "shppict") {
            $c = count($dest);
            for ($i = 2; $i < $c; $i++) {
                $this->formatEntry($dest[$i]);
            }
        } elseif ($dest[1]->word == "htmltag") {
            $c = count($dest);
            for ($i = 2; $i < $c; $i++) {
                $entry = $dest[$i];

                if ($entry instanceof \RtfHtmlPhp\Text) {
                    $this->output .= $entry->text;
                } else {
                    $this->formatEntry($entry);
                }
            }
        }
    }

    protected function formatEntry($entry)
    {
        if ($entry instanceof \RtfHtmlPhp\Group) {
            $this->processGroup($entry);
        } elseif ($entry instanceof \RtfHtmlPhp\ControlWord) {
            $this->formatControlWord($entry);
        } elseif ($entry instanceof \RtfHtmlPhp\ControlSymbol) {
            $this->formatControlSymbol($entry);
        } elseif ($entry instanceof \RtfHtmlPhp\Text) {
            $this->formatText($entry);
        }
    }

    protected function formatControlWord($word)
    {
        switch($word->word) {

        case 'fromhtml':
            $this->fromhtml = $word->parameter > 0;
            break;

        case 'htmlrtf':
            $this->state->htmlrtf = $word->parameter > 0;
            break;

        case 'plain': // Reset font formatting properties to default.
        case 'pard':  // Reset to default paragraph properties.
            $this->state->reset($this->defaultFont);
            break;

        // Font formatting properties:

        case 'b': // bold
            $this->state->bold = $word->parameter;
            break;
        case 'i': // italic
            $this->state->italic = $word->parameter;
            break;
        case 'ul': // underline
            $this->state->underline = $word->parameter;
            break;
        case 'ulnone': // no underline
            $this->state->underline = false;
            break;
        case 'strike': // strike-through
            $this->state->strike = $word->parameter;
            break;
        case 'v': // hidden
            $this->state->hidden = $word->parameter;
            break;
        case 'fs': // Font size
            $this->state->fontsize = ceil(($word->parameter / 24) * 16);
            break;
        case 'f': // Font
            $this->state->font = $word->parameter;
            break;
        case 'deff': // Store default font
            $this->defaultFont = $word->parameter;
            break;

         // Colors

        case 'cf':
        case 'chcfpat':
            $this->state->fontcolor = $word->parameter;
            break;
        case 'cb':
        case 'chcbpat':
            $this->state->background = $word->parameter;
            break;
        case 'highlight':
            $this->state->hcolor = $word->parameter;
            break;

        // Special characters

        case 'lquote':    $this->write($this->fromhtml ? "‘" : "&lsquo;"); break;  // &#145; &#8216;
        case 'rquote':    $this->write($this->fromhtml ? "’" : "&rsquo;"); break;  // &#146; &#8217;
        case 'ldblquote': $this->write($this->fromhtml ? "“" : "&ldquo;"); break;  // &#147; &#8220;
        case 'rdblquote': $this->write($this->fromhtml ? "”" : "&rdquo;"); break;  // &#148; &#8221;
        case 'bullet':    $this->write($this->fromhtml ? "•" : "&bull;");  break;  // &#149; &#8226;
        case 'endash':    $this->write($this->fromhtml ? "–" : "&ndash;"); break;  // &#150; &#8211;
        case 'emdash':    $this->write($this->fromhtml ? "—" : "&mdash;"); break;  // &#151; &#8212;
        case 'enspace':   $this->write($this->fromhtml ? " " : "&ensp;");  break;  // &#8194;
        case 'emspace':   $this->write($this->fromhtml ? " " : "&emsp;");  break;  // &#8195;
        case 'tab':       $this->write($this->fromhtml ? "\t" : "&nbsp;");  break;  // Character value 9
        case 'line':      $this->output .= $this->fromhtml ? "\n" : "<br/>"; break; // character value (line feed = &#10;) (carriage return = &#13;)

        // Unicode characters

        case 'u':
            $uchar = $this->decodeUnicode($word->parameter);
            $this->write($uchar);
            break;

        // Paragraphs

        case 'par':
        case 'row':
            if ($this->fromhtml) {
                $this->output .= "\n";
                break;
            }
            // Close previously opened tags
            $this->closeTags();
            // Begin a new paragraph
            $this->openTag('p');
            break;

        // Code pages

        case 'ansi':
        case 'mac':
        case 'pc':
        case 'pca':
            $this->rtfEncoding = $this->getEncodingFromCodepage($word->word);
            break;
        case 'ansicpg':
            if ($word->parameter) {
                $this->rtfEncoding = $this->getEncodingFromCodepage($word->parameter);
            }
            break;
        }
    }

    protected function decodeUnicode($code, $srcEnc = 'UTF-8')
    {
        $utf8 = false;

        if ($srcEnc != 'UTF-8') { // convert character to Unicode
            $utf8 = iconv($srcEnc, 'UTF-8', chr($code));
        }

        if ($this->encoding == 'HTML-ENTITIES') {
            return $utf8 !== false ? "&#{$this->ordUtf8($utf8)};" : "&#{$code};";
        }

        if ($this->encoding == 'UTF-8') {
            return $utf8 !== false ? $utf8 : mb_convert_encoding("&#{$code};", $this->encoding, 'HTML-ENTITIES');
        }

        return $utf8 !== false ? mb_convert_encoding($utf8, $this->encoding, 'UTF-8') :
            mb_convert_encoding("&#{$code};", $this->encoding, 'HTML-ENTITIES');
    }

    protected function write($txt)
    {
        // Ignore regions that are not part of the original (encapsulated) HTML content
        if ($this->state->htmlrtf) {
            return;
        }

        if ($this->fromhtml) {
            $this->output .= $txt;
            return;
        }

        if (!isset($this->openedTags['p'])) {
            // Create the first paragraph
            $this->openTag('p');
        }

        // Create a new 'span' element only when a style change occurs.
        // 1st case: style change occured
        // 2nd case: there is no change in style but the already created 'span'
        // element is somehow closed (ex. because of an end of paragraph)
        if (!$this->state->equals($this->previousState) || empty($this->openedTags['span'])) {
            // If applicable close previously opened 'span' tag
            $this->closeTag('span');

            $style = $this->state->printStyle();

            // Keep track of preceding style
            $this->previousState = clone $this->state;

            // Create style attribute and open span
            $attr = $style ? "style=\"{$style}\"" : "";
            $this->openTag('span', $attr);
        }

        $this->output .= $txt;
    }

    protected function openTag($tag, $attr = '')
    {
        // Ignore regions that are not part of the original (encapsulated) HTML content
        if ($this->fromhtml) {
            return;
        }

        $this->output .= $attr ? "<{$tag} {$attr}>" : "<{$tag}>";
        $this->openedTags[$tag] = true;
    }

    protected function closeTag($tag)
    {
        if ($this->fromhtml) {
            return;
        }

        if (!empty($this->openedTags[$tag])) {
            // Check for empty html elements
            if (substr($this->output, -strlen("<{$tag}>")) == "<{$tag}>") {
                switch ($tag) {
                case 'p': // Replace empty 'p' element with a line break
                    $this->output = substr($this->output, 0, -3) . "<br>";
                    break;
                default: // Delete empty elements
                    $this->output = substr($this->output, 0, -strlen("<{$tag}>"));
                    break;
                }
            } else {
                $this->output .= "</{$tag}>";
            }

            $this->openedTags[$tag] = false;
        }
    }

    /**
     * Closes all opened tags
     *
     * @return void
     */
    protected function closeTags()
    {
        // Close all opened tags
        foreach ($this->openedTags as $tag => $b) {
            $this->closeTag($tag);
        }
    }

    protected function formatControlSymbol($symbol)
    {
        if ($symbol->symbol == '\'') {
            $enc = $this->getSourceEncoding();
            $uchar = $this->decodeUnicode($symbol->parameter, $enc);
            $this->write($uchar);
        } elseif ($symbol->symbol == '~') {
            $this->write("&nbsp;"); // Non breaking space
        } elseif ($symbol->symbol == '-') {
            $this->write("&#173;"); // Optional hyphen
        } elseif ($symbol->symbol == '_') {
            $this->write("&#8209;"); // Non breaking hyphen
        } elseif ($symbol->symbol == '{') {
            $this->write("{"); // Non breaking hyphen
        }
    }

    protected function formatText($text)
    {
        // Convert special characters to HTML entities
        $txt = htmlspecialchars($text->text, ENT_NOQUOTES, 'UTF-8');
        if ($this->encoding == 'HTML-ENTITIES') {
            $this->write($txt);
        } else {
            $this->write(mb_convert_encoding($txt, $this->encoding, 'UTF-8'));
        }
    }

    protected function getSourceEncoding()
    {
        if (isset($this->state->font)) {
            if (isset(State::$fonttbl[$this->state->font]->codepage)) {
                return State::$fonttbl[$this->state->font]->codepage;
            }
            if (isset(State::$fonttbl[$this->state->font]->charset)) {
                return State::$fonttbl[$this->state->font]->charset;
            }
        }

        return $this->rtfEncoding;
    }

    /**
     * Convert RTF charset identifier into an encoding name (for iconv)
     *
     * @param int $charset Charset identifier
     *
     * @return string|null Encoding name or NULL on unknown CodePage
     */
    protected function getEncodingFromCharset($charset)
    {
        // maps windows character sets to iconv encoding names
        $map = array (
            0   => 'CP1252', // ANSI: Western Europe
            1   => 'CP1252', //*Default
            2   => 'CP1252', //*Symbol
            3   => null,     // Invalid
            77  => 'MAC',    //*also [MacRoman]: Macintosh
            128 => 'CP932',  //*or [Shift_JIS]?: Japanese
            129 => 'CP949',  //*also [UHC]: Korean (Hangul)
            130 => 'CP1361', //*also [JOHAB]: Korean (Johab)
            134 => 'CP936',  //*or [GB2312]?: Simplified Chinese
            136 => 'CP950',  //*or [BIG5]?: Traditional Chinese
            161 => 'CP1253', // Greek
            162 => 'CP1254', // Turkish (latin 5)
            163 => 'CP1258', // Vietnamese
            177 => 'CP1255', // Hebrew
            178 => 'CP1256', // Simplified Arabic
            179 => 'CP1256', //*Traditional Arabic
            180 => 'CP1256', //*Arabic User
            181 => 'CP1255', //*Hebrew User
            186 => 'CP1257', // Baltic
            204 => 'CP1251', // Russian (Cyrillic)
            222 => 'CP874',  // Thai
            238 => 'CP1250', // Eastern European (latin 2)
            254 => 'CP437',  //*also [IBM437][437]: PC437
            255 => 'CP437', //*OEM still PC437
        );

        if (isset($map[$charset])) {
            return $map[$charset];
        }

        return null;
    }

    /**
     * Convert RTF CodePage identifier into an encoding name (for iconv)
     *
     * @param string $cpg CodePage identifier
     *
     * @return string|null Encoding name or NULL on unknown CodePage
     */
    protected function getEncodingFromCodepage($cpg)
    {
        $map = array (
            'ansi' => 'CP1252',
            'mac'  => 'MAC',
            'pc'   => 'CP437',
            'pca'  => 'CP850',
            437 => 'CP437', // United States IBM
            708 => 'ASMO-708', // also [ISO-8859-6][ARABIC] Arabic
            /*  Not supported by iconv
            709, => '' // Arabic (ASMO 449+, BCON V4)
            710, => '' // Arabic (transparent Arabic)
            711, => '' // Arabic (Nafitha Enhanced)
            720, => '' // Arabic (transparent ASMO)
            */
            819 => 'CP819',   // Windows 3.1 (US and Western Europe)
            850 => 'CP850',   // IBM multilingual
            852 => 'CP852',   // Eastern European
            860 => 'CP860',   // Portuguese
            862 => 'CP862',   // Hebrew
            863 => 'CP863',   // French Canadian
            864 => 'CP864',   // Arabic
            865 => 'CP865',   // Norwegian
            866 => 'CP866',   // Soviet Union
            874 => 'CP874',   // Thai
            932 => 'CP932',   // Japanese
            936 => 'CP936',   // Simplified Chinese
            949 => 'CP949',   // Korean
            950 => 'CP950',   // Traditional Chinese
            1250 => 'CP1250',  // Windows 3.1 (Eastern European)
            1251 => 'CP1251',  // Windows 3.1 (Cyrillic)
            1252 => 'CP1252',  // Western European
            1253 => 'CP1253',  // Greek
            1254 => 'CP1254',  // Turkish
            1255 => 'CP1255',  // Hebrew
            1256 => 'CP1256',  // Arabic
            1257 => 'CP1257',  // Baltic
            1258 => 'CP1258',  // Vietnamese
            1361 => 'CP1361', // Johab
        );

        if (isset($map[$cpg])) {
            return $map[$cpg];
        }

        return null;
    }

    protected function ordUtf8($chr)
    {
        $ord0 = ord($chr);
        if ($ord0 <= 127) {
            return $ord0;
        }

        $ord1 = ord($chr[1]);
        if ($ord0 >= 192 && $ord0 <= 223) {
            return ($ord0 - 192) * 64 + ($ord1 - 128);
        }

        $ord2 = ord($chr[2]);
        if ($ord0 >= 224 && $ord0 <= 239) {
            return ($ord0 - 224) * 4096 + ($ord1 - 128) * 64 + ($ord2 - 128);
        }

        $ord3 = ord($chr[3]);
        if ($ord0 >= 240 && $ord0 <= 247) {
            return ($ord0 - 240) * 262144 + ($ord1 - 128) * 4096 + ($ord2 - 128) * 64 + ($ord3 - 128);
        }

        $ord4 = ord($chr[4]);
        if ($ord0 >= 248 && $ord0 <= 251) {
            return ($ord0 - 248) * 16777216 + ($ord1 - 128) * 262144 + ($ord2 - 128) * 4096 + ($ord3 - 128) * 64 + ($ord4 - 128);
        }

        if ($ord0 >= 252 && $ord0 <= 253) {
            return ($ord0 - 252) * 1073741824 + ($ord1 - 128) * 16777216 + ($ord2 - 128) * 262144 + ($ord3 - 128) * 4096 + ($ord4 - 128) * 64 + (ord($chr[5]) - 128);
        }

        // trigger_error("Invalid Unicode character: {$chr}");
    }
}
PK�c�\#�)�[[)roundcube/rtf-html-php/src/Html/Image.phpnu�[���<?php

namespace RtfHtmlPhp\Html;

class Image
{
    public $format;
    public $width;
    public $height;
    public $goalWidth;
    public $goalHeight;
    public $pcScaleX;
    public $pcScaleY;
    public $binarySize;
    public $imageData;


    /**
     * Object constructor.
     */
    public function __construct()
    {
        $this->reset();
    }

    /**
     * Resets the object to the initial state
     *
     * @return void
     */
    public function reset()
    {
        $this->format = 'bmp';
        $this->width = 0;         // in xExt if wmetafile otherwise in px
        $this->height = 0;        // in yExt if wmetafile otherwise in px
        $this->goalWidth = 0;     // in twips
        $this->goalHeight = 0;    // in twips
        $this->pcScaleX = 100;    // 100%
        $this->pcScaleY = 100;    // 100%
        $this->binarySize = null; // Number of bytes of the binary data
        $this->imageData = null;  // Binary or Hexadecimal Data
    }

    /**
     * Generate a HTML content for the image
     *
     * @return string Image tag content, An empty string for unsupported/empty image
     */
    public function printImage()
    {
        // process binary data
        if (isset($this->binarySize)) {
            // Not implemented
            return '';
        }

        if (empty($this->imageData)) {
            return '';
        }

        // process hexadecimal data
        $data = base64_encode(pack('H*', $this->imageData));

        // <img src="data:image/{FORMAT};base64,{#BDATA}" />
        return "<img src=\"data:image/{$this->format};base64,{$data}\" />";
    }
}
PK�c�\aP�J��)roundcube/rtf-html-php/src/Html/State.phpnu�[���<?php 

namespace RtfHtmlPhp\Html;

class State
{
    public static $fonttbl = [];
    public static $colortbl = [];
    public $bold;
    public $italic;
    public $underline;
    public $strike;
    public $hidden;
    public $fontsize;
    public $fontcolor;
    public $background;
    public $hcolor;
    public $font;
    public $htmlrtf;

    protected static $highlight = [
        1  => 'Black',
        2  => 'Blue',
        3  => 'Cyan',
        4  => 'Green',
        5  => 'Magenta',
        6  => 'Red',
        7  => 'Yellow',
        8  => 'Unused',
        9  => 'DarkBlue',
        10 => 'DarkCyan',
        11 => 'DarkGreen',
        12 => 'DarkMagenta',
        13 => 'DarkRed',
        14 => 'DarkYellow',
        15 => 'DarkGray',
        16 => 'LightGray'
    ];


    /**
     * Object constructor
     */
    public function __construct()
    {
        $this->reset();
    }

    /**
     * Store a font in the font table at the specified index.
     *
     * @param int  $index Font number
     * @param Font $font  Font object
     *
     * @return void
     */
    public static function setFont($index, Font $font)
    {
        State::$fonttbl[$index] = $font;
    }

    /**
     * Resets the object to the initial state
     *
     * @param string|null $defaultFont Font name
     *
     * @return void
     */
    public function reset($defaultFont = null)
    {
        $this->bold = false;
        $this->italic = false;
        $this->underline = false;
        $this->strike = false;
        $this->hidden = false;
        $this->fontsize = 0;
        $this->fontcolor = null;
        $this->background = null;
        $this->hcolor = null;
        $this->font = isset($defaultFont) ? $defaultFont : null;
        $this->htmlrtf = false;
    }

    /**
     * Generates css style for the state.
     *
     * @return string The css string
     */
    public function printStyle()
    {
        $style = [];

        if ($this->bold) {
            $style[] = "font-weight:bold";
        }

        if ($this->italic) {
            $style[] = "font-style:italic";
        }

        if ($this->underline) {
            $style[] = "text-decoration:underline";
        }

        // state->underline is a toggle switch variable so no need for
        // a dedicated state->end_underline variable
        // if($this->state->end_underline) {$span .= "text-decoration:none";}
        if ($this->strike) {
            $style[] = "text-decoration:line-through";
        }

        if ($this->hidden) {
            $style[] = "display:none";
        }

        if (isset($this->font)) {
            $font = self::$fonttbl[$this->font];
            $style[] = $font->toStyle();
        }

        if ($this->fontsize != 0) {
            $style[] = "font-size:{$this->fontsize}px";
        }

        // Font color:
        if (isset($this->fontcolor)) {
            // Check if color is set. in particular when it's the 'auto' color
            if (array_key_exists($this->fontcolor, self::$colortbl) && self::$colortbl[$this->fontcolor]) {
                $style[] = "color:" . self::$colortbl[$this->fontcolor];
            }
        }

        // Background color:
        if (isset($this->background)) {
            // Check if color is set. in particular when it's the 'auto' color
            if (array_key_exists($this->background, self::$colortbl) && self::$colortbl[$this->background]) {
                $style[] = "background-color:" . self::$colortbl[$this->background];
            }
        } elseif (isset($this->hcolor)) {
            // Highlight color:
            if (array_key_exists($this->hcolor, self::$highlight) && self::$highlight[$this->hcolor]) {
                $style[] = "background-color:" . self::$highlight[$this->hcolor];
            }
        }

        return empty($style) ? '' : implode(';', $style) . ';';
    }

    /**
     * Check whether this State is equal to another State.
     *
     * @param State $state A state to compare with
     *
     * @return bool True if the state is identical, False otherwise
     */
    public function equals($state)
    {
        if (!($state instanceof State)) {
            return false;
        }

        return $this->bold == $state->bold
            && $this->italic == $state->italic
            && $this->underline == $state->underline
            && $this->strike == $state->strike
            && $this->hidden == $state->hidden
            && $this->fontsize == $state->fontsize
            // Compare colors
            && $this->fontcolor == $state->fontcolor
            && $this->background == $state->background
            && $this->hcolor == $state->hcolor
            // Compare fonts
            && $this->font == $state->font;
    }
}
PK�c�\��P��(roundcube/rtf-html-php/src/Html/Font.phpnu�[���<?php

namespace RtfHtmlPhp\Html;

class Font
{
    public $family;
    public $fprq;
    public $name;
    public $charset;
    public $codepage;

    /**
     * Returns font style (font-family) string
     *
     * @return string A string including font-family: prefix. An empty string if font is not set
     */
    public function toStyle()
    {
        $list = [];

        if ($this->name) {
            $list[] = $this->name;
        }

        if ($this->family) {
            $list[] = $this->family;
        }

        if (count($list) == 0) {
            return '';
        }

        return "font-family:" . implode(',', $list);
    }
}
PK�c�\�
��,roundcube/rtf-html-php/src/ControlSymbol.phpnu�[���<?php 

namespace RtfHtmlPhp;

class ControlSymbol extends Element
{
    public $symbol;
    public $parameter = 0;

    /**
     * Returns string representation of the object for debug purposes
     *
     * @param int $level Indentation level
     *
     * @return string
     */
    public function toString($level)
    {
        return str_repeat("  ", $level) . "SYMBOL {$this->symbol} ({$this->parameter})\n";
    }
}
PK�c�\)%,M��&roundcube/rtf-html-php/src/Element.phpnu�[���<?php 

namespace RtfHtmlPhp;

/**
 * Element is the parent class of all RTF elements,
 * like Group, ControlWord and ControlSymbol.
 */
class Element
{
}
PK�c�\1�J//#roundcube/rtf-html-php/src/Text.phpnu�[���<?php 

namespace RtfHtmlPhp;

class Text extends Element
{
    public $text;

    /**
     * Create a new Text instance with string content.
     *
     * @param string $text The content
     */
    public function __construct($text)
    {
        $this->text = $text;
    }

    /**
     * Returns string representation of the object for debug purposes
     *
     * @param int $level Indentation level
     *
     * @return string
     */
    public function toString($level)
    {
        return str_repeat("  ", $level) . "TEXT {$this->text}\n";
    }
}
PK�c�\�!u��$roundcube/rtf-html-php/composer.jsonnu�[���{
  "name": "roundcube/rtf-html-php",
  "description": "RTF to HTML converter in PHP",
  "keywords": ["rtf", "converter"],
  "type": "library",
  "license": "GPL-2.0",
  "authors": [
    {
      "name": "Alexander van Oostenrijk",
      "email": "alex.vanoostenrijk@gmail.com"
    },
    {
      "name": "Aleksander Machniak",
      "email": "alec@alec.pl"
    }
  ],
  "scripts": {
    "test": [
      "phpunit tests"
    ]
  },
  "require": {
    "php": ">=5.4",
    "ext-iconv": "*",
    "ext-mbstring": "*"
  },
  "autoload": {
    "psr-4": {
      "RtfHtmlPhp\\": "src/"
    }
  },
  "require-dev": {
    "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6 || ^7 | ^9.6",
    "phpstan/phpstan": "^1.2"
  }
}
PK�c�\S����#roundcube/rtf-html-php/CHANGELOG.mdnu�[���# Changelog

## 2.2

- Fix "Creation of dynamic property" warnings
- Fix a couple of code errors found in static code analisys
- Enable testing on PHP 8.2 and 8.3

## 2.0

- Fork, various code style fixes, camel-case use
- Added some more tests
- Removed trigger_error() use
- Added support for encapsulated HTML
- Support PHP >= 5.4, up to 8.1

## 1.1

### Update 11 Sep '19:
- Split code up into several classes under `/src`
- Lots of code documentation
- Added some PHPUnit test cases
- Use namespace
- Set version to 1.1

## 1.0

### Update 26 Oct '18:

- Adds support for Font table extraction.
- Adds support for Pictures.
- Adds support for additional control symbols.
- Updates the way the parser parses unicode and its replacement character(s).
- Updated Html formatter: now it reads the proper encoding from RTF 
  document and/or from current font.
- Updated unicode conversion method: now it takes into account the 
  right encoding of the RTF document.

### Update 2 Sep '18:

- Unicode characters are now fully supported
- Font color & background are now supported
- Better HTML tag handling

### Update 11 Jun '18:

- Better display for text with altered font-size 

### Update 10 Mar '16:

- The RTF parser would either issue warnings or go into an infinite 
  loop when parsing a malformed RTF. Instead, it now returns `true` when 
  parsing was successful, and `false` if it was not.

### Update 23 Feb '16:

- The RTF to HTML converter can now be installed through Composer 
  (thanks to @felixkiss).

### Update 28 Oct '15:

- A bug causing control words to be misparsed occasionally is now fixed.

### Update 3 Sep ’14:

- Fixed bug: underlining would start but never end. Now it does.
- Feature request: images are now filtered out of the output.
PK�c�\J��p�F�Froundcube/rtf-html-php/LICENSEnu�[���                    GNU GENERAL PUBLIC LICENSE
                       Version 2, June 1991

 Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

                            Preamble

  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users.  This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it.  (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.)  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.

  To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have.  You must make sure that they, too, receive or can get the
source code.  And you must show them these terms so they know their
rights.

  We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.

  Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software.  If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.

  Finally, any free program is threatened constantly by software
patents.  We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary.  To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.

  The precise terms and conditions for copying, distribution and
modification follow.

                    GNU GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License.  The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language.  (Hereinafter, translation is included without limitation in
the term "modification".)  Each licensee is addressed as "you".

Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.

  1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.

You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.

  2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:

    a) You must cause the modified files to carry prominent notices
    stating that you changed the files and the date of any change.

    b) You must cause any work that you distribute or publish, that in
    whole or in part contains or is derived from the Program or any
    part thereof, to be licensed as a whole at no charge to all third
    parties under the terms of this License.

    c) If the modified program normally reads commands interactively
    when run, you must cause it, when started running for such
    interactive use in the most ordinary way, to print or display an
    announcement including an appropriate copyright notice and a
    notice that there is no warranty (or else, saying that you provide
    a warranty) and that users may redistribute the program under
    these conditions, and telling the user how to view a copy of this
    License.  (Exception: if the Program itself is interactive but
    does not normally print such an announcement, your work based on
    the Program is not required to print an announcement.)

These requirements apply to the modified work as a whole.  If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works.  But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.

In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.

  3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:

    a) Accompany it with the complete corresponding machine-readable
    source code, which must be distributed under the terms of Sections
    1 and 2 above on a medium customarily used for software interchange; or,

    b) Accompany it with a written offer, valid for at least three
    years, to give any third party, for a charge no more than your
    cost of physically performing source distribution, a complete
    machine-readable copy of the corresponding source code, to be
    distributed under the terms of Sections 1 and 2 above on a medium
    customarily used for software interchange; or,

    c) Accompany it with the information you received as to the offer
    to distribute corresponding source code.  (This alternative is
    allowed only for noncommercial distribution and only if you
    received the program in object code or executable form with such
    an offer, in accord with Subsection b above.)

The source code for a work means the preferred form of the work for
making modifications to it.  For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable.  However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.

If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.

  4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License.  Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.

  5. You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Program or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.

  6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.

  7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all.  For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.

If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.

It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.

This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.

  8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded.  In such case, this License incorporates
the limitation as if written in the body of this License.

  9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

Each version is given a distinguishing version number.  If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation.  If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.

  10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission.  For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this.  Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.

                            NO WARRANTY

  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.

  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.

                     END OF TERMS AND CONDITIONS

            How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

    {description}
    Copyright (C) {year}  {fullname}

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

Also add information on how to contact you by electronic and paper mail.

If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:

    Gnomovision version 69, Copyright (C) year name of author
    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
    This is free software, and you are welcome to redistribute it
    under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.

You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary.  Here is a sample; alter the names:

  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
  `Gnomovision' (which makes passes at compilers) written by James Hacker.

  {signature of Ty Coon}, 1 April 1989
  Ty Coon, President of Vice

This General Public License does not permit incorporating your program into
proprietary programs.  If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library.  If this is what you want to do, use the GNU Lesser General
Public License instead of this License.


PK�c�\�$�$�� roundcube/rtf-html-php/README.mdnu�[���# rtf-html-php

_An RTF to HTML converter in PHP_

In a recent project, I desperately needed an RTF to HTML converter written in PHP. Googling around turned up some matches, but I could not get them to work properly. Also, one of them called `passthru()` to use a RTF2HTML executable, which is something I didn’t want. I was looking for an RTF to HTML converter written purely in PHP.

Since I couldn’t find anything ready-made, I sat down and coded one up myself. It’s short, and it works, implementing the subset of RTF tags that you’ll need in HTML and ignoring the rest. As it turns out, the RTF format isn’t that complicated when you really look at it, but it isn’t something you code a parser for in 15 minutes either.

## How to use it

Install this package using composer. Then do this:

```php
use RtfHtmlPhp\Document;

$rtf = file_get_contents("test.rtf"); 
$document = new Document($rtf); // or use a string directly
```

`Document` will raise an exception if the RTF document could not be parsed.

If you’d like to see what the parser read (for debug purposes), then call this:

```php
echo $document;
```

To convert the parser’s parse tree to HTML, call this (but only if the RTF was successfully parsed):

```php
use RtfHtmlPhp\Html\HtmlFormatter;
$formatter = new HtmlFormatter();
echo $formatter->format($document);
```

For enhanced compatibility the default character encoding of the converted RTF unicode characters is set to `HTML-ENTITIES`. To change the default encoding, you can initialize the `Html` object with the desired encoding supported by `mb_list_encodings()`: ex. `UTF-8`

```php
$formatter = new HtmlFormatter('UTF-8');
```

## Install via Composer

```shell
composer require roundcube/rtf-to-html
```
PK�c�\�}�jj'guzzlehttp/guzzle/src/TransferStats.phpnu�[���<?php

namespace GuzzleHttp;

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UriInterface;

/**
 * Represents data at the point after it was transferred either successfully
 * or after a network error.
 */
final class TransferStats
{
    /**
     * @var RequestInterface
     */
    private $request;

    /**
     * @var ResponseInterface|null
     */
    private $response;

    /**
     * @var float|null
     */
    private $transferTime;

    /**
     * @var array
     */
    private $handlerStats;

    /**
     * @var mixed|null
     */
    private $handlerErrorData;

    /**
     * @param RequestInterface       $request          Request that was sent.
     * @param ResponseInterface|null $response         Response received (if any)
     * @param float|null             $transferTime     Total handler transfer time.
     * @param mixed                  $handlerErrorData Handler error data.
     * @param array                  $handlerStats     Handler specific stats.
     */
    public function __construct(
        RequestInterface $request,
        ResponseInterface $response = null,
        float $transferTime = null,
        $handlerErrorData = null,
        array $handlerStats = []
    ) {
        $this->request = $request;
        $this->response = $response;
        $this->transferTime = $transferTime;
        $this->handlerErrorData = $handlerErrorData;
        $this->handlerStats = $handlerStats;
    }

    public function getRequest(): RequestInterface
    {
        return $this->request;
    }

    /**
     * Returns the response that was received (if any).
     */
    public function getResponse(): ?ResponseInterface
    {
        return $this->response;
    }

    /**
     * Returns true if a response was received.
     */
    public function hasResponse(): bool
    {
        return $this->response !== null;
    }

    /**
     * Gets handler specific error data.
     *
     * This might be an exception, a integer representing an error code, or
     * anything else. Relying on this value assumes that you know what handler
     * you are using.
     *
     * @return mixed
     */
    public function getHandlerErrorData()
    {
        return $this->handlerErrorData;
    }

    /**
     * Get the effective URI the request was sent to.
     */
    public function getEffectiveUri(): UriInterface
    {
        return $this->request->getUri();
    }

    /**
     * Get the estimated time the request was being transferred by the handler.
     *
     * @return float|null Time in seconds.
     */
    public function getTransferTime(): ?float
    {
        return $this->transferTime;
    }

    /**
     * Gets an array of all of the handler specific transfer data.
     */
    public function getHandlerStats(): array
    {
        return $this->handlerStats;
    }

    /**
     * Get a specific handler statistic from the handler by name.
     *
     * @param string $stat Handler specific transfer stat to retrieve.
     *
     * @return mixed|null
     */
    public function getHandlerStat(string $stat)
    {
        return $this->handlerStats[$stat] ?? null;
    }
}
PK�c�\]�����1guzzlehttp/guzzle/src/BodySummarizerInterface.phpnu�[���<?php

namespace GuzzleHttp;

use Psr\Http\Message\MessageInterface;

interface BodySummarizerInterface
{
    /**
     * Returns a summarized message body.
     */
    public function summarize(MessageInterface $message): ?string;
}
PK�c�\����+�+$guzzlehttp/guzzle/src/Middleware.phpnu�[���<?php

namespace GuzzleHttp;

use GuzzleHttp\Cookie\CookieJarInterface;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Promise as P;
use GuzzleHttp\Promise\PromiseInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Log\LoggerInterface;

/**
 * Functions used to create and wrap handlers with handler middleware.
 */
final class Middleware
{
    /**
     * Middleware that adds cookies to requests.
     *
     * The options array must be set to a CookieJarInterface in order to use
     * cookies. This is typically handled for you by a client.
     *
     * @return callable Returns a function that accepts the next handler.
     */
    public static function cookies(): callable
    {
        return static function (callable $handler): callable {
            return static function ($request, array $options) use ($handler) {
                if (empty($options['cookies'])) {
                    return $handler($request, $options);
                } elseif (!($options['cookies'] instanceof CookieJarInterface)) {
                    throw new \InvalidArgumentException('cookies must be an instance of GuzzleHttp\Cookie\CookieJarInterface');
                }
                $cookieJar = $options['cookies'];
                $request = $cookieJar->withCookieHeader($request);

                return $handler($request, $options)
                    ->then(
                        static function (ResponseInterface $response) use ($cookieJar, $request): ResponseInterface {
                            $cookieJar->extractCookies($request, $response);

                            return $response;
                        }
                    );
            };
        };
    }

    /**
     * Middleware that throws exceptions for 4xx or 5xx responses when the
     * "http_errors" request option is set to true.
     *
     * @param BodySummarizerInterface|null $bodySummarizer The body summarizer to use in exception messages.
     *
     * @return callable(callable): callable Returns a function that accepts the next handler.
     */
    public static function httpErrors(BodySummarizerInterface $bodySummarizer = null): callable
    {
        return static function (callable $handler) use ($bodySummarizer): callable {
            return static function ($request, array $options) use ($handler, $bodySummarizer) {
                if (empty($options['http_errors'])) {
                    return $handler($request, $options);
                }

                return $handler($request, $options)->then(
                    static function (ResponseInterface $response) use ($request, $bodySummarizer) {
                        $code = $response->getStatusCode();
                        if ($code < 400) {
                            return $response;
                        }
                        throw RequestException::create($request, $response, null, [], $bodySummarizer);
                    }
                );
            };
        };
    }

    /**
     * Middleware that pushes history data to an ArrayAccess container.
     *
     * @param array|\ArrayAccess<int, array> $container Container to hold the history (by reference).
     *
     * @return callable(callable): callable Returns a function that accepts the next handler.
     *
     * @throws \InvalidArgumentException if container is not an array or ArrayAccess.
     */
    public static function history(&$container): callable
    {
        if (!\is_array($container) && !$container instanceof \ArrayAccess) {
            throw new \InvalidArgumentException('history container must be an array or object implementing ArrayAccess');
        }

        return static function (callable $handler) use (&$container): callable {
            return static function (RequestInterface $request, array $options) use ($handler, &$container) {
                return $handler($request, $options)->then(
                    static function ($value) use ($request, &$container, $options) {
                        $container[] = [
                            'request' => $request,
                            'response' => $value,
                            'error' => null,
                            'options' => $options,
                        ];

                        return $value;
                    },
                    static function ($reason) use ($request, &$container, $options) {
                        $container[] = [
                            'request' => $request,
                            'response' => null,
                            'error' => $reason,
                            'options' => $options,
                        ];

                        return P\Create::rejectionFor($reason);
                    }
                );
            };
        };
    }

    /**
     * Middleware that invokes a callback before and after sending a request.
     *
     * The provided listener cannot modify or alter the response. It simply
     * "taps" into the chain to be notified before returning the promise. The
     * before listener accepts a request and options array, and the after
     * listener accepts a request, options array, and response promise.
     *
     * @param callable $before Function to invoke before forwarding the request.
     * @param callable $after  Function invoked after forwarding.
     *
     * @return callable Returns a function that accepts the next handler.
     */
    public static function tap(callable $before = null, callable $after = null): callable
    {
        return static function (callable $handler) use ($before, $after): callable {
            return static function (RequestInterface $request, array $options) use ($handler, $before, $after) {
                if ($before) {
                    $before($request, $options);
                }
                $response = $handler($request, $options);
                if ($after) {
                    $after($request, $options, $response);
                }

                return $response;
            };
        };
    }

    /**
     * Middleware that handles request redirects.
     *
     * @return callable Returns a function that accepts the next handler.
     */
    public static function redirect(): callable
    {
        return static function (callable $handler): RedirectMiddleware {
            return new RedirectMiddleware($handler);
        };
    }

    /**
     * Middleware that retries requests based on the boolean result of
     * invoking the provided "decider" function.
     *
     * If no delay function is provided, a simple implementation of exponential
     * backoff will be utilized.
     *
     * @param callable $decider Function that accepts the number of retries,
     *                          a request, [response], and [exception] and
     *                          returns true if the request is to be retried.
     * @param callable $delay   Function that accepts the number of retries and
     *                          returns the number of milliseconds to delay.
     *
     * @return callable Returns a function that accepts the next handler.
     */
    public static function retry(callable $decider, callable $delay = null): callable
    {
        return static function (callable $handler) use ($decider, $delay): RetryMiddleware {
            return new RetryMiddleware($decider, $handler, $delay);
        };
    }

    /**
     * Middleware that logs requests, responses, and errors using a message
     * formatter.
     *
     * @phpstan-param \Psr\Log\LogLevel::* $logLevel  Level at which to log requests.
     *
     * @param LoggerInterface                            $logger    Logs messages.
     * @param MessageFormatterInterface|MessageFormatter $formatter Formatter used to create message strings.
     * @param string                                     $logLevel  Level at which to log requests.
     *
     * @return callable Returns a function that accepts the next handler.
     */
    public static function log(LoggerInterface $logger, $formatter, string $logLevel = 'info'): callable
    {
        // To be compatible with Guzzle 7.1.x we need to allow users to pass a MessageFormatter
        if (!$formatter instanceof MessageFormatter && !$formatter instanceof MessageFormatterInterface) {
            throw new \LogicException(sprintf('Argument 2 to %s::log() must be of type %s', self::class, MessageFormatterInterface::class));
        }

        return static function (callable $handler) use ($logger, $formatter, $logLevel): callable {
            return static function (RequestInterface $request, array $options = []) use ($handler, $logger, $formatter, $logLevel) {
                return $handler($request, $options)->then(
                    static function ($response) use ($logger, $request, $formatter, $logLevel): ResponseInterface {
                        $message = $formatter->format($request, $response);
                        $logger->log($logLevel, $message);

                        return $response;
                    },
                    static function ($reason) use ($logger, $request, $formatter): PromiseInterface {
                        $response = $reason instanceof RequestException ? $reason->getResponse() : null;
                        $message = $formatter->format($request, $response, P\Create::exceptionFor($reason));
                        $logger->error($message);

                        return P\Create::rejectionFor($reason);
                    }
                );
            };
        };
    }

    /**
     * This middleware adds a default content-type if possible, a default
     * content-length or transfer-encoding header, and the expect header.
     */
    public static function prepareBody(): callable
    {
        return static function (callable $handler): PrepareBodyMiddleware {
            return new PrepareBodyMiddleware($handler);
        };
    }

    /**
     * Middleware that applies a map function to the request before passing to
     * the next handler.
     *
     * @param callable $fn Function that accepts a RequestInterface and returns
     *                     a RequestInterface.
     */
    public static function mapRequest(callable $fn): callable
    {
        return static function (callable $handler) use ($fn): callable {
            return static function (RequestInterface $request, array $options) use ($handler, $fn) {
                return $handler($fn($request), $options);
            };
        };
    }

    /**
     * Middleware that applies a map function to the resolved promise's
     * response.
     *
     * @param callable $fn Function that accepts a ResponseInterface and
     *                     returns a ResponseInterface.
     */
    public static function mapResponse(callable $fn): callable
    {
        return static function (callable $handler) use ($fn): callable {
            return static function (RequestInterface $request, array $options) use ($handler, $fn) {
                return $handler($request, $options)->then($fn);
            };
        };
    }
}
PK�c�\G�Lss*guzzlehttp/guzzle/src/MessageFormatter.phpnu�[���<?php

namespace GuzzleHttp;

use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * Formats log messages using variable substitutions for requests, responses,
 * and other transactional data.
 *
 * The following variable substitutions are supported:
 *
 * - {request}:        Full HTTP request message
 * - {response}:       Full HTTP response message
 * - {ts}:             ISO 8601 date in GMT
 * - {date_iso_8601}   ISO 8601 date in GMT
 * - {date_common_log} Apache common log date using the configured timezone.
 * - {host}:           Host of the request
 * - {method}:         Method of the request
 * - {uri}:            URI of the request
 * - {version}:        Protocol version
 * - {target}:         Request target of the request (path + query + fragment)
 * - {hostname}:       Hostname of the machine that sent the request
 * - {code}:           Status code of the response (if available)
 * - {phrase}:         Reason phrase of the response  (if available)
 * - {error}:          Any error messages (if available)
 * - {req_header_*}:   Replace `*` with the lowercased name of a request header to add to the message
 * - {res_header_*}:   Replace `*` with the lowercased name of a response header to add to the message
 * - {req_headers}:    Request headers
 * - {res_headers}:    Response headers
 * - {req_body}:       Request body
 * - {res_body}:       Response body
 *
 * @final
 */
class MessageFormatter implements MessageFormatterInterface
{
    /**
     * Apache Common Log Format.
     *
     * @see https://httpd.apache.org/docs/2.4/logs.html#common
     *
     * @var string
     */
    public const CLF = '{hostname} {req_header_User-Agent} - [{date_common_log}] "{method} {target} HTTP/{version}" {code} {res_header_Content-Length}';
    public const DEBUG = ">>>>>>>>\n{request}\n<<<<<<<<\n{response}\n--------\n{error}";
    public const SHORT = '[{ts}] "{method} {target} HTTP/{version}" {code}';

    /**
     * @var string Template used to format log messages
     */
    private $template;

    /**
     * @param string $template Log message template
     */
    public function __construct(?string $template = self::CLF)
    {
        $this->template = $template ?: self::CLF;
    }

    /**
     * Returns a formatted message string.
     *
     * @param RequestInterface       $request  Request that was sent
     * @param ResponseInterface|null $response Response that was received
     * @param \Throwable|null        $error    Exception that was received
     */
    public function format(RequestInterface $request, ResponseInterface $response = null, \Throwable $error = null): string
    {
        $cache = [];

        /** @var string */
        return \preg_replace_callback(
            '/{\s*([A-Za-z_\-\.0-9]+)\s*}/',
            function (array $matches) use ($request, $response, $error, &$cache) {
                if (isset($cache[$matches[1]])) {
                    return $cache[$matches[1]];
                }

                $result = '';
                switch ($matches[1]) {
                    case 'request':
                        $result = Psr7\Message::toString($request);
                        break;
                    case 'response':
                        $result = $response ? Psr7\Message::toString($response) : '';
                        break;
                    case 'req_headers':
                        $result = \trim($request->getMethod()
                                .' '.$request->getRequestTarget())
                            .' HTTP/'.$request->getProtocolVersion()."\r\n"
                            .$this->headers($request);
                        break;
                    case 'res_headers':
                        $result = $response ?
                            \sprintf(
                                'HTTP/%s %d %s',
                                $response->getProtocolVersion(),
                                $response->getStatusCode(),
                                $response->getReasonPhrase()
                            )."\r\n".$this->headers($response)
                            : 'NULL';
                        break;
                    case 'req_body':
                        $result = $request->getBody()->__toString();
                        break;
                    case 'res_body':
                        if (!$response instanceof ResponseInterface) {
                            $result = 'NULL';
                            break;
                        }

                        $body = $response->getBody();

                        if (!$body->isSeekable()) {
                            $result = 'RESPONSE_NOT_LOGGEABLE';
                            break;
                        }

                        $result = $response->getBody()->__toString();
                        break;
                    case 'ts':
                    case 'date_iso_8601':
                        $result = \gmdate('c');
                        break;
                    case 'date_common_log':
                        $result = \date('d/M/Y:H:i:s O');
                        break;
                    case 'method':
                        $result = $request->getMethod();
                        break;
                    case 'version':
                        $result = $request->getProtocolVersion();
                        break;
                    case 'uri':
                    case 'url':
                        $result = $request->getUri()->__toString();
                        break;
                    case 'target':
                        $result = $request->getRequestTarget();
                        break;
                    case 'req_version':
                        $result = $request->getProtocolVersion();
                        break;
                    case 'res_version':
                        $result = $response
                            ? $response->getProtocolVersion()
                            : 'NULL';
                        break;
                    case 'host':
                        $result = $request->getHeaderLine('Host');
                        break;
                    case 'hostname':
                        $result = \gethostname();
                        break;
                    case 'code':
                        $result = $response ? $response->getStatusCode() : 'NULL';
                        break;
                    case 'phrase':
                        $result = $response ? $response->getReasonPhrase() : 'NULL';
                        break;
                    case 'error':
                        $result = $error ? $error->getMessage() : 'NULL';
                        break;
                    default:
                        // handle prefixed dynamic headers
                        if (\strpos($matches[1], 'req_header_') === 0) {
                            $result = $request->getHeaderLine(\substr($matches[1], 11));
                        } elseif (\strpos($matches[1], 'res_header_') === 0) {
                            $result = $response
                                ? $response->getHeaderLine(\substr($matches[1], 11))
                                : 'NULL';
                        }
                }

                $cache[$matches[1]] = $result;

                return $result;
            },
            $this->template
        );
    }

    /**
     * Get headers from message as string
     */
    private function headers(MessageInterface $message): string
    {
        $result = '';
        foreach ($message->getHeaders() as $name => $values) {
            $result .= $name.': '.\implode(', ', $values)."\r\n";
        }

        return \trim($result);
    }
}
PK�c�\N�33guzzlehttp/guzzle/src/Utils.phpnu�[���<?php

namespace GuzzleHttp;

use GuzzleHttp\Exception\InvalidArgumentException;
use GuzzleHttp\Handler\CurlHandler;
use GuzzleHttp\Handler\CurlMultiHandler;
use GuzzleHttp\Handler\Proxy;
use GuzzleHttp\Handler\StreamHandler;
use Psr\Http\Message\UriInterface;

final class Utils
{
    /**
     * Debug function used to describe the provided value type and class.
     *
     * @param mixed $input
     *
     * @return string Returns a string containing the type of the variable and
     *                if a class is provided, the class name.
     */
    public static function describeType($input): string
    {
        switch (\gettype($input)) {
            case 'object':
                return 'object('.\get_class($input).')';
            case 'array':
                return 'array('.\count($input).')';
            default:
                \ob_start();
                \var_dump($input);
                // normalize float vs double
                /** @var string $varDumpContent */
                $varDumpContent = \ob_get_clean();

                return \str_replace('double(', 'float(', \rtrim($varDumpContent));
        }
    }

    /**
     * Parses an array of header lines into an associative array of headers.
     *
     * @param iterable $lines Header lines array of strings in the following
     *                        format: "Name: Value"
     */
    public static function headersFromLines(iterable $lines): array
    {
        $headers = [];

        foreach ($lines as $line) {
            $parts = \explode(':', $line, 2);
            $headers[\trim($parts[0])][] = isset($parts[1]) ? \trim($parts[1]) : null;
        }

        return $headers;
    }

    /**
     * Returns a debug stream based on the provided variable.
     *
     * @param mixed $value Optional value
     *
     * @return resource
     */
    public static function debugResource($value = null)
    {
        if (\is_resource($value)) {
            return $value;
        }
        if (\defined('STDOUT')) {
            return \STDOUT;
        }

        return \GuzzleHttp\Psr7\Utils::tryFopen('php://output', 'w');
    }

    /**
     * Chooses and creates a default handler to use based on the environment.
     *
     * The returned handler is not wrapped by any default middlewares.
     *
     * @return callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface Returns the best handler for the given system.
     *
     * @throws \RuntimeException if no viable Handler is available.
     */
    public static function chooseHandler(): callable
    {
        $handler = null;

        if (\defined('CURLOPT_CUSTOMREQUEST')) {
            if (\function_exists('curl_multi_exec') && \function_exists('curl_exec')) {
                $handler = Proxy::wrapSync(new CurlMultiHandler(), new CurlHandler());
            } elseif (\function_exists('curl_exec')) {
                $handler = new CurlHandler();
            } elseif (\function_exists('curl_multi_exec')) {
                $handler = new CurlMultiHandler();
            }
        }

        if (\ini_get('allow_url_fopen')) {
            $handler = $handler
                ? Proxy::wrapStreaming($handler, new StreamHandler())
                : new StreamHandler();
        } elseif (!$handler) {
            throw new \RuntimeException('GuzzleHttp requires cURL, the allow_url_fopen ini setting, or a custom HTTP handler.');
        }

        return $handler;
    }

    /**
     * Get the default User-Agent string to use with Guzzle.
     */
    public static function defaultUserAgent(): string
    {
        return sprintf('GuzzleHttp/%d', ClientInterface::MAJOR_VERSION);
    }

    /**
     * Returns the default cacert bundle for the current system.
     *
     * First, the openssl.cafile and curl.cainfo php.ini settings are checked.
     * If those settings are not configured, then the common locations for
     * bundles found on Red Hat, CentOS, Fedora, Ubuntu, Debian, FreeBSD, OS X
     * and Windows are checked. If any of these file locations are found on
     * disk, they will be utilized.
     *
     * Note: the result of this function is cached for subsequent calls.
     *
     * @throws \RuntimeException if no bundle can be found.
     *
     * @deprecated Utils::defaultCaBundle will be removed in guzzlehttp/guzzle:8.0. This method is not needed in PHP 5.6+.
     */
    public static function defaultCaBundle(): string
    {
        static $cached = null;
        static $cafiles = [
            // Red Hat, CentOS, Fedora (provided by the ca-certificates package)
            '/etc/pki/tls/certs/ca-bundle.crt',
            // Ubuntu, Debian (provided by the ca-certificates package)
            '/etc/ssl/certs/ca-certificates.crt',
            // FreeBSD (provided by the ca_root_nss package)
            '/usr/local/share/certs/ca-root-nss.crt',
            // SLES 12 (provided by the ca-certificates package)
            '/var/lib/ca-certificates/ca-bundle.pem',
            // OS X provided by homebrew (using the default path)
            '/usr/local/etc/openssl/cert.pem',
            // Google app engine
            '/etc/ca-certificates.crt',
            // Windows?
            'C:\\windows\\system32\\curl-ca-bundle.crt',
            'C:\\windows\\curl-ca-bundle.crt',
        ];

        if ($cached) {
            return $cached;
        }

        if ($ca = \ini_get('openssl.cafile')) {
            return $cached = $ca;
        }

        if ($ca = \ini_get('curl.cainfo')) {
            return $cached = $ca;
        }

        foreach ($cafiles as $filename) {
            if (\file_exists($filename)) {
                return $cached = $filename;
            }
        }

        throw new \RuntimeException(
            <<< EOT
No system CA bundle could be found in any of the the common system locations.
PHP versions earlier than 5.6 are not properly configured to use the system's
CA bundle by default. In order to verify peer certificates, you will need to
supply the path on disk to a certificate bundle to the 'verify' request
option: https://docs.guzzlephp.org/en/latest/request-options.html#verify. If
you do not need a specific certificate bundle, then Mozilla provides a commonly
used CA bundle which can be downloaded here (provided by the maintainer of
cURL): https://curl.haxx.se/ca/cacert.pem. Once you have a CA bundle available
on disk, you can set the 'openssl.cafile' PHP ini setting to point to the path
to the file, allowing you to omit the 'verify' request option. See
https://curl.haxx.se/docs/sslcerts.html for more information.
EOT
        );
    }

    /**
     * Creates an associative array of lowercase header names to the actual
     * header casing.
     */
    public static function normalizeHeaderKeys(array $headers): array
    {
        $result = [];
        foreach (\array_keys($headers) as $key) {
            $result[\strtolower($key)] = $key;
        }

        return $result;
    }

    /**
     * Returns true if the provided host matches any of the no proxy areas.
     *
     * This method will strip a port from the host if it is present. Each pattern
     * can be matched with an exact match (e.g., "foo.com" == "foo.com") or a
     * partial match: (e.g., "foo.com" == "baz.foo.com" and ".foo.com" ==
     * "baz.foo.com", but ".foo.com" != "foo.com").
     *
     * Areas are matched in the following cases:
     * 1. "*" (without quotes) always matches any hosts.
     * 2. An exact match.
     * 3. The area starts with "." and the area is the last part of the host. e.g.
     *    '.mit.edu' will match any host that ends with '.mit.edu'.
     *
     * @param string   $host         Host to check against the patterns.
     * @param string[] $noProxyArray An array of host patterns.
     *
     * @throws InvalidArgumentException
     */
    public static function isHostInNoProxy(string $host, array $noProxyArray): bool
    {
        if (\strlen($host) === 0) {
            throw new InvalidArgumentException('Empty host provided');
        }

        // Strip port if present.
        [$host] = \explode(':', $host, 2);

        foreach ($noProxyArray as $area) {
            // Always match on wildcards.
            if ($area === '*') {
                return true;
            }

            if (empty($area)) {
                // Don't match on empty values.
                continue;
            }

            if ($area === $host) {
                // Exact matches.
                return true;
            }
            // Special match if the area when prefixed with ".". Remove any
            // existing leading "." and add a new leading ".".
            $area = '.'.\ltrim($area, '.');
            if (\substr($host, -\strlen($area)) === $area) {
                return true;
            }
        }

        return false;
    }

    /**
     * Wrapper for json_decode that throws when an error occurs.
     *
     * @param string $json    JSON data to parse
     * @param bool   $assoc   When true, returned objects will be converted
     *                        into associative arrays.
     * @param int    $depth   User specified recursion depth.
     * @param int    $options Bitmask of JSON decode options.
     *
     * @return object|array|string|int|float|bool|null
     *
     * @throws InvalidArgumentException if the JSON cannot be decoded.
     *
     * @see https://www.php.net/manual/en/function.json-decode.php
     */
    public static function jsonDecode(string $json, bool $assoc = false, int $depth = 512, int $options = 0)
    {
        $data = \json_decode($json, $assoc, $depth, $options);
        if (\JSON_ERROR_NONE !== \json_last_error()) {
            throw new InvalidArgumentException('json_decode error: '.\json_last_error_msg());
        }

        return $data;
    }

    /**
     * Wrapper for JSON encoding that throws when an error occurs.
     *
     * @param mixed $value   The value being encoded
     * @param int   $options JSON encode option bitmask
     * @param int   $depth   Set the maximum depth. Must be greater than zero.
     *
     * @throws InvalidArgumentException if the JSON cannot be encoded.
     *
     * @see https://www.php.net/manual/en/function.json-encode.php
     */
    public static function jsonEncode($value, int $options = 0, int $depth = 512): string
    {
        $json = \json_encode($value, $options, $depth);
        if (\JSON_ERROR_NONE !== \json_last_error()) {
            throw new InvalidArgumentException('json_encode error: '.\json_last_error_msg());
        }

        /** @var string */
        return $json;
    }

    /**
     * Wrapper for the hrtime() or microtime() functions
     * (depending on the PHP version, one of the two is used)
     *
     * @return float UNIX timestamp
     *
     * @internal
     */
    public static function currentTime(): float
    {
        return (float) \function_exists('hrtime') ? \hrtime(true) / 1e9 : \microtime(true);
    }

    /**
     * @throws InvalidArgumentException
     *
     * @internal
     */
    public static function idnUriConvert(UriInterface $uri, int $options = 0): UriInterface
    {
        if ($uri->getHost()) {
            $asciiHost = self::idnToAsci($uri->getHost(), $options, $info);
            if ($asciiHost === false) {
                $errorBitSet = $info['errors'] ?? 0;

                $errorConstants = array_filter(array_keys(get_defined_constants()), static function (string $name): bool {
                    return substr($name, 0, 11) === 'IDNA_ERROR_';
                });

                $errors = [];
                foreach ($errorConstants as $errorConstant) {
                    if ($errorBitSet & constant($errorConstant)) {
                        $errors[] = $errorConstant;
                    }
                }

                $errorMessage = 'IDN conversion failed';
                if ($errors) {
                    $errorMessage .= ' (errors: '.implode(', ', $errors).')';
                }

                throw new InvalidArgumentException($errorMessage);
            }
            if ($uri->getHost() !== $asciiHost) {
                // Replace URI only if the ASCII version is different
                $uri = $uri->withHost($asciiHost);
            }
        }

        return $uri;
    }

    /**
     * @internal
     */
    public static function getenv(string $name): ?string
    {
        if (isset($_SERVER[$name])) {
            return (string) $_SERVER[$name];
        }

        if (\PHP_SAPI === 'cli' && ($value = \getenv($name)) !== false && $value !== null) {
            return (string) $value;
        }

        return null;
    }

    /**
     * @return string|false
     */
    private static function idnToAsci(string $domain, int $options, ?array &$info = [])
    {
        if (\function_exists('idn_to_ascii') && \defined('INTL_IDNA_VARIANT_UTS46')) {
            return \idn_to_ascii($domain, $options, \INTL_IDNA_VARIANT_UTS46, $info);
        }

        throw new \Error('ext-idn or symfony/polyfill-intl-idn not loaded or too old');
    }
}
PK�c�\�^:ww(guzzlehttp/guzzle/src/BodySummarizer.phpnu�[���<?php

namespace GuzzleHttp;

use Psr\Http\Message\MessageInterface;

final class BodySummarizer implements BodySummarizerInterface
{
    /**
     * @var int|null
     */
    private $truncateAt;

    public function __construct(int $truncateAt = null)
    {
        $this->truncateAt = $truncateAt;
    }

    /**
     * Returns a summarized message body.
     */
    public function summarize(MessageInterface $message): ?string
    {
        return $this->truncateAt === null
            ? \GuzzleHttp\Psr7\Message::bodySummary($message)
            : \GuzzleHttp\Psr7\Message::bodySummary($message, $this->truncateAt);
    }
}
PK�c�\��Vx.#.#%guzzlehttp/guzzle/src/ClientTrait.phpnu�[���<?php

namespace GuzzleHttp;

use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Promise\PromiseInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UriInterface;

/**
 * Client interface for sending HTTP requests.
 */
trait ClientTrait
{
    /**
     * Create and send an HTTP request.
     *
     * Use an absolute path to override the base path of the client, or a
     * relative path to append to the base path of the client. The URL can
     * contain the query string as well.
     *
     * @param string              $method  HTTP method.
     * @param string|UriInterface $uri     URI object or string.
     * @param array               $options Request options to apply.
     *
     * @throws GuzzleException
     */
    abstract public function request(string $method, $uri, array $options = []): ResponseInterface;

    /**
     * Create and send an HTTP GET request.
     *
     * Use an absolute path to override the base path of the client, or a
     * relative path to append to the base path of the client. The URL can
     * contain the query string as well.
     *
     * @param string|UriInterface $uri     URI object or string.
     * @param array               $options Request options to apply.
     *
     * @throws GuzzleException
     */
    public function get($uri, array $options = []): ResponseInterface
    {
        return $this->request('GET', $uri, $options);
    }

    /**
     * Create and send an HTTP HEAD request.
     *
     * Use an absolute path to override the base path of the client, or a
     * relative path to append to the base path of the client. The URL can
     * contain the query string as well.
     *
     * @param string|UriInterface $uri     URI object or string.
     * @param array               $options Request options to apply.
     *
     * @throws GuzzleException
     */
    public function head($uri, array $options = []): ResponseInterface
    {
        return $this->request('HEAD', $uri, $options);
    }

    /**
     * Create and send an HTTP PUT request.
     *
     * Use an absolute path to override the base path of the client, or a
     * relative path to append to the base path of the client. The URL can
     * contain the query string as well.
     *
     * @param string|UriInterface $uri     URI object or string.
     * @param array               $options Request options to apply.
     *
     * @throws GuzzleException
     */
    public function put($uri, array $options = []): ResponseInterface
    {
        return $this->request('PUT', $uri, $options);
    }

    /**
     * Create and send an HTTP POST request.
     *
     * Use an absolute path to override the base path of the client, or a
     * relative path to append to the base path of the client. The URL can
     * contain the query string as well.
     *
     * @param string|UriInterface $uri     URI object or string.
     * @param array               $options Request options to apply.
     *
     * @throws GuzzleException
     */
    public function post($uri, array $options = []): ResponseInterface
    {
        return $this->request('POST', $uri, $options);
    }

    /**
     * Create and send an HTTP PATCH request.
     *
     * Use an absolute path to override the base path of the client, or a
     * relative path to append to the base path of the client. The URL can
     * contain the query string as well.
     *
     * @param string|UriInterface $uri     URI object or string.
     * @param array               $options Request options to apply.
     *
     * @throws GuzzleException
     */
    public function patch($uri, array $options = []): ResponseInterface
    {
        return $this->request('PATCH', $uri, $options);
    }

    /**
     * Create and send an HTTP DELETE request.
     *
     * Use an absolute path to override the base path of the client, or a
     * relative path to append to the base path of the client. The URL can
     * contain the query string as well.
     *
     * @param string|UriInterface $uri     URI object or string.
     * @param array               $options Request options to apply.
     *
     * @throws GuzzleException
     */
    public function delete($uri, array $options = []): ResponseInterface
    {
        return $this->request('DELETE', $uri, $options);
    }

    /**
     * Create and send an asynchronous HTTP request.
     *
     * Use an absolute path to override the base path of the client, or a
     * relative path to append to the base path of the client. The URL can
     * contain the query string as well. Use an array to provide a URL
     * template and additional variables to use in the URL template expansion.
     *
     * @param string              $method  HTTP method
     * @param string|UriInterface $uri     URI object or string.
     * @param array               $options Request options to apply.
     */
    abstract public function requestAsync(string $method, $uri, array $options = []): PromiseInterface;

    /**
     * Create and send an asynchronous HTTP GET request.
     *
     * Use an absolute path to override the base path of the client, or a
     * relative path to append to the base path of the client. The URL can
     * contain the query string as well. Use an array to provide a URL
     * template and additional variables to use in the URL template expansion.
     *
     * @param string|UriInterface $uri     URI object or string.
     * @param array               $options Request options to apply.
     */
    public function getAsync($uri, array $options = []): PromiseInterface
    {
        return $this->requestAsync('GET', $uri, $options);
    }

    /**
     * Create and send an asynchronous HTTP HEAD request.
     *
     * Use an absolute path to override the base path of the client, or a
     * relative path to append to the base path of the client. The URL can
     * contain the query string as well. Use an array to provide a URL
     * template and additional variables to use in the URL template expansion.
     *
     * @param string|UriInterface $uri     URI object or string.
     * @param array               $options Request options to apply.
     */
    public function headAsync($uri, array $options = []): PromiseInterface
    {
        return $this->requestAsync('HEAD', $uri, $options);
    }

    /**
     * Create and send an asynchronous HTTP PUT request.
     *
     * Use an absolute path to override the base path of the client, or a
     * relative path to append to the base path of the client. The URL can
     * contain the query string as well. Use an array to provide a URL
     * template and additional variables to use in the URL template expansion.
     *
     * @param string|UriInterface $uri     URI object or string.
     * @param array               $options Request options to apply.
     */
    public function putAsync($uri, array $options = []): PromiseInterface
    {
        return $this->requestAsync('PUT', $uri, $options);
    }

    /**
     * Create and send an asynchronous HTTP POST request.
     *
     * Use an absolute path to override the base path of the client, or a
     * relative path to append to the base path of the client. The URL can
     * contain the query string as well. Use an array to provide a URL
     * template and additional variables to use in the URL template expansion.
     *
     * @param string|UriInterface $uri     URI object or string.
     * @param array               $options Request options to apply.
     */
    public function postAsync($uri, array $options = []): PromiseInterface
    {
        return $this->requestAsync('POST', $uri, $options);
    }

    /**
     * Create and send an asynchronous HTTP PATCH request.
     *
     * Use an absolute path to override the base path of the client, or a
     * relative path to append to the base path of the client. The URL can
     * contain the query string as well. Use an array to provide a URL
     * template and additional variables to use in the URL template expansion.
     *
     * @param string|UriInterface $uri     URI object or string.
     * @param array               $options Request options to apply.
     */
    public function patchAsync($uri, array $options = []): PromiseInterface
    {
        return $this->requestAsync('PATCH', $uri, $options);
    }

    /**
     * Create and send an asynchronous HTTP DELETE request.
     *
     * Use an absolute path to override the base path of the client, or a
     * relative path to append to the base path of the client. The URL can
     * contain the query string as well. Use an array to provide a URL
     * template and additional variables to use in the URL template expansion.
     *
     * @param string|UriInterface $uri     URI object or string.
     * @param array               $options Request options to apply.
     */
    public function deleteAsync($uri, array $options = []): PromiseInterface
    {
        return $this->requestAsync('DELETE', $uri, $options);
    }
}
PK�c�\�F�j��3guzzlehttp/guzzle/src/Exception/ServerException.phpnu�[���<?php

namespace GuzzleHttp\Exception;

/**
 * Exception when a server error is encountered (5xx codes)
 */
class ServerException extends BadResponseException
{
}
PK�c�\�N�
��4guzzlehttp/guzzle/src/Exception/ConnectException.phpnu�[���<?php

namespace GuzzleHttp\Exception;

use Psr\Http\Client\NetworkExceptionInterface;
use Psr\Http\Message\RequestInterface;

/**
 * Exception thrown when a connection cannot be established.
 *
 * Note that no response is present for a ConnectException
 */
class ConnectException extends TransferException implements NetworkExceptionInterface
{
    /**
     * @var RequestInterface
     */
    private $request;

    /**
     * @var array
     */
    private $handlerContext;

    public function __construct(
        string $message,
        RequestInterface $request,
        \Throwable $previous = null,
        array $handlerContext = []
    ) {
        parent::__construct($message, 0, $previous);
        $this->request = $request;
        $this->handlerContext = $handlerContext;
    }

    /**
     * Get the request that caused the exception
     */
    public function getRequest(): RequestInterface
    {
        return $this->request;
    }

    /**
     * Get contextual information about the error from the underlying handler.
     *
     * The contents of this array will vary depending on which handler you are
     * using. It may also be just an empty array. Relying on this data will
     * couple you to a specific handler, but can give more debug information
     * when needed.
     */
    public function getHandlerContext(): array
    {
        return $this->handlerContext;
    }
}
PK�c�\5O\$��3guzzlehttp/guzzle/src/Exception/GuzzleException.phpnu�[���<?php

namespace GuzzleHttp\Exception;

use Psr\Http\Client\ClientExceptionInterface;

interface GuzzleException extends ClientExceptionInterface
{
}
PK�c�\c�o���<guzzlehttp/guzzle/src/Exception/InvalidArgumentException.phpnu�[���<?php

namespace GuzzleHttp\Exception;

final class InvalidArgumentException extends \InvalidArgumentException implements GuzzleException
{
}
PK�c�\��/�yy5guzzlehttp/guzzle/src/Exception/TransferException.phpnu�[���<?php

namespace GuzzleHttp\Exception;

class TransferException extends \RuntimeException implements GuzzleException
{
}
PK�c�\0؜���4guzzlehttp/guzzle/src/Exception/RequestException.phpnu�[���<?php

namespace GuzzleHttp\Exception;

use GuzzleHttp\BodySummarizer;
use GuzzleHttp\BodySummarizerInterface;
use Psr\Http\Client\RequestExceptionInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UriInterface;

/**
 * HTTP Request exception
 */
class RequestException extends TransferException implements RequestExceptionInterface
{
    /**
     * @var RequestInterface
     */
    private $request;

    /**
     * @var ResponseInterface|null
     */
    private $response;

    /**
     * @var array
     */
    private $handlerContext;

    public function __construct(
        string $message,
        RequestInterface $request,
        ResponseInterface $response = null,
        \Throwable $previous = null,
        array $handlerContext = []
    ) {
        // Set the code of the exception if the response is set and not future.
        $code = $response ? $response->getStatusCode() : 0;
        parent::__construct($message, $code, $previous);
        $this->request = $request;
        $this->response = $response;
        $this->handlerContext = $handlerContext;
    }

    /**
     * Wrap non-RequestExceptions with a RequestException
     */
    public static function wrapException(RequestInterface $request, \Throwable $e): RequestException
    {
        return $e instanceof RequestException ? $e : new RequestException($e->getMessage(), $request, null, $e);
    }

    /**
     * Factory method to create a new exception with a normalized error message
     *
     * @param RequestInterface             $request        Request sent
     * @param ResponseInterface            $response       Response received
     * @param \Throwable|null              $previous       Previous exception
     * @param array                        $handlerContext Optional handler context
     * @param BodySummarizerInterface|null $bodySummarizer Optional body summarizer
     */
    public static function create(
        RequestInterface $request,
        ResponseInterface $response = null,
        \Throwable $previous = null,
        array $handlerContext = [],
        BodySummarizerInterface $bodySummarizer = null
    ): self {
        if (!$response) {
            return new self(
                'Error completing request',
                $request,
                null,
                $previous,
                $handlerContext
            );
        }

        $level = (int) \floor($response->getStatusCode() / 100);
        if ($level === 4) {
            $label = 'Client error';
            $className = ClientException::class;
        } elseif ($level === 5) {
            $label = 'Server error';
            $className = ServerException::class;
        } else {
            $label = 'Unsuccessful request';
            $className = __CLASS__;
        }

        $uri = $request->getUri();
        $uri = static::obfuscateUri($uri);

        // Client Error: `GET /` resulted in a `404 Not Found` response:
        // <html> ... (truncated)
        $message = \sprintf(
            '%s: `%s %s` resulted in a `%s %s` response',
            $label,
            $request->getMethod(),
            $uri->__toString(),
            $response->getStatusCode(),
            $response->getReasonPhrase()
        );

        $summary = ($bodySummarizer ?? new BodySummarizer())->summarize($response);

        if ($summary !== null) {
            $message .= ":\n{$summary}\n";
        }

        return new $className($message, $request, $response, $previous, $handlerContext);
    }

    /**
     * Obfuscates URI if there is a username and a password present
     */
    private static function obfuscateUri(UriInterface $uri): UriInterface
    {
        $userInfo = $uri->getUserInfo();

        if (false !== ($pos = \strpos($userInfo, ':'))) {
            return $uri->withUserInfo(\substr($userInfo, 0, $pos), '***');
        }

        return $uri;
    }

    /**
     * Get the request that caused the exception
     */
    public function getRequest(): RequestInterface
    {
        return $this->request;
    }

    /**
     * Get the associated response
     */
    public function getResponse(): ?ResponseInterface
    {
        return $this->response;
    }

    /**
     * Check if a response was received
     */
    public function hasResponse(): bool
    {
        return $this->response !== null;
    }

    /**
     * Get contextual information about the error from the underlying handler.
     *
     * The contents of this array will vary depending on which handler you are
     * using. It may also be just an empty array. Relying on this data will
     * couple you to a specific handler, but can give more debug information
     * when needed.
     */
    public function getHandlerContext(): array
    {
        return $this->handlerContext;
    }
}
PK�c�\j���3guzzlehttp/guzzle/src/Exception/ClientException.phpnu�[���<?php

namespace GuzzleHttp\Exception;

/**
 * Exception when a client error is encountered (4xx codes)
 */
class ClientException extends BadResponseException
{
}
PK�c�\��Om��8guzzlehttp/guzzle/src/Exception/BadResponseException.phpnu�[���<?php

namespace GuzzleHttp\Exception;

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * Exception when an HTTP error occurs (4xx or 5xx error)
 */
class BadResponseException extends RequestException
{
    public function __construct(
        string $message,
        RequestInterface $request,
        ResponseInterface $response,
        \Throwable $previous = null,
        array $handlerContext = []
    ) {
        parent::__construct($message, $request, $response, $previous, $handlerContext);
    }

    /**
     * Current exception and the ones that extend it will always have a response.
     */
    public function hasResponse(): bool
    {
        return true;
    }

    /**
     * This function narrows the return type from the parent class and does not allow it to be nullable.
     */
    public function getResponse(): ResponseInterface
    {
        /** @var ResponseInterface */
        return parent::getResponse();
    }
}
PK�c�\$��ee=guzzlehttp/guzzle/src/Exception/TooManyRedirectsException.phpnu�[���<?php

namespace GuzzleHttp\Exception;

class TooManyRedirectsException extends RequestException
{
}
PK�c�\�t�//3guzzlehttp/guzzle/src/MessageFormatterInterface.phpnu�[���<?php

namespace GuzzleHttp;

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

interface MessageFormatterInterface
{
    /**
     * Returns a formatted message string.
     *
     * @param RequestInterface       $request  Request that was sent
     * @param ResponseInterface|null $response Response that was received
     * @param \Throwable|null        $error    Exception that was received
     */
    public function format(RequestInterface $request, ResponseInterface $response = null, \Throwable $error = null): string;
}
PK�c�\�����1guzzlehttp/guzzle/src/Cookie/SessionCookieJar.phpnu�[���<?php

namespace GuzzleHttp\Cookie;

/**
 * Persists cookies in the client session
 */
class SessionCookieJar extends CookieJar
{
    /**
     * @var string session key
     */
    private $sessionKey;

    /**
     * @var bool Control whether to persist session cookies or not.
     */
    private $storeSessionCookies;

    /**
     * Create a new SessionCookieJar object
     *
     * @param string $sessionKey          Session key name to store the cookie
     *                                    data in session
     * @param bool   $storeSessionCookies Set to true to store session cookies
     *                                    in the cookie jar.
     */
    public function __construct(string $sessionKey, bool $storeSessionCookies = false)
    {
        parent::__construct();
        $this->sessionKey = $sessionKey;
        $this->storeSessionCookies = $storeSessionCookies;
        $this->load();
    }

    /**
     * Saves cookies to session when shutting down
     */
    public function __destruct()
    {
        $this->save();
    }

    /**
     * Save cookies to the client session
     */
    public function save(): void
    {
        $json = [];
        /** @var SetCookie $cookie */
        foreach ($this as $cookie) {
            if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) {
                $json[] = $cookie->toArray();
            }
        }

        $_SESSION[$this->sessionKey] = \json_encode($json);
    }

    /**
     * Load the contents of the client session into the data array
     */
    protected function load(): void
    {
        if (!isset($_SESSION[$this->sessionKey])) {
            return;
        }
        $data = \json_decode($_SESSION[$this->sessionKey], true);
        if (\is_array($data)) {
            foreach ($data as $cookie) {
                $this->setCookie(new SetCookie($cookie));
            }
        } elseif (\strlen($data)) {
            throw new \RuntimeException('Invalid cookie data');
        }
    }
}
PK�c�\(�>%>%*guzzlehttp/guzzle/src/Cookie/CookieJar.phpnu�[���<?php

namespace GuzzleHttp\Cookie;

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * Cookie jar that stores cookies as an array
 */
class CookieJar implements CookieJarInterface
{
    /**
     * @var SetCookie[] Loaded cookie data
     */
    private $cookies = [];

    /**
     * @var bool
     */
    private $strictMode;

    /**
     * @param bool  $strictMode  Set to true to throw exceptions when invalid
     *                           cookies are added to the cookie jar.
     * @param array $cookieArray Array of SetCookie objects or a hash of
     *                           arrays that can be used with the SetCookie
     *                           constructor
     */
    public function __construct(bool $strictMode = false, array $cookieArray = [])
    {
        $this->strictMode = $strictMode;

        foreach ($cookieArray as $cookie) {
            if (!($cookie instanceof SetCookie)) {
                $cookie = new SetCookie($cookie);
            }
            $this->setCookie($cookie);
        }
    }

    /**
     * Create a new Cookie jar from an associative array and domain.
     *
     * @param array  $cookies Cookies to create the jar from
     * @param string $domain  Domain to set the cookies to
     */
    public static function fromArray(array $cookies, string $domain): self
    {
        $cookieJar = new self();
        foreach ($cookies as $name => $value) {
            $cookieJar->setCookie(new SetCookie([
                'Domain' => $domain,
                'Name' => $name,
                'Value' => $value,
                'Discard' => true,
            ]));
        }

        return $cookieJar;
    }

    /**
     * Evaluate if this cookie should be persisted to storage
     * that survives between requests.
     *
     * @param SetCookie $cookie              Being evaluated.
     * @param bool      $allowSessionCookies If we should persist session cookies
     */
    public static function shouldPersist(SetCookie $cookie, bool $allowSessionCookies = false): bool
    {
        if ($cookie->getExpires() || $allowSessionCookies) {
            if (!$cookie->getDiscard()) {
                return true;
            }
        }

        return false;
    }

    /**
     * Finds and returns the cookie based on the name
     *
     * @param string $name cookie name to search for
     *
     * @return SetCookie|null cookie that was found or null if not found
     */
    public function getCookieByName(string $name): ?SetCookie
    {
        foreach ($this->cookies as $cookie) {
            if ($cookie->getName() !== null && \strcasecmp($cookie->getName(), $name) === 0) {
                return $cookie;
            }
        }

        return null;
    }

    public function toArray(): array
    {
        return \array_map(static function (SetCookie $cookie): array {
            return $cookie->toArray();
        }, $this->getIterator()->getArrayCopy());
    }

    public function clear(string $domain = null, string $path = null, string $name = null): void
    {
        if (!$domain) {
            $this->cookies = [];

            return;
        } elseif (!$path) {
            $this->cookies = \array_filter(
                $this->cookies,
                static function (SetCookie $cookie) use ($domain): bool {
                    return !$cookie->matchesDomain($domain);
                }
            );
        } elseif (!$name) {
            $this->cookies = \array_filter(
                $this->cookies,
                static function (SetCookie $cookie) use ($path, $domain): bool {
                    return !($cookie->matchesPath($path)
                        && $cookie->matchesDomain($domain));
                }
            );
        } else {
            $this->cookies = \array_filter(
                $this->cookies,
                static function (SetCookie $cookie) use ($path, $domain, $name) {
                    return !($cookie->getName() == $name
                        && $cookie->matchesPath($path)
                        && $cookie->matchesDomain($domain));
                }
            );
        }
    }

    public function clearSessionCookies(): void
    {
        $this->cookies = \array_filter(
            $this->cookies,
            static function (SetCookie $cookie): bool {
                return !$cookie->getDiscard() && $cookie->getExpires();
            }
        );
    }

    public function setCookie(SetCookie $cookie): bool
    {
        // If the name string is empty (but not 0), ignore the set-cookie
        // string entirely.
        $name = $cookie->getName();
        if (!$name && $name !== '0') {
            return false;
        }

        // Only allow cookies with set and valid domain, name, value
        $result = $cookie->validate();
        if ($result !== true) {
            if ($this->strictMode) {
                throw new \RuntimeException('Invalid cookie: '.$result);
            }
            $this->removeCookieIfEmpty($cookie);

            return false;
        }

        // Resolve conflicts with previously set cookies
        foreach ($this->cookies as $i => $c) {
            // Two cookies are identical, when their path, and domain are
            // identical.
            if ($c->getPath() != $cookie->getPath()
                || $c->getDomain() != $cookie->getDomain()
                || $c->getName() != $cookie->getName()
            ) {
                continue;
            }

            // The previously set cookie is a discard cookie and this one is
            // not so allow the new cookie to be set
            if (!$cookie->getDiscard() && $c->getDiscard()) {
                unset($this->cookies[$i]);
                continue;
            }

            // If the new cookie's expiration is further into the future, then
            // replace the old cookie
            if ($cookie->getExpires() > $c->getExpires()) {
                unset($this->cookies[$i]);
                continue;
            }

            // If the value has changed, we better change it
            if ($cookie->getValue() !== $c->getValue()) {
                unset($this->cookies[$i]);
                continue;
            }

            // The cookie exists, so no need to continue
            return false;
        }

        $this->cookies[] = $cookie;

        return true;
    }

    public function count(): int
    {
        return \count($this->cookies);
    }

    /**
     * @return \ArrayIterator<int, SetCookie>
     */
    public function getIterator(): \ArrayIterator
    {
        return new \ArrayIterator(\array_values($this->cookies));
    }

    public function extractCookies(RequestInterface $request, ResponseInterface $response): void
    {
        if ($cookieHeader = $response->getHeader('Set-Cookie')) {
            foreach ($cookieHeader as $cookie) {
                $sc = SetCookie::fromString($cookie);
                if (!$sc->getDomain()) {
                    $sc->setDomain($request->getUri()->getHost());
                }
                if (0 !== \strpos($sc->getPath(), '/')) {
                    $sc->setPath($this->getCookiePathFromRequest($request));
                }
                if (!$sc->matchesDomain($request->getUri()->getHost())) {
                    continue;
                }
                // Note: At this point `$sc->getDomain()` being a public suffix should
                // be rejected, but we don't want to pull in the full PSL dependency.
                $this->setCookie($sc);
            }
        }
    }

    /**
     * Computes cookie path following RFC 6265 section 5.1.4
     *
     * @see https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.4
     */
    private function getCookiePathFromRequest(RequestInterface $request): string
    {
        $uriPath = $request->getUri()->getPath();
        if ('' === $uriPath) {
            return '/';
        }
        if (0 !== \strpos($uriPath, '/')) {
            return '/';
        }
        if ('/' === $uriPath) {
            return '/';
        }
        $lastSlashPos = \strrpos($uriPath, '/');
        if (0 === $lastSlashPos || false === $lastSlashPos) {
            return '/';
        }

        return \substr($uriPath, 0, $lastSlashPos);
    }

    public function withCookieHeader(RequestInterface $request): RequestInterface
    {
        $values = [];
        $uri = $request->getUri();
        $scheme = $uri->getScheme();
        $host = $uri->getHost();
        $path = $uri->getPath() ?: '/';

        foreach ($this->cookies as $cookie) {
            if ($cookie->matchesPath($path)
                && $cookie->matchesDomain($host)
                && !$cookie->isExpired()
                && (!$cookie->getSecure() || $scheme === 'https')
            ) {
                $values[] = $cookie->getName().'='
                    .$cookie->getValue();
            }
        }

        return $values
            ? $request->withHeader('Cookie', \implode('; ', $values))
            : $request;
    }

    /**
     * If a cookie already exists and the server asks to set it again with a
     * null value, the cookie must be deleted.
     */
    private function removeCookieIfEmpty(SetCookie $cookie): void
    {
        $cookieValue = $cookie->getValue();
        if ($cookieValue === null || $cookieValue === '') {
            $this->clear(
                $cookie->getDomain(),
                $cookie->getPath(),
                $cookie->getName()
            );
        }
    }
}
PK�c�\_���7�7*guzzlehttp/guzzle/src/Cookie/SetCookie.phpnu�[���<?php

namespace GuzzleHttp\Cookie;

/**
 * Set-Cookie object
 */
class SetCookie
{
    /**
     * @var array
     */
    private static $defaults = [
        'Name' => null,
        'Value' => null,
        'Domain' => null,
        'Path' => '/',
        'Max-Age' => null,
        'Expires' => null,
        'Secure' => false,
        'Discard' => false,
        'HttpOnly' => false,
    ];

    /**
     * @var array Cookie data
     */
    private $data;

    /**
     * Create a new SetCookie object from a string.
     *
     * @param string $cookie Set-Cookie header string
     */
    public static function fromString(string $cookie): self
    {
        // Create the default return array
        $data = self::$defaults;
        // Explode the cookie string using a series of semicolons
        $pieces = \array_filter(\array_map('trim', \explode(';', $cookie)));
        // The name of the cookie (first kvp) must exist and include an equal sign.
        if (!isset($pieces[0]) || \strpos($pieces[0], '=') === false) {
            return new self($data);
        }

        // Add the cookie pieces into the parsed data array
        foreach ($pieces as $part) {
            $cookieParts = \explode('=', $part, 2);
            $key = \trim($cookieParts[0]);
            $value = isset($cookieParts[1])
                ? \trim($cookieParts[1], " \n\r\t\0\x0B")
                : true;

            // Only check for non-cookies when cookies have been found
            if (!isset($data['Name'])) {
                $data['Name'] = $key;
                $data['Value'] = $value;
            } else {
                foreach (\array_keys(self::$defaults) as $search) {
                    if (!\strcasecmp($search, $key)) {
                        if ($search === 'Max-Age') {
                            if (is_numeric($value)) {
                                $data[$search] = (int) $value;
                            }
                        } else {
                            $data[$search] = $value;
                        }
                        continue 2;
                    }
                }
                $data[$key] = $value;
            }
        }

        return new self($data);
    }

    /**
     * @param array $data Array of cookie data provided by a Cookie parser
     */
    public function __construct(array $data = [])
    {
        $this->data = self::$defaults;

        if (isset($data['Name'])) {
            $this->setName($data['Name']);
        }

        if (isset($data['Value'])) {
            $this->setValue($data['Value']);
        }

        if (isset($data['Domain'])) {
            $this->setDomain($data['Domain']);
        }

        if (isset($data['Path'])) {
            $this->setPath($data['Path']);
        }

        if (isset($data['Max-Age'])) {
            $this->setMaxAge($data['Max-Age']);
        }

        if (isset($data['Expires'])) {
            $this->setExpires($data['Expires']);
        }

        if (isset($data['Secure'])) {
            $this->setSecure($data['Secure']);
        }

        if (isset($data['Discard'])) {
            $this->setDiscard($data['Discard']);
        }

        if (isset($data['HttpOnly'])) {
            $this->setHttpOnly($data['HttpOnly']);
        }

        // Set the remaining values that don't have extra validation logic
        foreach (array_diff(array_keys($data), array_keys(self::$defaults)) as $key) {
            $this->data[$key] = $data[$key];
        }

        // Extract the Expires value and turn it into a UNIX timestamp if needed
        if (!$this->getExpires() && $this->getMaxAge()) {
            // Calculate the Expires date
            $this->setExpires(\time() + $this->getMaxAge());
        } elseif (null !== ($expires = $this->getExpires()) && !\is_numeric($expires)) {
            $this->setExpires($expires);
        }
    }

    public function __toString()
    {
        $str = $this->data['Name'].'='.($this->data['Value'] ?? '').'; ';
        foreach ($this->data as $k => $v) {
            if ($k !== 'Name' && $k !== 'Value' && $v !== null && $v !== false) {
                if ($k === 'Expires') {
                    $str .= 'Expires='.\gmdate('D, d M Y H:i:s \G\M\T', $v).'; ';
                } else {
                    $str .= ($v === true ? $k : "{$k}={$v}").'; ';
                }
            }
        }

        return \rtrim($str, '; ');
    }

    public function toArray(): array
    {
        return $this->data;
    }

    /**
     * Get the cookie name.
     *
     * @return string
     */
    public function getName()
    {
        return $this->data['Name'];
    }

    /**
     * Set the cookie name.
     *
     * @param string $name Cookie name
     */
    public function setName($name): void
    {
        if (!is_string($name)) {
            trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
        }

        $this->data['Name'] = (string) $name;
    }

    /**
     * Get the cookie value.
     *
     * @return string|null
     */
    public function getValue()
    {
        return $this->data['Value'];
    }

    /**
     * Set the cookie value.
     *
     * @param string $value Cookie value
     */
    public function setValue($value): void
    {
        if (!is_string($value)) {
            trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
        }

        $this->data['Value'] = (string) $value;
    }

    /**
     * Get the domain.
     *
     * @return string|null
     */
    public function getDomain()
    {
        return $this->data['Domain'];
    }

    /**
     * Set the domain of the cookie.
     *
     * @param string|null $domain
     */
    public function setDomain($domain): void
    {
        if (!is_string($domain) && null !== $domain) {
            trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string or null to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
        }

        $this->data['Domain'] = null === $domain ? null : (string) $domain;
    }

    /**
     * Get the path.
     *
     * @return string
     */
    public function getPath()
    {
        return $this->data['Path'];
    }

    /**
     * Set the path of the cookie.
     *
     * @param string $path Path of the cookie
     */
    public function setPath($path): void
    {
        if (!is_string($path)) {
            trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
        }

        $this->data['Path'] = (string) $path;
    }

    /**
     * Maximum lifetime of the cookie in seconds.
     *
     * @return int|null
     */
    public function getMaxAge()
    {
        return null === $this->data['Max-Age'] ? null : (int) $this->data['Max-Age'];
    }

    /**
     * Set the max-age of the cookie.
     *
     * @param int|null $maxAge Max age of the cookie in seconds
     */
    public function setMaxAge($maxAge): void
    {
        if (!is_int($maxAge) && null !== $maxAge) {
            trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing an int or null to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
        }

        $this->data['Max-Age'] = $maxAge === null ? null : (int) $maxAge;
    }

    /**
     * The UNIX timestamp when the cookie Expires.
     *
     * @return string|int|null
     */
    public function getExpires()
    {
        return $this->data['Expires'];
    }

    /**
     * Set the unix timestamp for which the cookie will expire.
     *
     * @param int|string|null $timestamp Unix timestamp or any English textual datetime description.
     */
    public function setExpires($timestamp): void
    {
        if (!is_int($timestamp) && !is_string($timestamp) && null !== $timestamp) {
            trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing an int, string or null to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
        }

        $this->data['Expires'] = null === $timestamp ? null : (\is_numeric($timestamp) ? (int) $timestamp : \strtotime((string) $timestamp));
    }

    /**
     * Get whether or not this is a secure cookie.
     *
     * @return bool
     */
    public function getSecure()
    {
        return $this->data['Secure'];
    }

    /**
     * Set whether or not the cookie is secure.
     *
     * @param bool $secure Set to true or false if secure
     */
    public function setSecure($secure): void
    {
        if (!is_bool($secure)) {
            trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a bool to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
        }

        $this->data['Secure'] = (bool) $secure;
    }

    /**
     * Get whether or not this is a session cookie.
     *
     * @return bool|null
     */
    public function getDiscard()
    {
        return $this->data['Discard'];
    }

    /**
     * Set whether or not this is a session cookie.
     *
     * @param bool $discard Set to true or false if this is a session cookie
     */
    public function setDiscard($discard): void
    {
        if (!is_bool($discard)) {
            trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a bool to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
        }

        $this->data['Discard'] = (bool) $discard;
    }

    /**
     * Get whether or not this is an HTTP only cookie.
     *
     * @return bool
     */
    public function getHttpOnly()
    {
        return $this->data['HttpOnly'];
    }

    /**
     * Set whether or not this is an HTTP only cookie.
     *
     * @param bool $httpOnly Set to true or false if this is HTTP only
     */
    public function setHttpOnly($httpOnly): void
    {
        if (!is_bool($httpOnly)) {
            trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a bool to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
        }

        $this->data['HttpOnly'] = (bool) $httpOnly;
    }

    /**
     * Check if the cookie matches a path value.
     *
     * A request-path path-matches a given cookie-path if at least one of
     * the following conditions holds:
     *
     * - The cookie-path and the request-path are identical.
     * - The cookie-path is a prefix of the request-path, and the last
     *   character of the cookie-path is %x2F ("/").
     * - The cookie-path is a prefix of the request-path, and the first
     *   character of the request-path that is not included in the cookie-
     *   path is a %x2F ("/") character.
     *
     * @param string $requestPath Path to check against
     */
    public function matchesPath(string $requestPath): bool
    {
        $cookiePath = $this->getPath();

        // Match on exact matches or when path is the default empty "/"
        if ($cookiePath === '/' || $cookiePath == $requestPath) {
            return true;
        }

        // Ensure that the cookie-path is a prefix of the request path.
        if (0 !== \strpos($requestPath, $cookiePath)) {
            return false;
        }

        // Match if the last character of the cookie-path is "/"
        if (\substr($cookiePath, -1, 1) === '/') {
            return true;
        }

        // Match if the first character not included in cookie path is "/"
        return \substr($requestPath, \strlen($cookiePath), 1) === '/';
    }

    /**
     * Check if the cookie matches a domain value.
     *
     * @param string $domain Domain to check against
     */
    public function matchesDomain(string $domain): bool
    {
        $cookieDomain = $this->getDomain();
        if (null === $cookieDomain) {
            return true;
        }

        // Remove the leading '.' as per spec in RFC 6265.
        // https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.3
        $cookieDomain = \ltrim(\strtolower($cookieDomain), '.');

        $domain = \strtolower($domain);

        // Domain not set or exact match.
        if ('' === $cookieDomain || $domain === $cookieDomain) {
            return true;
        }

        // Matching the subdomain according to RFC 6265.
        // https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.3
        if (\filter_var($domain, \FILTER_VALIDATE_IP)) {
            return false;
        }

        return (bool) \preg_match('/\.'.\preg_quote($cookieDomain, '/').'$/', $domain);
    }

    /**
     * Check if the cookie is expired.
     */
    public function isExpired(): bool
    {
        return $this->getExpires() !== null && \time() > $this->getExpires();
    }

    /**
     * Check if the cookie is valid according to RFC 6265.
     *
     * @return bool|string Returns true if valid or an error message if invalid
     */
    public function validate()
    {
        $name = $this->getName();
        if ($name === '') {
            return 'The cookie name must not be empty';
        }

        // Check if any of the invalid characters are present in the cookie name
        if (\preg_match(
            '/[\x00-\x20\x22\x28-\x29\x2c\x2f\x3a-\x40\x5c\x7b\x7d\x7f]/',
            $name
        )) {
            return 'Cookie name must not contain invalid characters: ASCII '
                .'Control characters (0-31;127), space, tab and the '
                .'following characters: ()<>@,;:\"/?={}';
        }

        // Value must not be null. 0 and empty string are valid. Empty strings
        // are technically against RFC 6265, but known to happen in the wild.
        $value = $this->getValue();
        if ($value === null) {
            return 'The cookie value must not be empty';
        }

        // Domains must not be empty, but can be 0. "0" is not a valid internet
        // domain, but may be used as server name in a private network.
        $domain = $this->getDomain();
        if ($domain === null || $domain === '') {
            return 'The cookie domain must not be empty';
        }

        return true;
    }
}
PK�c�\��Y�
�
.guzzlehttp/guzzle/src/Cookie/FileCookieJar.phpnu�[���<?php

namespace GuzzleHttp\Cookie;

use GuzzleHttp\Utils;

/**
 * Persists non-session cookies using a JSON formatted file
 */
class FileCookieJar extends CookieJar
{
    /**
     * @var string filename
     */
    private $filename;

    /**
     * @var bool Control whether to persist session cookies or not.
     */
    private $storeSessionCookies;

    /**
     * Create a new FileCookieJar object
     *
     * @param string $cookieFile          File to store the cookie data
     * @param bool   $storeSessionCookies Set to true to store session cookies
     *                                    in the cookie jar.
     *
     * @throws \RuntimeException if the file cannot be found or created
     */
    public function __construct(string $cookieFile, bool $storeSessionCookies = false)
    {
        parent::__construct();
        $this->filename = $cookieFile;
        $this->storeSessionCookies = $storeSessionCookies;

        if (\file_exists($cookieFile)) {
            $this->load($cookieFile);
        }
    }

    /**
     * Saves the file when shutting down
     */
    public function __destruct()
    {
        $this->save($this->filename);
    }

    /**
     * Saves the cookies to a file.
     *
     * @param string $filename File to save
     *
     * @throws \RuntimeException if the file cannot be found or created
     */
    public function save(string $filename): void
    {
        $json = [];
        /** @var SetCookie $cookie */
        foreach ($this as $cookie) {
            if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) {
                $json[] = $cookie->toArray();
            }
        }

        $jsonStr = Utils::jsonEncode($json);
        if (false === \file_put_contents($filename, $jsonStr, \LOCK_EX)) {
            throw new \RuntimeException("Unable to save file {$filename}");
        }
    }

    /**
     * Load cookies from a JSON formatted file.
     *
     * Old cookies are kept unless overwritten by newly loaded ones.
     *
     * @param string $filename Cookie file to load.
     *
     * @throws \RuntimeException if the file cannot be loaded.
     */
    public function load(string $filename): void
    {
        $json = \file_get_contents($filename);
        if (false === $json) {
            throw new \RuntimeException("Unable to load file {$filename}");
        }
        if ($json === '') {
            return;
        }

        $data = Utils::jsonDecode($json, true);
        if (\is_array($data)) {
            foreach ($data as $cookie) {
                $this->setCookie(new SetCookie($cookie));
            }
        } elseif (\is_scalar($data) && !empty($data)) {
            throw new \RuntimeException("Invalid cookie file: {$filename}");
        }
    }
}
PK�c�\U��

3guzzlehttp/guzzle/src/Cookie/CookieJarInterface.phpnu�[���<?php

namespace GuzzleHttp\Cookie;

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * Stores HTTP cookies.
 *
 * It extracts cookies from HTTP requests, and returns them in HTTP responses.
 * CookieJarInterface instances automatically expire contained cookies when
 * necessary. Subclasses are also responsible for storing and retrieving
 * cookies from a file, database, etc.
 *
 * @see https://docs.python.org/2/library/cookielib.html Inspiration
 *
 * @extends \IteratorAggregate<SetCookie>
 */
interface CookieJarInterface extends \Countable, \IteratorAggregate
{
    /**
     * Create a request with added cookie headers.
     *
     * If no matching cookies are found in the cookie jar, then no Cookie
     * header is added to the request and the same request is returned.
     *
     * @param RequestInterface $request Request object to modify.
     *
     * @return RequestInterface returns the modified request.
     */
    public function withCookieHeader(RequestInterface $request): RequestInterface;

    /**
     * Extract cookies from an HTTP response and store them in the CookieJar.
     *
     * @param RequestInterface  $request  Request that was sent
     * @param ResponseInterface $response Response that was received
     */
    public function extractCookies(RequestInterface $request, ResponseInterface $response): void;

    /**
     * Sets a cookie in the cookie jar.
     *
     * @param SetCookie $cookie Cookie to set.
     *
     * @return bool Returns true on success or false on failure
     */
    public function setCookie(SetCookie $cookie): bool;

    /**
     * Remove cookies currently held in the cookie jar.
     *
     * Invoking this method without arguments will empty the whole cookie jar.
     * If given a $domain argument only cookies belonging to that domain will
     * be removed. If given a $domain and $path argument, cookies belonging to
     * the specified path within that domain are removed. If given all three
     * arguments, then the cookie with the specified name, path and domain is
     * removed.
     *
     * @param string|null $domain Clears cookies matching a domain
     * @param string|null $path   Clears cookies matching a domain and path
     * @param string|null $name   Clears cookies matching a domain, path, and name
     */
    public function clear(string $domain = null, string $path = null, string $name = null): void;

    /**
     * Discard all sessions cookies.
     *
     * Removes cookies that don't have an expire field or a have a discard
     * field set to true. To be called when the user agent shuts down according
     * to RFC 2965.
     */
    public function clearSessionCookies(): void;

    /**
     * Converts the cookie jar to an array.
     */
    public function toArray(): array;
}
PK�c�\��.88#guzzlehttp/guzzle/src/functions.phpnu�[���<?php

namespace GuzzleHttp;

/**
 * Debug function used to describe the provided value type and class.
 *
 * @param mixed $input Any type of variable to describe the type of. This
 *                     parameter misses a typehint because of that.
 *
 * @return string Returns a string containing the type of the variable and
 *                if a class is provided, the class name.
 *
 * @deprecated describe_type will be removed in guzzlehttp/guzzle:8.0. Use Utils::describeType instead.
 */
function describe_type($input): string
{
    return Utils::describeType($input);
}

/**
 * Parses an array of header lines into an associative array of headers.
 *
 * @param iterable $lines Header lines array of strings in the following
 *                        format: "Name: Value"
 *
 * @deprecated headers_from_lines will be removed in guzzlehttp/guzzle:8.0. Use Utils::headersFromLines instead.
 */
function headers_from_lines(iterable $lines): array
{
    return Utils::headersFromLines($lines);
}

/**
 * Returns a debug stream based on the provided variable.
 *
 * @param mixed $value Optional value
 *
 * @return resource
 *
 * @deprecated debug_resource will be removed in guzzlehttp/guzzle:8.0. Use Utils::debugResource instead.
 */
function debug_resource($value = null)
{
    return Utils::debugResource($value);
}

/**
 * Chooses and creates a default handler to use based on the environment.
 *
 * The returned handler is not wrapped by any default middlewares.
 *
 * @return callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface Returns the best handler for the given system.
 *
 * @throws \RuntimeException if no viable Handler is available.
 *
 * @deprecated choose_handler will be removed in guzzlehttp/guzzle:8.0. Use Utils::chooseHandler instead.
 */
function choose_handler(): callable
{
    return Utils::chooseHandler();
}

/**
 * Get the default User-Agent string to use with Guzzle.
 *
 * @deprecated default_user_agent will be removed in guzzlehttp/guzzle:8.0. Use Utils::defaultUserAgent instead.
 */
function default_user_agent(): string
{
    return Utils::defaultUserAgent();
}

/**
 * Returns the default cacert bundle for the current system.
 *
 * First, the openssl.cafile and curl.cainfo php.ini settings are checked.
 * If those settings are not configured, then the common locations for
 * bundles found on Red Hat, CentOS, Fedora, Ubuntu, Debian, FreeBSD, OS X
 * and Windows are checked. If any of these file locations are found on
 * disk, they will be utilized.
 *
 * Note: the result of this function is cached for subsequent calls.
 *
 * @throws \RuntimeException if no bundle can be found.
 *
 * @deprecated default_ca_bundle will be removed in guzzlehttp/guzzle:8.0. This function is not needed in PHP 5.6+.
 */
function default_ca_bundle(): string
{
    return Utils::defaultCaBundle();
}

/**
 * Creates an associative array of lowercase header names to the actual
 * header casing.
 *
 * @deprecated normalize_header_keys will be removed in guzzlehttp/guzzle:8.0. Use Utils::normalizeHeaderKeys instead.
 */
function normalize_header_keys(array $headers): array
{
    return Utils::normalizeHeaderKeys($headers);
}

/**
 * Returns true if the provided host matches any of the no proxy areas.
 *
 * This method will strip a port from the host if it is present. Each pattern
 * can be matched with an exact match (e.g., "foo.com" == "foo.com") or a
 * partial match: (e.g., "foo.com" == "baz.foo.com" and ".foo.com" ==
 * "baz.foo.com", but ".foo.com" != "foo.com").
 *
 * Areas are matched in the following cases:
 * 1. "*" (without quotes) always matches any hosts.
 * 2. An exact match.
 * 3. The area starts with "." and the area is the last part of the host. e.g.
 *    '.mit.edu' will match any host that ends with '.mit.edu'.
 *
 * @param string   $host         Host to check against the patterns.
 * @param string[] $noProxyArray An array of host patterns.
 *
 * @throws Exception\InvalidArgumentException
 *
 * @deprecated is_host_in_noproxy will be removed in guzzlehttp/guzzle:8.0. Use Utils::isHostInNoProxy instead.
 */
function is_host_in_noproxy(string $host, array $noProxyArray): bool
{
    return Utils::isHostInNoProxy($host, $noProxyArray);
}

/**
 * Wrapper for json_decode that throws when an error occurs.
 *
 * @param string $json    JSON data to parse
 * @param bool   $assoc   When true, returned objects will be converted
 *                        into associative arrays.
 * @param int    $depth   User specified recursion depth.
 * @param int    $options Bitmask of JSON decode options.
 *
 * @return object|array|string|int|float|bool|null
 *
 * @throws Exception\InvalidArgumentException if the JSON cannot be decoded.
 *
 * @see https://www.php.net/manual/en/function.json-decode.php
 * @deprecated json_decode will be removed in guzzlehttp/guzzle:8.0. Use Utils::jsonDecode instead.
 */
function json_decode(string $json, bool $assoc = false, int $depth = 512, int $options = 0)
{
    return Utils::jsonDecode($json, $assoc, $depth, $options);
}

/**
 * Wrapper for JSON encoding that throws when an error occurs.
 *
 * @param mixed $value   The value being encoded
 * @param int   $options JSON encode option bitmask
 * @param int   $depth   Set the maximum depth. Must be greater than zero.
 *
 * @throws Exception\InvalidArgumentException if the JSON cannot be encoded.
 *
 * @see https://www.php.net/manual/en/function.json-encode.php
 * @deprecated json_encode will be removed in guzzlehttp/guzzle:8.0. Use Utils::jsonEncode instead.
 */
function json_encode($value, int $options = 0, int $depth = 512): string
{
    return Utils::jsonEncode($value, $options, $depth);
}
PK�c�\����HH guzzlehttp/guzzle/src/Client.phpnu�[���<?php

namespace GuzzleHttp;

use GuzzleHttp\Cookie\CookieJar;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Exception\InvalidArgumentException;
use GuzzleHttp\Promise as P;
use GuzzleHttp\Promise\PromiseInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UriInterface;

/**
 * @final
 */
class Client implements ClientInterface, \Psr\Http\Client\ClientInterface
{
    use ClientTrait;

    /**
     * @var array Default request options
     */
    private $config;

    /**
     * Clients accept an array of constructor parameters.
     *
     * Here's an example of creating a client using a base_uri and an array of
     * default request options to apply to each request:
     *
     *     $client = new Client([
     *         'base_uri'        => 'http://www.foo.com/1.0/',
     *         'timeout'         => 0,
     *         'allow_redirects' => false,
     *         'proxy'           => '192.168.16.1:10'
     *     ]);
     *
     * Client configuration settings include the following options:
     *
     * - handler: (callable) Function that transfers HTTP requests over the
     *   wire. The function is called with a Psr7\Http\Message\RequestInterface
     *   and array of transfer options, and must return a
     *   GuzzleHttp\Promise\PromiseInterface that is fulfilled with a
     *   Psr7\Http\Message\ResponseInterface on success.
     *   If no handler is provided, a default handler will be created
     *   that enables all of the request options below by attaching all of the
     *   default middleware to the handler.
     * - base_uri: (string|UriInterface) Base URI of the client that is merged
     *   into relative URIs. Can be a string or instance of UriInterface.
     * - **: any request option
     *
     * @param array $config Client configuration settings.
     *
     * @see \GuzzleHttp\RequestOptions for a list of available request options.
     */
    public function __construct(array $config = [])
    {
        if (!isset($config['handler'])) {
            $config['handler'] = HandlerStack::create();
        } elseif (!\is_callable($config['handler'])) {
            throw new InvalidArgumentException('handler must be a callable');
        }

        // Convert the base_uri to a UriInterface
        if (isset($config['base_uri'])) {
            $config['base_uri'] = Psr7\Utils::uriFor($config['base_uri']);
        }

        $this->configureDefaults($config);
    }

    /**
     * @param string $method
     * @param array  $args
     *
     * @return PromiseInterface|ResponseInterface
     *
     * @deprecated Client::__call will be removed in guzzlehttp/guzzle:8.0.
     */
    public function __call($method, $args)
    {
        if (\count($args) < 1) {
            throw new InvalidArgumentException('Magic request methods require a URI and optional options array');
        }

        $uri = $args[0];
        $opts = $args[1] ?? [];

        return \substr($method, -5) === 'Async'
            ? $this->requestAsync(\substr($method, 0, -5), $uri, $opts)
            : $this->request($method, $uri, $opts);
    }

    /**
     * Asynchronously send an HTTP request.
     *
     * @param array $options Request options to apply to the given
     *                       request and to the transfer. See \GuzzleHttp\RequestOptions.
     */
    public function sendAsync(RequestInterface $request, array $options = []): PromiseInterface
    {
        // Merge the base URI into the request URI if needed.
        $options = $this->prepareDefaults($options);

        return $this->transfer(
            $request->withUri($this->buildUri($request->getUri(), $options), $request->hasHeader('Host')),
            $options
        );
    }

    /**
     * Send an HTTP request.
     *
     * @param array $options Request options to apply to the given
     *                       request and to the transfer. See \GuzzleHttp\RequestOptions.
     *
     * @throws GuzzleException
     */
    public function send(RequestInterface $request, array $options = []): ResponseInterface
    {
        $options[RequestOptions::SYNCHRONOUS] = true;

        return $this->sendAsync($request, $options)->wait();
    }

    /**
     * The HttpClient PSR (PSR-18) specify this method.
     *
     * {@inheritDoc}
     */
    public function sendRequest(RequestInterface $request): ResponseInterface
    {
        $options[RequestOptions::SYNCHRONOUS] = true;
        $options[RequestOptions::ALLOW_REDIRECTS] = false;
        $options[RequestOptions::HTTP_ERRORS] = false;

        return $this->sendAsync($request, $options)->wait();
    }

    /**
     * Create and send an asynchronous HTTP request.
     *
     * Use an absolute path to override the base path of the client, or a
     * relative path to append to the base path of the client. The URL can
     * contain the query string as well. Use an array to provide a URL
     * template and additional variables to use in the URL template expansion.
     *
     * @param string              $method  HTTP method
     * @param string|UriInterface $uri     URI object or string.
     * @param array               $options Request options to apply. See \GuzzleHttp\RequestOptions.
     */
    public function requestAsync(string $method, $uri = '', array $options = []): PromiseInterface
    {
        $options = $this->prepareDefaults($options);
        // Remove request modifying parameter because it can be done up-front.
        $headers = $options['headers'] ?? [];
        $body = $options['body'] ?? null;
        $version = $options['version'] ?? '1.1';
        // Merge the URI into the base URI.
        $uri = $this->buildUri(Psr7\Utils::uriFor($uri), $options);
        if (\is_array($body)) {
            throw $this->invalidBody();
        }
        $request = new Psr7\Request($method, $uri, $headers, $body, $version);
        // Remove the option so that they are not doubly-applied.
        unset($options['headers'], $options['body'], $options['version']);

        return $this->transfer($request, $options);
    }

    /**
     * Create and send an HTTP request.
     *
     * Use an absolute path to override the base path of the client, or a
     * relative path to append to the base path of the client. The URL can
     * contain the query string as well.
     *
     * @param string              $method  HTTP method.
     * @param string|UriInterface $uri     URI object or string.
     * @param array               $options Request options to apply. See \GuzzleHttp\RequestOptions.
     *
     * @throws GuzzleException
     */
    public function request(string $method, $uri = '', array $options = []): ResponseInterface
    {
        $options[RequestOptions::SYNCHRONOUS] = true;

        return $this->requestAsync($method, $uri, $options)->wait();
    }

    /**
     * Get a client configuration option.
     *
     * These options include default request options of the client, a "handler"
     * (if utilized by the concrete client), and a "base_uri" if utilized by
     * the concrete client.
     *
     * @param string|null $option The config option to retrieve.
     *
     * @return mixed
     *
     * @deprecated Client::getConfig will be removed in guzzlehttp/guzzle:8.0.
     */
    public function getConfig(string $option = null)
    {
        return $option === null
            ? $this->config
            : ($this->config[$option] ?? null);
    }

    private function buildUri(UriInterface $uri, array $config): UriInterface
    {
        if (isset($config['base_uri'])) {
            $uri = Psr7\UriResolver::resolve(Psr7\Utils::uriFor($config['base_uri']), $uri);
        }

        if (isset($config['idn_conversion']) && ($config['idn_conversion'] !== false)) {
            $idnOptions = ($config['idn_conversion'] === true) ? \IDNA_DEFAULT : $config['idn_conversion'];
            $uri = Utils::idnUriConvert($uri, $idnOptions);
        }

        return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri;
    }

    /**
     * Configures the default options for a client.
     */
    private function configureDefaults(array $config): void
    {
        $defaults = [
            'allow_redirects' => RedirectMiddleware::$defaultSettings,
            'http_errors' => true,
            'decode_content' => true,
            'verify' => true,
            'cookies' => false,
            'idn_conversion' => false,
        ];

        // Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set.

        // We can only trust the HTTP_PROXY environment variable in a CLI
        // process due to the fact that PHP has no reliable mechanism to
        // get environment variables that start with "HTTP_".
        if (\PHP_SAPI === 'cli' && ($proxy = Utils::getenv('HTTP_PROXY'))) {
            $defaults['proxy']['http'] = $proxy;
        }

        if ($proxy = Utils::getenv('HTTPS_PROXY')) {
            $defaults['proxy']['https'] = $proxy;
        }

        if ($noProxy = Utils::getenv('NO_PROXY')) {
            $cleanedNoProxy = \str_replace(' ', '', $noProxy);
            $defaults['proxy']['no'] = \explode(',', $cleanedNoProxy);
        }

        $this->config = $config + $defaults;

        if (!empty($config['cookies']) && $config['cookies'] === true) {
            $this->config['cookies'] = new CookieJar();
        }

        // Add the default user-agent header.
        if (!isset($this->config['headers'])) {
            $this->config['headers'] = ['User-Agent' => Utils::defaultUserAgent()];
        } else {
            // Add the User-Agent header if one was not already set.
            foreach (\array_keys($this->config['headers']) as $name) {
                if (\strtolower($name) === 'user-agent') {
                    return;
                }
            }
            $this->config['headers']['User-Agent'] = Utils::defaultUserAgent();
        }
    }

    /**
     * Merges default options into the array.
     *
     * @param array $options Options to modify by reference
     */
    private function prepareDefaults(array $options): array
    {
        $defaults = $this->config;

        if (!empty($defaults['headers'])) {
            // Default headers are only added if they are not present.
            $defaults['_conditional'] = $defaults['headers'];
            unset($defaults['headers']);
        }

        // Special handling for headers is required as they are added as
        // conditional headers and as headers passed to a request ctor.
        if (\array_key_exists('headers', $options)) {
            // Allows default headers to be unset.
            if ($options['headers'] === null) {
                $defaults['_conditional'] = [];
                unset($options['headers']);
            } elseif (!\is_array($options['headers'])) {
                throw new InvalidArgumentException('headers must be an array');
            }
        }

        // Shallow merge defaults underneath options.
        $result = $options + $defaults;

        // Remove null values.
        foreach ($result as $k => $v) {
            if ($v === null) {
                unset($result[$k]);
            }
        }

        return $result;
    }

    /**
     * Transfers the given request and applies request options.
     *
     * The URI of the request is not modified and the request options are used
     * as-is without merging in default options.
     *
     * @param array $options See \GuzzleHttp\RequestOptions.
     */
    private function transfer(RequestInterface $request, array $options): PromiseInterface
    {
        $request = $this->applyOptions($request, $options);
        /** @var HandlerStack $handler */
        $handler = $options['handler'];

        try {
            return P\Create::promiseFor($handler($request, $options));
        } catch (\Exception $e) {
            return P\Create::rejectionFor($e);
        }
    }

    /**
     * Applies the array of request options to a request.
     */
    private function applyOptions(RequestInterface $request, array &$options): RequestInterface
    {
        $modify = [
            'set_headers' => [],
        ];

        if (isset($options['headers'])) {
            if (array_keys($options['headers']) === range(0, count($options['headers']) - 1)) {
                throw new InvalidArgumentException('The headers array must have header name as keys.');
            }
            $modify['set_headers'] = $options['headers'];
            unset($options['headers']);
        }

        if (isset($options['form_params'])) {
            if (isset($options['multipart'])) {
                throw new InvalidArgumentException('You cannot use '
                    .'form_params and multipart at the same time. Use the '
                    .'form_params option if you want to send application/'
                    .'x-www-form-urlencoded requests, and the multipart '
                    .'option to send multipart/form-data requests.');
            }
            $options['body'] = \http_build_query($options['form_params'], '', '&');
            unset($options['form_params']);
            // Ensure that we don't have the header in different case and set the new value.
            $options['_conditional'] = Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']);
            $options['_conditional']['Content-Type'] = 'application/x-www-form-urlencoded';
        }

        if (isset($options['multipart'])) {
            $options['body'] = new Psr7\MultipartStream($options['multipart']);
            unset($options['multipart']);
        }

        if (isset($options['json'])) {
            $options['body'] = Utils::jsonEncode($options['json']);
            unset($options['json']);
            // Ensure that we don't have the header in different case and set the new value.
            $options['_conditional'] = Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']);
            $options['_conditional']['Content-Type'] = 'application/json';
        }

        if (!empty($options['decode_content'])
            && $options['decode_content'] !== true
        ) {
            // Ensure that we don't have the header in different case and set the new value.
            $options['_conditional'] = Psr7\Utils::caselessRemove(['Accept-Encoding'], $options['_conditional']);
            $modify['set_headers']['Accept-Encoding'] = $options['decode_content'];
        }

        if (isset($options['body'])) {
            if (\is_array($options['body'])) {
                throw $this->invalidBody();
            }
            $modify['body'] = Psr7\Utils::streamFor($options['body']);
            unset($options['body']);
        }

        if (!empty($options['auth']) && \is_array($options['auth'])) {
            $value = $options['auth'];
            $type = isset($value[2]) ? \strtolower($value[2]) : 'basic';
            switch ($type) {
                case 'basic':
                    // Ensure that we don't have the header in different case and set the new value.
                    $modify['set_headers'] = Psr7\Utils::caselessRemove(['Authorization'], $modify['set_headers']);
                    $modify['set_headers']['Authorization'] = 'Basic '
                        .\base64_encode("$value[0]:$value[1]");
                    break;
                case 'digest':
                    // @todo: Do not rely on curl
                    $options['curl'][\CURLOPT_HTTPAUTH] = \CURLAUTH_DIGEST;
                    $options['curl'][\CURLOPT_USERPWD] = "$value[0]:$value[1]";
                    break;
                case 'ntlm':
                    $options['curl'][\CURLOPT_HTTPAUTH] = \CURLAUTH_NTLM;
                    $options['curl'][\CURLOPT_USERPWD] = "$value[0]:$value[1]";
                    break;
            }
        }

        if (isset($options['query'])) {
            $value = $options['query'];
            if (\is_array($value)) {
                $value = \http_build_query($value, '', '&', \PHP_QUERY_RFC3986);
            }
            if (!\is_string($value)) {
                throw new InvalidArgumentException('query must be a string or array');
            }
            $modify['query'] = $value;
            unset($options['query']);
        }

        // Ensure that sink is not an invalid value.
        if (isset($options['sink'])) {
            // TODO: Add more sink validation?
            if (\is_bool($options['sink'])) {
                throw new InvalidArgumentException('sink must not be a boolean');
            }
        }

        if (isset($options['version'])) {
            $modify['version'] = $options['version'];
        }

        $request = Psr7\Utils::modifyRequest($request, $modify);
        if ($request->getBody() instanceof Psr7\MultipartStream) {
            // Use a multipart/form-data POST if a Content-Type is not set.
            // Ensure that we don't have the header in different case and set the new value.
            $options['_conditional'] = Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']);
            $options['_conditional']['Content-Type'] = 'multipart/form-data; boundary='
                .$request->getBody()->getBoundary();
        }

        // Merge in conditional headers if they are not present.
        if (isset($options['_conditional'])) {
            // Build up the changes so it's in a single clone of the message.
            $modify = [];
            foreach ($options['_conditional'] as $k => $v) {
                if (!$request->hasHeader($k)) {
                    $modify['set_headers'][$k] = $v;
                }
            }
            $request = Psr7\Utils::modifyRequest($request, $modify);
            // Don't pass this internal value along to middleware/handlers.
            unset($options['_conditional']);
        }

        return $request;
    }

    /**
     * Return an InvalidArgumentException with pre-set message.
     */
    private function invalidBody(): InvalidArgumentException
    {
        return new InvalidArgumentException('Passing in the "body" request '
            .'option as an array to send a request is not supported. '
            .'Please use the "form_params" request option to send a '
            .'application/x-www-form-urlencoded request, or the "multipart" '
            .'request option to send a multipart/form-data request.');
    }
}
PK�c�\��P��6guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.phpnu�[���<?php

namespace GuzzleHttp\Handler;

use Psr\Http\Message\RequestInterface;

interface CurlFactoryInterface
{
    /**
     * Creates a cURL handle resource.
     *
     * @param RequestInterface $request Request
     * @param array            $options Transfer options
     *
     * @throws \RuntimeException when an option cannot be applied
     */
    public function create(RequestInterface $request, array $options): EasyHandle;

    /**
     * Release an easy handle, allowing it to be reused or closed.
     *
     * This function must call unset on the easy handle's "handle" property.
     */
    public function release(EasyHandle $easy): void;
}
PK�c�\��af

-guzzlehttp/guzzle/src/Handler/MockHandler.phpnu�[���<?php

namespace GuzzleHttp\Handler;

use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Promise as P;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\TransferStats;
use GuzzleHttp\Utils;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;

/**
 * Handler that returns responses or throw exceptions from a queue.
 *
 * @final
 */
class MockHandler implements \Countable
{
    /**
     * @var array
     */
    private $queue = [];

    /**
     * @var RequestInterface|null
     */
    private $lastRequest;

    /**
     * @var array
     */
    private $lastOptions = [];

    /**
     * @var callable|null
     */
    private $onFulfilled;

    /**
     * @var callable|null
     */
    private $onRejected;

    /**
     * Creates a new MockHandler that uses the default handler stack list of
     * middlewares.
     *
     * @param array|null    $queue       Array of responses, callables, or exceptions.
     * @param callable|null $onFulfilled Callback to invoke when the return value is fulfilled.
     * @param callable|null $onRejected  Callback to invoke when the return value is rejected.
     */
    public static function createWithMiddleware(array $queue = null, callable $onFulfilled = null, callable $onRejected = null): HandlerStack
    {
        return HandlerStack::create(new self($queue, $onFulfilled, $onRejected));
    }

    /**
     * The passed in value must be an array of
     * {@see \Psr\Http\Message\ResponseInterface} objects, Exceptions,
     * callables, or Promises.
     *
     * @param array<int, mixed>|null $queue       The parameters to be passed to the append function, as an indexed array.
     * @param callable|null          $onFulfilled Callback to invoke when the return value is fulfilled.
     * @param callable|null          $onRejected  Callback to invoke when the return value is rejected.
     */
    public function __construct(array $queue = null, callable $onFulfilled = null, callable $onRejected = null)
    {
        $this->onFulfilled = $onFulfilled;
        $this->onRejected = $onRejected;

        if ($queue) {
            // array_values included for BC
            $this->append(...array_values($queue));
        }
    }

    public function __invoke(RequestInterface $request, array $options): PromiseInterface
    {
        if (!$this->queue) {
            throw new \OutOfBoundsException('Mock queue is empty');
        }

        if (isset($options['delay']) && \is_numeric($options['delay'])) {
            \usleep((int) $options['delay'] * 1000);
        }

        $this->lastRequest = $request;
        $this->lastOptions = $options;
        $response = \array_shift($this->queue);

        if (isset($options['on_headers'])) {
            if (!\is_callable($options['on_headers'])) {
                throw new \InvalidArgumentException('on_headers must be callable');
            }
            try {
                $options['on_headers']($response);
            } catch (\Exception $e) {
                $msg = 'An error was encountered during the on_headers event';
                $response = new RequestException($msg, $request, $response, $e);
            }
        }

        if (\is_callable($response)) {
            $response = $response($request, $options);
        }

        $response = $response instanceof \Throwable
            ? P\Create::rejectionFor($response)
            : P\Create::promiseFor($response);

        return $response->then(
            function (?ResponseInterface $value) use ($request, $options) {
                $this->invokeStats($request, $options, $value);
                if ($this->onFulfilled) {
                    ($this->onFulfilled)($value);
                }

                if ($value !== null && isset($options['sink'])) {
                    $contents = (string) $value->getBody();
                    $sink = $options['sink'];

                    if (\is_resource($sink)) {
                        \fwrite($sink, $contents);
                    } elseif (\is_string($sink)) {
                        \file_put_contents($sink, $contents);
                    } elseif ($sink instanceof StreamInterface) {
                        $sink->write($contents);
                    }
                }

                return $value;
            },
            function ($reason) use ($request, $options) {
                $this->invokeStats($request, $options, null, $reason);
                if ($this->onRejected) {
                    ($this->onRejected)($reason);
                }

                return P\Create::rejectionFor($reason);
            }
        );
    }

    /**
     * Adds one or more variadic requests, exceptions, callables, or promises
     * to the queue.
     *
     * @param mixed ...$values
     */
    public function append(...$values): void
    {
        foreach ($values as $value) {
            if ($value instanceof ResponseInterface
                || $value instanceof \Throwable
                || $value instanceof PromiseInterface
                || \is_callable($value)
            ) {
                $this->queue[] = $value;
            } else {
                throw new \TypeError('Expected a Response, Promise, Throwable or callable. Found '.Utils::describeType($value));
            }
        }
    }

    /**
     * Get the last received request.
     */
    public function getLastRequest(): ?RequestInterface
    {
        return $this->lastRequest;
    }

    /**
     * Get the last received request options.
     */
    public function getLastOptions(): array
    {
        return $this->lastOptions;
    }

    /**
     * Returns the number of remaining items in the queue.
     */
    public function count(): int
    {
        return \count($this->queue);
    }

    public function reset(): void
    {
        $this->queue = [];
    }

    /**
     * @param mixed $reason Promise or reason.
     */
    private function invokeStats(
        RequestInterface $request,
        array $options,
        ResponseInterface $response = null,
        $reason = null
    ): void {
        if (isset($options['on_stats'])) {
            $transferTime = $options['transfer_time'] ?? 0;
            $stats = new TransferStats($request, $response, $transferTime, $reason);
            ($options['on_stats'])($stats);
        }
    }
}
PK�c�\�I]22-guzzlehttp/guzzle/src/Handler/CurlHandler.phpnu�[���<?php

namespace GuzzleHttp\Handler;

use GuzzleHttp\Promise\PromiseInterface;
use Psr\Http\Message\RequestInterface;

/**
 * HTTP handler that uses cURL easy handles as a transport layer.
 *
 * When using the CurlHandler, custom curl options can be specified as an
 * associative array of curl option constants mapping to values in the
 * **curl** key of the "client" key of the request.
 *
 * @final
 */
class CurlHandler
{
    /**
     * @var CurlFactoryInterface
     */
    private $factory;

    /**
     * Accepts an associative array of options:
     *
     * - handle_factory: Optional curl factory used to create cURL handles.
     *
     * @param array{handle_factory?: ?CurlFactoryInterface} $options Array of options to use with the handler
     */
    public function __construct(array $options = [])
    {
        $this->factory = $options['handle_factory']
            ?? new CurlFactory(3);
    }

    public function __invoke(RequestInterface $request, array $options): PromiseInterface
    {
        if (isset($options['delay'])) {
            \usleep($options['delay'] * 1000);
        }

        $easy = $this->factory->create($request, $options);
        \curl_exec($easy->handle);
        $easy->errno = \curl_errno($easy->handle);

        return CurlFactory::finish($this, $easy, $this->factory);
    }
}
PK�c�\�ju$�`�`-guzzlehttp/guzzle/src/Handler/CurlFactory.phpnu�[���<?php

namespace GuzzleHttp\Handler;

use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Promise as P;
use GuzzleHttp\Promise\FulfilledPromise;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Psr7\LazyOpenStream;
use GuzzleHttp\TransferStats;
use GuzzleHttp\Utils;
use Psr\Http\Message\RequestInterface;

/**
 * Creates curl resources from a request
 *
 * @final
 */
class CurlFactory implements CurlFactoryInterface
{
    public const CURL_VERSION_STR = 'curl_version';

    /**
     * @deprecated
     */
    public const LOW_CURL_VERSION_NUMBER = '7.21.2';

    /**
     * @var resource[]|\CurlHandle[]
     */
    private $handles = [];

    /**
     * @var int Total number of idle handles to keep in cache
     */
    private $maxHandles;

    /**
     * @param int $maxHandles Maximum number of idle handles.
     */
    public function __construct(int $maxHandles)
    {
        $this->maxHandles = $maxHandles;
    }

    public function create(RequestInterface $request, array $options): EasyHandle
    {
        if (isset($options['curl']['body_as_string'])) {
            $options['_body_as_string'] = $options['curl']['body_as_string'];
            unset($options['curl']['body_as_string']);
        }

        $easy = new EasyHandle();
        $easy->request = $request;
        $easy->options = $options;
        $conf = $this->getDefaultConf($easy);
        $this->applyMethod($easy, $conf);
        $this->applyHandlerOptions($easy, $conf);
        $this->applyHeaders($easy, $conf);
        unset($conf['_headers']);

        // Add handler options from the request configuration options
        if (isset($options['curl'])) {
            $conf = \array_replace($conf, $options['curl']);
        }

        $conf[\CURLOPT_HEADERFUNCTION] = $this->createHeaderFn($easy);
        $easy->handle = $this->handles ? \array_pop($this->handles) : \curl_init();
        curl_setopt_array($easy->handle, $conf);

        return $easy;
    }

    public function release(EasyHandle $easy): void
    {
        $resource = $easy->handle;
        unset($easy->handle);

        if (\count($this->handles) >= $this->maxHandles) {
            \curl_close($resource);
        } else {
            // Remove all callback functions as they can hold onto references
            // and are not cleaned up by curl_reset. Using curl_setopt_array
            // does not work for some reason, so removing each one
            // individually.
            \curl_setopt($resource, \CURLOPT_HEADERFUNCTION, null);
            \curl_setopt($resource, \CURLOPT_READFUNCTION, null);
            \curl_setopt($resource, \CURLOPT_WRITEFUNCTION, null);
            \curl_setopt($resource, \CURLOPT_PROGRESSFUNCTION, null);
            \curl_reset($resource);
            $this->handles[] = $resource;
        }
    }

    /**
     * Completes a cURL transaction, either returning a response promise or a
     * rejected promise.
     *
     * @param callable(RequestInterface, array): PromiseInterface $handler
     * @param CurlFactoryInterface                                $factory Dictates how the handle is released
     */
    public static function finish(callable $handler, EasyHandle $easy, CurlFactoryInterface $factory): PromiseInterface
    {
        if (isset($easy->options['on_stats'])) {
            self::invokeStats($easy);
        }

        if (!$easy->response || $easy->errno) {
            return self::finishError($handler, $easy, $factory);
        }

        // Return the response if it is present and there is no error.
        $factory->release($easy);

        // Rewind the body of the response if possible.
        $body = $easy->response->getBody();
        if ($body->isSeekable()) {
            $body->rewind();
        }

        return new FulfilledPromise($easy->response);
    }

    private static function invokeStats(EasyHandle $easy): void
    {
        $curlStats = \curl_getinfo($easy->handle);
        $curlStats['appconnect_time'] = \curl_getinfo($easy->handle, \CURLINFO_APPCONNECT_TIME);
        $stats = new TransferStats(
            $easy->request,
            $easy->response,
            $curlStats['total_time'],
            $easy->errno,
            $curlStats
        );
        ($easy->options['on_stats'])($stats);
    }

    /**
     * @param callable(RequestInterface, array): PromiseInterface $handler
     */
    private static function finishError(callable $handler, EasyHandle $easy, CurlFactoryInterface $factory): PromiseInterface
    {
        // Get error information and release the handle to the factory.
        $ctx = [
            'errno' => $easy->errno,
            'error' => \curl_error($easy->handle),
            'appconnect_time' => \curl_getinfo($easy->handle, \CURLINFO_APPCONNECT_TIME),
        ] + \curl_getinfo($easy->handle);
        $ctx[self::CURL_VERSION_STR] = \curl_version()['version'];
        $factory->release($easy);

        // Retry when nothing is present or when curl failed to rewind.
        if (empty($easy->options['_err_message']) && (!$easy->errno || $easy->errno == 65)) {
            return self::retryFailedRewind($handler, $easy, $ctx);
        }

        return self::createRejection($easy, $ctx);
    }

    private static function createRejection(EasyHandle $easy, array $ctx): PromiseInterface
    {
        static $connectionErrors = [
            \CURLE_OPERATION_TIMEOUTED => true,
            \CURLE_COULDNT_RESOLVE_HOST => true,
            \CURLE_COULDNT_CONNECT => true,
            \CURLE_SSL_CONNECT_ERROR => true,
            \CURLE_GOT_NOTHING => true,
        ];

        if ($easy->createResponseException) {
            return P\Create::rejectionFor(
                new RequestException(
                    'An error was encountered while creating the response',
                    $easy->request,
                    $easy->response,
                    $easy->createResponseException,
                    $ctx
                )
            );
        }

        // If an exception was encountered during the onHeaders event, then
        // return a rejected promise that wraps that exception.
        if ($easy->onHeadersException) {
            return P\Create::rejectionFor(
                new RequestException(
                    'An error was encountered during the on_headers event',
                    $easy->request,
                    $easy->response,
                    $easy->onHeadersException,
                    $ctx
                )
            );
        }

        $message = \sprintf(
            'cURL error %s: %s (%s)',
            $ctx['errno'],
            $ctx['error'],
            'see https://curl.haxx.se/libcurl/c/libcurl-errors.html'
        );
        $uriString = (string) $easy->request->getUri();
        if ($uriString !== '' && false === \strpos($ctx['error'], $uriString)) {
            $message .= \sprintf(' for %s', $uriString);
        }

        // Create a connection exception if it was a specific error code.
        $error = isset($connectionErrors[$easy->errno])
            ? new ConnectException($message, $easy->request, null, $ctx)
            : new RequestException($message, $easy->request, $easy->response, null, $ctx);

        return P\Create::rejectionFor($error);
    }

    /**
     * @return array<int|string, mixed>
     */
    private function getDefaultConf(EasyHandle $easy): array
    {
        $conf = [
            '_headers' => $easy->request->getHeaders(),
            \CURLOPT_CUSTOMREQUEST => $easy->request->getMethod(),
            \CURLOPT_URL => (string) $easy->request->getUri()->withFragment(''),
            \CURLOPT_RETURNTRANSFER => false,
            \CURLOPT_HEADER => false,
            \CURLOPT_CONNECTTIMEOUT => 300,
        ];

        if (\defined('CURLOPT_PROTOCOLS')) {
            $conf[\CURLOPT_PROTOCOLS] = \CURLPROTO_HTTP | \CURLPROTO_HTTPS;
        }

        $version = $easy->request->getProtocolVersion();
        if ($version == 1.1) {
            $conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_1;
        } elseif ($version == 2.0) {
            $conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_2_0;
        } else {
            $conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_0;
        }

        return $conf;
    }

    private function applyMethod(EasyHandle $easy, array &$conf): void
    {
        $body = $easy->request->getBody();
        $size = $body->getSize();

        if ($size === null || $size > 0) {
            $this->applyBody($easy->request, $easy->options, $conf);

            return;
        }

        $method = $easy->request->getMethod();
        if ($method === 'PUT' || $method === 'POST') {
            // See https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.2
            if (!$easy->request->hasHeader('Content-Length')) {
                $conf[\CURLOPT_HTTPHEADER][] = 'Content-Length: 0';
            }
        } elseif ($method === 'HEAD') {
            $conf[\CURLOPT_NOBODY] = true;
            unset(
                $conf[\CURLOPT_WRITEFUNCTION],
                $conf[\CURLOPT_READFUNCTION],
                $conf[\CURLOPT_FILE],
                $conf[\CURLOPT_INFILE]
            );
        }
    }

    private function applyBody(RequestInterface $request, array $options, array &$conf): void
    {
        $size = $request->hasHeader('Content-Length')
            ? (int) $request->getHeaderLine('Content-Length')
            : null;

        // Send the body as a string if the size is less than 1MB OR if the
        // [curl][body_as_string] request value is set.
        if (($size !== null && $size < 1000000) || !empty($options['_body_as_string'])) {
            $conf[\CURLOPT_POSTFIELDS] = (string) $request->getBody();
            // Don't duplicate the Content-Length header
            $this->removeHeader('Content-Length', $conf);
            $this->removeHeader('Transfer-Encoding', $conf);
        } else {
            $conf[\CURLOPT_UPLOAD] = true;
            if ($size !== null) {
                $conf[\CURLOPT_INFILESIZE] = $size;
                $this->removeHeader('Content-Length', $conf);
            }
            $body = $request->getBody();
            if ($body->isSeekable()) {
                $body->rewind();
            }
            $conf[\CURLOPT_READFUNCTION] = static function ($ch, $fd, $length) use ($body) {
                return $body->read($length);
            };
        }

        // If the Expect header is not present, prevent curl from adding it
        if (!$request->hasHeader('Expect')) {
            $conf[\CURLOPT_HTTPHEADER][] = 'Expect:';
        }

        // cURL sometimes adds a content-type by default. Prevent this.
        if (!$request->hasHeader('Content-Type')) {
            $conf[\CURLOPT_HTTPHEADER][] = 'Content-Type:';
        }
    }

    private function applyHeaders(EasyHandle $easy, array &$conf): void
    {
        foreach ($conf['_headers'] as $name => $values) {
            foreach ($values as $value) {
                $value = (string) $value;
                if ($value === '') {
                    // cURL requires a special format for empty headers.
                    // See https://github.com/guzzle/guzzle/issues/1882 for more details.
                    $conf[\CURLOPT_HTTPHEADER][] = "$name;";
                } else {
                    $conf[\CURLOPT_HTTPHEADER][] = "$name: $value";
                }
            }
        }

        // Remove the Accept header if one was not set
        if (!$easy->request->hasHeader('Accept')) {
            $conf[\CURLOPT_HTTPHEADER][] = 'Accept:';
        }
    }

    /**
     * Remove a header from the options array.
     *
     * @param string $name    Case-insensitive header to remove
     * @param array  $options Array of options to modify
     */
    private function removeHeader(string $name, array &$options): void
    {
        foreach (\array_keys($options['_headers']) as $key) {
            if (!\strcasecmp($key, $name)) {
                unset($options['_headers'][$key]);

                return;
            }
        }
    }

    private function applyHandlerOptions(EasyHandle $easy, array &$conf): void
    {
        $options = $easy->options;
        if (isset($options['verify'])) {
            if ($options['verify'] === false) {
                unset($conf[\CURLOPT_CAINFO]);
                $conf[\CURLOPT_SSL_VERIFYHOST] = 0;
                $conf[\CURLOPT_SSL_VERIFYPEER] = false;
            } else {
                $conf[\CURLOPT_SSL_VERIFYHOST] = 2;
                $conf[\CURLOPT_SSL_VERIFYPEER] = true;
                if (\is_string($options['verify'])) {
                    // Throw an error if the file/folder/link path is not valid or doesn't exist.
                    if (!\file_exists($options['verify'])) {
                        throw new \InvalidArgumentException("SSL CA bundle not found: {$options['verify']}");
                    }
                    // If it's a directory or a link to a directory use CURLOPT_CAPATH.
                    // If not, it's probably a file, or a link to a file, so use CURLOPT_CAINFO.
                    if (
                        \is_dir($options['verify'])
                        || (
                            \is_link($options['verify']) === true
                            && ($verifyLink = \readlink($options['verify'])) !== false
                            && \is_dir($verifyLink)
                        )
                    ) {
                        $conf[\CURLOPT_CAPATH] = $options['verify'];
                    } else {
                        $conf[\CURLOPT_CAINFO] = $options['verify'];
                    }
                }
            }
        }

        if (!isset($options['curl'][\CURLOPT_ENCODING]) && !empty($options['decode_content'])) {
            $accept = $easy->request->getHeaderLine('Accept-Encoding');
            if ($accept) {
                $conf[\CURLOPT_ENCODING] = $accept;
            } else {
                // The empty string enables all available decoders and implicitly
                // sets a matching 'Accept-Encoding' header.
                $conf[\CURLOPT_ENCODING] = '';
                // But as the user did not specify any acceptable encodings we need
                // to overwrite this implicit header with an empty one.
                $conf[\CURLOPT_HTTPHEADER][] = 'Accept-Encoding:';
            }
        }

        if (!isset($options['sink'])) {
            // Use a default temp stream if no sink was set.
            $options['sink'] = \GuzzleHttp\Psr7\Utils::tryFopen('php://temp', 'w+');
        }
        $sink = $options['sink'];
        if (!\is_string($sink)) {
            $sink = \GuzzleHttp\Psr7\Utils::streamFor($sink);
        } elseif (!\is_dir(\dirname($sink))) {
            // Ensure that the directory exists before failing in curl.
            throw new \RuntimeException(\sprintf('Directory %s does not exist for sink value of %s', \dirname($sink), $sink));
        } else {
            $sink = new LazyOpenStream($sink, 'w+');
        }
        $easy->sink = $sink;
        $conf[\CURLOPT_WRITEFUNCTION] = static function ($ch, $write) use ($sink): int {
            return $sink->write($write);
        };

        $timeoutRequiresNoSignal = false;
        if (isset($options['timeout'])) {
            $timeoutRequiresNoSignal |= $options['timeout'] < 1;
            $conf[\CURLOPT_TIMEOUT_MS] = $options['timeout'] * 1000;
        }

        // CURL default value is CURL_IPRESOLVE_WHATEVER
        if (isset($options['force_ip_resolve'])) {
            if ('v4' === $options['force_ip_resolve']) {
                $conf[\CURLOPT_IPRESOLVE] = \CURL_IPRESOLVE_V4;
            } elseif ('v6' === $options['force_ip_resolve']) {
                $conf[\CURLOPT_IPRESOLVE] = \CURL_IPRESOLVE_V6;
            }
        }

        if (isset($options['connect_timeout'])) {
            $timeoutRequiresNoSignal |= $options['connect_timeout'] < 1;
            $conf[\CURLOPT_CONNECTTIMEOUT_MS] = $options['connect_timeout'] * 1000;
        }

        if ($timeoutRequiresNoSignal && \strtoupper(\substr(\PHP_OS, 0, 3)) !== 'WIN') {
            $conf[\CURLOPT_NOSIGNAL] = true;
        }

        if (isset($options['proxy'])) {
            if (!\is_array($options['proxy'])) {
                $conf[\CURLOPT_PROXY] = $options['proxy'];
            } else {
                $scheme = $easy->request->getUri()->getScheme();
                if (isset($options['proxy'][$scheme])) {
                    $host = $easy->request->getUri()->getHost();
                    if (isset($options['proxy']['no']) && Utils::isHostInNoProxy($host, $options['proxy']['no'])) {
                        unset($conf[\CURLOPT_PROXY]);
                    } else {
                        $conf[\CURLOPT_PROXY] = $options['proxy'][$scheme];
                    }
                }
            }
        }

        if (isset($options['crypto_method'])) {
            if (\STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT === $options['crypto_method']) {
                if (!defined('CURL_SSLVERSION_TLSv1_0')) {
                    throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.0 not supported by your version of cURL');
                }
                $conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_0;
            } elseif (\STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT === $options['crypto_method']) {
                if (!defined('CURL_SSLVERSION_TLSv1_1')) {
                    throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.1 not supported by your version of cURL');
                }
                $conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_1;
            } elseif (\STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT === $options['crypto_method']) {
                if (!defined('CURL_SSLVERSION_TLSv1_2')) {
                    throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.2 not supported by your version of cURL');
                }
                $conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_2;
            } elseif (defined('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT') && \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT === $options['crypto_method']) {
                if (!defined('CURL_SSLVERSION_TLSv1_3')) {
                    throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.3 not supported by your version of cURL');
                }
                $conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_3;
            } else {
                throw new \InvalidArgumentException('Invalid crypto_method request option: unknown version provided');
            }
        }

        if (isset($options['cert'])) {
            $cert = $options['cert'];
            if (\is_array($cert)) {
                $conf[\CURLOPT_SSLCERTPASSWD] = $cert[1];
                $cert = $cert[0];
            }
            if (!\file_exists($cert)) {
                throw new \InvalidArgumentException("SSL certificate not found: {$cert}");
            }
            // OpenSSL (versions 0.9.3 and later) also support "P12" for PKCS#12-encoded files.
            // see https://curl.se/libcurl/c/CURLOPT_SSLCERTTYPE.html
            $ext = pathinfo($cert, \PATHINFO_EXTENSION);
            if (preg_match('#^(der|p12)$#i', $ext)) {
                $conf[\CURLOPT_SSLCERTTYPE] = strtoupper($ext);
            }
            $conf[\CURLOPT_SSLCERT] = $cert;
        }

        if (isset($options['ssl_key'])) {
            if (\is_array($options['ssl_key'])) {
                if (\count($options['ssl_key']) === 2) {
                    [$sslKey, $conf[\CURLOPT_SSLKEYPASSWD]] = $options['ssl_key'];
                } else {
                    [$sslKey] = $options['ssl_key'];
                }
            }

            $sslKey = $sslKey ?? $options['ssl_key'];

            if (!\file_exists($sslKey)) {
                throw new \InvalidArgumentException("SSL private key not found: {$sslKey}");
            }
            $conf[\CURLOPT_SSLKEY] = $sslKey;
        }

        if (isset($options['progress'])) {
            $progress = $options['progress'];
            if (!\is_callable($progress)) {
                throw new \InvalidArgumentException('progress client option must be callable');
            }
            $conf[\CURLOPT_NOPROGRESS] = false;
            $conf[\CURLOPT_PROGRESSFUNCTION] = static function ($resource, int $downloadSize, int $downloaded, int $uploadSize, int $uploaded) use ($progress) {
                $progress($downloadSize, $downloaded, $uploadSize, $uploaded);
            };
        }

        if (!empty($options['debug'])) {
            $conf[\CURLOPT_STDERR] = Utils::debugResource($options['debug']);
            $conf[\CURLOPT_VERBOSE] = true;
        }
    }

    /**
     * This function ensures that a response was set on a transaction. If one
     * was not set, then the request is retried if possible. This error
     * typically means you are sending a payload, curl encountered a
     * "Connection died, retrying a fresh connect" error, tried to rewind the
     * stream, and then encountered a "necessary data rewind wasn't possible"
     * error, causing the request to be sent through curl_multi_info_read()
     * without an error status.
     *
     * @param callable(RequestInterface, array): PromiseInterface $handler
     */
    private static function retryFailedRewind(callable $handler, EasyHandle $easy, array $ctx): PromiseInterface
    {
        try {
            // Only rewind if the body has been read from.
            $body = $easy->request->getBody();
            if ($body->tell() > 0) {
                $body->rewind();
            }
        } catch (\RuntimeException $e) {
            $ctx['error'] = 'The connection unexpectedly failed without '
                .'providing an error. The request would have been retried, '
                .'but attempting to rewind the request body failed. '
                .'Exception: '.$e;

            return self::createRejection($easy, $ctx);
        }

        // Retry no more than 3 times before giving up.
        if (!isset($easy->options['_curl_retries'])) {
            $easy->options['_curl_retries'] = 1;
        } elseif ($easy->options['_curl_retries'] == 2) {
            $ctx['error'] = 'The cURL request was retried 3 times '
                .'and did not succeed. The most likely reason for the failure '
                .'is that cURL was unable to rewind the body of the request '
                .'and subsequent retries resulted in the same error. Turn on '
                .'the debug option to see what went wrong. See '
                .'https://bugs.php.net/bug.php?id=47204 for more information.';

            return self::createRejection($easy, $ctx);
        } else {
            ++$easy->options['_curl_retries'];
        }

        return $handler($easy->request, $easy->options);
    }

    private function createHeaderFn(EasyHandle $easy): callable
    {
        if (isset($easy->options['on_headers'])) {
            $onHeaders = $easy->options['on_headers'];

            if (!\is_callable($onHeaders)) {
                throw new \InvalidArgumentException('on_headers must be callable');
            }
        } else {
            $onHeaders = null;
        }

        return static function ($ch, $h) use (
            $onHeaders,
            $easy,
            &$startingResponse
        ) {
            $value = \trim($h);
            if ($value === '') {
                $startingResponse = true;
                try {
                    $easy->createResponse();
                } catch (\Exception $e) {
                    $easy->createResponseException = $e;

                    return -1;
                }
                if ($onHeaders !== null) {
                    try {
                        $onHeaders($easy->response);
                    } catch (\Exception $e) {
                        // Associate the exception with the handle and trigger
                        // a curl header write error by returning 0.
                        $easy->onHeadersException = $e;

                        return -1;
                    }
                }
            } elseif ($startingResponse) {
                $startingResponse = false;
                $easy->headers = [$value];
            } else {
                $easy->headers[] = $value;
            }

            return \strlen($h);
        };
    }

    public function __destruct()
    {
        foreach ($this->handles as $id => $handle) {
            \curl_close($handle);
            unset($this->handles[$id]);
        }
    }
}
PK�c�\k(��WW2guzzlehttp/guzzle/src/Handler/CurlMultiHandler.phpnu�[���<?php

namespace GuzzleHttp\Handler;

use GuzzleHttp\Promise as P;
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Utils;
use Psr\Http\Message\RequestInterface;

/**
 * Returns an asynchronous response using curl_multi_* functions.
 *
 * When using the CurlMultiHandler, custom curl options can be specified as an
 * associative array of curl option constants mapping to values in the
 * **curl** key of the provided request options.
 *
 * @final
 */
class CurlMultiHandler
{
    /**
     * @var CurlFactoryInterface
     */
    private $factory;

    /**
     * @var int
     */
    private $selectTimeout;

    /**
     * @var int Will be higher than 0 when `curl_multi_exec` is still running.
     */
    private $active = 0;

    /**
     * @var array Request entry handles, indexed by handle id in `addRequest`.
     *
     * @see CurlMultiHandler::addRequest
     */
    private $handles = [];

    /**
     * @var array<int, float> An array of delay times, indexed by handle id in `addRequest`.
     *
     * @see CurlMultiHandler::addRequest
     */
    private $delays = [];

    /**
     * @var array<mixed> An associative array of CURLMOPT_* options and corresponding values for curl_multi_setopt()
     */
    private $options = [];

    /** @var resource|\CurlMultiHandle */
    private $_mh;

    /**
     * This handler accepts the following options:
     *
     * - handle_factory: An optional factory  used to create curl handles
     * - select_timeout: Optional timeout (in seconds) to block before timing
     *   out while selecting curl handles. Defaults to 1 second.
     * - options: An associative array of CURLMOPT_* options and
     *   corresponding values for curl_multi_setopt()
     */
    public function __construct(array $options = [])
    {
        $this->factory = $options['handle_factory'] ?? new CurlFactory(50);

        if (isset($options['select_timeout'])) {
            $this->selectTimeout = $options['select_timeout'];
        } elseif ($selectTimeout = Utils::getenv('GUZZLE_CURL_SELECT_TIMEOUT')) {
            @trigger_error('Since guzzlehttp/guzzle 7.2.0: Using environment variable GUZZLE_CURL_SELECT_TIMEOUT is deprecated. Use option "select_timeout" instead.', \E_USER_DEPRECATED);
            $this->selectTimeout = (int) $selectTimeout;
        } else {
            $this->selectTimeout = 1;
        }

        $this->options = $options['options'] ?? [];

        // unsetting the property forces the first access to go through
        // __get().
        unset($this->_mh);
    }

    /**
     * @param string $name
     *
     * @return resource|\CurlMultiHandle
     *
     * @throws \BadMethodCallException when another field as `_mh` will be gotten
     * @throws \RuntimeException       when curl can not initialize a multi handle
     */
    public function __get($name)
    {
        if ($name !== '_mh') {
            throw new \BadMethodCallException("Can not get other property as '_mh'.");
        }

        $multiHandle = \curl_multi_init();

        if (false === $multiHandle) {
            throw new \RuntimeException('Can not initialize curl multi handle.');
        }

        $this->_mh = $multiHandle;

        foreach ($this->options as $option => $value) {
            // A warning is raised in case of a wrong option.
            curl_multi_setopt($this->_mh, $option, $value);
        }

        return $this->_mh;
    }

    public function __destruct()
    {
        if (isset($this->_mh)) {
            \curl_multi_close($this->_mh);
            unset($this->_mh);
        }
    }

    public function __invoke(RequestInterface $request, array $options): PromiseInterface
    {
        $easy = $this->factory->create($request, $options);
        $id = (int) $easy->handle;

        $promise = new Promise(
            [$this, 'execute'],
            function () use ($id) {
                return $this->cancel($id);
            }
        );

        $this->addRequest(['easy' => $easy, 'deferred' => $promise]);

        return $promise;
    }

    /**
     * Ticks the curl event loop.
     */
    public function tick(): void
    {
        // Add any delayed handles if needed.
        if ($this->delays) {
            $currentTime = Utils::currentTime();
            foreach ($this->delays as $id => $delay) {
                if ($currentTime >= $delay) {
                    unset($this->delays[$id]);
                    \curl_multi_add_handle(
                        $this->_mh,
                        $this->handles[$id]['easy']->handle
                    );
                }
            }
        }

        // Step through the task queue which may add additional requests.
        P\Utils::queue()->run();

        if ($this->active && \curl_multi_select($this->_mh, $this->selectTimeout) === -1) {
            // Perform a usleep if a select returns -1.
            // See: https://bugs.php.net/bug.php?id=61141
            \usleep(250);
        }

        while (\curl_multi_exec($this->_mh, $this->active) === \CURLM_CALL_MULTI_PERFORM) {
        }

        $this->processMessages();
    }

    /**
     * Runs until all outstanding connections have completed.
     */
    public function execute(): void
    {
        $queue = P\Utils::queue();

        while ($this->handles || !$queue->isEmpty()) {
            // If there are no transfers, then sleep for the next delay
            if (!$this->active && $this->delays) {
                \usleep($this->timeToNext());
            }
            $this->tick();
        }
    }

    private function addRequest(array $entry): void
    {
        $easy = $entry['easy'];
        $id = (int) $easy->handle;
        $this->handles[$id] = $entry;
        if (empty($easy->options['delay'])) {
            \curl_multi_add_handle($this->_mh, $easy->handle);
        } else {
            $this->delays[$id] = Utils::currentTime() + ($easy->options['delay'] / 1000);
        }
    }

    /**
     * Cancels a handle from sending and removes references to it.
     *
     * @param int $id Handle ID to cancel and remove.
     *
     * @return bool True on success, false on failure.
     */
    private function cancel($id): bool
    {
        if (!is_int($id)) {
            trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing an integer to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
        }

        // Cannot cancel if it has been processed.
        if (!isset($this->handles[$id])) {
            return false;
        }

        $handle = $this->handles[$id]['easy']->handle;
        unset($this->delays[$id], $this->handles[$id]);
        \curl_multi_remove_handle($this->_mh, $handle);
        \curl_close($handle);

        return true;
    }

    private function processMessages(): void
    {
        while ($done = \curl_multi_info_read($this->_mh)) {
            if ($done['msg'] !== \CURLMSG_DONE) {
                // if it's not done, then it would be premature to remove the handle. ref https://github.com/guzzle/guzzle/pull/2892#issuecomment-945150216
                continue;
            }
            $id = (int) $done['handle'];
            \curl_multi_remove_handle($this->_mh, $done['handle']);

            if (!isset($this->handles[$id])) {
                // Probably was cancelled.
                continue;
            }

            $entry = $this->handles[$id];
            unset($this->handles[$id], $this->delays[$id]);
            $entry['easy']->errno = $done['result'];
            $entry['deferred']->resolve(
                CurlFactory::finish($this, $entry['easy'], $this->factory)
            );
        }
    }

    private function timeToNext(): int
    {
        $currentTime = Utils::currentTime();
        $nextTime = \PHP_INT_MAX;
        foreach ($this->delays as $time) {
            if ($time < $nextTime) {
                $nextTime = $time;
            }
        }

        return ((int) \max(0, $nextTime - $currentTime)) * 1000000;
    }
}
PK�c�\�?�CTT,guzzlehttp/guzzle/src/Handler/EasyHandle.phpnu�[���<?php

namespace GuzzleHttp\Handler;

use GuzzleHttp\Psr7\Response;
use GuzzleHttp\Utils;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;

/**
 * Represents a cURL easy handle and the data it populates.
 *
 * @internal
 */
final class EasyHandle
{
    /**
     * @var resource|\CurlHandle cURL resource
     */
    public $handle;

    /**
     * @var StreamInterface Where data is being written
     */
    public $sink;

    /**
     * @var array Received HTTP headers so far
     */
    public $headers = [];

    /**
     * @var ResponseInterface|null Received response (if any)
     */
    public $response;

    /**
     * @var RequestInterface Request being sent
     */
    public $request;

    /**
     * @var array Request options
     */
    public $options = [];

    /**
     * @var int cURL error number (if any)
     */
    public $errno = 0;

    /**
     * @var \Throwable|null Exception during on_headers (if any)
     */
    public $onHeadersException;

    /**
     * @var \Exception|null Exception during createResponse (if any)
     */
    public $createResponseException;

    /**
     * Attach a response to the easy handle based on the received headers.
     *
     * @throws \RuntimeException if no headers have been received or the first
     *                           header line is invalid.
     */
    public function createResponse(): void
    {
        [$ver, $status, $reason, $headers] = HeaderProcessor::parseHeaders($this->headers);

        $normalizedKeys = Utils::normalizeHeaderKeys($headers);

        if (!empty($this->options['decode_content']) && isset($normalizedKeys['content-encoding'])) {
            $headers['x-encoded-content-encoding'] = $headers[$normalizedKeys['content-encoding']];
            unset($headers[$normalizedKeys['content-encoding']]);
            if (isset($normalizedKeys['content-length'])) {
                $headers['x-encoded-content-length'] = $headers[$normalizedKeys['content-length']];

                $bodyLength = (int) $this->sink->getSize();
                if ($bodyLength) {
                    $headers[$normalizedKeys['content-length']] = $bodyLength;
                } else {
                    unset($headers[$normalizedKeys['content-length']]);
                }
            }
        }

        // Attach a response to the easy handle with the parsed headers.
        $this->response = new Response(
            $status,
            $headers,
            $this->sink,
            $ver,
            $reason
        );
    }

    /**
     * @param string $name
     *
     * @return void
     *
     * @throws \BadMethodCallException
     */
    public function __get($name)
    {
        $msg = $name === 'handle' ? 'The EasyHandle has been released' : 'Invalid property: '.$name;
        throw new \BadMethodCallException($msg);
    }
}
PK�c�\7�{  1guzzlehttp/guzzle/src/Handler/HeaderProcessor.phpnu�[���<?php

namespace GuzzleHttp\Handler;

use GuzzleHttp\Utils;

/**
 * @internal
 */
final class HeaderProcessor
{
    /**
     * Returns the HTTP version, status code, reason phrase, and headers.
     *
     * @param string[] $headers
     *
     * @return array{0:string, 1:int, 2:?string, 3:array}
     *
     * @throws \RuntimeException
     */
    public static function parseHeaders(array $headers): array
    {
        if ($headers === []) {
            throw new \RuntimeException('Expected a non-empty array of header data');
        }

        $parts = \explode(' ', \array_shift($headers), 3);
        $version = \explode('/', $parts[0])[1] ?? null;

        if ($version === null) {
            throw new \RuntimeException('HTTP version missing from header data');
        }

        $status = $parts[1] ?? null;

        if ($status === null) {
            throw new \RuntimeException('HTTP status code missing from header data');
        }

        return [$version, (int) $status, $parts[2] ?? null, Utils::headersFromLines($headers)];
    }
}
PK�c�\��9���'guzzlehttp/guzzle/src/Handler/Proxy.phpnu�[���<?php

namespace GuzzleHttp\Handler;

use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\RequestOptions;
use Psr\Http\Message\RequestInterface;

/**
 * Provides basic proxies for handlers.
 *
 * @final
 */
class Proxy
{
    /**
     * Sends synchronous requests to a specific handler while sending all other
     * requests to another handler.
     *
     * @param callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface $default Handler used for normal responses
     * @param callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface $sync    Handler used for synchronous responses.
     *
     * @return callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface Returns the composed handler.
     */
    public static function wrapSync(callable $default, callable $sync): callable
    {
        return static function (RequestInterface $request, array $options) use ($default, $sync): PromiseInterface {
            return empty($options[RequestOptions::SYNCHRONOUS]) ? $default($request, $options) : $sync($request, $options);
        };
    }

    /**
     * Sends streaming requests to a streaming compatible handler while sending
     * all other requests to a default handler.
     *
     * This, for example, could be useful for taking advantage of the
     * performance benefits of curl while still supporting true streaming
     * through the StreamHandler.
     *
     * @param callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface $default   Handler used for non-streaming responses
     * @param callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface $streaming Handler used for streaming responses
     *
     * @return callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface Returns the composed handler.
     */
    public static function wrapStreaming(callable $default, callable $streaming): callable
    {
        return static function (RequestInterface $request, array $options) use ($default, $streaming): PromiseInterface {
            return empty($options['stream']) ? $default($request, $options) : $streaming($request, $options);
        };
    }
}
PK�c�\�b�VSVS/guzzlehttp/guzzle/src/Handler/StreamHandler.phpnu�[���<?php

namespace GuzzleHttp\Handler;

use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Promise as P;
use GuzzleHttp\Promise\FulfilledPromise;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Psr7;
use GuzzleHttp\TransferStats;
use GuzzleHttp\Utils;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriInterface;

/**
 * HTTP handler that uses PHP's HTTP stream wrapper.
 *
 * @final
 */
class StreamHandler
{
    /**
     * @var array
     */
    private $lastHeaders = [];

    /**
     * Sends an HTTP request.
     *
     * @param RequestInterface $request Request to send.
     * @param array            $options Request transfer options.
     */
    public function __invoke(RequestInterface $request, array $options): PromiseInterface
    {
        // Sleep if there is a delay specified.
        if (isset($options['delay'])) {
            \usleep($options['delay'] * 1000);
        }

        $startTime = isset($options['on_stats']) ? Utils::currentTime() : null;

        try {
            // Does not support the expect header.
            $request = $request->withoutHeader('Expect');

            // Append a content-length header if body size is zero to match
            // cURL's behavior.
            if (0 === $request->getBody()->getSize()) {
                $request = $request->withHeader('Content-Length', '0');
            }

            return $this->createResponse(
                $request,
                $options,
                $this->createStream($request, $options),
                $startTime
            );
        } catch (\InvalidArgumentException $e) {
            throw $e;
        } catch (\Exception $e) {
            // Determine if the error was a networking error.
            $message = $e->getMessage();
            // This list can probably get more comprehensive.
            if (false !== \strpos($message, 'getaddrinfo') // DNS lookup failed
                || false !== \strpos($message, 'Connection refused')
                || false !== \strpos($message, "couldn't connect to host") // error on HHVM
                || false !== \strpos($message, 'connection attempt failed')
            ) {
                $e = new ConnectException($e->getMessage(), $request, $e);
            } else {
                $e = RequestException::wrapException($request, $e);
            }
            $this->invokeStats($options, $request, $startTime, null, $e);

            return P\Create::rejectionFor($e);
        }
    }

    private function invokeStats(
        array $options,
        RequestInterface $request,
        ?float $startTime,
        ResponseInterface $response = null,
        \Throwable $error = null
    ): void {
        if (isset($options['on_stats'])) {
            $stats = new TransferStats($request, $response, Utils::currentTime() - $startTime, $error, []);
            ($options['on_stats'])($stats);
        }
    }

    /**
     * @param resource $stream
     */
    private function createResponse(RequestInterface $request, array $options, $stream, ?float $startTime): PromiseInterface
    {
        $hdrs = $this->lastHeaders;
        $this->lastHeaders = [];

        try {
            [$ver, $status, $reason, $headers] = HeaderProcessor::parseHeaders($hdrs);
        } catch (\Exception $e) {
            return P\Create::rejectionFor(
                new RequestException('An error was encountered while creating the response', $request, null, $e)
            );
        }

        [$stream, $headers] = $this->checkDecode($options, $headers, $stream);
        $stream = Psr7\Utils::streamFor($stream);
        $sink = $stream;

        if (\strcasecmp('HEAD', $request->getMethod())) {
            $sink = $this->createSink($stream, $options);
        }

        try {
            $response = new Psr7\Response($status, $headers, $sink, $ver, $reason);
        } catch (\Exception $e) {
            return P\Create::rejectionFor(
                new RequestException('An error was encountered while creating the response', $request, null, $e)
            );
        }

        if (isset($options['on_headers'])) {
            try {
                $options['on_headers']($response);
            } catch (\Exception $e) {
                return P\Create::rejectionFor(
                    new RequestException('An error was encountered during the on_headers event', $request, $response, $e)
                );
            }
        }

        // Do not drain when the request is a HEAD request because they have
        // no body.
        if ($sink !== $stream) {
            $this->drain($stream, $sink, $response->getHeaderLine('Content-Length'));
        }

        $this->invokeStats($options, $request, $startTime, $response, null);

        return new FulfilledPromise($response);
    }

    private function createSink(StreamInterface $stream, array $options): StreamInterface
    {
        if (!empty($options['stream'])) {
            return $stream;
        }

        $sink = $options['sink'] ?? Psr7\Utils::tryFopen('php://temp', 'r+');

        return \is_string($sink) ? new Psr7\LazyOpenStream($sink, 'w+') : Psr7\Utils::streamFor($sink);
    }

    /**
     * @param resource $stream
     */
    private function checkDecode(array $options, array $headers, $stream): array
    {
        // Automatically decode responses when instructed.
        if (!empty($options['decode_content'])) {
            $normalizedKeys = Utils::normalizeHeaderKeys($headers);
            if (isset($normalizedKeys['content-encoding'])) {
                $encoding = $headers[$normalizedKeys['content-encoding']];
                if ($encoding[0] === 'gzip' || $encoding[0] === 'deflate') {
                    $stream = new Psr7\InflateStream(Psr7\Utils::streamFor($stream));
                    $headers['x-encoded-content-encoding'] = $headers[$normalizedKeys['content-encoding']];

                    // Remove content-encoding header
                    unset($headers[$normalizedKeys['content-encoding']]);

                    // Fix content-length header
                    if (isset($normalizedKeys['content-length'])) {
                        $headers['x-encoded-content-length'] = $headers[$normalizedKeys['content-length']];
                        $length = (int) $stream->getSize();
                        if ($length === 0) {
                            unset($headers[$normalizedKeys['content-length']]);
                        } else {
                            $headers[$normalizedKeys['content-length']] = [$length];
                        }
                    }
                }
            }
        }

        return [$stream, $headers];
    }

    /**
     * Drains the source stream into the "sink" client option.
     *
     * @param string $contentLength Header specifying the amount of
     *                              data to read.
     *
     * @throws \RuntimeException when the sink option is invalid.
     */
    private function drain(StreamInterface $source, StreamInterface $sink, string $contentLength): StreamInterface
    {
        // If a content-length header is provided, then stop reading once
        // that number of bytes has been read. This can prevent infinitely
        // reading from a stream when dealing with servers that do not honor
        // Connection: Close headers.
        Psr7\Utils::copyToStream(
            $source,
            $sink,
            (\strlen($contentLength) > 0 && (int) $contentLength > 0) ? (int) $contentLength : -1
        );

        $sink->seek(0);
        $source->close();

        return $sink;
    }

    /**
     * Create a resource and check to ensure it was created successfully
     *
     * @param callable $callback Callable that returns stream resource
     *
     * @return resource
     *
     * @throws \RuntimeException on error
     */
    private function createResource(callable $callback)
    {
        $errors = [];
        \set_error_handler(static function ($_, $msg, $file, $line) use (&$errors): bool {
            $errors[] = [
                'message' => $msg,
                'file' => $file,
                'line' => $line,
            ];

            return true;
        });

        try {
            $resource = $callback();
        } finally {
            \restore_error_handler();
        }

        if (!$resource) {
            $message = 'Error creating resource: ';
            foreach ($errors as $err) {
                foreach ($err as $key => $value) {
                    $message .= "[$key] $value".\PHP_EOL;
                }
            }
            throw new \RuntimeException(\trim($message));
        }

        return $resource;
    }

    /**
     * @return resource
     */
    private function createStream(RequestInterface $request, array $options)
    {
        static $methods;
        if (!$methods) {
            $methods = \array_flip(\get_class_methods(__CLASS__));
        }

        if (!\in_array($request->getUri()->getScheme(), ['http', 'https'])) {
            throw new RequestException(\sprintf("The scheme '%s' is not supported.", $request->getUri()->getScheme()), $request);
        }

        // HTTP/1.1 streams using the PHP stream wrapper require a
        // Connection: close header
        if ($request->getProtocolVersion() == '1.1'
            && !$request->hasHeader('Connection')
        ) {
            $request = $request->withHeader('Connection', 'close');
        }

        // Ensure SSL is verified by default
        if (!isset($options['verify'])) {
            $options['verify'] = true;
        }

        $params = [];
        $context = $this->getDefaultContext($request);

        if (isset($options['on_headers']) && !\is_callable($options['on_headers'])) {
            throw new \InvalidArgumentException('on_headers must be callable');
        }

        if (!empty($options)) {
            foreach ($options as $key => $value) {
                $method = "add_{$key}";
                if (isset($methods[$method])) {
                    $this->{$method}($request, $context, $value, $params);
                }
            }
        }

        if (isset($options['stream_context'])) {
            if (!\is_array($options['stream_context'])) {
                throw new \InvalidArgumentException('stream_context must be an array');
            }
            $context = \array_replace_recursive($context, $options['stream_context']);
        }

        // Microsoft NTLM authentication only supported with curl handler
        if (isset($options['auth'][2]) && 'ntlm' === $options['auth'][2]) {
            throw new \InvalidArgumentException('Microsoft NTLM authentication only supported with curl handler');
        }

        $uri = $this->resolveHost($request, $options);

        $contextResource = $this->createResource(
            static function () use ($context, $params) {
                return \stream_context_create($context, $params);
            }
        );

        return $this->createResource(
            function () use ($uri, &$http_response_header, $contextResource, $context, $options, $request) {
                $resource = @\fopen((string) $uri, 'r', false, $contextResource);
                $this->lastHeaders = $http_response_header ?? [];

                if (false === $resource) {
                    throw new ConnectException(sprintf('Connection refused for URI %s', $uri), $request, null, $context);
                }

                if (isset($options['read_timeout'])) {
                    $readTimeout = $options['read_timeout'];
                    $sec = (int) $readTimeout;
                    $usec = ($readTimeout - $sec) * 100000;
                    \stream_set_timeout($resource, $sec, $usec);
                }

                return $resource;
            }
        );
    }

    private function resolveHost(RequestInterface $request, array $options): UriInterface
    {
        $uri = $request->getUri();

        if (isset($options['force_ip_resolve']) && !\filter_var($uri->getHost(), \FILTER_VALIDATE_IP)) {
            if ('v4' === $options['force_ip_resolve']) {
                $records = \dns_get_record($uri->getHost(), \DNS_A);
                if (false === $records || !isset($records[0]['ip'])) {
                    throw new ConnectException(\sprintf("Could not resolve IPv4 address for host '%s'", $uri->getHost()), $request);
                }

                return $uri->withHost($records[0]['ip']);
            }
            if ('v6' === $options['force_ip_resolve']) {
                $records = \dns_get_record($uri->getHost(), \DNS_AAAA);
                if (false === $records || !isset($records[0]['ipv6'])) {
                    throw new ConnectException(\sprintf("Could not resolve IPv6 address for host '%s'", $uri->getHost()), $request);
                }

                return $uri->withHost('['.$records[0]['ipv6'].']');
            }
        }

        return $uri;
    }

    private function getDefaultContext(RequestInterface $request): array
    {
        $headers = '';
        foreach ($request->getHeaders() as $name => $value) {
            foreach ($value as $val) {
                $headers .= "$name: $val\r\n";
            }
        }

        $context = [
            'http' => [
                'method' => $request->getMethod(),
                'header' => $headers,
                'protocol_version' => $request->getProtocolVersion(),
                'ignore_errors' => true,
                'follow_location' => 0,
            ],
            'ssl' => [
                'peer_name' => $request->getUri()->getHost(),
            ],
        ];

        $body = (string) $request->getBody();

        if ('' !== $body) {
            $context['http']['content'] = $body;
            // Prevent the HTTP handler from adding a Content-Type header.
            if (!$request->hasHeader('Content-Type')) {
                $context['http']['header'] .= "Content-Type:\r\n";
            }
        }

        $context['http']['header'] = \rtrim($context['http']['header']);

        return $context;
    }

    /**
     * @param mixed $value as passed via Request transfer options.
     */
    private function add_proxy(RequestInterface $request, array &$options, $value, array &$params): void
    {
        $uri = null;

        if (!\is_array($value)) {
            $uri = $value;
        } else {
            $scheme = $request->getUri()->getScheme();
            if (isset($value[$scheme])) {
                if (!isset($value['no']) || !Utils::isHostInNoProxy($request->getUri()->getHost(), $value['no'])) {
                    $uri = $value[$scheme];
                }
            }
        }

        if (!$uri) {
            return;
        }

        $parsed = $this->parse_proxy($uri);
        $options['http']['proxy'] = $parsed['proxy'];

        if ($parsed['auth']) {
            if (!isset($options['http']['header'])) {
                $options['http']['header'] = [];
            }
            $options['http']['header'] .= "\r\nProxy-Authorization: {$parsed['auth']}";
        }
    }

    /**
     * Parses the given proxy URL to make it compatible with the format PHP's stream context expects.
     */
    private function parse_proxy(string $url): array
    {
        $parsed = \parse_url($url);

        if ($parsed !== false && isset($parsed['scheme']) && $parsed['scheme'] === 'http') {
            if (isset($parsed['host']) && isset($parsed['port'])) {
                $auth = null;
                if (isset($parsed['user']) && isset($parsed['pass'])) {
                    $auth = \base64_encode("{$parsed['user']}:{$parsed['pass']}");
                }

                return [
                    'proxy' => "tcp://{$parsed['host']}:{$parsed['port']}",
                    'auth' => $auth ? "Basic {$auth}" : null,
                ];
            }
        }

        // Return proxy as-is.
        return [
            'proxy' => $url,
            'auth' => null,
        ];
    }

    /**
     * @param mixed $value as passed via Request transfer options.
     */
    private function add_timeout(RequestInterface $request, array &$options, $value, array &$params): void
    {
        if ($value > 0) {
            $options['http']['timeout'] = $value;
        }
    }

    /**
     * @param mixed $value as passed via Request transfer options.
     */
    private function add_crypto_method(RequestInterface $request, array &$options, $value, array &$params): void
    {
        if (
            $value === \STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT
            || $value === \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT
            || $value === \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT
            || (defined('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT') && $value === \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT)
        ) {
            $options['http']['crypto_method'] = $value;

            return;
        }

        throw new \InvalidArgumentException('Invalid crypto_method request option: unknown version provided');
    }

    /**
     * @param mixed $value as passed via Request transfer options.
     */
    private function add_verify(RequestInterface $request, array &$options, $value, array &$params): void
    {
        if ($value === false) {
            $options['ssl']['verify_peer'] = false;
            $options['ssl']['verify_peer_name'] = false;

            return;
        }

        if (\is_string($value)) {
            $options['ssl']['cafile'] = $value;
            if (!\file_exists($value)) {
                throw new \RuntimeException("SSL CA bundle not found: $value");
            }
        } elseif ($value !== true) {
            throw new \InvalidArgumentException('Invalid verify request option');
        }

        $options['ssl']['verify_peer'] = true;
        $options['ssl']['verify_peer_name'] = true;
        $options['ssl']['allow_self_signed'] = false;
    }

    /**
     * @param mixed $value as passed via Request transfer options.
     */
    private function add_cert(RequestInterface $request, array &$options, $value, array &$params): void
    {
        if (\is_array($value)) {
            $options['ssl']['passphrase'] = $value[1];
            $value = $value[0];
        }

        if (!\file_exists($value)) {
            throw new \RuntimeException("SSL certificate not found: {$value}");
        }

        $options['ssl']['local_cert'] = $value;
    }

    /**
     * @param mixed $value as passed via Request transfer options.
     */
    private function add_progress(RequestInterface $request, array &$options, $value, array &$params): void
    {
        self::addNotification(
            $params,
            static function ($code, $a, $b, $c, $transferred, $total) use ($value) {
                if ($code == \STREAM_NOTIFY_PROGRESS) {
                    // The upload progress cannot be determined. Use 0 for cURL compatibility:
                    // https://curl.se/libcurl/c/CURLOPT_PROGRESSFUNCTION.html
                    $value($total, $transferred, 0, 0);
                }
            }
        );
    }

    /**
     * @param mixed $value as passed via Request transfer options.
     */
    private function add_debug(RequestInterface $request, array &$options, $value, array &$params): void
    {
        if ($value === false) {
            return;
        }

        static $map = [
            \STREAM_NOTIFY_CONNECT => 'CONNECT',
            \STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED',
            \STREAM_NOTIFY_AUTH_RESULT => 'AUTH_RESULT',
            \STREAM_NOTIFY_MIME_TYPE_IS => 'MIME_TYPE_IS',
            \STREAM_NOTIFY_FILE_SIZE_IS => 'FILE_SIZE_IS',
            \STREAM_NOTIFY_REDIRECTED => 'REDIRECTED',
            \STREAM_NOTIFY_PROGRESS => 'PROGRESS',
            \STREAM_NOTIFY_FAILURE => 'FAILURE',
            \STREAM_NOTIFY_COMPLETED => 'COMPLETED',
            \STREAM_NOTIFY_RESOLVE => 'RESOLVE',
        ];
        static $args = ['severity', 'message', 'message_code', 'bytes_transferred', 'bytes_max'];

        $value = Utils::debugResource($value);
        $ident = $request->getMethod().' '.$request->getUri()->withFragment('');
        self::addNotification(
            $params,
            static function (int $code, ...$passed) use ($ident, $value, $map, $args): void {
                \fprintf($value, '<%s> [%s] ', $ident, $map[$code]);
                foreach (\array_filter($passed) as $i => $v) {
                    \fwrite($value, $args[$i].': "'.$v.'" ');
                }
                \fwrite($value, "\n");
            }
        );
    }

    private static function addNotification(array &$params, callable $notify): void
    {
        // Wrap the existing function if needed.
        if (!isset($params['notification'])) {
            $params['notification'] = $notify;
        } else {
            $params['notification'] = self::callArray([
                $params['notification'],
                $notify,
            ]);
        }
    }

    private static function callArray(array $functions): callable
    {
        return static function (...$args) use ($functions) {
            foreach ($functions as $fn) {
                $fn(...$args);
            }
        };
    }
}
PK�c�\L�+llguzzlehttp/guzzle/src/Pool.phpnu�[���<?php

namespace GuzzleHttp;

use GuzzleHttp\Promise as P;
use GuzzleHttp\Promise\EachPromise;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Promise\PromisorInterface;
use Psr\Http\Message\RequestInterface;

/**
 * Sends an iterator of requests concurrently using a capped pool size.
 *
 * The pool will read from an iterator until it is cancelled or until the
 * iterator is consumed. When a request is yielded, the request is sent after
 * applying the "request_options" request options (if provided in the ctor).
 *
 * When a function is yielded by the iterator, the function is provided the
 * "request_options" array that should be merged on top of any existing
 * options, and the function MUST then return a wait-able promise.
 *
 * @final
 */
class Pool implements PromisorInterface
{
    /**
     * @var EachPromise
     */
    private $each;

    /**
     * @param ClientInterface $client   Client used to send the requests.
     * @param array|\Iterator $requests Requests or functions that return
     *                                  requests to send concurrently.
     * @param array           $config   Associative array of options
     *                                  - concurrency: (int) Maximum number of requests to send concurrently
     *                                  - options: Array of request options to apply to each request.
     *                                  - fulfilled: (callable) Function to invoke when a request completes.
     *                                  - rejected: (callable) Function to invoke when a request is rejected.
     */
    public function __construct(ClientInterface $client, $requests, array $config = [])
    {
        if (!isset($config['concurrency'])) {
            $config['concurrency'] = 25;
        }

        if (isset($config['options'])) {
            $opts = $config['options'];
            unset($config['options']);
        } else {
            $opts = [];
        }

        $iterable = P\Create::iterFor($requests);
        $requests = static function () use ($iterable, $client, $opts) {
            foreach ($iterable as $key => $rfn) {
                if ($rfn instanceof RequestInterface) {
                    yield $key => $client->sendAsync($rfn, $opts);
                } elseif (\is_callable($rfn)) {
                    yield $key => $rfn($opts);
                } else {
                    throw new \InvalidArgumentException('Each value yielded by the iterator must be a Psr7\Http\Message\RequestInterface or a callable that returns a promise that fulfills with a Psr7\Message\Http\ResponseInterface object.');
                }
            }
        };

        $this->each = new EachPromise($requests(), $config);
    }

    /**
     * Get promise
     */
    public function promise(): PromiseInterface
    {
        return $this->each->promise();
    }

    /**
     * Sends multiple requests concurrently and returns an array of responses
     * and exceptions that uses the same ordering as the provided requests.
     *
     * IMPORTANT: This method keeps every request and response in memory, and
     * as such, is NOT recommended when sending a large number or an
     * indeterminate number of requests concurrently.
     *
     * @param ClientInterface $client   Client used to send the requests
     * @param array|\Iterator $requests Requests to send concurrently.
     * @param array           $options  Passes through the options available in
     *                                  {@see \GuzzleHttp\Pool::__construct}
     *
     * @return array Returns an array containing the response or an exception
     *               in the same order that the requests were sent.
     *
     * @throws \InvalidArgumentException if the event format is incorrect.
     */
    public static function batch(ClientInterface $client, $requests, array $options = []): array
    {
        $res = [];
        self::cmpCallback($options, 'fulfilled', $res);
        self::cmpCallback($options, 'rejected', $res);
        $pool = new static($client, $requests, $options);
        $pool->promise()->wait();
        \ksort($res);

        return $res;
    }

    /**
     * Execute callback(s)
     */
    private static function cmpCallback(array &$options, string $name, array &$results): void
    {
        if (!isset($options[$name])) {
            $options[$name] = static function ($v, $k) use (&$results) {
                $results[$k] = $v;
            };
        } else {
            $currentFn = $options[$name];
            $options[$name] = static function ($v, $k) use (&$results, $currentFn) {
                $currentFn($v, $k);
                $results[$k] = $v;
            };
        }
    }
}
PK�c�\���TT)guzzlehttp/guzzle/src/ClientInterface.phpnu�[���<?php

namespace GuzzleHttp;

use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Promise\PromiseInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UriInterface;

/**
 * Client interface for sending HTTP requests.
 */
interface ClientInterface
{
    /**
     * The Guzzle major version.
     */
    public const MAJOR_VERSION = 7;

    /**
     * Send an HTTP request.
     *
     * @param RequestInterface $request Request to send
     * @param array            $options Request options to apply to the given
     *                                  request and to the transfer.
     *
     * @throws GuzzleException
     */
    public function send(RequestInterface $request, array $options = []): ResponseInterface;

    /**
     * Asynchronously send an HTTP request.
     *
     * @param RequestInterface $request Request to send
     * @param array            $options Request options to apply to the given
     *                                  request and to the transfer.
     */
    public function sendAsync(RequestInterface $request, array $options = []): PromiseInterface;

    /**
     * Create and send an HTTP request.
     *
     * Use an absolute path to override the base path of the client, or a
     * relative path to append to the base path of the client. The URL can
     * contain the query string as well.
     *
     * @param string              $method  HTTP method.
     * @param string|UriInterface $uri     URI object or string.
     * @param array               $options Request options to apply.
     *
     * @throws GuzzleException
     */
    public function request(string $method, $uri, array $options = []): ResponseInterface;

    /**
     * Create and send an asynchronous HTTP request.
     *
     * Use an absolute path to override the base path of the client, or a
     * relative path to append to the base path of the client. The URL can
     * contain the query string as well. Use an array to provide a URL
     * template and additional variables to use in the URL template expansion.
     *
     * @param string              $method  HTTP method
     * @param string|UriInterface $uri     URI object or string.
     * @param array               $options Request options to apply.
     */
    public function requestAsync(string $method, $uri, array $options = []): PromiseInterface;

    /**
     * Get a client configuration option.
     *
     * These options include default request options of the client, a "handler"
     * (if utilized by the concrete client), and a "base_uri" if utilized by
     * the concrete client.
     *
     * @param string|null $option The config option to retrieve.
     *
     * @return mixed
     *
     * @deprecated ClientInterface::getConfig will be removed in guzzlehttp/guzzle:8.0.
     */
    public function getConfig(string $option = null);
}
PK�c�\E�9��+guzzlehttp/guzzle/src/functions_include.phpnu�[���<?php

// Don't redefine the functions if included multiple times.
if (!\function_exists('GuzzleHttp\describe_type')) {
    require __DIR__.'/functions.php';
}
PK�c�\DS��,guzzlehttp/guzzle/src/RedirectMiddleware.phpnu�[���<?php

namespace GuzzleHttp;

use GuzzleHttp\Exception\BadResponseException;
use GuzzleHttp\Exception\TooManyRedirectsException;
use GuzzleHttp\Promise\PromiseInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UriInterface;

/**
 * Request redirect middleware.
 *
 * Apply this middleware like other middleware using
 * {@see \GuzzleHttp\Middleware::redirect()}.
 *
 * @final
 */
class RedirectMiddleware
{
    public const HISTORY_HEADER = 'X-Guzzle-Redirect-History';

    public const STATUS_HISTORY_HEADER = 'X-Guzzle-Redirect-Status-History';

    /**
     * @var array
     */
    public static $defaultSettings = [
        'max' => 5,
        'protocols' => ['http', 'https'],
        'strict' => false,
        'referer' => false,
        'track_redirects' => false,
    ];

    /**
     * @var callable(RequestInterface, array): PromiseInterface
     */
    private $nextHandler;

    /**
     * @param callable(RequestInterface, array): PromiseInterface $nextHandler Next handler to invoke.
     */
    public function __construct(callable $nextHandler)
    {
        $this->nextHandler = $nextHandler;
    }

    public function __invoke(RequestInterface $request, array $options): PromiseInterface
    {
        $fn = $this->nextHandler;

        if (empty($options['allow_redirects'])) {
            return $fn($request, $options);
        }

        if ($options['allow_redirects'] === true) {
            $options['allow_redirects'] = self::$defaultSettings;
        } elseif (!\is_array($options['allow_redirects'])) {
            throw new \InvalidArgumentException('allow_redirects must be true, false, or array');
        } else {
            // Merge the default settings with the provided settings
            $options['allow_redirects'] += self::$defaultSettings;
        }

        if (empty($options['allow_redirects']['max'])) {
            return $fn($request, $options);
        }

        return $fn($request, $options)
            ->then(function (ResponseInterface $response) use ($request, $options) {
                return $this->checkRedirect($request, $options, $response);
            });
    }

    /**
     * @return ResponseInterface|PromiseInterface
     */
    public function checkRedirect(RequestInterface $request, array $options, ResponseInterface $response)
    {
        if (\strpos((string) $response->getStatusCode(), '3') !== 0
            || !$response->hasHeader('Location')
        ) {
            return $response;
        }

        $this->guardMax($request, $response, $options);
        $nextRequest = $this->modifyRequest($request, $options, $response);

        // If authorization is handled by curl, unset it if URI is cross-origin.
        if (Psr7\UriComparator::isCrossOrigin($request->getUri(), $nextRequest->getUri()) && defined('\CURLOPT_HTTPAUTH')) {
            unset(
                $options['curl'][\CURLOPT_HTTPAUTH],
                $options['curl'][\CURLOPT_USERPWD]
            );
        }

        if (isset($options['allow_redirects']['on_redirect'])) {
            ($options['allow_redirects']['on_redirect'])(
                $request,
                $response,
                $nextRequest->getUri()
            );
        }

        $promise = $this($nextRequest, $options);

        // Add headers to be able to track history of redirects.
        if (!empty($options['allow_redirects']['track_redirects'])) {
            return $this->withTracking(
                $promise,
                (string) $nextRequest->getUri(),
                $response->getStatusCode()
            );
        }

        return $promise;
    }

    /**
     * Enable tracking on promise.
     */
    private function withTracking(PromiseInterface $promise, string $uri, int $statusCode): PromiseInterface
    {
        return $promise->then(
            static function (ResponseInterface $response) use ($uri, $statusCode) {
                // Note that we are pushing to the front of the list as this
                // would be an earlier response than what is currently present
                // in the history header.
                $historyHeader = $response->getHeader(self::HISTORY_HEADER);
                $statusHeader = $response->getHeader(self::STATUS_HISTORY_HEADER);
                \array_unshift($historyHeader, $uri);
                \array_unshift($statusHeader, (string) $statusCode);

                return $response->withHeader(self::HISTORY_HEADER, $historyHeader)
                                ->withHeader(self::STATUS_HISTORY_HEADER, $statusHeader);
            }
        );
    }

    /**
     * Check for too many redirects.
     *
     * @throws TooManyRedirectsException Too many redirects.
     */
    private function guardMax(RequestInterface $request, ResponseInterface $response, array &$options): void
    {
        $current = $options['__redirect_count']
            ?? 0;
        $options['__redirect_count'] = $current + 1;
        $max = $options['allow_redirects']['max'];

        if ($options['__redirect_count'] > $max) {
            throw new TooManyRedirectsException("Will not follow more than {$max} redirects", $request, $response);
        }
    }

    public function modifyRequest(RequestInterface $request, array $options, ResponseInterface $response): RequestInterface
    {
        // Request modifications to apply.
        $modify = [];
        $protocols = $options['allow_redirects']['protocols'];

        // Use a GET request if this is an entity enclosing request and we are
        // not forcing RFC compliance, but rather emulating what all browsers
        // would do.
        $statusCode = $response->getStatusCode();
        if ($statusCode == 303
            || ($statusCode <= 302 && !$options['allow_redirects']['strict'])
        ) {
            $safeMethods = ['GET', 'HEAD', 'OPTIONS'];
            $requestMethod = $request->getMethod();

            $modify['method'] = in_array($requestMethod, $safeMethods) ? $requestMethod : 'GET';
            $modify['body'] = '';
        }

        $uri = self::redirectUri($request, $response, $protocols);
        if (isset($options['idn_conversion']) && ($options['idn_conversion'] !== false)) {
            $idnOptions = ($options['idn_conversion'] === true) ? \IDNA_DEFAULT : $options['idn_conversion'];
            $uri = Utils::idnUriConvert($uri, $idnOptions);
        }

        $modify['uri'] = $uri;
        Psr7\Message::rewindBody($request);

        // Add the Referer header if it is told to do so and only
        // add the header if we are not redirecting from https to http.
        if ($options['allow_redirects']['referer']
            && $modify['uri']->getScheme() === $request->getUri()->getScheme()
        ) {
            $uri = $request->getUri()->withUserInfo('');
            $modify['set_headers']['Referer'] = (string) $uri;
        } else {
            $modify['remove_headers'][] = 'Referer';
        }

        // Remove Authorization and Cookie headers if URI is cross-origin.
        if (Psr7\UriComparator::isCrossOrigin($request->getUri(), $modify['uri'])) {
            $modify['remove_headers'][] = 'Authorization';
            $modify['remove_headers'][] = 'Cookie';
        }

        return Psr7\Utils::modifyRequest($request, $modify);
    }

    /**
     * Set the appropriate URL on the request based on the location header.
     */
    private static function redirectUri(
        RequestInterface $request,
        ResponseInterface $response,
        array $protocols
    ): UriInterface {
        $location = Psr7\UriResolver::resolve(
            $request->getUri(),
            new Psr7\Uri($response->getHeaderLine('Location'))
        );

        // Ensure that the redirect URI is allowed based on the protocols.
        if (!\in_array($location->getScheme(), $protocols)) {
            throw new BadResponseException(\sprintf('Redirect URI, %s, does not use one of the allowed redirect protocols: %s', $location, \implode(', ', $protocols)), $request, $response);
        }

        return $location;
    }
}
PK�c�\���)guzzlehttp/guzzle/src/RetryMiddleware.phpnu�[���<?php

namespace GuzzleHttp;

use GuzzleHttp\Promise as P;
use GuzzleHttp\Promise\PromiseInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * Middleware that retries requests based on the boolean result of
 * invoking the provided "decider" function.
 *
 * @final
 */
class RetryMiddleware
{
    /**
     * @var callable(RequestInterface, array): PromiseInterface
     */
    private $nextHandler;

    /**
     * @var callable
     */
    private $decider;

    /**
     * @var callable(int)
     */
    private $delay;

    /**
     * @param callable                                            $decider     Function that accepts the number of retries,
     *                                                                         a request, [response], and [exception] and
     *                                                                         returns true if the request is to be
     *                                                                         retried.
     * @param callable(RequestInterface, array): PromiseInterface $nextHandler Next handler to invoke.
     * @param (callable(int): int)|null                           $delay       Function that accepts the number of retries
     *                                                                         and returns the number of
     *                                                                         milliseconds to delay.
     */
    public function __construct(callable $decider, callable $nextHandler, callable $delay = null)
    {
        $this->decider = $decider;
        $this->nextHandler = $nextHandler;
        $this->delay = $delay ?: __CLASS__.'::exponentialDelay';
    }

    /**
     * Default exponential backoff delay function.
     *
     * @return int milliseconds.
     */
    public static function exponentialDelay(int $retries): int
    {
        return (int) 2 ** ($retries - 1) * 1000;
    }

    public function __invoke(RequestInterface $request, array $options): PromiseInterface
    {
        if (!isset($options['retries'])) {
            $options['retries'] = 0;
        }

        $fn = $this->nextHandler;

        return $fn($request, $options)
            ->then(
                $this->onFulfilled($request, $options),
                $this->onRejected($request, $options)
            );
    }

    /**
     * Execute fulfilled closure
     */
    private function onFulfilled(RequestInterface $request, array $options): callable
    {
        return function ($value) use ($request, $options) {
            if (!($this->decider)(
                $options['retries'],
                $request,
                $value,
                null
            )) {
                return $value;
            }

            return $this->doRetry($request, $options, $value);
        };
    }

    /**
     * Execute rejected closure
     */
    private function onRejected(RequestInterface $req, array $options): callable
    {
        return function ($reason) use ($req, $options) {
            if (!($this->decider)(
                $options['retries'],
                $req,
                null,
                $reason
            )) {
                return P\Create::rejectionFor($reason);
            }

            return $this->doRetry($req, $options);
        };
    }

    private function doRetry(RequestInterface $request, array $options, ResponseInterface $response = null): PromiseInterface
    {
        $options['delay'] = ($this->delay)(++$options['retries'], $response, $request);

        return $this($request, $options);
    }
}
PK�c�\��HGMM/guzzlehttp/guzzle/src/PrepareBodyMiddleware.phpnu�[���<?php

namespace GuzzleHttp;

use GuzzleHttp\Promise\PromiseInterface;
use Psr\Http\Message\RequestInterface;

/**
 * Prepares requests that contain a body, adding the Content-Length,
 * Content-Type, and Expect headers.
 *
 * @final
 */
class PrepareBodyMiddleware
{
    /**
     * @var callable(RequestInterface, array): PromiseInterface
     */
    private $nextHandler;

    /**
     * @param callable(RequestInterface, array): PromiseInterface $nextHandler Next handler to invoke.
     */
    public function __construct(callable $nextHandler)
    {
        $this->nextHandler = $nextHandler;
    }

    public function __invoke(RequestInterface $request, array $options): PromiseInterface
    {
        $fn = $this->nextHandler;

        // Don't do anything if the request has no body.
        if ($request->getBody()->getSize() === 0) {
            return $fn($request, $options);
        }

        $modify = [];

        // Add a default content-type if possible.
        if (!$request->hasHeader('Content-Type')) {
            if ($uri = $request->getBody()->getMetadata('uri')) {
                if (is_string($uri) && $type = Psr7\MimeType::fromFilename($uri)) {
                    $modify['set_headers']['Content-Type'] = $type;
                }
            }
        }

        // Add a default content-length or transfer-encoding header.
        if (!$request->hasHeader('Content-Length')
            && !$request->hasHeader('Transfer-Encoding')
        ) {
            $size = $request->getBody()->getSize();
            if ($size !== null) {
                $modify['set_headers']['Content-Length'] = $size;
            } else {
                $modify['set_headers']['Transfer-Encoding'] = 'chunked';
            }
        }

        // Add the expect header if needed.
        $this->addExpectHeader($request, $options, $modify);

        return $fn(Psr7\Utils::modifyRequest($request, $modify), $options);
    }

    /**
     * Add expect header
     */
    private function addExpectHeader(RequestInterface $request, array $options, array &$modify): void
    {
        // Determine if the Expect header should be used
        if ($request->hasHeader('Expect')) {
            return;
        }

        $expect = $options['expect'] ?? null;

        // Return if disabled or if you're not using HTTP/1.1 or HTTP/2.0
        if ($expect === false || $request->getProtocolVersion() < 1.1) {
            return;
        }

        // The expect header is unconditionally enabled
        if ($expect === true) {
            $modify['set_headers']['Expect'] = '100-Continue';

            return;
        }

        // By default, send the expect header when the payload is > 1mb
        if ($expect === null) {
            $expect = 1048576;
        }

        // Always add if the body cannot be rewound, the size cannot be
        // determined, or the size is greater than the cutoff threshold
        $body = $request->getBody();
        $size = $body->getSize();

        if ($size === null || $size >= (int) $expect || !$body->isSeekable()) {
            $modify['set_headers']['Expect'] = '100-Continue';
        }
    }
}
PK�c�\l���""&guzzlehttp/guzzle/src/HandlerStack.phpnu�[���<?php

namespace GuzzleHttp;

use GuzzleHttp\Promise\PromiseInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * Creates a composed Guzzle handler function by stacking middlewares on top of
 * an HTTP handler function.
 *
 * @final
 */
class HandlerStack
{
    /**
     * @var (callable(RequestInterface, array): PromiseInterface)|null
     */
    private $handler;

    /**
     * @var array{(callable(callable(RequestInterface, array): PromiseInterface): callable), (string|null)}[]
     */
    private $stack = [];

    /**
     * @var (callable(RequestInterface, array): PromiseInterface)|null
     */
    private $cached;

    /**
     * Creates a default handler stack that can be used by clients.
     *
     * The returned handler will wrap the provided handler or use the most
     * appropriate default handler for your system. The returned HandlerStack has
     * support for cookies, redirects, HTTP error exceptions, and preparing a body
     * before sending.
     *
     * The returned handler stack can be passed to a client in the "handler"
     * option.
     *
     * @param (callable(RequestInterface, array): PromiseInterface)|null $handler HTTP handler function to use with the stack. If no
     *                                                                            handler is provided, the best handler for your
     *                                                                            system will be utilized.
     */
    public static function create(callable $handler = null): self
    {
        $stack = new self($handler ?: Utils::chooseHandler());
        $stack->push(Middleware::httpErrors(), 'http_errors');
        $stack->push(Middleware::redirect(), 'allow_redirects');
        $stack->push(Middleware::cookies(), 'cookies');
        $stack->push(Middleware::prepareBody(), 'prepare_body');

        return $stack;
    }

    /**
     * @param (callable(RequestInterface, array): PromiseInterface)|null $handler Underlying HTTP handler.
     */
    public function __construct(callable $handler = null)
    {
        $this->handler = $handler;
    }

    /**
     * Invokes the handler stack as a composed handler
     *
     * @return ResponseInterface|PromiseInterface
     */
    public function __invoke(RequestInterface $request, array $options)
    {
        $handler = $this->resolve();

        return $handler($request, $options);
    }

    /**
     * Dumps a string representation of the stack.
     *
     * @return string
     */
    public function __toString()
    {
        $depth = 0;
        $stack = [];

        if ($this->handler !== null) {
            $stack[] = '0) Handler: '.$this->debugCallable($this->handler);
        }

        $result = '';
        foreach (\array_reverse($this->stack) as $tuple) {
            ++$depth;
            $str = "{$depth}) Name: '{$tuple[1]}', ";
            $str .= 'Function: '.$this->debugCallable($tuple[0]);
            $result = "> {$str}\n{$result}";
            $stack[] = $str;
        }

        foreach (\array_keys($stack) as $k) {
            $result .= "< {$stack[$k]}\n";
        }

        return $result;
    }

    /**
     * Set the HTTP handler that actually returns a promise.
     *
     * @param callable(RequestInterface, array): PromiseInterface $handler Accepts a request and array of options and
     *                                                                     returns a Promise.
     */
    public function setHandler(callable $handler): void
    {
        $this->handler = $handler;
        $this->cached = null;
    }

    /**
     * Returns true if the builder has a handler.
     */
    public function hasHandler(): bool
    {
        return $this->handler !== null;
    }

    /**
     * Unshift a middleware to the bottom of the stack.
     *
     * @param callable(callable): callable $middleware Middleware function
     * @param string                       $name       Name to register for this middleware.
     */
    public function unshift(callable $middleware, string $name = null): void
    {
        \array_unshift($this->stack, [$middleware, $name]);
        $this->cached = null;
    }

    /**
     * Push a middleware to the top of the stack.
     *
     * @param callable(callable): callable $middleware Middleware function
     * @param string                       $name       Name to register for this middleware.
     */
    public function push(callable $middleware, string $name = ''): void
    {
        $this->stack[] = [$middleware, $name];
        $this->cached = null;
    }

    /**
     * Add a middleware before another middleware by name.
     *
     * @param string                       $findName   Middleware to find
     * @param callable(callable): callable $middleware Middleware function
     * @param string                       $withName   Name to register for this middleware.
     */
    public function before(string $findName, callable $middleware, string $withName = ''): void
    {
        $this->splice($findName, $withName, $middleware, true);
    }

    /**
     * Add a middleware after another middleware by name.
     *
     * @param string                       $findName   Middleware to find
     * @param callable(callable): callable $middleware Middleware function
     * @param string                       $withName   Name to register for this middleware.
     */
    public function after(string $findName, callable $middleware, string $withName = ''): void
    {
        $this->splice($findName, $withName, $middleware, false);
    }

    /**
     * Remove a middleware by instance or name from the stack.
     *
     * @param callable|string $remove Middleware to remove by instance or name.
     */
    public function remove($remove): void
    {
        if (!is_string($remove) && !is_callable($remove)) {
            trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a callable or string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
        }

        $this->cached = null;
        $idx = \is_callable($remove) ? 0 : 1;
        $this->stack = \array_values(\array_filter(
            $this->stack,
            static function ($tuple) use ($idx, $remove) {
                return $tuple[$idx] !== $remove;
            }
        ));
    }

    /**
     * Compose the middleware and handler into a single callable function.
     *
     * @return callable(RequestInterface, array): PromiseInterface
     */
    public function resolve(): callable
    {
        if ($this->cached === null) {
            if (($prev = $this->handler) === null) {
                throw new \LogicException('No handler has been specified');
            }

            foreach (\array_reverse($this->stack) as $fn) {
                /** @var callable(RequestInterface, array): PromiseInterface $prev */
                $prev = $fn[0]($prev);
            }

            $this->cached = $prev;
        }

        return $this->cached;
    }

    private function findByName(string $name): int
    {
        foreach ($this->stack as $k => $v) {
            if ($v[1] === $name) {
                return $k;
            }
        }

        throw new \InvalidArgumentException("Middleware not found: $name");
    }

    /**
     * Splices a function into the middleware list at a specific position.
     */
    private function splice(string $findName, string $withName, callable $middleware, bool $before): void
    {
        $this->cached = null;
        $idx = $this->findByName($findName);
        $tuple = [$middleware, $withName];

        if ($before) {
            if ($idx === 0) {
                \array_unshift($this->stack, $tuple);
            } else {
                $replacement = [$tuple, $this->stack[$idx]];
                \array_splice($this->stack, $idx, 1, $replacement);
            }
        } elseif ($idx === \count($this->stack) - 1) {
            $this->stack[] = $tuple;
        } else {
            $replacement = [$this->stack[$idx], $tuple];
            \array_splice($this->stack, $idx, 1, $replacement);
        }
    }

    /**
     * Provides a debug string for a given callable.
     *
     * @param callable|string $fn Function to write as a string.
     */
    private function debugCallable($fn): string
    {
        if (\is_string($fn)) {
            return "callable({$fn})";
        }

        if (\is_array($fn)) {
            return \is_string($fn[0])
                ? "callable({$fn[0]}::{$fn[1]})"
                : "callable(['".\get_class($fn[0])."', '{$fn[1]}'])";
        }

        /** @var object $fn */
        return 'callable('.\spl_object_hash($fn).')';
    }
}
PK�c�\��n�*�*(guzzlehttp/guzzle/src/RequestOptions.phpnu�[���<?php

namespace GuzzleHttp;

/**
 * This class contains a list of built-in Guzzle request options.
 *
 * @see https://docs.guzzlephp.org/en/latest/request-options.html
 */
final class RequestOptions
{
    /**
     * allow_redirects: (bool|array) Controls redirect behavior. Pass false
     * to disable redirects, pass true to enable redirects, pass an
     * associative to provide custom redirect settings. Defaults to "false".
     * This option only works if your handler has the RedirectMiddleware. When
     * passing an associative array, you can provide the following key value
     * pairs:
     *
     * - max: (int, default=5) maximum number of allowed redirects.
     * - strict: (bool, default=false) Set to true to use strict redirects
     *   meaning redirect POST requests with POST requests vs. doing what most
     *   browsers do which is redirect POST requests with GET requests
     * - referer: (bool, default=false) Set to true to enable the Referer
     *   header.
     * - protocols: (array, default=['http', 'https']) Allowed redirect
     *   protocols.
     * - on_redirect: (callable) PHP callable that is invoked when a redirect
     *   is encountered. The callable is invoked with the request, the redirect
     *   response that was received, and the effective URI. Any return value
     *   from the on_redirect function is ignored.
     */
    public const ALLOW_REDIRECTS = 'allow_redirects';

    /**
     * auth: (array) Pass an array of HTTP authentication parameters to use
     * with the request. The array must contain the username in index [0],
     * the password in index [1], and you can optionally provide a built-in
     * authentication type in index [2]. Pass null to disable authentication
     * for a request.
     */
    public const AUTH = 'auth';

    /**
     * body: (resource|string|null|int|float|StreamInterface|callable|\Iterator)
     * Body to send in the request.
     */
    public const BODY = 'body';

    /**
     * cert: (string|array) Set to a string to specify the path to a file
     * containing a PEM formatted SSL client side certificate. If a password
     * is required, then set cert to an array containing the path to the PEM
     * file in the first array element followed by the certificate password
     * in the second array element.
     */
    public const CERT = 'cert';

    /**
     * cookies: (bool|GuzzleHttp\Cookie\CookieJarInterface, default=false)
     * Specifies whether or not cookies are used in a request or what cookie
     * jar to use or what cookies to send. This option only works if your
     * handler has the `cookie` middleware. Valid values are `false` and
     * an instance of {@see \GuzzleHttp\Cookie\CookieJarInterface}.
     */
    public const COOKIES = 'cookies';

    /**
     * connect_timeout: (float, default=0) Float describing the number of
     * seconds to wait while trying to connect to a server. Use 0 to wait
     * 300 seconds (the default behavior).
     */
    public const CONNECT_TIMEOUT = 'connect_timeout';

    /**
     * crypto_method: (int) A value describing the minimum TLS protocol
     * version to use.
     *
     * This setting must be set to one of the
     * ``STREAM_CRYPTO_METHOD_TLS*_CLIENT`` constants. PHP 7.4 or higher is
     * required in order to use TLS 1.3, and cURL 7.34.0 or higher is required
     * in order to specify a crypto method, with cURL 7.52.0 or higher being
     * required to use TLS 1.3.
     */
    public const CRYPTO_METHOD = 'crypto_method';

    /**
     * debug: (bool|resource) Set to true or set to a PHP stream returned by
     * fopen()  enable debug output with the HTTP handler used to send a
     * request.
     */
    public const DEBUG = 'debug';

    /**
     * decode_content: (bool, default=true) Specify whether or not
     * Content-Encoding responses (gzip, deflate, etc.) are automatically
     * decoded.
     */
    public const DECODE_CONTENT = 'decode_content';

    /**
     * delay: (int) The amount of time to delay before sending in milliseconds.
     */
    public const DELAY = 'delay';

    /**
     * expect: (bool|integer) Controls the behavior of the
     * "Expect: 100-Continue" header.
     *
     * Set to `true` to enable the "Expect: 100-Continue" header for all
     * requests that sends a body. Set to `false` to disable the
     * "Expect: 100-Continue" header for all requests. Set to a number so that
     * the size of the payload must be greater than the number in order to send
     * the Expect header. Setting to a number will send the Expect header for
     * all requests in which the size of the payload cannot be determined or
     * where the body is not rewindable.
     *
     * By default, Guzzle will add the "Expect: 100-Continue" header when the
     * size of the body of a request is greater than 1 MB and a request is
     * using HTTP/1.1.
     */
    public const EXPECT = 'expect';

    /**
     * form_params: (array) Associative array of form field names to values
     * where each value is a string or array of strings. Sets the Content-Type
     * header to application/x-www-form-urlencoded when no Content-Type header
     * is already present.
     */
    public const FORM_PARAMS = 'form_params';

    /**
     * headers: (array) Associative array of HTTP headers. Each value MUST be
     * a string or array of strings.
     */
    public const HEADERS = 'headers';

    /**
     * http_errors: (bool, default=true) Set to false to disable exceptions
     * when a non- successful HTTP response is received. By default,
     * exceptions will be thrown for 4xx and 5xx responses. This option only
     * works if your handler has the `httpErrors` middleware.
     */
    public const HTTP_ERRORS = 'http_errors';

    /**
     * idn: (bool|int, default=true) A combination of IDNA_* constants for
     * idn_to_ascii() PHP's function (see "options" parameter). Set to false to
     * disable IDN support completely, or to true to use the default
     * configuration (IDNA_DEFAULT constant).
     */
    public const IDN_CONVERSION = 'idn_conversion';

    /**
     * json: (mixed) Adds JSON data to a request. The provided value is JSON
     * encoded and a Content-Type header of application/json will be added to
     * the request if no Content-Type header is already present.
     */
    public const JSON = 'json';

    /**
     * multipart: (array) Array of associative arrays, each containing a
     * required "name" key mapping to the form field, name, a required
     * "contents" key mapping to a StreamInterface|resource|string, an
     * optional "headers" associative array of custom headers, and an
     * optional "filename" key mapping to a string to send as the filename in
     * the part. If no "filename" key is present, then no "filename" attribute
     * will be added to the part.
     */
    public const MULTIPART = 'multipart';

    /**
     * on_headers: (callable) A callable that is invoked when the HTTP headers
     * of the response have been received but the body has not yet begun to
     * download.
     */
    public const ON_HEADERS = 'on_headers';

    /**
     * on_stats: (callable) allows you to get access to transfer statistics of
     * a request and access the lower level transfer details of the handler
     * associated with your client. ``on_stats`` is a callable that is invoked
     * when a handler has finished sending a request. The callback is invoked
     * with transfer statistics about the request, the response received, or
     * the error encountered. Included in the data is the total amount of time
     * taken to send the request.
     */
    public const ON_STATS = 'on_stats';

    /**
     * progress: (callable) Defines a function to invoke when transfer
     * progress is made. The function accepts the following positional
     * arguments: the total number of bytes expected to be downloaded, the
     * number of bytes downloaded so far, the number of bytes expected to be
     * uploaded, the number of bytes uploaded so far.
     */
    public const PROGRESS = 'progress';

    /**
     * proxy: (string|array) Pass a string to specify an HTTP proxy, or an
     * array to specify different proxies for different protocols (where the
     * key is the protocol and the value is a proxy string).
     */
    public const PROXY = 'proxy';

    /**
     * query: (array|string) Associative array of query string values to add
     * to the request. This option uses PHP's http_build_query() to create
     * the string representation. Pass a string value if you need more
     * control than what this method provides
     */
    public const QUERY = 'query';

    /**
     * sink: (resource|string|StreamInterface) Where the data of the
     * response is written to. Defaults to a PHP temp stream. Providing a
     * string will write data to a file by the given name.
     */
    public const SINK = 'sink';

    /**
     * synchronous: (bool) Set to true to inform HTTP handlers that you intend
     * on waiting on the response. This can be useful for optimizations. Note
     * that a promise is still returned if you are using one of the async
     * client methods.
     */
    public const SYNCHRONOUS = 'synchronous';

    /**
     * ssl_key: (array|string) Specify the path to a file containing a private
     * SSL key in PEM format. If a password is required, then set to an array
     * containing the path to the SSL key in the first array element followed
     * by the password required for the certificate in the second element.
     */
    public const SSL_KEY = 'ssl_key';

    /**
     * stream: Set to true to attempt to stream a response rather than
     * download it all up-front.
     */
    public const STREAM = 'stream';

    /**
     * verify: (bool|string, default=true) Describes the SSL certificate
     * verification behavior of a request. Set to true to enable SSL
     * certificate verification using the system CA bundle when available
     * (the default). Set to false to disable certificate verification (this
     * is insecure!). Set to a string to provide the path to a CA bundle on
     * disk to enable verification using a custom certificate.
     */
    public const VERIFY = 'verify';

    /**
     * timeout: (float, default=0) Float describing the timeout of the
     * request in seconds. Use 0 to wait indefinitely (the default behavior).
     */
    public const TIMEOUT = 'timeout';

    /**
     * read_timeout: (float, default=default_socket_timeout ini setting) Float describing
     * the body read timeout, for stream requests.
     */
    public const READ_TIMEOUT = 'read_timeout';

    /**
     * version: (float) Specifies the HTTP protocol version to attempt to use.
     */
    public const VERSION = 'version';

    /**
     * force_ip_resolve: (bool) Force client to use only ipv4 or ipv6 protocol
     */
    public const FORCE_IP_RESOLVE = 'force_ip_resolve';
}
PK�c�\99f�uuguzzlehttp/guzzle/composer.jsonnu�[���{
    "name": "guzzlehttp/guzzle",
    "description": "Guzzle is a PHP HTTP client library",
    "keywords": [
        "framework",
        "http",
        "rest",
        "web service",
        "curl",
        "client",
        "HTTP client",
        "PSR-7",
        "PSR-18"
    ],
    "license": "MIT",
    "authors": [
        {
            "name": "Graham Campbell",
            "email": "hello@gjcampbell.co.uk",
            "homepage": "https://github.com/GrahamCampbell"
        },
        {
            "name": "Michael Dowling",
            "email": "mtdowling@gmail.com",
            "homepage": "https://github.com/mtdowling"
        },
        {
            "name": "Jeremy Lindblom",
            "email": "jeremeamia@gmail.com",
            "homepage": "https://github.com/jeremeamia"
        },
        {
            "name": "George Mponos",
            "email": "gmponos@gmail.com",
            "homepage": "https://github.com/gmponos"
        },
        {
            "name": "Tobias Nyholm",
            "email": "tobias.nyholm@gmail.com",
            "homepage": "https://github.com/Nyholm"
        },
        {
            "name": "Márk Sági-Kazár",
            "email": "mark.sagikazar@gmail.com",
            "homepage": "https://github.com/sagikazarmark"
        },
        {
            "name": "Tobias Schultze",
            "email": "webmaster@tubo-world.de",
            "homepage": "https://github.com/Tobion"
        }
    ],
    "require": {
        "php": "^7.2.5 || ^8.0",
        "ext-json": "*",
        "guzzlehttp/promises": "^1.5.3 || ^2.0.1",
        "guzzlehttp/psr7": "^1.9.1 || ^2.5.1",
        "psr/http-client": "^1.0",
        "symfony/deprecation-contracts": "^2.2 || ^3.0"
    },
    "provide": {
        "psr/http-client-implementation": "1.0"
    },
    "require-dev": {
        "ext-curl": "*",
        "bamarni/composer-bin-plugin": "^1.8.2",
        "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999",
        "php-http/message-factory": "^1.1",
        "phpunit/phpunit": "^8.5.36 || ^9.6.15",
        "psr/log": "^1.1 || ^2.0 || ^3.0"
    },
    "suggest": {
        "ext-curl": "Required for CURL handler support",
        "ext-intl": "Required for Internationalized Domain Name (IDN) support",
        "psr/log": "Required for using the Log middleware"
    },
    "config": {
        "allow-plugins": {
            "bamarni/composer-bin-plugin": true
        },
        "preferred-install": "dist",
        "sort-packages": true
    },
    "extra": {
        "bamarni-bin": {
            "bin-links": true,
            "forward-command": false
        }
    },
    "autoload": {
        "psr-4": {
            "GuzzleHttp\\": "src/"
        },
        "files": [
            "src/functions_include.php"
        ]
    },
    "autoload-dev": {
        "psr-4": {
            "GuzzleHttp\\Tests\\": "tests/"
        }
    }
}
PK�c�\ژ3=aTaTguzzlehttp/guzzle/CHANGELOG.mdnu�[���# Change Log

Please refer to [UPGRADING](UPGRADING.md) guide for upgrading to a major version.


## 7.8.1 - 2023-12-03

### Changed

- Updated links in docs to their canonical versions
- Replaced `call_user_func*` with native calls


## 7.8.0 - 2023-08-27

### Added

- Support for PHP 8.3
- Added automatic closing of handles on `CurlFactory` object destruction


## 7.7.1 - 2023-08-27

### Changed

- Remove the need for `AllowDynamicProperties` in `CurlMultiHandler`


## 7.7.0 - 2023-05-21

### Added

- Support `guzzlehttp/promises` v2


## 7.6.1 - 2023-05-15

### Fixed

- Fix `SetCookie::fromString` MaxAge deprecation warning and skip invalid MaxAge values


## 7.6.0 - 2023-05-14

### Added

- Support for setting the minimum TLS version in a unified way
- Apply on request the version set in options parameters


## 7.5.2 - 2023-05-14

### Fixed

- Fixed set cookie constructor validation
- Fixed handling of files with `'0'` body

### Changed

- Corrected docs and default connect timeout value to 300 seconds


## 7.5.1 - 2023-04-17

### Fixed

- Fixed `NO_PROXY` settings so that setting the `proxy` option to `no` overrides the env variable

### Changed

- Adjusted `guzzlehttp/psr7` version constraint to `^1.9.1 || ^2.4.5`


## 7.5.0 - 2022-08-28

### Added

- Support PHP 8.2
- Add request to delay closure params


## 7.4.5 - 2022-06-20

### Fixed

* Fix change in port should be considered a change in origin
* Fix `CURLOPT_HTTPAUTH` option not cleared on change of origin


## 7.4.4 - 2022-06-09

### Fixed

* Fix failure to strip Authorization header on HTTP downgrade
* Fix failure to strip the Cookie header on change in host or HTTP downgrade


## 7.4.3 - 2022-05-25

### Fixed

* Fix cross-domain cookie leakage


## 7.4.2 - 2022-03-20

### Fixed

- Remove curl auth on cross-domain redirects to align with the Authorization HTTP header
- Reject non-HTTP schemes in StreamHandler
- Set a default ssl.peer_name context in StreamHandler to allow `force_ip_resolve`


## 7.4.1 - 2021-12-06

### Changed

- Replaced implicit URI to string coercion [#2946](https://github.com/guzzle/guzzle/pull/2946)
- Allow `symfony/deprecation-contracts` version 3 [#2961](https://github.com/guzzle/guzzle/pull/2961)

### Fixed

- Only close curl handle if it's done [#2950](https://github.com/guzzle/guzzle/pull/2950)


## 7.4.0 - 2021-10-18

### Added

- Support PHP 8.1 [#2929](https://github.com/guzzle/guzzle/pull/2929), [#2939](https://github.com/guzzle/guzzle/pull/2939)
- Support `psr/log` version 2 and 3 [#2943](https://github.com/guzzle/guzzle/pull/2943)

### Fixed

- Make sure we always call `restore_error_handler()` [#2915](https://github.com/guzzle/guzzle/pull/2915)
- Fix progress parameter type compatibility between the cURL and stream handlers [#2936](https://github.com/guzzle/guzzle/pull/2936)
- Throw `InvalidArgumentException` when an incorrect `headers` array is provided [#2916](https://github.com/guzzle/guzzle/pull/2916), [#2942](https://github.com/guzzle/guzzle/pull/2942)

### Changed

- Be more strict with types [#2914](https://github.com/guzzle/guzzle/pull/2914), [#2917](https://github.com/guzzle/guzzle/pull/2917), [#2919](https://github.com/guzzle/guzzle/pull/2919), [#2945](https://github.com/guzzle/guzzle/pull/2945)


## 7.3.0 - 2021-03-23

### Added

- Support for DER and P12 certificates [#2413](https://github.com/guzzle/guzzle/pull/2413)
- Support the cURL (http://) scheme for StreamHandler proxies [#2850](https://github.com/guzzle/guzzle/pull/2850)
- Support for `guzzlehttp/psr7:^2.0` [#2878](https://github.com/guzzle/guzzle/pull/2878)

### Fixed

- Handle exceptions on invalid header consistently between PHP versions and handlers [#2872](https://github.com/guzzle/guzzle/pull/2872)


## 7.2.0 - 2020-10-10

### Added

- Support for PHP 8 [#2712](https://github.com/guzzle/guzzle/pull/2712), [#2715](https://github.com/guzzle/guzzle/pull/2715), [#2789](https://github.com/guzzle/guzzle/pull/2789)
- Support passing a body summarizer to the http errors middleware [#2795](https://github.com/guzzle/guzzle/pull/2795)

### Fixed

- Handle exceptions during response creation [#2591](https://github.com/guzzle/guzzle/pull/2591)
- Fix CURLOPT_ENCODING not to be overwritten [#2595](https://github.com/guzzle/guzzle/pull/2595)
- Make sure the Request always has a body object [#2804](https://github.com/guzzle/guzzle/pull/2804)

### Changed

- The `TooManyRedirectsException` has a response [#2660](https://github.com/guzzle/guzzle/pull/2660)
- Avoid "functions" from dependencies [#2712](https://github.com/guzzle/guzzle/pull/2712)

### Deprecated

- Using environment variable GUZZLE_CURL_SELECT_TIMEOUT [#2786](https://github.com/guzzle/guzzle/pull/2786)


## 7.1.1 - 2020-09-30

### Fixed

- Incorrect EOF detection for response body streams on Windows.

### Changed

- We dont connect curl `sink` on HEAD requests.
- Removed some PHP 5 workarounds


## 7.1.0 - 2020-09-22

### Added

- `GuzzleHttp\MessageFormatterInterface`

### Fixed

- Fixed issue that caused cookies with no value not to be stored.
- On redirects, we allow all safe methods like GET, HEAD and OPTIONS.
- Fixed logging on empty responses.
- Make sure MessageFormatter::format returns string

### Deprecated

- All functions in `GuzzleHttp` has been deprecated. Use static methods on `Utils` instead.
- `ClientInterface::getConfig()`
- `Client::getConfig()`
- `Client::__call()`
- `Utils::defaultCaBundle()`
- `CurlFactory::LOW_CURL_VERSION_NUMBER`


## 7.0.1 - 2020-06-27

* Fix multiply defined functions fatal error [#2699](https://github.com/guzzle/guzzle/pull/2699)


## 7.0.0 - 2020-06-27

No changes since 7.0.0-rc1.


## 7.0.0-rc1 - 2020-06-15

### Changed

* Use error level for logging errors in Middleware [#2629](https://github.com/guzzle/guzzle/pull/2629)
* Disabled IDN support by default and require ext-intl to use it [#2675](https://github.com/guzzle/guzzle/pull/2675)


## 7.0.0-beta2 - 2020-05-25

### Added

* Using `Utils` class instead of functions in the `GuzzleHttp` namespace. [#2546](https://github.com/guzzle/guzzle/pull/2546)
* `ClientInterface::MAJOR_VERSION` [#2583](https://github.com/guzzle/guzzle/pull/2583)

### Changed

* Avoid the `getenv` function when unsafe [#2531](https://github.com/guzzle/guzzle/pull/2531)
* Added real client methods [#2529](https://github.com/guzzle/guzzle/pull/2529)
* Avoid functions due to global install conflicts [#2546](https://github.com/guzzle/guzzle/pull/2546)
* Use Symfony intl-idn polyfill [#2550](https://github.com/guzzle/guzzle/pull/2550)
* Adding methods for HTTP verbs like `Client::get()`, `Client::head()`, `Client::patch()` etc [#2529](https://github.com/guzzle/guzzle/pull/2529)
* `ConnectException` extends `TransferException` [#2541](https://github.com/guzzle/guzzle/pull/2541)
* Updated the default User Agent to "GuzzleHttp/7" [#2654](https://github.com/guzzle/guzzle/pull/2654)

### Fixed

* Various intl icu issues [#2626](https://github.com/guzzle/guzzle/pull/2626)

### Removed

* Pool option `pool_size` [#2528](https://github.com/guzzle/guzzle/pull/2528)


## 7.0.0-beta1 - 2019-12-30

The diff might look very big but 95% of Guzzle users will be able to upgrade without modification.
Please see [the upgrade document](UPGRADING.md) that describes all BC breaking changes.

### Added

* Implement PSR-18 and dropped PHP 5 support [#2421](https://github.com/guzzle/guzzle/pull/2421) [#2474](https://github.com/guzzle/guzzle/pull/2474)
* PHP 7 types [#2442](https://github.com/guzzle/guzzle/pull/2442) [#2449](https://github.com/guzzle/guzzle/pull/2449) [#2466](https://github.com/guzzle/guzzle/pull/2466) [#2497](https://github.com/guzzle/guzzle/pull/2497) [#2499](https://github.com/guzzle/guzzle/pull/2499)
* IDN support for redirects [2424](https://github.com/guzzle/guzzle/pull/2424)

### Changed

* Dont allow passing null as third argument to `BadResponseException::__construct()` [#2427](https://github.com/guzzle/guzzle/pull/2427)
* Use SAPI constant instead of method call [#2450](https://github.com/guzzle/guzzle/pull/2450)
* Use native function invocation [#2444](https://github.com/guzzle/guzzle/pull/2444)
* Better defaults for PHP installations with old ICU lib [2454](https://github.com/guzzle/guzzle/pull/2454)
* Added visibility to all constants [#2462](https://github.com/guzzle/guzzle/pull/2462)
* Dont allow passing `null` as URI to `Client::request()` and `Client::requestAsync()` [#2461](https://github.com/guzzle/guzzle/pull/2461)
* Widen the exception argument to throwable [#2495](https://github.com/guzzle/guzzle/pull/2495)

### Fixed

* Logging when Promise rejected with a string [#2311](https://github.com/guzzle/guzzle/pull/2311)

### Removed

* Class `SeekException` [#2162](https://github.com/guzzle/guzzle/pull/2162)
* `RequestException::getResponseBodySummary()` [#2425](https://github.com/guzzle/guzzle/pull/2425)
* `CookieJar::getCookieValue()` [#2433](https://github.com/guzzle/guzzle/pull/2433)
* `uri_template()` and `UriTemplate` [#2440](https://github.com/guzzle/guzzle/pull/2440)
* Request options `save_to` and `exceptions` [#2464](https://github.com/guzzle/guzzle/pull/2464)


## 6.5.2 - 2019-12-23

* idn_to_ascii() fix for old PHP versions [#2489](https://github.com/guzzle/guzzle/pull/2489)


## 6.5.1 - 2019-12-21

* Better defaults for PHP installations with old ICU lib [#2454](https://github.com/guzzle/guzzle/pull/2454)
* IDN support for redirects [#2424](https://github.com/guzzle/guzzle/pull/2424)


## 6.5.0 - 2019-12-07

* Improvement: Added support for reset internal queue in MockHandler. [#2143](https://github.com/guzzle/guzzle/pull/2143)
* Improvement: Added support to pass arbitrary options to `curl_multi_init`. [#2287](https://github.com/guzzle/guzzle/pull/2287)
* Fix: Gracefully handle passing `null` to the `header` option. [#2132](https://github.com/guzzle/guzzle/pull/2132)
* Fix: `RetryMiddleware` did not do exponential delay between retires due unit mismatch. [#2132](https://github.com/guzzle/guzzle/pull/2132)
* Fix: Prevent undefined offset when using array for ssl_key options. [#2348](https://github.com/guzzle/guzzle/pull/2348)
* Deprecated `ClientInterface::VERSION`


## 6.4.1 - 2019-10-23

* No `guzzle.phar` was created in 6.4.0 due expired API token. This release will fix that
* Added `parent::__construct()` to `FileCookieJar` and `SessionCookieJar`


## 6.4.0 - 2019-10-23

* Improvement: Improved error messages when using curl < 7.21.2 [#2108](https://github.com/guzzle/guzzle/pull/2108)
* Fix: Test if response is readable before returning a summary in `RequestException::getResponseBodySummary()` [#2081](https://github.com/guzzle/guzzle/pull/2081)
* Fix: Add support for GUZZLE_CURL_SELECT_TIMEOUT environment variable [#2161](https://github.com/guzzle/guzzle/pull/2161)
* Improvement: Added `GuzzleHttp\Exception\InvalidArgumentException` [#2163](https://github.com/guzzle/guzzle/pull/2163)
* Improvement: Added `GuzzleHttp\_current_time()` to use `hrtime()` if that function exists. [#2242](https://github.com/guzzle/guzzle/pull/2242)
* Improvement: Added curl's `appconnect_time` in `TransferStats` [#2284](https://github.com/guzzle/guzzle/pull/2284)
* Improvement: Make GuzzleException extend Throwable wherever it's available [#2273](https://github.com/guzzle/guzzle/pull/2273)
* Fix: Prevent concurrent writes to file when saving `CookieJar` [#2335](https://github.com/guzzle/guzzle/pull/2335)
* Improvement: Update `MockHandler` so we can test transfer time [#2362](https://github.com/guzzle/guzzle/pull/2362)


## 6.3.3 - 2018-04-22

* Fix: Default headers when decode_content is specified


## 6.3.2 - 2018-03-26

* Fix: Release process


## 6.3.1 - 2018-03-26

* Bug fix: Parsing 0 epoch expiry times in cookies [#2014](https://github.com/guzzle/guzzle/pull/2014)
* Improvement: Better ConnectException detection [#2012](https://github.com/guzzle/guzzle/pull/2012)
* Bug fix: Malformed domain that contains a "/" [#1999](https://github.com/guzzle/guzzle/pull/1999)
* Bug fix: Undefined offset when a cookie has no first key-value pair [#1998](https://github.com/guzzle/guzzle/pull/1998)
* Improvement: Support PHPUnit 6 [#1953](https://github.com/guzzle/guzzle/pull/1953)
* Bug fix: Support empty headers [#1915](https://github.com/guzzle/guzzle/pull/1915)
* Bug fix: Ignore case during header modifications [#1916](https://github.com/guzzle/guzzle/pull/1916)

+ Minor code cleanups, documentation fixes and clarifications.


## 6.3.0 - 2017-06-22

* Feature: force IP resolution (ipv4 or ipv6) [#1608](https://github.com/guzzle/guzzle/pull/1608), [#1659](https://github.com/guzzle/guzzle/pull/1659)
* Improvement: Don't include summary in exception message when body is empty [#1621](https://github.com/guzzle/guzzle/pull/1621)
* Improvement: Handle `on_headers` option in MockHandler [#1580](https://github.com/guzzle/guzzle/pull/1580)
* Improvement: Added SUSE Linux CA path [#1609](https://github.com/guzzle/guzzle/issues/1609)
* Improvement: Use class reference for getting the name of the class instead of using hardcoded strings [#1641](https://github.com/guzzle/guzzle/pull/1641)
* Feature: Added `read_timeout` option [#1611](https://github.com/guzzle/guzzle/pull/1611)
* Bug fix: PHP 7.x fixes [#1685](https://github.com/guzzle/guzzle/pull/1685), [#1686](https://github.com/guzzle/guzzle/pull/1686), [#1811](https://github.com/guzzle/guzzle/pull/1811)
* Deprecation: BadResponseException instantiation without a response [#1642](https://github.com/guzzle/guzzle/pull/1642)
* Feature: Added NTLM auth [#1569](https://github.com/guzzle/guzzle/pull/1569)
* Feature: Track redirect HTTP status codes [#1711](https://github.com/guzzle/guzzle/pull/1711)
* Improvement: Check handler type during construction [#1745](https://github.com/guzzle/guzzle/pull/1745)
* Improvement: Always include the Content-Length if there's a body [#1721](https://github.com/guzzle/guzzle/pull/1721)
* Feature: Added convenience method to access a cookie by name [#1318](https://github.com/guzzle/guzzle/pull/1318)
* Bug fix: Fill `CURLOPT_CAPATH` and `CURLOPT_CAINFO` properly [#1684](https://github.com/guzzle/guzzle/pull/1684)
* Improvement:  	Use `\GuzzleHttp\Promise\rejection_for` function instead of object init [#1827](https://github.com/guzzle/guzzle/pull/1827)

+ Minor code cleanups, documentation fixes and clarifications.


## 6.2.3 - 2017-02-28

* Fix deprecations with guzzle/psr7 version 1.4


## 6.2.2 - 2016-10-08

* Allow to pass nullable Response to delay callable
* Only add scheme when host is present
* Fix drain case where content-length is the literal string zero
* Obfuscate in-URL credentials in exceptions


## 6.2.1 - 2016-07-18

* Address HTTP_PROXY security vulnerability, CVE-2016-5385:
  https://httpoxy.org/
* Fixing timeout bug with StreamHandler:
  https://github.com/guzzle/guzzle/pull/1488
* Only read up to `Content-Length` in PHP StreamHandler to avoid timeouts when
  a server does not honor `Connection: close`.
* Ignore URI fragment when sending requests.


## 6.2.0 - 2016-03-21

* Feature: added `GuzzleHttp\json_encode` and `GuzzleHttp\json_decode`.
  https://github.com/guzzle/guzzle/pull/1389
* Bug fix: Fix sleep calculation when waiting for delayed requests.
  https://github.com/guzzle/guzzle/pull/1324
* Feature: More flexible history containers.
  https://github.com/guzzle/guzzle/pull/1373
* Bug fix: defer sink stream opening in StreamHandler.
  https://github.com/guzzle/guzzle/pull/1377
* Bug fix: do not attempt to escape cookie values.
  https://github.com/guzzle/guzzle/pull/1406
* Feature: report original content encoding and length on decoded responses.
  https://github.com/guzzle/guzzle/pull/1409
* Bug fix: rewind seekable request bodies before dispatching to cURL.
  https://github.com/guzzle/guzzle/pull/1422
* Bug fix: provide an empty string to `http_build_query` for HHVM workaround.
  https://github.com/guzzle/guzzle/pull/1367


## 6.1.1 - 2015-11-22

* Bug fix: Proxy::wrapSync() now correctly proxies to the appropriate handler
  https://github.com/guzzle/guzzle/commit/911bcbc8b434adce64e223a6d1d14e9a8f63e4e4
* Feature: HandlerStack is now more generic.
  https://github.com/guzzle/guzzle/commit/f2102941331cda544745eedd97fc8fd46e1ee33e
* Bug fix: setting verify to false in the StreamHandler now disables peer
  verification. https://github.com/guzzle/guzzle/issues/1256
* Feature: Middleware now uses an exception factory, including more error
  context. https://github.com/guzzle/guzzle/pull/1282
* Feature: better support for disabled functions.
  https://github.com/guzzle/guzzle/pull/1287
* Bug fix: fixed regression where MockHandler was not using `sink`.
  https://github.com/guzzle/guzzle/pull/1292


## 6.1.0 - 2015-09-08

* Feature: Added the `on_stats` request option to provide access to transfer
  statistics for requests. https://github.com/guzzle/guzzle/pull/1202
* Feature: Added the ability to persist session cookies in CookieJars.
  https://github.com/guzzle/guzzle/pull/1195
* Feature: Some compatibility updates for Google APP Engine
  https://github.com/guzzle/guzzle/pull/1216
* Feature: Added support for NO_PROXY to prevent the use of a proxy based on
  a simple set of rules. https://github.com/guzzle/guzzle/pull/1197
* Feature: Cookies can now contain square brackets.
  https://github.com/guzzle/guzzle/pull/1237
* Bug fix: Now correctly parsing `=` inside of quotes in Cookies.
  https://github.com/guzzle/guzzle/pull/1232
* Bug fix: Cusotm cURL options now correctly override curl options of the
  same name. https://github.com/guzzle/guzzle/pull/1221
* Bug fix: Content-Type header is now added when using an explicitly provided
  multipart body. https://github.com/guzzle/guzzle/pull/1218
* Bug fix: Now ignoring Set-Cookie headers that have no name.
* Bug fix: Reason phrase is no longer cast to an int in some cases in the
  cURL handler. https://github.com/guzzle/guzzle/pull/1187
* Bug fix: Remove the Authorization header when redirecting if the Host
  header changes. https://github.com/guzzle/guzzle/pull/1207
* Bug fix: Cookie path matching fixes
  https://github.com/guzzle/guzzle/issues/1129
* Bug fix: Fixing the cURL `body_as_string` setting
  https://github.com/guzzle/guzzle/pull/1201
* Bug fix: quotes are no longer stripped when parsing cookies.
  https://github.com/guzzle/guzzle/issues/1172
* Bug fix: `form_params` and `query` now always uses the `&` separator.
  https://github.com/guzzle/guzzle/pull/1163
* Bug fix: Adding a Content-Length to PHP stream wrapper requests if not set.
  https://github.com/guzzle/guzzle/pull/1189


## 6.0.2 - 2015-07-04

* Fixed a memory leak in the curl handlers in which references to callbacks
  were not being removed by `curl_reset`.
* Cookies are now extracted properly before redirects.
* Cookies now allow more character ranges.
* Decoded Content-Encoding responses are now modified to correctly reflect
  their state if the encoding was automatically removed by a handler. This
  means that the `Content-Encoding` header may be removed an the
  `Content-Length` modified to reflect the message size after removing the
  encoding.
* Added a more explicit error message when trying to use `form_params` and
  `multipart` in the same request.
* Several fixes for HHVM support.
* Functions are now conditionally required using an additional level of
  indirection to help with global Composer installations.


## 6.0.1 - 2015-05-27

* Fixed a bug with serializing the `query` request option where the `&`
  separator was missing.
* Added a better error message for when `body` is provided as an array. Please
  use `form_params` or `multipart` instead.
* Various doc fixes.


## 6.0.0 - 2015-05-26

* See the UPGRADING.md document for more information.
* Added `multipart` and `form_params` request options.
* Added `synchronous` request option.
* Added the `on_headers` request option.
* Fixed `expect` handling.
* No longer adding default middlewares in the client ctor. These need to be
  present on the provided handler in order to work.
* Requests are no longer initiated when sending async requests with the
  CurlMultiHandler. This prevents unexpected recursion from requests completing
  while ticking the cURL loop.
* Removed the semantics of setting `default` to `true`. This is no longer
  required now that the cURL loop is not ticked for async requests.
* Added request and response logging middleware.
* No longer allowing self signed certificates when using the StreamHandler.
* Ensuring that `sink` is valid if saving to a file.
* Request exceptions now include a "handler context" which provides handler
  specific contextual information.
* Added `GuzzleHttp\RequestOptions` to allow request options to be applied
  using constants.
* `$maxHandles` has been removed from CurlMultiHandler.
* `MultipartPostBody` is now part of the `guzzlehttp/psr7` package.


## 5.3.0 - 2015-05-19

* Mock now supports `save_to`
* Marked `AbstractRequestEvent::getTransaction()` as public.
* Fixed a bug in which multiple headers using different casing would overwrite
  previous headers in the associative array.
* Added `Utils::getDefaultHandler()`
* Marked `GuzzleHttp\Client::getDefaultUserAgent` as deprecated.
* URL scheme is now always lowercased.


## 6.0.0-beta.1

* Requires PHP >= 5.5
* Updated to use PSR-7
  * Requires immutable messages, which basically means an event based system
    owned by a request instance is no longer possible.
  * Utilizing the [Guzzle PSR-7 package](https://github.com/guzzle/psr7).
  * Removed the dependency on `guzzlehttp/streams`. These stream abstractions
    are available in the `guzzlehttp/psr7` package under the `GuzzleHttp\Psr7`
    namespace.
* Added middleware and handler system
  * Replaced the Guzzle event and subscriber system with a middleware system.
  * No longer depends on RingPHP, but rather places the HTTP handlers directly
    in Guzzle, operating on PSR-7 messages.
  * Retry logic is now encapsulated in `GuzzleHttp\Middleware::retry`, which
    means the `guzzlehttp/retry-subscriber` is now obsolete.
  * Mocking responses is now handled using `GuzzleHttp\Handler\MockHandler`.
* Asynchronous responses
  * No longer supports the `future` request option to send an async request.
    Instead, use one of the `*Async` methods of a client (e.g., `requestAsync`,
    `getAsync`, etc.).
  * Utilizing `GuzzleHttp\Promise` instead of React's promise library to avoid
    recursion required by chaining and forwarding react promises. See
    https://github.com/guzzle/promises
  * Added `requestAsync` and `sendAsync` to send request asynchronously.
  * Added magic methods for `getAsync()`, `postAsync()`, etc. to send requests
    asynchronously.
* Request options
  * POST and form updates
    * Added the `form_fields` and `form_files` request options.
    * Removed the `GuzzleHttp\Post` namespace.
    * The `body` request option no longer accepts an array for POST requests.
  * The `exceptions` request option has been deprecated in favor of the
    `http_errors` request options.
  * The `save_to` request option has been deprecated in favor of `sink` request
    option.
* Clients no longer accept an array of URI template string and variables for
  URI variables. You will need to expand URI templates before passing them
  into a client constructor or request method.
* Client methods `get()`, `post()`, `put()`, `patch()`, `options()`, etc. are
  now magic methods that will send synchronous requests.
* Replaced `Utils.php` with plain functions in `functions.php`.
* Removed `GuzzleHttp\Collection`.
* Removed `GuzzleHttp\BatchResults`. Batched pool results are now returned as
  an array.
* Removed `GuzzleHttp\Query`. Query string handling is now handled using an
  associative array passed into the `query` request option. The query string
  is serialized using PHP's `http_build_query`. If you need more control, you
  can pass the query string in as a string.
* `GuzzleHttp\QueryParser` has been replaced with the
  `GuzzleHttp\Psr7\parse_query`.


## 5.2.0 - 2015-01-27

* Added `AppliesHeadersInterface` to make applying headers to a request based
  on the body more generic and not specific to `PostBodyInterface`.
* Reduced the number of stack frames needed to send requests.
* Nested futures are now resolved in the client rather than the RequestFsm
* Finishing state transitions is now handled in the RequestFsm rather than the
  RingBridge.
* Added a guard in the Pool class to not use recursion for request retries.


## 5.1.0 - 2014-12-19

* Pool class no longer uses recursion when a request is intercepted.
* The size of a Pool can now be dynamically adjusted using a callback.
  See https://github.com/guzzle/guzzle/pull/943.
* Setting a request option to `null` when creating a request with a client will
  ensure that the option is not set. This allows you to overwrite default
  request options on a per-request basis.
  See https://github.com/guzzle/guzzle/pull/937.
* Added the ability to limit which protocols are allowed for redirects by
  specifying a `protocols` array in the `allow_redirects` request option.
* Nested futures due to retries are now resolved when waiting for synchronous
  responses. See https://github.com/guzzle/guzzle/pull/947.
* `"0"` is now an allowed URI path. See
  https://github.com/guzzle/guzzle/pull/935.
* `Query` no longer typehints on the `$query` argument in the constructor,
  allowing for strings and arrays.
* Exceptions thrown in the `end` event are now correctly wrapped with Guzzle
  specific exceptions if necessary.


## 5.0.3 - 2014-11-03

This change updates query strings so that they are treated as un-encoded values
by default where the value represents an un-encoded value to send over the
wire. A Query object then encodes the value before sending over the wire. This
means that even value query string values (e.g., ":") are url encoded. This
makes the Query class match PHP's http_build_query function. However, if you
want to send requests over the wire using valid query string characters that do
not need to be encoded, then you can provide a string to Url::setQuery() and
pass true as the second argument to specify that the query string is a raw
string that should not be parsed or encoded (unless a call to getQuery() is
subsequently made, forcing the query-string to be converted into a Query
object).


## 5.0.2 - 2014-10-30

* Added a trailing `\r\n` to multipart/form-data payloads. See
  https://github.com/guzzle/guzzle/pull/871
* Added a `GuzzleHttp\Pool::send()` convenience method to match the docs.
* Status codes are now returned as integers. See
  https://github.com/guzzle/guzzle/issues/881
* No longer overwriting an existing `application/x-www-form-urlencoded` header
  when sending POST requests, allowing for customized headers. See
  https://github.com/guzzle/guzzle/issues/877
* Improved path URL serialization.

  * No longer double percent-encoding characters in the path or query string if
    they are already encoded.
  * Now properly encoding the supplied path to a URL object, instead of only
    encoding ' ' and '?'.
  * Note: This has been changed in 5.0.3 to now encode query string values by
    default unless the `rawString` argument is provided when setting the query
    string on a URL: Now allowing many more characters to be present in the
    query string without being percent encoded. See
    https://datatracker.ietf.org/doc/html/rfc3986#appendix-A


## 5.0.1 - 2014-10-16

Bugfix release.

* Fixed an issue where connection errors still returned response object in
  error and end events event though the response is unusable. This has been
  corrected so that a response is not returned in the `getResponse` method of
  these events if the response did not complete. https://github.com/guzzle/guzzle/issues/867
* Fixed an issue where transfer statistics were not being populated in the
  RingBridge. https://github.com/guzzle/guzzle/issues/866


## 5.0.0 - 2014-10-12

Adding support for non-blocking responses and some minor API cleanup.

### New Features

* Added support for non-blocking responses based on `guzzlehttp/guzzle-ring`.
* Added a public API for creating a default HTTP adapter.
* Updated the redirect plugin to be non-blocking so that redirects are sent
  concurrently. Other plugins like this can now be updated to be non-blocking.
* Added a "progress" event so that you can get upload and download progress
  events.
* Added `GuzzleHttp\Pool` which implements FutureInterface and transfers
  requests concurrently using a capped pool size as efficiently as possible.
* Added `hasListeners()` to EmitterInterface.
* Removed `GuzzleHttp\ClientInterface::sendAll` and marked
  `GuzzleHttp\Client::sendAll` as deprecated (it's still there, just not the
  recommended way).

### Breaking changes

The breaking changes in this release are relatively minor. The biggest thing to
look out for is that request and response objects no longer implement fluent
interfaces.

* Removed the fluent interfaces (i.e., `return $this`) from requests,
  responses, `GuzzleHttp\Collection`, `GuzzleHttp\Url`,
  `GuzzleHttp\Query`, `GuzzleHttp\Post\PostBody`, and
  `GuzzleHttp\Cookie\SetCookie`. This blog post provides a good outline of
  why I did this: https://ocramius.github.io/blog/fluent-interfaces-are-evil/.
  This also makes the Guzzle message interfaces compatible with the current
  PSR-7 message proposal.
* Removed "functions.php", so that Guzzle is truly PSR-4 compliant. Except
  for the HTTP request functions from function.php, these functions are now
  implemented in `GuzzleHttp\Utils` using camelCase. `GuzzleHttp\json_decode`
  moved to `GuzzleHttp\Utils::jsonDecode`. `GuzzleHttp\get_path` moved to
  `GuzzleHttp\Utils::getPath`. `GuzzleHttp\set_path` moved to
  `GuzzleHttp\Utils::setPath`. `GuzzleHttp\batch` should now be
  `GuzzleHttp\Pool::batch`, which returns an `objectStorage`. Using functions.php
  caused problems for many users: they aren't PSR-4 compliant, require an
  explicit include, and needed an if-guard to ensure that the functions are not
  declared multiple times.
* Rewrote adapter layer.
    * Removing all classes from `GuzzleHttp\Adapter`, these are now
      implemented as callables that are stored in `GuzzleHttp\Ring\Client`.
    * Removed the concept of "parallel adapters". Sending requests serially or
      concurrently is now handled using a single adapter.
    * Moved `GuzzleHttp\Adapter\Transaction` to `GuzzleHttp\Transaction`. The
      Transaction object now exposes the request, response, and client as public
      properties. The getters and setters have been removed.
* Removed the "headers" event. This event was only useful for changing the
  body a response once the headers of the response were known. You can implement
  a similar behavior in a number of ways. One example might be to use a
  FnStream that has access to the transaction being sent. For example, when the
  first byte is written, you could check if the response headers match your
  expectations, and if so, change the actual stream body that is being
  written to.
* Removed the `asArray` parameter from
  `GuzzleHttp\Message\MessageInterface::getHeader`. If you want to get a header
  value as an array, then use the newly added `getHeaderAsArray()` method of
  `MessageInterface`. This change makes the Guzzle interfaces compatible with
  the PSR-7 interfaces.
* `GuzzleHttp\Message\MessageFactory` no longer allows subclasses to add
  custom request options using double-dispatch (this was an implementation
  detail). Instead, you should now provide an associative array to the
  constructor which is a mapping of the request option name mapping to a
  function that applies the option value to a request.
* Removed the concept of "throwImmediately" from exceptions and error events.
  This control mechanism was used to stop a transfer of concurrent requests
  from completing. This can now be handled by throwing the exception or by
  cancelling a pool of requests or each outstanding future request individually.
* Updated to "GuzzleHttp\Streams" 3.0.
    * `GuzzleHttp\Stream\StreamInterface::getContents()` no longer accepts a
      `maxLen` parameter. This update makes the Guzzle streams project
      compatible with the current PSR-7 proposal.
    * `GuzzleHttp\Stream\Stream::__construct`,
      `GuzzleHttp\Stream\Stream::factory`, and
      `GuzzleHttp\Stream\Utils::create` no longer accept a size in the second
      argument. They now accept an associative array of options, including the
      "size" key and "metadata" key which can be used to provide custom metadata.


## 4.2.2 - 2014-09-08

* Fixed a memory leak in the CurlAdapter when reusing cURL handles.
* No longer using `request_fulluri` in stream adapter proxies.
* Relative redirects are now based on the last response, not the first response.

## 4.2.1 - 2014-08-19

* Ensuring that the StreamAdapter does not always add a Content-Type header
* Adding automated github releases with a phar and zip

## 4.2.0 - 2014-08-17

* Now merging in default options using a case-insensitive comparison.
  Closes https://github.com/guzzle/guzzle/issues/767
* Added the ability to automatically decode `Content-Encoding` response bodies
  using the `decode_content` request option. This is set to `true` by default
  to decode the response body if it comes over the wire with a
  `Content-Encoding`. Set this value to `false` to disable decoding the
  response content, and pass a string to provide a request `Accept-Encoding`
  header and turn on automatic response decoding. This feature now allows you
  to pass an `Accept-Encoding` header in the headers of a request but still
  disable automatic response decoding.
  Closes https://github.com/guzzle/guzzle/issues/764
* Added the ability to throw an exception immediately when transferring
  requests in parallel. Closes https://github.com/guzzle/guzzle/issues/760
* Updating guzzlehttp/streams dependency to ~2.1
* No longer utilizing the now deprecated namespaced methods from the stream
  package.

## 4.1.8 - 2014-08-14

* Fixed an issue in the CurlFactory that caused setting the `stream=false`
  request option to throw an exception.
  See: https://github.com/guzzle/guzzle/issues/769
* TransactionIterator now calls rewind on the inner iterator.
  See: https://github.com/guzzle/guzzle/pull/765
* You can now set the `Content-Type` header to `multipart/form-data`
  when creating POST requests to force multipart bodies.
  See https://github.com/guzzle/guzzle/issues/768

## 4.1.7 - 2014-08-07

* Fixed an error in the HistoryPlugin that caused the same request and response
  to be logged multiple times when an HTTP protocol error occurs.
* Ensuring that cURL does not add a default Content-Type when no Content-Type
  has been supplied by the user. This prevents the adapter layer from modifying
  the request that is sent over the wire after any listeners may have already
  put the request in a desired state (e.g., signed the request).
* Throwing an exception when you attempt to send requests that have the
  "stream" set to true in parallel using the MultiAdapter.
* Only calling curl_multi_select when there are active cURL handles. This was
  previously changed and caused performance problems on some systems due to PHP
  always selecting until the maximum select timeout.
* Fixed a bug where multipart/form-data POST fields were not correctly
  aggregated (e.g., values with "&").

## 4.1.6 - 2014-08-03

* Added helper methods to make it easier to represent messages as strings,
  including getting the start line and getting headers as a string.

## 4.1.5 - 2014-08-02

* Automatically retrying cURL "Connection died, retrying a fresh connect"
  errors when possible.
* cURL implementation cleanup
* Allowing multiple event subscriber listeners to be registered per event by
  passing an array of arrays of listener configuration.

## 4.1.4 - 2014-07-22

* Fixed a bug that caused multi-part POST requests with more than one field to
  serialize incorrectly.
* Paths can now be set to "0"
* `ResponseInterface::xml` now accepts a `libxml_options` option and added a
  missing default argument that was required when parsing XML response bodies.
* A `save_to` stream is now created lazily, which means that files are not
  created on disk unless a request succeeds.

## 4.1.3 - 2014-07-15

* Various fixes to multipart/form-data POST uploads
* Wrapping function.php in an if-statement to ensure Guzzle can be used
  globally and in a Composer install
* Fixed an issue with generating and merging in events to an event array
* POST headers are only applied before sending a request to allow you to change
  the query aggregator used before uploading
* Added much more robust query string parsing
* Fixed various parsing and normalization issues with URLs
* Fixing an issue where multi-valued headers were not being utilized correctly
  in the StreamAdapter

## 4.1.2 - 2014-06-18

* Added support for sending payloads with GET requests

## 4.1.1 - 2014-06-08

* Fixed an issue related to using custom message factory options in subclasses
* Fixed an issue with nested form fields in a multi-part POST
* Fixed an issue with using the `json` request option for POST requests
* Added `ToArrayInterface` to `GuzzleHttp\Cookie\CookieJar`

## 4.1.0 - 2014-05-27

* Added a `json` request option to easily serialize JSON payloads.
* Added a `GuzzleHttp\json_decode()` wrapper to safely parse JSON.
* Added `setPort()` and `getPort()` to `GuzzleHttp\Message\RequestInterface`.
* Added the ability to provide an emitter to a client in the client constructor.
* Added the ability to persist a cookie session using $_SESSION.
* Added a trait that can be used to add event listeners to an iterator.
* Removed request method constants from RequestInterface.
* Fixed warning when invalid request start-lines are received.
* Updated MessageFactory to work with custom request option methods.
* Updated cacert bundle to latest build.

4.0.2 (2014-04-16)
------------------

* Proxy requests using the StreamAdapter now properly use request_fulluri (#632)
* Added the ability to set scalars as POST fields (#628)

## 4.0.1 - 2014-04-04

* The HTTP status code of a response is now set as the exception code of
  RequestException objects.
* 303 redirects will now correctly switch from POST to GET requests.
* The default parallel adapter of a client now correctly uses the MultiAdapter.
* HasDataTrait now initializes the internal data array as an empty array so
  that the toArray() method always returns an array.

## 4.0.0 - 2014-03-29

* For information on changes and upgrading, see:
  https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40
* Added `GuzzleHttp\batch()` as a convenience function for sending requests in
  parallel without needing to write asynchronous code.
* Restructured how events are added to `GuzzleHttp\ClientInterface::sendAll()`.
  You can now pass a callable or an array of associative arrays where each
  associative array contains the "fn", "priority", and "once" keys.

## 4.0.0.rc-2 - 2014-03-25

* Removed `getConfig()` and `setConfig()` from clients to avoid confusion
  around whether things like base_url, message_factory, etc. should be able to
  be retrieved or modified.
* Added `getDefaultOption()` and `setDefaultOption()` to ClientInterface
* functions.php functions were renamed using snake_case to match PHP idioms
* Added support for `HTTP_PROXY`, `HTTPS_PROXY`, and
  `GUZZLE_CURL_SELECT_TIMEOUT` environment variables
* Added the ability to specify custom `sendAll()` event priorities
* Added the ability to specify custom stream context options to the stream
  adapter.
* Added a functions.php function for `get_path()` and `set_path()`
* CurlAdapter and MultiAdapter now use a callable to generate curl resources
* MockAdapter now properly reads a body and emits a `headers` event
* Updated Url class to check if a scheme and host are set before adding ":"
  and "//". This allows empty Url (e.g., "") to be serialized as "".
* Parsing invalid XML no longer emits warnings
* Curl classes now properly throw AdapterExceptions
* Various performance optimizations
* Streams are created with the faster `Stream\create()` function
* Marked deprecation_proxy() as internal
* Test server is now a collection of static methods on a class

## 4.0.0-rc.1 - 2014-03-15

* See https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40

## 3.8.1 - 2014-01-28

* Bug: Always using GET requests when redirecting from a 303 response
* Bug: CURLOPT_SSL_VERIFYHOST is now correctly set to false when setting `$certificateAuthority` to false in
  `Guzzle\Http\ClientInterface::setSslVerification()`
* Bug: RedirectPlugin now uses strict RFC 3986 compliance when combining a base URL with a relative URL
* Bug: The body of a request can now be set to `"0"`
* Sending PHP stream requests no longer forces `HTTP/1.0`
* Adding more information to ExceptionCollection exceptions so that users have more context, including a stack trace of
  each sub-exception
* Updated the `$ref` attribute in service descriptions to merge over any existing parameters of a schema (rather than
  clobbering everything).
* Merging URLs will now use the query string object from the relative URL (thus allowing custom query aggregators)
* Query strings are now parsed in a way that they do no convert empty keys with no value to have a dangling `=`.
  For example `foo&bar=baz` is now correctly parsed and recognized as `foo&bar=baz` rather than `foo=&bar=baz`.
* Now properly escaping the regular expression delimiter when matching Cookie domains.
* Network access is now disabled when loading XML documents

## 3.8.0 - 2013-12-05

* Added the ability to define a POST name for a file
* JSON response parsing now properly walks additionalProperties
* cURL error code 18 is now retried automatically in the BackoffPlugin
* Fixed a cURL error when URLs contain fragments
* Fixed an issue in the BackoffPlugin retry event where it was trying to access all exceptions as if they were
  CurlExceptions
* CURLOPT_PROGRESS function fix for PHP 5.5 (69fcc1e)
* Added the ability for Guzzle to work with older versions of cURL that do not support `CURLOPT_TIMEOUT_MS`
* Fixed a bug that was encountered when parsing empty header parameters
* UriTemplate now has a `setRegex()` method to match the docs
* The `debug` request parameter now checks if it is truthy rather than if it exists
* Setting the `debug` request parameter to true shows verbose cURL output instead of using the LogPlugin
* Added the ability to combine URLs using strict RFC 3986 compliance
* Command objects can now return the validation errors encountered by the command
* Various fixes to cache revalidation (#437 and 29797e5)
* Various fixes to the AsyncPlugin
* Cleaned up build scripts

## 3.7.4 - 2013-10-02

* Bug fix: 0 is now an allowed value in a description parameter that has a default value (#430)
* Bug fix: SchemaFormatter now returns an integer when formatting to a Unix timestamp
  (see https://github.com/aws/aws-sdk-php/issues/147)
* Bug fix: Cleaned up and fixed URL dot segment removal to properly resolve internal dots
* Minimum PHP version is now properly specified as 5.3.3 (up from 5.3.2) (#420)
* Updated the bundled cacert.pem (#419)
* OauthPlugin now supports adding authentication to headers or query string (#425)

## 3.7.3 - 2013-09-08

* Added the ability to get the exception associated with a request/command when using `MultiTransferException` and
  `CommandTransferException`.
* Setting `additionalParameters` of a response to false is now honored when parsing responses with a service description
* Schemas are only injected into response models when explicitly configured.
* No longer guessing Content-Type based on the path of a request. Content-Type is now only guessed based on the path of
  an EntityBody.
* Bug fix: ChunkedIterator can now properly chunk a \Traversable as well as an \Iterator.
* Bug fix: FilterIterator now relies on `\Iterator` instead of `\Traversable`.
* Bug fix: Gracefully handling malformed responses in RequestMediator::writeResponseBody()
* Bug fix: Replaced call to canCache with canCacheRequest in the CallbackCanCacheStrategy of the CachePlugin
* Bug fix: Visiting XML attributes first before visiting XML children when serializing requests
* Bug fix: Properly parsing headers that contain commas contained in quotes
* Bug fix: mimetype guessing based on a filename is now case-insensitive

## 3.7.2 - 2013-08-02

* Bug fix: Properly URL encoding paths when using the PHP-only version of the UriTemplate expander
  See https://github.com/guzzle/guzzle/issues/371
* Bug fix: Cookie domains are now matched correctly according to RFC 6265
  See https://github.com/guzzle/guzzle/issues/377
* Bug fix: GET parameters are now used when calculating an OAuth signature
* Bug fix: Fixed an issue with cache revalidation where the If-None-Match header was being double quoted
* `Guzzle\Common\AbstractHasDispatcher::dispatch()` now returns the event that was dispatched
* `Guzzle\Http\QueryString::factory()` now guesses the most appropriate query aggregator to used based on the input.
  See https://github.com/guzzle/guzzle/issues/379
* Added a way to add custom domain objects to service description parsing using the `operation.parse_class` event. See
  https://github.com/guzzle/guzzle/pull/380
* cURL multi cleanup and optimizations

## 3.7.1 - 2013-07-05

* Bug fix: Setting default options on a client now works
* Bug fix: Setting options on HEAD requests now works. See #352
* Bug fix: Moving stream factory before send event to before building the stream. See #353
* Bug fix: Cookies no longer match on IP addresses per RFC 6265
* Bug fix: Correctly parsing header parameters that are in `<>` and quotes
* Added `cert` and `ssl_key` as request options
* `Host` header can now diverge from the host part of a URL if the header is set manually
* `Guzzle\Service\Command\LocationVisitor\Request\XmlVisitor` was rewritten to change from using SimpleXML to XMLWriter
* OAuth parameters are only added via the plugin if they aren't already set
* Exceptions are now thrown when a URL cannot be parsed
* Returning `false` if `Guzzle\Http\EntityBody::getContentMd5()` fails
* Not setting a `Content-MD5` on a command if calculating the Content-MD5 fails via the CommandContentMd5Plugin

## 3.7.0 - 2013-06-10

* See UPGRADING.md for more information on how to upgrade.
* Requests now support the ability to specify an array of $options when creating a request to more easily modify a
  request. You can pass a 'request.options' configuration setting to a client to apply default request options to
  every request created by a client (e.g. default query string variables, headers, curl options, etc.).
* Added a static facade class that allows you to use Guzzle with static methods and mount the class to `\Guzzle`.
  See `Guzzle\Http\StaticClient::mount`.
* Added `command.request_options` to `Guzzle\Service\Command\AbstractCommand` to pass request options to requests
      created by a command (e.g. custom headers, query string variables, timeout settings, etc.).
* Stream size in `Guzzle\Stream\PhpStreamRequestFactory` will now be set if Content-Length is returned in the
  headers of a response
* Added `Guzzle\Common\Collection::setPath($path, $value)` to set a value into an array using a nested key
  (e.g. `$collection->setPath('foo/baz/bar', 'test'); echo $collection['foo']['bar']['bar'];`)
* ServiceBuilders now support storing and retrieving arbitrary data
* CachePlugin can now purge all resources for a given URI
* CachePlugin can automatically purge matching cached items when a non-idempotent request is sent to a resource
* CachePlugin now uses the Vary header to determine if a resource is a cache hit
* `Guzzle\Http\Message\Response` now implements `\Serializable`
* Added `Guzzle\Cache\CacheAdapterFactory::fromCache()` to more easily create cache adapters
* `Guzzle\Service\ClientInterface::execute()` now accepts an array, single command, or Traversable
* Fixed a bug in `Guzzle\Http\Message\Header\Link::addLink()`
* Better handling of calculating the size of a stream in `Guzzle\Stream\Stream` using fstat() and caching the size
* `Guzzle\Common\Exception\ExceptionCollection` now creates a more readable exception message
* Fixing BC break: Added back the MonologLogAdapter implementation rather than extending from PsrLog so that older
  Symfony users can still use the old version of Monolog.
* Fixing BC break: Added the implementation back in for `Guzzle\Http\Message\AbstractMessage::getTokenizedHeader()`.
  Now triggering an E_USER_DEPRECATED warning when used. Use `$message->getHeader()->parseParams()`.
* Several performance improvements to `Guzzle\Common\Collection`
* Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`:
  createRequest, head, delete, put, patch, post, options, prepareRequest
* Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()`
* Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface`
* Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to
  `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a
  resource, string, or EntityBody into the $options parameter to specify the download location of the response.
* Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a
  default `array()`
* Added `Guzzle\Stream\StreamInterface::isRepeatable`
* Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use
  $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or
  $client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))`.
* Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use $client->getConfig()->getPath('request.options/headers')`.
* Removed `Guzzle\Http\ClientInterface::expandTemplate()`
* Removed `Guzzle\Http\ClientInterface::setRequestFactory()`
* Removed `Guzzle\Http\ClientInterface::getCurlMulti()`
* Removed `Guzzle\Http\Message\RequestInterface::canCache`
* Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect`
* Removed `Guzzle\Http\Message\RequestInterface::isRedirect`
* Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods.
* You can now enable E_USER_DEPRECATED warnings to see if you are using a deprecated method by setting
  `Guzzle\Common\Version::$emitWarnings` to true.
* Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use
      `$request->getResponseBody()->isRepeatable()` instead.
* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use
  `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use
  `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
* Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead.
* Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead.
* Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated
* Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand.
  These will work through Guzzle 4.0
* Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use [request.options][params].
* Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client.
* Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use $client->getConfig()->getPath('request.options/headers')`.
* Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`.
* Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8.
* Marked `Guzzle\Common\Collection::inject()` as deprecated.
* Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest');`
* CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a
  CacheStorageInterface. These two objects and interface will be removed in a future version.
* Always setting X-cache headers on cached responses
* Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin
* `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface
  $request, Response $response);`
* `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);`
* `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);`
* Added `CacheStorageInterface::purge($url)`
* `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin
  $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache,
  CanCacheStrategyInterface $canCache = null)`
* Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)`

## 3.6.0 - 2013-05-29

* ServiceDescription now implements ToArrayInterface
* Added command.hidden_params to blacklist certain headers from being treated as additionalParameters
* Guzzle can now correctly parse incomplete URLs
* Mixed casing of headers are now forced to be a single consistent casing across all values for that header.
* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution
* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader().
* Specific header implementations can be created for complex headers. When a message creates a header, it uses a
  HeaderFactory which can map specific headers to specific header classes. There is now a Link header and
  CacheControl header implementation.
* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate
* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti()
* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in
  Guzzle\Http\Curl\RequestMediator
* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string.
* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface
* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders()
* Removed Guzzle\Parser\ParserRegister::get(). Use getParser()
* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser().
* All response header helper functions return a string rather than mixing Header objects and strings inconsistently
* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle
  directly via interfaces
* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist
  but are a no-op until removed.
* Most classes that used to require a `Guzzle\Service\Command\CommandInterface` typehint now request a
  `Guzzle\Service\Command\ArrayCommandInterface`.
* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response
  on a request while the request is still being transferred
* The ability to case-insensitively search for header values
* Guzzle\Http\Message\Header::hasExactHeader
* Guzzle\Http\Message\Header::raw. Use getAll()
* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object
  instead.
* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess
* Added the ability to cast Model objects to a string to view debug information.

## 3.5.0 - 2013-05-13

* Bug: Fixed a regression so that request responses are parsed only once per oncomplete event rather than multiple times
* Bug: Better cleanup of one-time events across the board (when an event is meant to fire once, it will now remove
  itself from the EventDispatcher)
* Bug: `Guzzle\Log\MessageFormatter` now properly writes "total_time" and "connect_time" values
* Bug: Cloning an EntityEnclosingRequest now clones the EntityBody too
* Bug: Fixed an undefined index error when parsing nested JSON responses with a sentAs parameter that reference a
  non-existent key
* Bug: All __call() method arguments are now required (helps with mocking frameworks)
* Deprecating Response::getRequest() and now using a shallow clone of a request object to remove a circular reference
  to help with refcount based garbage collection of resources created by sending a request
* Deprecating ZF1 cache and log adapters. These will be removed in the next major version.
* Deprecating `Response::getPreviousResponse()` (method signature still exists, but it's deprecated). Use the
  HistoryPlugin for a history.
* Added a `responseBody` alias for the `response_body` location
* Refactored internals to no longer rely on Response::getRequest()
* HistoryPlugin can now be cast to a string
* HistoryPlugin now logs transactions rather than requests and responses to more accurately keep track of the requests
  and responses that are sent over the wire
* Added `getEffectiveUrl()` and `getRedirectCount()` to Response objects

## 3.4.3 - 2013-04-30

* Bug fix: Fixing bug introduced in 3.4.2 where redirect responses are duplicated on the final redirected response
* Added a check to re-extract the temp cacert bundle from the phar before sending each request

## 3.4.2 - 2013-04-29

* Bug fix: Stream objects now work correctly with "a" and "a+" modes
* Bug fix: Removing `Transfer-Encoding: chunked` header when a Content-Length is present
* Bug fix: AsyncPlugin no longer forces HEAD requests
* Bug fix: DateTime timezones are now properly handled when using the service description schema formatter
* Bug fix: CachePlugin now properly handles stale-if-error directives when a request to the origin server fails
* Setting a response on a request will write to the custom request body from the response body if one is specified
* LogPlugin now writes to php://output when STDERR is undefined
* Added the ability to set multiple POST files for the same key in a single call
* application/x-www-form-urlencoded POSTs now use the utf-8 charset by default
* Added the ability to queue CurlExceptions to the MockPlugin
* Cleaned up how manual responses are queued on requests (removed "queued_response" and now using request.before_send)
* Configuration loading now allows remote files

## 3.4.1 - 2013-04-16

* Large refactoring to how CurlMulti handles work. There is now a proxy that sits in front of a pool of CurlMulti
  handles. This greatly simplifies the implementation, fixes a couple bugs, and provides a small performance boost.
* Exceptions are now properly grouped when sending requests in parallel
* Redirects are now properly aggregated when a multi transaction fails
* Redirects now set the response on the original object even in the event of a failure
* Bug fix: Model names are now properly set even when using $refs
* Added support for PHP 5.5's CurlFile to prevent warnings with the deprecated @ syntax
* Added support for oauth_callback in OAuth signatures
* Added support for oauth_verifier in OAuth signatures
* Added support to attempt to retrieve a command first literally, then ucfirst, the with inflection

## 3.4.0 - 2013-04-11

* Bug fix: URLs are now resolved correctly based on https://datatracker.ietf.org/doc/html/rfc3986#section-5.2. #289
* Bug fix: Absolute URLs with a path in a service description will now properly override the base URL. #289
* Bug fix: Parsing a query string with a single PHP array value will now result in an array. #263
* Bug fix: Better normalization of the User-Agent header to prevent duplicate headers. #264.
* Bug fix: Added `number` type to service descriptions.
* Bug fix: empty parameters are removed from an OAuth signature
* Bug fix: Revalidating a cache entry prefers the Last-Modified over the Date header
* Bug fix: Fixed "array to string" error when validating a union of types in a service description
* Bug fix: Removed code that attempted to determine the size of a stream when data is written to the stream
* Bug fix: Not including an `oauth_token` if the value is null in the OauthPlugin.
* Bug fix: Now correctly aggregating successful requests and failed requests in CurlMulti when a redirect occurs.
* The new default CURLOPT_TIMEOUT setting has been increased to 150 seconds so that Guzzle works on poor connections.
* Added a feature to EntityEnclosingRequest::setBody() that will automatically set the Content-Type of the request if
  the Content-Type can be determined based on the entity body or the path of the request.
* Added the ability to overwrite configuration settings in a client when grabbing a throwaway client from a builder.
* Added support for a PSR-3 LogAdapter.
* Added a `command.after_prepare` event
* Added `oauth_callback` parameter to the OauthPlugin
* Added the ability to create a custom stream class when using a stream factory
* Added a CachingEntityBody decorator
* Added support for `additionalParameters` in service descriptions to define how custom parameters are serialized.
* The bundled SSL certificate is now provided in the phar file and extracted when running Guzzle from a phar.
* You can now send any EntityEnclosingRequest with POST fields or POST files and cURL will handle creating bodies
* POST requests using a custom entity body are now treated exactly like PUT requests but with a custom cURL method. This
  means that the redirect behavior of POST requests with custom bodies will not be the same as POST requests that use
  POST fields or files (the latter is only used when emulating a form POST in the browser).
* Lots of cleanup to CurlHandle::factory and RequestFactory::createRequest

## 3.3.1 - 2013-03-10

* Added the ability to create PHP streaming responses from HTTP requests
* Bug fix: Running any filters when parsing response headers with service descriptions
* Bug fix: OauthPlugin fixes to allow for multi-dimensional array signing, and sorting parameters before signing
* Bug fix: Removed the adding of default empty arrays and false Booleans to responses in order to be consistent across
  response location visitors.
* Bug fix: Removed the possibility of creating configuration files with circular dependencies
* RequestFactory::create() now uses the key of a POST file when setting the POST file name
* Added xmlAllowEmpty to serialize an XML body even if no XML specific parameters are set

## 3.3.0 - 2013-03-03

* A large number of performance optimizations have been made
* Bug fix: Added 'wb' as a valid write mode for streams
* Bug fix: `Guzzle\Http\Message\Response::json()` now allows scalar values to be returned
* Bug fix: Fixed bug in `Guzzle\Http\Message\Response` where wrapping quotes were stripped from `getEtag()`
* BC: Removed `Guzzle\Http\Utils` class
* BC: Setting a service description on a client will no longer modify the client's command factories.
* BC: Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using
  the 'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io'
* BC: `Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getSteamType()` are no longer converted to
  lowercase
* Operation parameter objects are now lazy loaded internally
* Added ErrorResponsePlugin that can throw errors for responses defined in service description operations' errorResponses
* Added support for instantiating responseType=class responseClass classes. Classes must implement
  `Guzzle\Service\Command\ResponseClassInterface`
* Added support for additionalProperties for top-level parameters in responseType=model responseClasses. These
  additional properties also support locations and can be used to parse JSON responses where the outermost part of the
  JSON is an array
* Added support for nested renaming of JSON models (rename sentAs to name)
* CachePlugin
    * Added support for stale-if-error so that the CachePlugin can now serve stale content from the cache on error
    * Debug headers can now added to cached response in the CachePlugin

## 3.2.0 - 2013-02-14

* CurlMulti is no longer reused globally. A new multi object is created per-client. This helps to isolate clients.
* URLs with no path no longer contain a "/" by default
* Guzzle\Http\QueryString does no longer manages the leading "?". This is now handled in Guzzle\Http\Url.
* BadResponseException no longer includes the full request and response message
* Adding setData() to Guzzle\Service\Description\ServiceDescriptionInterface
* Adding getResponseBody() to Guzzle\Http\Message\RequestInterface
* Various updates to classes to use ServiceDescriptionInterface type hints rather than ServiceDescription
* Header values can now be normalized into distinct values when multiple headers are combined with a comma separated list
* xmlEncoding can now be customized for the XML declaration of a XML service description operation
* Guzzle\Http\QueryString now uses Guzzle\Http\QueryAggregator\QueryAggregatorInterface objects to add custom value
  aggregation and no longer uses callbacks
* The URL encoding implementation of Guzzle\Http\QueryString can now be customized
* Bug fix: Filters were not always invoked for array service description parameters
* Bug fix: Redirects now use a target response body rather than a temporary response body
* Bug fix: The default exponential backoff BackoffPlugin was not giving when the request threshold was exceeded
* Bug fix: Guzzle now takes the first found value when grabbing Cache-Control directives

## 3.1.2 - 2013-01-27

* Refactored how operation responses are parsed. Visitors now include a before() method responsible for parsing the
  response body. For example, the XmlVisitor now parses the XML response into an array in the before() method.
* Fixed an issue where cURL would not automatically decompress responses when the Accept-Encoding header was sent
* CURLOPT_SSL_VERIFYHOST is never set to 1 because it is deprecated (see 5e0ff2ef20f839e19d1eeb298f90ba3598784444)
* Fixed a bug where redirect responses were not chained correctly using getPreviousResponse()
* Setting default headers on a client after setting the user-agent will not erase the user-agent setting

## 3.1.1 - 2013-01-20

* Adding wildcard support to Guzzle\Common\Collection::getPath()
* Adding alias support to ServiceBuilder configs
* Adding Guzzle\Service\Resource\CompositeResourceIteratorFactory and cleaning up factory interface

## 3.1.0 - 2013-01-12

* BC: CurlException now extends from RequestException rather than BadResponseException
* BC: Renamed Guzzle\Plugin\Cache\CanCacheStrategyInterface::canCache() to canCacheRequest() and added CanCacheResponse()
* Added getData to ServiceDescriptionInterface
* Added context array to RequestInterface::setState()
* Bug: Removing hard dependency on the BackoffPlugin from Guzzle\Http
* Bug: Adding required content-type when JSON request visitor adds JSON to a command
* Bug: Fixing the serialization of a service description with custom data
* Made it easier to deal with exceptions thrown when transferring commands or requests in parallel by providing
  an array of successful and failed responses
* Moved getPath from Guzzle\Service\Resource\Model to Guzzle\Common\Collection
* Added Guzzle\Http\IoEmittingEntityBody
* Moved command filtration from validators to location visitors
* Added `extends` attributes to service description parameters
* Added getModels to ServiceDescriptionInterface

## 3.0.7 - 2012-12-19

* Fixing phar detection when forcing a cacert to system if null or true
* Allowing filename to be passed to `Guzzle\Http\Message\Request::setResponseBody()`
* Cleaning up `Guzzle\Common\Collection::inject` method
* Adding a response_body location to service descriptions

## 3.0.6 - 2012-12-09

* CurlMulti performance improvements
* Adding setErrorResponses() to Operation
* composer.json tweaks

## 3.0.5 - 2012-11-18

* Bug: Fixing an infinite recursion bug caused from revalidating with the CachePlugin
* Bug: Response body can now be a string containing "0"
* Bug: Using Guzzle inside of a phar uses system by default but now allows for a custom cacert
* Bug: QueryString::fromString now properly parses query string parameters that contain equal signs
* Added support for XML attributes in service description responses
* DefaultRequestSerializer now supports array URI parameter values for URI template expansion
* Added better mimetype guessing to requests and post files

## 3.0.4 - 2012-11-11

* Bug: Fixed a bug when adding multiple cookies to a request to use the correct glue value
* Bug: Cookies can now be added that have a name, domain, or value set to "0"
* Bug: Using the system cacert bundle when using the Phar
* Added json and xml methods to Response to make it easier to parse JSON and XML response data into data structures
* Enhanced cookie jar de-duplication
* Added the ability to enable strict cookie jars that throw exceptions when invalid cookies are added
* Added setStream to StreamInterface to actually make it possible to implement custom rewind behavior for entity bodies
* Added the ability to create any sort of hash for a stream rather than just an MD5 hash

## 3.0.3 - 2012-11-04

* Implementing redirects in PHP rather than cURL
* Added PECL URI template extension and using as default parser if available
* Bug: Fixed Content-Length parsing of Response factory
* Adding rewind() method to entity bodies and streams. Allows for custom rewinding of non-repeatable streams.
* Adding ToArrayInterface throughout library
* Fixing OauthPlugin to create unique nonce values per request

## 3.0.2 - 2012-10-25

* Magic methods are enabled by default on clients
* Magic methods return the result of a command
* Service clients no longer require a base_url option in the factory
* Bug: Fixed an issue with URI templates where null template variables were being expanded

## 3.0.1 - 2012-10-22

* Models can now be used like regular collection objects by calling filter, map, etc.
* Models no longer require a Parameter structure or initial data in the constructor
* Added a custom AppendIterator to get around a PHP bug with the `\AppendIterator`

## 3.0.0 - 2012-10-15

* Rewrote service description format to be based on Swagger
    * Now based on JSON schema
    * Added nested input structures and nested response models
    * Support for JSON and XML input and output models
    * Renamed `commands` to `operations`
    * Removed dot class notation
    * Removed custom types
* Broke the project into smaller top-level namespaces to be more component friendly
* Removed support for XML configs and descriptions. Use arrays or JSON files.
* Removed the Validation component and Inspector
* Moved all cookie code to Guzzle\Plugin\Cookie
* Magic methods on a Guzzle\Service\Client now return the command un-executed.
* Calling getResult() or getResponse() on a command will lazily execute the command if needed.
* Now shipping with cURL's CA certs and using it by default
* Added previousResponse() method to response objects
* No longer sending Accept and Accept-Encoding headers on every request
* Only sending an Expect header by default when a payload is greater than 1MB
* Added/moved client options:
    * curl.blacklist to curl.option.blacklist
    * Added ssl.certificate_authority
* Added a Guzzle\Iterator component
* Moved plugins from Guzzle\Http\Plugin to Guzzle\Plugin
* Added a more robust backoff retry strategy (replaced the ExponentialBackoffPlugin)
* Added a more robust caching plugin
* Added setBody to response objects
* Updating LogPlugin to use a more flexible MessageFormatter
* Added a completely revamped build process
* Cleaning up Collection class and removing default values from the get method
* Fixed ZF2 cache adapters

## 2.8.8 - 2012-10-15

* Bug: Fixed a cookie issue that caused dot prefixed domains to not match where popular browsers did

## 2.8.7 - 2012-09-30

* Bug: Fixed config file aliases for JSON includes
* Bug: Fixed cookie bug on a request object by using CookieParser to parse cookies on requests
* Bug: Removing the path to a file when sending a Content-Disposition header on a POST upload
* Bug: Hardening request and response parsing to account for missing parts
* Bug: Fixed PEAR packaging
* Bug: Fixed Request::getInfo
* Bug: Fixed cases where CURLM_CALL_MULTI_PERFORM return codes were causing curl transactions to fail
* Adding the ability for the namespace Iterator factory to look in multiple directories
* Added more getters/setters/removers from service descriptions
* Added the ability to remove POST fields from OAuth signatures
* OAuth plugin now supports 2-legged OAuth

## 2.8.6 - 2012-09-05

* Added the ability to modify and build service descriptions
* Added the use of visitors to apply parameters to locations in service descriptions using the dynamic command
* Added a `json` parameter location
* Now allowing dot notation for classes in the CacheAdapterFactory
* Using the union of two arrays rather than an array_merge when extending service builder services and service params
* Ensuring that a service is a string before doing strpos() checks on it when substituting services for references
  in service builder config files.
* Services defined in two different config files that include one another will by default replace the previously
  defined service, but you can now create services that extend themselves and merge their settings over the previous
* The JsonLoader now supports aliasing filenames with different filenames. This allows you to alias something like
  '_default' with a default JSON configuration file.

## 2.8.5 - 2012-08-29

* Bug: Suppressed empty arrays from URI templates
* Bug: Added the missing $options argument from ServiceDescription::factory to enable caching
* Added support for HTTP responses that do not contain a reason phrase in the start-line
* AbstractCommand commands are now invokable
* Added a way to get the data used when signing an Oauth request before a request is sent

## 2.8.4 - 2012-08-15

* Bug: Custom delay time calculations are no longer ignored in the ExponentialBackoffPlugin
* Added the ability to transfer entity bodies as a string rather than streamed. This gets around curl error 65. Set `body_as_string` in a request's curl options to enable.
* Added a StreamInterface, EntityBodyInterface, and added ftell() to Guzzle\Common\Stream
* Added an AbstractEntityBodyDecorator and a ReadLimitEntityBody decorator to transfer only a subset of a decorated stream
* Stream and EntityBody objects will now return the file position to the previous position after a read required operation (e.g. getContentMd5())
* Added additional response status codes
* Removed SSL information from the default User-Agent header
* DELETE requests can now send an entity body
* Added an EventDispatcher to the ExponentialBackoffPlugin and added an ExponentialBackoffLogger to log backoff retries
* Added the ability of the MockPlugin to consume mocked request bodies
* LogPlugin now exposes request and response objects in the extras array

## 2.8.3 - 2012-07-30

* Bug: Fixed a case where empty POST requests were sent as GET requests
* Bug: Fixed a bug in ExponentialBackoffPlugin that caused fatal errors when retrying an EntityEnclosingRequest that does not have a body
* Bug: Setting the response body of a request to null after completing a request, not when setting the state of a request to new
* Added multiple inheritance to service description commands
* Added an ApiCommandInterface and added `getParamNames()` and `hasParam()`
* Removed the default 2mb size cutoff from the Md5ValidatorPlugin so that it now defaults to validating everything
* Changed CurlMulti::perform to pass a smaller timeout to CurlMulti::executeHandles

## 2.8.2 - 2012-07-24

* Bug: Query string values set to 0 are no longer dropped from the query string
* Bug: A Collection object is no longer created each time a call is made to `Guzzle\Service\Command\AbstractCommand::getRequestHeaders()`
* Bug: `+` is now treated as an encoded space when parsing query strings
* QueryString and Collection performance improvements
* Allowing dot notation for class paths in filters attribute of a service descriptions

## 2.8.1 - 2012-07-16

* Loosening Event Dispatcher dependency
* POST redirects can now be customized using CURLOPT_POSTREDIR

## 2.8.0 - 2012-07-15

* BC: Guzzle\Http\Query
    * Query strings with empty variables will always show an equal sign unless the variable is set to QueryString::BLANK (e.g. ?acl= vs ?acl)
    * Changed isEncodingValues() and isEncodingFields() to isUrlEncoding()
    * Changed setEncodeValues(bool) and setEncodeFields(bool) to useUrlEncoding(bool)
    * Changed the aggregation functions of QueryString to be static methods
    * Can now use fromString() with querystrings that have a leading ?
* cURL configuration values can be specified in service descriptions using `curl.` prefixed parameters
* Content-Length is set to 0 before emitting the request.before_send event when sending an empty request body
* Cookies are no longer URL decoded by default
* Bug: URI template variables set to null are no longer expanded

## 2.7.2 - 2012-07-02

* BC: Moving things to get ready for subtree splits. Moving Inflection into Common. Moving Guzzle\Http\Parser to Guzzle\Parser.
* BC: Removing Guzzle\Common\Batch\Batch::count() and replacing it with isEmpty()
* CachePlugin now allows for a custom request parameter function to check if a request can be cached
* Bug fix: CachePlugin now only caches GET and HEAD requests by default
* Bug fix: Using header glue when transferring headers over the wire
* Allowing deeply nested arrays for composite variables in URI templates
* Batch divisors can now return iterators or arrays

## 2.7.1 - 2012-06-26

* Minor patch to update version number in UA string
* Updating build process

## 2.7.0 - 2012-06-25

* BC: Inflection classes moved to Guzzle\Inflection. No longer static methods. Can now inject custom inflectors into classes.
* BC: Removed magic setX methods from commands
* BC: Magic methods mapped to service description commands are now inflected in the command factory rather than the client __call() method
* Verbose cURL options are no longer enabled by default. Set curl.debug to true on a client to enable.
* Bug: Now allowing colons in a response start-line (e.g. HTTP/1.1 503 Service Unavailable: Back-end server is at capacity)
* Guzzle\Service\Resource\ResourceIteratorApplyBatched now internally uses the Guzzle\Common\Batch namespace
* Added Guzzle\Service\Plugin namespace and a PluginCollectionPlugin
* Added the ability to set POST fields and files in a service description
* Guzzle\Http\EntityBody::factory() now accepts objects with a __toString() method
* Adding a command.before_prepare event to clients
* Added BatchClosureTransfer and BatchClosureDivisor
* BatchTransferException now includes references to the batch divisor and transfer strategies
* Fixed some tests so that they pass more reliably
* Added Guzzle\Common\Log\ArrayLogAdapter

## 2.6.6 - 2012-06-10

* BC: Removing Guzzle\Http\Plugin\BatchQueuePlugin
* BC: Removing Guzzle\Service\Command\CommandSet
* Adding generic batching system (replaces the batch queue plugin and command set)
* Updating ZF cache and log adapters and now using ZF's composer repository
* Bug: Setting the name of each ApiParam when creating through an ApiCommand
* Adding result_type, result_doc, deprecated, and doc_url to service descriptions
* Bug: Changed the default cookie header casing back to 'Cookie'

## 2.6.5 - 2012-06-03

* BC: Renaming Guzzle\Http\Message\RequestInterface::getResourceUri() to getResource()
* BC: Removing unused AUTH_BASIC and AUTH_DIGEST constants from
* BC: Guzzle\Http\Cookie is now used to manage Set-Cookie data, not Cookie data
* BC: Renaming methods in the CookieJarInterface
* Moving almost all cookie logic out of the CookiePlugin and into the Cookie or CookieJar implementations
* Making the default glue for HTTP headers ';' instead of ','
* Adding a removeValue to Guzzle\Http\Message\Header
* Adding getCookies() to request interface.
* Making it easier to add event subscribers to HasDispatcherInterface classes. Can now directly call addSubscriber()

## 2.6.4 - 2012-05-30

* BC: Cleaning up how POST files are stored in EntityEnclosingRequest objects. Adding PostFile class.
* BC: Moving ApiCommand specific functionality from the Inspector and on to the ApiCommand
* Bug: Fixing magic method command calls on clients
* Bug: Email constraint only validates strings
* Bug: Aggregate POST fields when POST files are present in curl handle
* Bug: Fixing default User-Agent header
* Bug: Only appending or prepending parameters in commands if they are specified
* Bug: Not requiring response reason phrases or status codes to match a predefined list of codes
* Allowing the use of dot notation for class namespaces when using instance_of constraint
* Added any_match validation constraint
* Added an AsyncPlugin
* Passing request object to the calculateWait method of the ExponentialBackoffPlugin
* Allowing the result of a command object to be changed
* Parsing location and type sub values when instantiating a service description rather than over and over at runtime

## 2.6.3 - 2012-05-23

* [BC] Guzzle\Common\FromConfigInterface no longer requires any config options.
* [BC] Refactoring how POST files are stored on an EntityEnclosingRequest. They are now separate from POST fields.
* You can now use an array of data when creating PUT request bodies in the request factory.
* Removing the requirement that HTTPS requests needed a Cache-Control: public directive to be cacheable.
* [Http] Adding support for Content-Type in multipart POST uploads per upload
* [Http] Added support for uploading multiple files using the same name (foo[0], foo[1])
* Adding more POST data operations for easier manipulation of POST data.
* You can now set empty POST fields.
* The body of a request is only shown on EntityEnclosingRequest objects that do not use POST files.
* Split the Guzzle\Service\Inspector::validateConfig method into two methods. One to initialize when a command is created, and one to validate.
* CS updates

## 2.6.2 - 2012-05-19

* [Http] Better handling of nested scope requests in CurlMulti.  Requests are now always prepares in the send() method rather than the addRequest() method.

## 2.6.1 - 2012-05-19

* [BC] Removing 'path' support in service descriptions.  Use 'uri'.
* [BC] Guzzle\Service\Inspector::parseDocBlock is now protected. Adding getApiParamsForClass() with cache.
* [BC] Removing Guzzle\Common\NullObject.  Use https://github.com/mtdowling/NullObject if you need it.
* [BC] Removing Guzzle\Common\XmlElement.
* All commands, both dynamic and concrete, have ApiCommand objects.
* Adding a fix for CurlMulti so that if all of the connections encounter some sort of curl error, then the loop exits.
* Adding checks to EntityEnclosingRequest so that empty POST files and fields are ignored.
* Making the method signature of Guzzle\Service\Builder\ServiceBuilder::factory more flexible.

## 2.6.0 - 2012-05-15

* [BC] Moving Guzzle\Service\Builder to Guzzle\Service\Builder\ServiceBuilder
* [BC] Executing a Command returns the result of the command rather than the command
* [BC] Moving all HTTP parsing logic to Guzzle\Http\Parsers. Allows for faster C implementations if needed.
* [BC] Changing the Guzzle\Http\Message\Response::setProtocol() method to accept a protocol and version in separate args.
* [BC] Moving ResourceIterator* to Guzzle\Service\Resource
* [BC] Completely refactored ResourceIterators to iterate over a cloned command object
* [BC] Moved Guzzle\Http\UriTemplate to Guzzle\Http\Parser\UriTemplate\UriTemplate
* [BC] Guzzle\Guzzle is now deprecated
* Moving Guzzle\Common\Guzzle::inject to Guzzle\Common\Collection::inject
* Adding Guzzle\Version class to give version information about Guzzle
* Adding Guzzle\Http\Utils class to provide getDefaultUserAgent() and getHttpDate()
* Adding Guzzle\Curl\CurlVersion to manage caching curl_version() data
* ServiceDescription and ServiceBuilder are now cacheable using similar configs
* Changing the format of XML and JSON service builder configs.  Backwards compatible.
* Cleaned up Cookie parsing
* Trimming the default Guzzle User-Agent header
* Adding a setOnComplete() method to Commands that is called when a command completes
* Keeping track of requests that were mocked in the MockPlugin
* Fixed a caching bug in the CacheAdapterFactory
* Inspector objects can be injected into a Command object
* Refactoring a lot of code and tests to be case insensitive when dealing with headers
* Adding Guzzle\Http\Message\HeaderComparison for easy comparison of HTTP headers using a DSL
* Adding the ability to set global option overrides to service builder configs
* Adding the ability to include other service builder config files from within XML and JSON files
* Moving the parseQuery method out of Url and on to QueryString::fromString() as a static factory method.

## 2.5.0 - 2012-05-08

* Major performance improvements
* [BC] Simplifying Guzzle\Common\Collection.  Please check to see if you are using features that are now deprecated.
* [BC] Using a custom validation system that allows a flyweight implementation for much faster validation. No longer using Symfony2 Validation component.
* [BC] No longer supporting "{{ }}" for injecting into command or UriTemplates.  Use "{}"
* Added the ability to passed parameters to all requests created by a client
* Added callback functionality to the ExponentialBackoffPlugin
* Using microtime in ExponentialBackoffPlugin to allow more granular backoff strategies.
* Rewinding request stream bodies when retrying requests
* Exception is thrown when JSON response body cannot be decoded
* Added configurable magic method calls to clients and commands.  This is off by default.
* Fixed a defect that added a hash to every parsed URL part
* Fixed duplicate none generation for OauthPlugin.
* Emitting an event each time a client is generated by a ServiceBuilder
* Using an ApiParams object instead of a Collection for parameters of an ApiCommand
* cache.* request parameters should be renamed to params.cache.*
* Added the ability to set arbitrary curl options on requests (disable_wire, progress, etc.). See CurlHandle.
* Added the ability to disable type validation of service descriptions
* ServiceDescriptions and ServiceBuilders are now Serializable
PK�c�\�Շ���guzzlehttp/guzzle/LICENSEnu�[���The MIT License (MIT)

Copyright (c) 2011 Michael Dowling <mtdowling@gmail.com>
Copyright (c) 2012 Jeremy Lindblom <jeremeamia@gmail.com>
Copyright (c) 2014 Graham Campbell <hello@gjcampbell.co.uk>
Copyright (c) 2015 Márk Sági-Kazár <mark.sagikazar@gmail.com>
Copyright (c) 2015 Tobias Schultze <webmaster@tubo-world.de>
Copyright (c) 2016 Tobias Nyholm <tobias.nyholm@gmail.com>
Copyright (c) 2016 George Mponos <gmponos@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
PK�c�\��y�y�guzzlehttp/guzzle/UPGRADING.mdnu�[���Guzzle Upgrade Guide
====================

6.0 to 7.0
----------

In order to take advantage of the new features of PHP, Guzzle dropped the support
of PHP 5. The minimum supported PHP version is now PHP 7.2. Type hints and return
types for functions and methods have been added wherever possible. 

Please make sure:
- You are calling a function or a method with the correct type.
- If you extend a class of Guzzle; update all signatures on methods you override.

#### Other backwards compatibility breaking changes

- Class `GuzzleHttp\UriTemplate` is removed.
- Class `GuzzleHttp\Exception\SeekException` is removed.
- Classes `GuzzleHttp\Exception\BadResponseException`, `GuzzleHttp\Exception\ClientException`, 
  `GuzzleHttp\Exception\ServerException` can no longer be initialized with an empty
  Response as argument.
- Class `GuzzleHttp\Exception\ConnectException` now extends `GuzzleHttp\Exception\TransferException`
  instead of `GuzzleHttp\Exception\RequestException`.
- Function `GuzzleHttp\Exception\ConnectException::getResponse()` is removed.
- Function `GuzzleHttp\Exception\ConnectException::hasResponse()` is removed.
- Constant `GuzzleHttp\ClientInterface::VERSION` is removed. Added `GuzzleHttp\ClientInterface::MAJOR_VERSION` instead.
- Function `GuzzleHttp\Exception\RequestException::getResponseBodySummary` is removed.
  Use `\GuzzleHttp\Psr7\get_message_body_summary` as an alternative.
- Function `GuzzleHttp\Cookie\CookieJar::getCookieValue` is removed.
- Request option `exceptions` is removed. Please use `http_errors`.
- Request option `save_to` is removed. Please use `sink`.
- Pool option `pool_size` is removed. Please use `concurrency`.
- We now look for environment variables in the `$_SERVER` super global, due to thread safety issues with `getenv`. We continue to fallback to `getenv` in CLI environments, for maximum compatibility.
- The `get`, `head`, `put`, `post`, `patch`, `delete`, `getAsync`, `headAsync`, `putAsync`, `postAsync`, `patchAsync`, and `deleteAsync` methods are now implemented as genuine methods on `GuzzleHttp\Client`, with strong typing. The original `__call` implementation remains unchanged for now, for maximum backwards compatibility, but won't be invoked under normal operation.
- The `log` middleware will log the errors with level `error` instead of `notice` 
- Support for international domain names (IDN) is now disabled by default, and enabling it requires installing ext-intl, linked against a modern version of the C library (ICU 4.6 or higher).

#### Native functions calls

All internal native functions calls of Guzzle are now prefixed with a slash. This
change makes it impossible for method overloading by other libraries or applications.
Example:

```php
// Before:
curl_version();

// After:
\curl_version();
```

For the full diff you can check [here](https://github.com/guzzle/guzzle/compare/6.5.4..master).

5.0 to 6.0
----------

Guzzle now uses [PSR-7](https://www.php-fig.org/psr/psr-7/) for HTTP messages.
Due to the fact that these messages are immutable, this prompted a refactoring
of Guzzle to use a middleware based system rather than an event system. Any
HTTP message interaction (e.g., `GuzzleHttp\Message\Request`) need to be
updated to work with the new immutable PSR-7 request and response objects. Any
event listeners or subscribers need to be updated to become middleware
functions that wrap handlers (or are injected into a
`GuzzleHttp\HandlerStack`).

- Removed `GuzzleHttp\BatchResults`
- Removed `GuzzleHttp\Collection`
- Removed `GuzzleHttp\HasDataTrait`
- Removed `GuzzleHttp\ToArrayInterface`
- The `guzzlehttp/streams` dependency has been removed. Stream functionality
  is now present in the `GuzzleHttp\Psr7` namespace provided by the
  `guzzlehttp/psr7` package.
- Guzzle no longer uses ReactPHP promises and now uses the
  `guzzlehttp/promises` library. We use a custom promise library for three
  significant reasons:
  1. React promises (at the time of writing this) are recursive. Promise
     chaining and promise resolution will eventually blow the stack. Guzzle
     promises are not recursive as they use a sort of trampolining technique.
     Note: there has been movement in the React project to modify promises to
     no longer utilize recursion.
  2. Guzzle needs to have the ability to synchronously block on a promise to
     wait for a result. Guzzle promises allows this functionality (and does
     not require the use of recursion).
  3. Because we need to be able to wait on a result, doing so using React
     promises requires wrapping react promises with RingPHP futures. This
     overhead is no longer needed, reducing stack sizes, reducing complexity,
     and improving performance.
- `GuzzleHttp\Mimetypes` has been moved to a function in
  `GuzzleHttp\Psr7\mimetype_from_extension` and
  `GuzzleHttp\Psr7\mimetype_from_filename`.
- `GuzzleHttp\Query` and `GuzzleHttp\QueryParser` have been removed. Query
  strings must now be passed into request objects as strings, or provided to
  the `query` request option when creating requests with clients. The `query`
  option uses PHP's `http_build_query` to convert an array to a string. If you
  need a different serialization technique, you will need to pass the query
  string in as a string. There are a couple helper functions that will make
  working with query strings easier: `GuzzleHttp\Psr7\parse_query` and
  `GuzzleHttp\Psr7\build_query`.
- Guzzle no longer has a dependency on RingPHP. Due to the use of a middleware
  system based on PSR-7, using RingPHP and it's middleware system as well adds
  more complexity than the benefits it provides. All HTTP handlers that were
  present in RingPHP have been modified to work directly with PSR-7 messages
  and placed in the `GuzzleHttp\Handler` namespace. This significantly reduces
  complexity in Guzzle, removes a dependency, and improves performance. RingPHP
  will be maintained for Guzzle 5 support, but will no longer be a part of
  Guzzle 6.
- As Guzzle now uses a middleware based systems the event system and RingPHP
  integration has been removed. Note: while the event system has been removed,
  it is possible to add your own type of event system that is powered by the
  middleware system.
  - Removed the `Event` namespace.
  - Removed the `Subscriber` namespace.
  - Removed `Transaction` class
  - Removed `RequestFsm`
  - Removed `RingBridge`
  - `GuzzleHttp\Subscriber\Cookie` is now provided by
    `GuzzleHttp\Middleware::cookies`
  - `GuzzleHttp\Subscriber\HttpError` is now provided by
    `GuzzleHttp\Middleware::httpError`
  - `GuzzleHttp\Subscriber\History` is now provided by
    `GuzzleHttp\Middleware::history`
  - `GuzzleHttp\Subscriber\Mock` is now provided by
    `GuzzleHttp\Handler\MockHandler`
  - `GuzzleHttp\Subscriber\Prepare` is now provided by
    `GuzzleHttp\PrepareBodyMiddleware`
  - `GuzzleHttp\Subscriber\Redirect` is now provided by
    `GuzzleHttp\RedirectMiddleware`
- Guzzle now uses `Psr\Http\Message\UriInterface` (implements in
  `GuzzleHttp\Psr7\Uri`) for URI support. `GuzzleHttp\Url` is now gone.
- Static functions in `GuzzleHttp\Utils` have been moved to namespaced
  functions under the `GuzzleHttp` namespace. This requires either a Composer
  based autoloader or you to include functions.php.
- `GuzzleHttp\ClientInterface::getDefaultOption` has been renamed to
  `GuzzleHttp\ClientInterface::getConfig`.
- `GuzzleHttp\ClientInterface::setDefaultOption` has been removed.
- The `json` and `xml` methods of response objects has been removed. With the
  migration to strictly adhering to PSR-7 as the interface for Guzzle messages,
  adding methods to message interfaces would actually require Guzzle messages
  to extend from PSR-7 messages rather then work with them directly.

## Migrating to middleware

The change to PSR-7 unfortunately required significant refactoring to Guzzle
due to the fact that PSR-7 messages are immutable. Guzzle 5 relied on an event
system from plugins. The event system relied on mutability of HTTP messages and
side effects in order to work. With immutable messages, you have to change your
workflow to become more about either returning a value (e.g., functional
middlewares) or setting a value on an object. Guzzle v6 has chosen the
functional middleware approach.

Instead of using the event system to listen for things like the `before` event,
you now create a stack based middleware function that intercepts a request on
the way in and the promise of the response on the way out. This is a much
simpler and more predictable approach than the event system and works nicely
with PSR-7 middleware. Due to the use of promises, the middleware system is
also asynchronous.

v5:

```php
use GuzzleHttp\Event\BeforeEvent;
$client = new GuzzleHttp\Client();
// Get the emitter and listen to the before event.
$client->getEmitter()->on('before', function (BeforeEvent $e) {
    // Guzzle v5 events relied on mutation
    $e->getRequest()->setHeader('X-Foo', 'Bar');
});
```

v6:

In v6, you can modify the request before it is sent using the `mapRequest`
middleware. The idiomatic way in v6 to modify the request/response lifecycle is
to setup a handler middleware stack up front and inject the handler into a
client.

```php
use GuzzleHttp\Middleware;
// Create a handler stack that has all of the default middlewares attached
$handler = GuzzleHttp\HandlerStack::create();
// Push the handler onto the handler stack
$handler->push(Middleware::mapRequest(function (RequestInterface $request) {
    // Notice that we have to return a request object
    return $request->withHeader('X-Foo', 'Bar');
}));
// Inject the handler into the client
$client = new GuzzleHttp\Client(['handler' => $handler]);
```

## POST Requests

This version added the [`form_params`](https://docs.guzzlephp.org/en/latest/request-options.html#form_params)
and `multipart` request options. `form_params` is an associative array of
strings or array of strings and is used to serialize an
`application/x-www-form-urlencoded` POST request. The
[`multipart`](https://docs.guzzlephp.org/en/latest/request-options.html#multipart)
option is now used to send a multipart/form-data POST request.

`GuzzleHttp\Post\PostFile` has been removed. Use the `multipart` option to add
POST files to a multipart/form-data request.

The `body` option no longer accepts an array to send POST requests. Please use
`multipart` or `form_params` instead.

The `base_url` option has been renamed to `base_uri`.

4.x to 5.0
----------

## Rewritten Adapter Layer

Guzzle now uses [RingPHP](https://ringphp.readthedocs.org/en/latest) to send
HTTP requests. The `adapter` option in a `GuzzleHttp\Client` constructor
is still supported, but it has now been renamed to `handler`. Instead of
passing a `GuzzleHttp\Adapter\AdapterInterface`, you must now pass a PHP
`callable` that follows the RingPHP specification.

## Removed Fluent Interfaces

[Fluent interfaces were removed](https://ocramius.github.io/blog/fluent-interfaces-are-evil/)
from the following classes:

- `GuzzleHttp\Collection`
- `GuzzleHttp\Url`
- `GuzzleHttp\Query`
- `GuzzleHttp\Post\PostBody`
- `GuzzleHttp\Cookie\SetCookie`

## Removed functions.php

Removed "functions.php", so that Guzzle is truly PSR-4 compliant. The following
functions can be used as replacements.

- `GuzzleHttp\json_decode` -> `GuzzleHttp\Utils::jsonDecode`
- `GuzzleHttp\get_path` -> `GuzzleHttp\Utils::getPath`
- `GuzzleHttp\Utils::setPath` -> `GuzzleHttp\set_path`
- `GuzzleHttp\Pool::batch` -> `GuzzleHttp\batch`. This function is, however,
  deprecated in favor of using `GuzzleHttp\Pool::batch()`.

The "procedural" global client has been removed with no replacement (e.g.,
`GuzzleHttp\get()`, `GuzzleHttp\post()`, etc.). Use a `GuzzleHttp\Client`
object as a replacement.

## `throwImmediately` has been removed

The concept of "throwImmediately" has been removed from exceptions and error
events. This control mechanism was used to stop a transfer of concurrent
requests from completing. This can now be handled by throwing the exception or
by cancelling a pool of requests or each outstanding future request
individually.

## headers event has been removed

Removed the "headers" event. This event was only useful for changing the
body a response once the headers of the response were known. You can implement
a similar behavior in a number of ways. One example might be to use a
FnStream that has access to the transaction being sent. For example, when the
first byte is written, you could check if the response headers match your
expectations, and if so, change the actual stream body that is being
written to.

## Updates to HTTP Messages

Removed the `asArray` parameter from
`GuzzleHttp\Message\MessageInterface::getHeader`. If you want to get a header
value as an array, then use the newly added `getHeaderAsArray()` method of
`MessageInterface`. This change makes the Guzzle interfaces compatible with
the PSR-7 interfaces.

3.x to 4.0
----------

## Overarching changes:

- Now requires PHP 5.4 or greater.
- No longer requires cURL to send requests.
- Guzzle no longer wraps every exception it throws. Only exceptions that are
  recoverable are now wrapped by Guzzle.
- Various namespaces have been removed or renamed.
- No longer requiring the Symfony EventDispatcher. A custom event dispatcher
  based on the Symfony EventDispatcher is
  now utilized in `GuzzleHttp\Event\EmitterInterface` (resulting in significant
  speed and functionality improvements).

Changes per Guzzle 3.x namespace are described below.

## Batch

The `Guzzle\Batch` namespace has been removed. This is best left to
third-parties to implement on top of Guzzle's core HTTP library.

## Cache

The `Guzzle\Cache` namespace has been removed. (Todo: No suitable replacement
has been implemented yet, but hoping to utilize a PSR cache interface).

## Common

- Removed all of the wrapped exceptions. It's better to use the standard PHP
  library for unrecoverable exceptions.
- `FromConfigInterface` has been removed.
- `Guzzle\Common\Version` has been removed. The VERSION constant can be found
  at `GuzzleHttp\ClientInterface::VERSION`.

### Collection

- `getAll` has been removed. Use `toArray` to convert a collection to an array.
- `inject` has been removed.
- `keySearch` has been removed.
- `getPath` no longer supports wildcard expressions. Use something better like
  JMESPath for this.
- `setPath` now supports appending to an existing array via the `[]` notation.

### Events

Guzzle no longer requires Symfony's EventDispatcher component. Guzzle now uses
`GuzzleHttp\Event\Emitter`.

- `Symfony\Component\EventDispatcher\EventDispatcherInterface` is replaced by
  `GuzzleHttp\Event\EmitterInterface`.
- `Symfony\Component\EventDispatcher\EventDispatcher` is replaced by
  `GuzzleHttp\Event\Emitter`.
- `Symfony\Component\EventDispatcher\Event` is replaced by
  `GuzzleHttp\Event\Event`, and Guzzle now has an EventInterface in
  `GuzzleHttp\Event\EventInterface`.
- `AbstractHasDispatcher` has moved to a trait, `HasEmitterTrait`, and
  `HasDispatcherInterface` has moved to `HasEmitterInterface`. Retrieving the
  event emitter of a request, client, etc. now uses the `getEmitter` method
  rather than the `getDispatcher` method.

#### Emitter

- Use the `once()` method to add a listener that automatically removes itself
  the first time it is invoked.
- Use the `listeners()` method to retrieve a list of event listeners rather than
  the `getListeners()` method.
- Use `emit()` instead of `dispatch()` to emit an event from an emitter.
- Use `attach()` instead of `addSubscriber()` and `detach()` instead of
  `removeSubscriber()`.

```php
$mock = new Mock();
// 3.x
$request->getEventDispatcher()->addSubscriber($mock);
$request->getEventDispatcher()->removeSubscriber($mock);
// 4.x
$request->getEmitter()->attach($mock);
$request->getEmitter()->detach($mock);
```

Use the `on()` method to add a listener rather than the `addListener()` method.

```php
// 3.x
$request->getEventDispatcher()->addListener('foo', function (Event $event) { /* ... */ } );
// 4.x
$request->getEmitter()->on('foo', function (Event $event, $name) { /* ... */ } );
```

## Http

### General changes

- The cacert.pem certificate has been moved to `src/cacert.pem`.
- Added the concept of adapters that are used to transfer requests over the
  wire.
- Simplified the event system.
- Sending requests in parallel is still possible, but batching is no longer a
  concept of the HTTP layer. Instead, you must use the `complete` and `error`
  events to asynchronously manage parallel request transfers.
- `Guzzle\Http\Url` has moved to `GuzzleHttp\Url`.
- `Guzzle\Http\QueryString` has moved to `GuzzleHttp\Query`.
- QueryAggregators have been rewritten so that they are simply callable
  functions.
- `GuzzleHttp\StaticClient` has been removed. Use the functions provided in
  `functions.php` for an easy to use static client instance.
- Exceptions in `GuzzleHttp\Exception` have been updated to all extend from
  `GuzzleHttp\Exception\TransferException`.

### Client

Calling methods like `get()`, `post()`, `head()`, etc. no longer create and
return a request, but rather creates a request, sends the request, and returns
the response.

```php
// 3.0
$request = $client->get('/');
$response = $request->send();

// 4.0
$response = $client->get('/');

// or, to mirror the previous behavior
$request = $client->createRequest('GET', '/');
$response = $client->send($request);
```

`GuzzleHttp\ClientInterface` has changed.

- The `send` method no longer accepts more than one request. Use `sendAll` to
  send multiple requests in parallel.
- `setUserAgent()` has been removed. Use a default request option instead. You
  could, for example, do something like:
  `$client->setConfig('defaults/headers/User-Agent', 'Foo/Bar ' . $client::getDefaultUserAgent())`.
- `setSslVerification()` has been removed. Use default request options instead,
  like `$client->setConfig('defaults/verify', true)`.

`GuzzleHttp\Client` has changed.

- The constructor now accepts only an associative array. You can include a
  `base_url` string or array to use a URI template as the base URL of a client.
  You can also specify a `defaults` key that is an associative array of default
  request options. You can pass an `adapter` to use a custom adapter,
  `batch_adapter` to use a custom adapter for sending requests in parallel, or
  a `message_factory` to change the factory used to create HTTP requests and
  responses.
- The client no longer emits a `client.create_request` event.
- Creating requests with a client no longer automatically utilize a URI
  template. You must pass an array into a creational method (e.g.,
  `createRequest`, `get`, `put`, etc.) in order to expand a URI template.

### Messages

Messages no longer have references to their counterparts (i.e., a request no
longer has a reference to it's response, and a response no loger has a
reference to its request). This association is now managed through a
`GuzzleHttp\Adapter\TransactionInterface` object. You can get references to
these transaction objects using request events that are emitted over the
lifecycle of a request.

#### Requests with a body

- `GuzzleHttp\Message\EntityEnclosingRequest` and
  `GuzzleHttp\Message\EntityEnclosingRequestInterface` have been removed. The
  separation between requests that contain a body and requests that do not
  contain a body has been removed, and now `GuzzleHttp\Message\RequestInterface`
  handles both use cases.
- Any method that previously accepts a `GuzzleHttp\Response` object now accept a
  `GuzzleHttp\Message\ResponseInterface`.
- `GuzzleHttp\Message\RequestFactoryInterface` has been renamed to
  `GuzzleHttp\Message\MessageFactoryInterface`. This interface is used to create
  both requests and responses and is implemented in
  `GuzzleHttp\Message\MessageFactory`.
- POST field and file methods have been removed from the request object. You
  must now use the methods made available to `GuzzleHttp\Post\PostBodyInterface`
  to control the format of a POST body. Requests that are created using a
  standard `GuzzleHttp\Message\MessageFactoryInterface` will automatically use
  a `GuzzleHttp\Post\PostBody` body if the body was passed as an array or if
  the method is POST and no body is provided.

```php
$request = $client->createRequest('POST', '/');
$request->getBody()->setField('foo', 'bar');
$request->getBody()->addFile(new PostFile('file_key', fopen('/path/to/content', 'r')));
```

#### Headers

- `GuzzleHttp\Message\Header` has been removed. Header values are now simply
  represented by an array of values or as a string. Header values are returned
  as a string by default when retrieving a header value from a message. You can
  pass an optional argument of `true` to retrieve a header value as an array
  of strings instead of a single concatenated string.
- `GuzzleHttp\PostFile` and `GuzzleHttp\PostFileInterface` have been moved to
  `GuzzleHttp\Post`. This interface has been simplified and now allows the
  addition of arbitrary headers.
- Custom headers like `GuzzleHttp\Message\Header\Link` have been removed. Most
  of the custom headers are now handled separately in specific
  subscribers/plugins, and `GuzzleHttp\Message\HeaderValues::parseParams()` has
  been updated to properly handle headers that contain parameters (like the
  `Link` header).

#### Responses

- `GuzzleHttp\Message\Response::getInfo()` and
  `GuzzleHttp\Message\Response::setInfo()` have been removed. Use the event
  system to retrieve this type of information.
- `GuzzleHttp\Message\Response::getRawHeaders()` has been removed.
- `GuzzleHttp\Message\Response::getMessage()` has been removed.
- `GuzzleHttp\Message\Response::calculateAge()` and other cache specific
  methods have moved to the CacheSubscriber.
- Header specific helper functions like `getContentMd5()` have been removed.
  Just use `getHeader('Content-MD5')` instead.
- `GuzzleHttp\Message\Response::setRequest()` and
  `GuzzleHttp\Message\Response::getRequest()` have been removed. Use the event
  system to work with request and response objects as a transaction.
- `GuzzleHttp\Message\Response::getRedirectCount()` has been removed. Use the
  Redirect subscriber instead.
- `GuzzleHttp\Message\Response::isSuccessful()` and other related methods have
  been removed. Use `getStatusCode()` instead.

#### Streaming responses

Streaming requests can now be created by a client directly, returning a
`GuzzleHttp\Message\ResponseInterface` object that contains a body stream
referencing an open PHP HTTP stream.

```php
// 3.0
use Guzzle\Stream\PhpStreamRequestFactory;
$request = $client->get('/');
$factory = new PhpStreamRequestFactory();
$stream = $factory->fromRequest($request);
$data = $stream->read(1024);

// 4.0
$response = $client->get('/', ['stream' => true]);
// Read some data off of the stream in the response body
$data = $response->getBody()->read(1024);
```

#### Redirects

The `configureRedirects()` method has been removed in favor of a
`allow_redirects` request option.

```php
// Standard redirects with a default of a max of 5 redirects
$request = $client->createRequest('GET', '/', ['allow_redirects' => true]);

// Strict redirects with a custom number of redirects
$request = $client->createRequest('GET', '/', [
    'allow_redirects' => ['max' => 5, 'strict' => true]
]);
```

#### EntityBody

EntityBody interfaces and classes have been removed or moved to
`GuzzleHttp\Stream`. All classes and interfaces that once required
`GuzzleHttp\EntityBodyInterface` now require
`GuzzleHttp\Stream\StreamInterface`. Creating a new body for a request no
longer uses `GuzzleHttp\EntityBody::factory` but now uses
`GuzzleHttp\Stream\Stream::factory` or even better:
`GuzzleHttp\Stream\create()`.

- `Guzzle\Http\EntityBodyInterface` is now `GuzzleHttp\Stream\StreamInterface`
- `Guzzle\Http\EntityBody` is now `GuzzleHttp\Stream\Stream`
- `Guzzle\Http\CachingEntityBody` is now `GuzzleHttp\Stream\CachingStream`
- `Guzzle\Http\ReadLimitEntityBody` is now `GuzzleHttp\Stream\LimitStream`
- `Guzzle\Http\IoEmittyinEntityBody` has been removed.

#### Request lifecycle events

Requests previously submitted a large number of requests. The number of events
emitted over the lifecycle of a request has been significantly reduced to make
it easier to understand how to extend the behavior of a request. All events
emitted during the lifecycle of a request now emit a custom
`GuzzleHttp\Event\EventInterface` object that contains context providing
methods and a way in which to modify the transaction at that specific point in
time (e.g., intercept the request and set a response on the transaction).

- `request.before_send` has been renamed to `before` and now emits a
  `GuzzleHttp\Event\BeforeEvent`
- `request.complete` has been renamed to `complete` and now emits a
  `GuzzleHttp\Event\CompleteEvent`.
- `request.sent` has been removed. Use `complete`.
- `request.success` has been removed. Use `complete`.
- `error` is now an event that emits a `GuzzleHttp\Event\ErrorEvent`.
- `request.exception` has been removed. Use `error`.
- `request.receive.status_line` has been removed.
- `curl.callback.progress` has been removed. Use a custom `StreamInterface` to
  maintain a status update.
- `curl.callback.write` has been removed. Use a custom `StreamInterface` to
  intercept writes.
- `curl.callback.read` has been removed. Use a custom `StreamInterface` to
  intercept reads.

`headers` is a new event that is emitted after the response headers of a
request have been received before the body of the response is downloaded. This
event emits a `GuzzleHttp\Event\HeadersEvent`.

You can intercept a request and inject a response using the `intercept()` event
of a `GuzzleHttp\Event\BeforeEvent`, `GuzzleHttp\Event\CompleteEvent`, and
`GuzzleHttp\Event\ErrorEvent` event.

See: https://docs.guzzlephp.org/en/latest/events.html

## Inflection

The `Guzzle\Inflection` namespace has been removed. This is not a core concern
of Guzzle.

## Iterator

The `Guzzle\Iterator` namespace has been removed.

- `Guzzle\Iterator\AppendIterator`, `Guzzle\Iterator\ChunkedIterator`, and
  `Guzzle\Iterator\MethodProxyIterator` are nice, but not a core requirement of
  Guzzle itself.
- `Guzzle\Iterator\FilterIterator` is no longer needed because an equivalent
  class is shipped with PHP 5.4.
- `Guzzle\Iterator\MapIterator` is not really needed when using PHP 5.5 because
  it's easier to just wrap an iterator in a generator that maps values.

For a replacement of these iterators, see https://github.com/nikic/iter

## Log

The LogPlugin has moved to https://github.com/guzzle/log-subscriber. The
`Guzzle\Log` namespace has been removed. Guzzle now relies on
`Psr\Log\LoggerInterface` for all logging. The MessageFormatter class has been
moved to `GuzzleHttp\Subscriber\Log\Formatter`.

## Parser

The `Guzzle\Parser` namespace has been removed. This was previously used to
make it possible to plug in custom parsers for cookies, messages, URI
templates, and URLs; however, this level of complexity is not needed in Guzzle
so it has been removed.

- Cookie: Cookie parsing logic has been moved to
  `GuzzleHttp\Cookie\SetCookie::fromString`.
- Message: Message parsing logic for both requests and responses has been moved
  to `GuzzleHttp\Message\MessageFactory::fromMessage`. Message parsing is only
  used in debugging or deserializing messages, so it doesn't make sense for
  Guzzle as a library to add this level of complexity to parsing messages.
- UriTemplate: URI template parsing has been moved to
  `GuzzleHttp\UriTemplate`. The Guzzle library will automatically use the PECL
  URI template library if it is installed.
- Url: URL parsing is now performed in `GuzzleHttp\Url::fromString` (previously
  it was `Guzzle\Http\Url::factory()`). If custom URL parsing is necessary,
  then developers are free to subclass `GuzzleHttp\Url`.

## Plugin

The `Guzzle\Plugin` namespace has been renamed to `GuzzleHttp\Subscriber`.
Several plugins are shipping with the core Guzzle library under this namespace.

- `GuzzleHttp\Subscriber\Cookie`: Replaces the old CookiePlugin. Cookie jar
  code has moved to `GuzzleHttp\Cookie`.
- `GuzzleHttp\Subscriber\History`: Replaces the old HistoryPlugin.
- `GuzzleHttp\Subscriber\HttpError`: Throws errors when a bad HTTP response is
  received.
- `GuzzleHttp\Subscriber\Mock`: Replaces the old MockPlugin.
- `GuzzleHttp\Subscriber\Prepare`: Prepares the body of a request just before
  sending. This subscriber is attached to all requests by default.
- `GuzzleHttp\Subscriber\Redirect`: Replaces the RedirectPlugin.

The following plugins have been removed (third-parties are free to re-implement
these if needed):

- `GuzzleHttp\Plugin\Async` has been removed.
- `GuzzleHttp\Plugin\CurlAuth` has been removed.
- `GuzzleHttp\Plugin\ErrorResponse\ErrorResponsePlugin` has been removed. This
  functionality should instead be implemented with event listeners that occur
  after normal response parsing occurs in the guzzle/command package.

The following plugins are not part of the core Guzzle package, but are provided
in separate repositories:

- `Guzzle\Http\Plugin\BackoffPlugin` has been rewritten to be much simpler
  to build custom retry policies using simple functions rather than various
  chained classes. See: https://github.com/guzzle/retry-subscriber
- `Guzzle\Http\Plugin\Cache\CachePlugin` has moved to
  https://github.com/guzzle/cache-subscriber
- `Guzzle\Http\Plugin\Log\LogPlugin` has moved to
  https://github.com/guzzle/log-subscriber
- `Guzzle\Http\Plugin\Md5\Md5Plugin` has moved to
  https://github.com/guzzle/message-integrity-subscriber
- `Guzzle\Http\Plugin\Mock\MockPlugin` has moved to
  `GuzzleHttp\Subscriber\MockSubscriber`.
- `Guzzle\Http\Plugin\Oauth\OauthPlugin` has moved to
  https://github.com/guzzle/oauth-subscriber

## Service

The service description layer of Guzzle has moved into two separate packages:

- https://github.com/guzzle/command Provides a high level abstraction over web
  services by representing web service operations using commands.
- https://github.com/guzzle/guzzle-services Provides an implementation of
  guzzle/command that provides request serialization and response parsing using
  Guzzle service descriptions.

## Stream

Stream have moved to a separate package available at
https://github.com/guzzle/streams.

`Guzzle\Stream\StreamInterface` has been given a large update to cleanly take
on the responsibilities of `Guzzle\Http\EntityBody` and
`Guzzle\Http\EntityBodyInterface` now that they have been removed. The number
of methods implemented by the `StreamInterface` has been drastically reduced to
allow developers to more easily extend and decorate stream behavior.

## Removed methods from StreamInterface

- `getStream` and `setStream` have been removed to better encapsulate streams.
- `getMetadata` and `setMetadata` have been removed in favor of
  `GuzzleHttp\Stream\MetadataStreamInterface`.
- `getWrapper`, `getWrapperData`, `getStreamType`, and `getUri` have all been
  removed. This data is accessible when
  using streams that implement `GuzzleHttp\Stream\MetadataStreamInterface`.
- `rewind` has been removed. Use `seek(0)` for a similar behavior.

## Renamed methods

- `detachStream` has been renamed to `detach`.
- `feof` has been renamed to `eof`.
- `ftell` has been renamed to `tell`.
- `readLine` has moved from an instance method to a static class method of
  `GuzzleHttp\Stream\Stream`.

## Metadata streams

`GuzzleHttp\Stream\MetadataStreamInterface` has been added to denote streams
that contain additional metadata accessible via `getMetadata()`.
`GuzzleHttp\Stream\StreamInterface::getMetadata` and
`GuzzleHttp\Stream\StreamInterface::setMetadata` have been removed.

## StreamRequestFactory

The entire concept of the StreamRequestFactory has been removed. The way this
was used in Guzzle 3 broke the actual interface of sending streaming requests
(instead of getting back a Response, you got a StreamInterface). Streaming
PHP requests are now implemented through the `GuzzleHttp\Adapter\StreamAdapter`.

3.6 to 3.7
----------

### Deprecations

- You can now enable E_USER_DEPRECATED warnings to see if you are using any deprecated methods.:

```php
\Guzzle\Common\Version::$emitWarnings = true;
```

The following APIs and options have been marked as deprecated:

- Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use `$request->getResponseBody()->isRepeatable()` instead.
- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
- Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead.
- Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead.
- Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated
- Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client.
- Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8.
- Marked `Guzzle\Common\Collection::inject()` as deprecated.
- Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use
  `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));` or
  `$client->setDefaultOption('auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));`

3.7 introduces `request.options` as a parameter for a client configuration and as an optional argument to all creational
request methods. When paired with a client's configuration settings, these options allow you to specify default settings
for various aspects of a request. Because these options make other previous configuration options redundant, several
configuration options and methods of a client and AbstractCommand have been deprecated.

- Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use `$client->getDefaultOption('headers')`.
- Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use `$client->setDefaultOption('headers/{header_name}', 'value')`.
- Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use `$client->setDefaultOption('params/{param_name}', 'value')`
- Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand. These will work through Guzzle 4.0

        $command = $client->getCommand('foo', array(
            'command.headers' => array('Test' => '123'),
            'command.response_body' => '/path/to/file'
        ));

        // Should be changed to:

        $command = $client->getCommand('foo', array(
            'command.request_options' => array(
                'headers' => array('Test' => '123'),
                'save_as' => '/path/to/file'
            )
        ));

### Interface changes

Additions and changes (you will need to update any implementations or subclasses you may have created):

- Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`:
  createRequest, head, delete, put, patch, post, options, prepareRequest
- Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()`
- Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface`
- Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to
  `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a
  resource, string, or EntityBody into the $options parameter to specify the download location of the response.
- Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a
  default `array()`
- Added `Guzzle\Stream\StreamInterface::isRepeatable`
- Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods.

The following methods were removed from interfaces. All of these methods are still available in the concrete classes
that implement them, but you should update your code to use alternative methods:

- Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use
  `$client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or
  `$client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))` or
  `$client->setDefaultOption('headers/{header_name}', 'value')`. or
  `$client->setDefaultOption('headers', array('header_name' => 'value'))`.
- Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use `$client->getConfig()->getPath('request.options/headers')`.
- Removed `Guzzle\Http\ClientInterface::expandTemplate()`. This is an implementation detail.
- Removed `Guzzle\Http\ClientInterface::setRequestFactory()`. This is an implementation detail.
- Removed `Guzzle\Http\ClientInterface::getCurlMulti()`. This is a very specific implementation detail.
- Removed `Guzzle\Http\Message\RequestInterface::canCache`. Use the CachePlugin.
- Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect`. Use the HistoryPlugin.
- Removed `Guzzle\Http\Message\RequestInterface::isRedirect`. Use the HistoryPlugin.

### Cache plugin breaking changes

- CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a
  CacheStorageInterface. These two objects and interface will be removed in a future version.
- Always setting X-cache headers on cached responses
- Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin
- `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface
  $request, Response $response);`
- `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);`
- `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);`
- Added `CacheStorageInterface::purge($url)`
- `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin
  $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache,
  CanCacheStrategyInterface $canCache = null)`
- Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)`

3.5 to 3.6
----------

* Mixed casing of headers are now forced to be a single consistent casing across all values for that header.
* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution
* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader().
  For example, setHeader() first removes the header using unset on a HeaderCollection and then calls addHeader().
  Keeping the Host header and URL host in sync is now handled by overriding the addHeader method in Request.
* Specific header implementations can be created for complex headers. When a message creates a header, it uses a
  HeaderFactory which can map specific headers to specific header classes. There is now a Link header and
  CacheControl header implementation.
* Moved getLinks() from Response to just be used on a Link header object.

If you previously relied on Guzzle\Http\Message\Header::raw(), then you will need to update your code to use the
HeaderInterface (e.g. toArray(), getAll(), etc.).

### Interface changes

* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate
* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti()
* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in
  Guzzle\Http\Curl\RequestMediator
* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string.
* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface
* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders()

### Removed deprecated functions

* Removed Guzzle\Parser\ParserRegister::get(). Use getParser()
* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser().

### Deprecations

* The ability to case-insensitively search for header values
* Guzzle\Http\Message\Header::hasExactHeader
* Guzzle\Http\Message\Header::raw. Use getAll()
* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object
  instead.

### Other changes

* All response header helper functions return a string rather than mixing Header objects and strings inconsistently
* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle
  directly via interfaces
* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist
  but are a no-op until removed.
* Most classes that used to require a `Guzzle\Service\Command\CommandInterface` typehint now request a
  `Guzzle\Service\Command\ArrayCommandInterface`.
* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response
  on a request while the request is still being transferred
* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess

3.3 to 3.4
----------

Base URLs of a client now follow the rules of https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.2 when merging URLs.

3.2 to 3.3
----------

### Response::getEtag() quote stripping removed

`Guzzle\Http\Message\Response::getEtag()` no longer strips quotes around the ETag response header

### Removed `Guzzle\Http\Utils`

The `Guzzle\Http\Utils` class was removed. This class was only used for testing.

### Stream wrapper and type

`Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getStreamType()` are no longer converted to lowercase.

### curl.emit_io became emit_io

Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using the
'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io'

3.1 to 3.2
----------

### CurlMulti is no longer reused globally

Before 3.2, the same CurlMulti object was reused globally for each client. This can cause issue where plugins added
to a single client can pollute requests dispatched from other clients.

If you still wish to reuse the same CurlMulti object with each client, then you can add a listener to the
ServiceBuilder's `service_builder.create_client` event to inject a custom CurlMulti object into each client as it is
created.

```php
$multi = new Guzzle\Http\Curl\CurlMulti();
$builder = Guzzle\Service\Builder\ServiceBuilder::factory('/path/to/config.json');
$builder->addListener('service_builder.create_client', function ($event) use ($multi) {
    $event['client']->setCurlMulti($multi);
}
});
```

### No default path

URLs no longer have a default path value of '/' if no path was specified.

Before:

```php
$request = $client->get('http://www.foo.com');
echo $request->getUrl();
// >> http://www.foo.com/
```

After:

```php
$request = $client->get('http://www.foo.com');
echo $request->getUrl();
// >> http://www.foo.com
```

### Less verbose BadResponseException

The exception message for `Guzzle\Http\Exception\BadResponseException` no longer contains the full HTTP request and
response information. You can, however, get access to the request and response object by calling `getRequest()` or
`getResponse()` on the exception object.

### Query parameter aggregation

Multi-valued query parameters are no longer aggregated using a callback function. `Guzzle\Http\Query` now has a
setAggregator() method that accepts a `Guzzle\Http\QueryAggregator\QueryAggregatorInterface` object. This object is
responsible for handling the aggregation of multi-valued query string variables into a flattened hash.

2.8 to 3.x
----------

### Guzzle\Service\Inspector

Change `\Guzzle\Service\Inspector::fromConfig` to `\Guzzle\Common\Collection::fromConfig`

**Before**

```php
use Guzzle\Service\Inspector;

class YourClient extends \Guzzle\Service\Client
{
    public static function factory($config = array())
    {
        $default = array();
        $required = array('base_url', 'username', 'api_key');
        $config = Inspector::fromConfig($config, $default, $required);

        $client = new self(
            $config->get('base_url'),
            $config->get('username'),
            $config->get('api_key')
        );
        $client->setConfig($config);

        $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json'));

        return $client;
    }
```

**After**

```php
use Guzzle\Common\Collection;

class YourClient extends \Guzzle\Service\Client
{
    public static function factory($config = array())
    {
        $default = array();
        $required = array('base_url', 'username', 'api_key');
        $config = Collection::fromConfig($config, $default, $required);

        $client = new self(
            $config->get('base_url'),
            $config->get('username'),
            $config->get('api_key')
        );
        $client->setConfig($config);

        $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json'));

        return $client;
    }
```

### Convert XML Service Descriptions to JSON

**Before**

```xml
<?xml version="1.0" encoding="UTF-8"?>
<client>
    <commands>
        <!-- Groups -->
        <command name="list_groups" method="GET" uri="groups.json">
            <doc>Get a list of groups</doc>
        </command>
        <command name="search_groups" method="GET" uri='search.json?query="{{query}} type:group"'>
            <doc>Uses a search query to get a list of groups</doc>
            <param name="query" type="string" required="true" />
        </command>
        <command name="create_group" method="POST" uri="groups.json">
            <doc>Create a group</doc>
            <param name="data" type="array" location="body" filters="json_encode" doc="Group JSON"/>
            <param name="Content-Type" location="header" static="application/json"/>
        </command>
        <command name="delete_group" method="DELETE" uri="groups/{{id}}.json">
            <doc>Delete a group by ID</doc>
            <param name="id" type="integer" required="true"/>
        </command>
        <command name="get_group" method="GET" uri="groups/{{id}}.json">
            <param name="id" type="integer" required="true"/>
        </command>
        <command name="update_group" method="PUT" uri="groups/{{id}}.json">
            <doc>Update a group</doc>
            <param name="id" type="integer" required="true"/>
            <param name="data" type="array" location="body" filters="json_encode" doc="Group JSON"/>
            <param name="Content-Type" location="header" static="application/json"/>
        </command>
    </commands>
</client>
```

**After**

```json
{
    "name":       "Zendesk REST API v2",
    "apiVersion": "2012-12-31",
    "description":"Provides access to Zendesk views, groups, tickets, ticket fields, and users",
    "operations": {
        "list_groups":  {
            "httpMethod":"GET",
            "uri":       "groups.json",
            "summary":   "Get a list of groups"
        },
        "search_groups":{
            "httpMethod":"GET",
            "uri":       "search.json?query=\"{query} type:group\"",
            "summary":   "Uses a search query to get a list of groups",
            "parameters":{
                "query":{
                    "location":   "uri",
                    "description":"Zendesk Search Query",
                    "type":       "string",
                    "required":   true
                }
            }
        },
        "create_group": {
            "httpMethod":"POST",
            "uri":       "groups.json",
            "summary":   "Create a group",
            "parameters":{
                "data":        {
                    "type":       "array",
                    "location":   "body",
                    "description":"Group JSON",
                    "filters":    "json_encode",
                    "required":   true
                },
                "Content-Type":{
                    "type":    "string",
                    "location":"header",
                    "static":  "application/json"
                }
            }
        },
        "delete_group": {
            "httpMethod":"DELETE",
            "uri":       "groups/{id}.json",
            "summary":   "Delete a group",
            "parameters":{
                "id":{
                    "location":   "uri",
                    "description":"Group to delete by ID",
                    "type":       "integer",
                    "required":   true
                }
            }
        },
        "get_group":    {
            "httpMethod":"GET",
            "uri":       "groups/{id}.json",
            "summary":   "Get a ticket",
            "parameters":{
                "id":{
                    "location":   "uri",
                    "description":"Group to get by ID",
                    "type":       "integer",
                    "required":   true
                }
            }
        },
        "update_group": {
            "httpMethod":"PUT",
            "uri":       "groups/{id}.json",
            "summary":   "Update a group",
            "parameters":{
                "id":          {
                    "location":   "uri",
                    "description":"Group to update by ID",
                    "type":       "integer",
                    "required":   true
                },
                "data":        {
                    "type":       "array",
                    "location":   "body",
                    "description":"Group JSON",
                    "filters":    "json_encode",
                    "required":   true
                },
                "Content-Type":{
                    "type":    "string",
                    "location":"header",
                    "static":  "application/json"
                }
            }
        }
}
```

### Guzzle\Service\Description\ServiceDescription

Commands are now called Operations

**Before**

```php
use Guzzle\Service\Description\ServiceDescription;

$sd = new ServiceDescription();
$sd->getCommands();     // @returns ApiCommandInterface[]
$sd->hasCommand($name);
$sd->getCommand($name); // @returns ApiCommandInterface|null
$sd->addCommand($command); // @param ApiCommandInterface $command
```

**After**

```php
use Guzzle\Service\Description\ServiceDescription;

$sd = new ServiceDescription();
$sd->getOperations();           // @returns OperationInterface[]
$sd->hasOperation($name);
$sd->getOperation($name);       // @returns OperationInterface|null
$sd->addOperation($operation);  // @param OperationInterface $operation
```

### Guzzle\Common\Inflection\Inflector

Namespace is now `Guzzle\Inflection\Inflector`

### Guzzle\Http\Plugin

Namespace is now `Guzzle\Plugin`. Many other changes occur within this namespace and are detailed in their own sections below.

### Guzzle\Http\Plugin\LogPlugin and Guzzle\Common\Log

Now `Guzzle\Plugin\Log\LogPlugin` and `Guzzle\Log` respectively.

**Before**

```php
use Guzzle\Common\Log\ClosureLogAdapter;
use Guzzle\Http\Plugin\LogPlugin;

/** @var \Guzzle\Http\Client */
$client;

// $verbosity is an integer indicating desired message verbosity level
$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $verbosity = LogPlugin::LOG_VERBOSE);
```

**After**

```php
use Guzzle\Log\ClosureLogAdapter;
use Guzzle\Log\MessageFormatter;
use Guzzle\Plugin\Log\LogPlugin;

/** @var \Guzzle\Http\Client */
$client;

// $format is a string indicating desired message format -- @see MessageFormatter
$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $format = MessageFormatter::DEBUG_FORMAT);
```

### Guzzle\Http\Plugin\CurlAuthPlugin

Now `Guzzle\Plugin\CurlAuth\CurlAuthPlugin`.

### Guzzle\Http\Plugin\ExponentialBackoffPlugin

Now `Guzzle\Plugin\Backoff\BackoffPlugin`, and other changes.

**Before**

```php
use Guzzle\Http\Plugin\ExponentialBackoffPlugin;

$backoffPlugin = new ExponentialBackoffPlugin($maxRetries, array_merge(
        ExponentialBackoffPlugin::getDefaultFailureCodes(), array(429)
    ));

$client->addSubscriber($backoffPlugin);
```

**After**

```php
use Guzzle\Plugin\Backoff\BackoffPlugin;
use Guzzle\Plugin\Backoff\HttpBackoffStrategy;

// Use convenient factory method instead -- see implementation for ideas of what
// you can do with chaining backoff strategies
$backoffPlugin = BackoffPlugin::getExponentialBackoff($maxRetries, array_merge(
        HttpBackoffStrategy::getDefaultFailureCodes(), array(429)
    ));
$client->addSubscriber($backoffPlugin);
```

### Known Issues

#### [BUG] Accept-Encoding header behavior changed unintentionally.

(See #217) (Fixed in 09daeb8c666fb44499a0646d655a8ae36456575e)

In version 2.8 setting the `Accept-Encoding` header would set the CURLOPT_ENCODING option, which permitted cURL to
properly handle gzip/deflate compressed responses from the server. In versions affected by this bug this does not happen.
See issue #217 for a workaround, or use a version containing the fix.
PK�c�\�a���guzzlehttp/guzzle/README.mdnu�[���![Guzzle](.github/logo.png?raw=true)

# Guzzle, PHP HTTP client

[![Latest Version](https://img.shields.io/github/release/guzzle/guzzle.svg?style=flat-square)](https://github.com/guzzle/guzzle/releases)
[![Build Status](https://img.shields.io/github/actions/workflow/status/guzzle/guzzle/ci.yml?label=ci%20build&style=flat-square)](https://github.com/guzzle/guzzle/actions?query=workflow%3ACI)
[![Total Downloads](https://img.shields.io/packagist/dt/guzzlehttp/guzzle.svg?style=flat-square)](https://packagist.org/packages/guzzlehttp/guzzle)

Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and
trivial to integrate with web services.

- Simple interface for building query strings, POST requests, streaming large
  uploads, streaming large downloads, using HTTP cookies, uploading JSON data,
  etc...
- Can send both synchronous and asynchronous requests using the same interface.
- Uses PSR-7 interfaces for requests, responses, and streams. This allows you
  to utilize other PSR-7 compatible libraries with Guzzle.
- Supports PSR-18 allowing interoperability between other PSR-18 HTTP Clients.
- Abstracts away the underlying HTTP transport, allowing you to write
  environment and transport agnostic code; i.e., no hard dependency on cURL,
  PHP streams, sockets, or non-blocking event loops.
- Middleware system allows you to augment and compose client behavior.

```php
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.github.com/repos/guzzle/guzzle');

echo $response->getStatusCode(); // 200
echo $response->getHeaderLine('content-type'); // 'application/json; charset=utf8'
echo $response->getBody(); // '{"id": 1420053, "name": "guzzle", ...}'

// Send an asynchronous request.
$request = new \GuzzleHttp\Psr7\Request('GET', 'http://httpbin.org');
$promise = $client->sendAsync($request)->then(function ($response) {
    echo 'I completed! ' . $response->getBody();
});

$promise->wait();
```

## Help and docs

We use GitHub issues only to discuss bugs and new features. For support please refer to:

- [Documentation](https://docs.guzzlephp.org)
- [Stack Overflow](https://stackoverflow.com/questions/tagged/guzzle)
- [#guzzle](https://app.slack.com/client/T0D2S9JCT/CE6UAAKL4) channel on [PHP-HTTP Slack](https://slack.httplug.io/)
- [Gitter](https://gitter.im/guzzle/guzzle)


## Installing Guzzle

The recommended way to install Guzzle is through
[Composer](https://getcomposer.org/).

```bash
composer require guzzlehttp/guzzle
```


## Version Guidance

| Version | Status              | Packagist           | Namespace    | Repo                | Docs                | PSR-7 | PHP Version  |
|---------|---------------------|---------------------|--------------|---------------------|---------------------|-------|--------------|
| 3.x     | EOL                 | `guzzle/guzzle`     | `Guzzle`     | [v3][guzzle-3-repo] | [v3][guzzle-3-docs] | No    | >=5.3.3,<7.0 |
| 4.x     | EOL                 | `guzzlehttp/guzzle` | `GuzzleHttp` | [v4][guzzle-4-repo] | N/A                 | No    | >=5.4,<7.0   |
| 5.x     | EOL                 | `guzzlehttp/guzzle` | `GuzzleHttp` | [v5][guzzle-5-repo] | [v5][guzzle-5-docs] | No    | >=5.4,<7.4   |
| 6.x     | Security fixes only | `guzzlehttp/guzzle` | `GuzzleHttp` | [v6][guzzle-6-repo] | [v6][guzzle-6-docs] | Yes   | >=5.5,<8.0   |
| 7.x     | Latest              | `guzzlehttp/guzzle` | `GuzzleHttp` | [v7][guzzle-7-repo] | [v7][guzzle-7-docs] | Yes   | >=7.2.5,<8.4 |

[guzzle-3-repo]: https://github.com/guzzle/guzzle3
[guzzle-4-repo]: https://github.com/guzzle/guzzle/tree/4.x
[guzzle-5-repo]: https://github.com/guzzle/guzzle/tree/5.3
[guzzle-6-repo]: https://github.com/guzzle/guzzle/tree/6.5
[guzzle-7-repo]: https://github.com/guzzle/guzzle
[guzzle-3-docs]: https://guzzle3.readthedocs.io/
[guzzle-5-docs]: https://docs.guzzlephp.org/en/5.3/
[guzzle-6-docs]: https://docs.guzzlephp.org/en/6.5/
[guzzle-7-docs]: https://docs.guzzlephp.org/en/latest/


## Security

If you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see [Security Policy](https://github.com/guzzle/guzzle/security/policy) for more information.

## License

Guzzle is made available under the MIT License (MIT). Please see [License File](LICENSE) for more information.

## For Enterprise

Available as part of the Tidelift Subscription

The maintainers of Guzzle and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-guzzlehttp-guzzle?utm_source=packagist-guzzlehttp-guzzle&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)
PK�c�\�>=��.guzzlehttp/promises/src/TaskQueueInterface.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise;

interface TaskQueueInterface
{
    /**
     * Returns true if the queue is empty.
     */
    public function isEmpty(): bool;

    /**
     * Adds a task to the queue that will be executed the next time run is
     * called.
     */
    public function add(callable $task): void;

    /**
     * Execute all of the pending task in the queue.
     */
    public function run(): void;
}
PK�c�\�d�1��+guzzlehttp/promises/src/RejectedPromise.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise;

/**
 * A promise that has been rejected.
 *
 * Thenning off of this promise will invoke the onRejected callback
 * immediately and ignore other callbacks.
 *
 * @final
 */
class RejectedPromise implements PromiseInterface
{
    private $reason;

    /**
     * @param mixed $reason
     */
    public function __construct($reason)
    {
        if (is_object($reason) && method_exists($reason, 'then')) {
            throw new \InvalidArgumentException(
                'You cannot create a RejectedPromise with a promise.'
            );
        }

        $this->reason = $reason;
    }

    public function then(
        callable $onFulfilled = null,
        callable $onRejected = null
    ): PromiseInterface {
        // If there's no onRejected callback then just return self.
        if (!$onRejected) {
            return $this;
        }

        $queue = Utils::queue();
        $reason = $this->reason;
        $p = new Promise([$queue, 'run']);
        $queue->add(static function () use ($p, $reason, $onRejected): void {
            if (Is::pending($p)) {
                try {
                    // Return a resolved promise if onRejected does not throw.
                    $p->resolve($onRejected($reason));
                } catch (\Throwable $e) {
                    // onRejected threw, so return a rejected promise.
                    $p->reject($e);
                }
            }
        });

        return $p;
    }

    public function otherwise(callable $onRejected): PromiseInterface
    {
        return $this->then(null, $onRejected);
    }

    public function wait(bool $unwrap = true)
    {
        if ($unwrap) {
            throw Create::exceptionFor($this->reason);
        }

        return null;
    }

    public function getState(): string
    {
        return self::REJECTED;
    }

    public function resolve($value): void
    {
        throw new \LogicException('Cannot resolve a rejected promise');
    }

    public function reject($reason): void
    {
        if ($reason !== $this->reason) {
            throw new \LogicException('Cannot reject a rejected promise');
        }
    }

    public function cancel(): void
    {
        // pass
    }
}
PK�c�\�� � !guzzlehttp/promises/src/Utils.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise;

final class Utils
{
    /**
     * Get the global task queue used for promise resolution.
     *
     * This task queue MUST be run in an event loop in order for promises to be
     * settled asynchronously. It will be automatically run when synchronously
     * waiting on a promise.
     *
     * <code>
     * while ($eventLoop->isRunning()) {
     *     GuzzleHttp\Promise\Utils::queue()->run();
     * }
     * </code>
     *
     * @param TaskQueueInterface|null $assign Optionally specify a new queue instance.
     */
    public static function queue(TaskQueueInterface $assign = null): TaskQueueInterface
    {
        static $queue;

        if ($assign) {
            $queue = $assign;
        } elseif (!$queue) {
            $queue = new TaskQueue();
        }

        return $queue;
    }

    /**
     * Adds a function to run in the task queue when it is next `run()` and
     * returns a promise that is fulfilled or rejected with the result.
     *
     * @param callable $task Task function to run.
     */
    public static function task(callable $task): PromiseInterface
    {
        $queue = self::queue();
        $promise = new Promise([$queue, 'run']);
        $queue->add(function () use ($task, $promise): void {
            try {
                if (Is::pending($promise)) {
                    $promise->resolve($task());
                }
            } catch (\Throwable $e) {
                $promise->reject($e);
            }
        });

        return $promise;
    }

    /**
     * Synchronously waits on a promise to resolve and returns an inspection
     * state array.
     *
     * Returns a state associative array containing a "state" key mapping to a
     * valid promise state. If the state of the promise is "fulfilled", the
     * array will contain a "value" key mapping to the fulfilled value of the
     * promise. If the promise is rejected, the array will contain a "reason"
     * key mapping to the rejection reason of the promise.
     *
     * @param PromiseInterface $promise Promise or value.
     */
    public static function inspect(PromiseInterface $promise): array
    {
        try {
            return [
                'state' => PromiseInterface::FULFILLED,
                'value' => $promise->wait(),
            ];
        } catch (RejectionException $e) {
            return ['state' => PromiseInterface::REJECTED, 'reason' => $e->getReason()];
        } catch (\Throwable $e) {
            return ['state' => PromiseInterface::REJECTED, 'reason' => $e];
        }
    }

    /**
     * Waits on all of the provided promises, but does not unwrap rejected
     * promises as thrown exception.
     *
     * Returns an array of inspection state arrays.
     *
     * @see inspect for the inspection state array format.
     *
     * @param PromiseInterface[] $promises Traversable of promises to wait upon.
     */
    public static function inspectAll($promises): array
    {
        $results = [];
        foreach ($promises as $key => $promise) {
            $results[$key] = self::inspect($promise);
        }

        return $results;
    }

    /**
     * Waits on all of the provided promises and returns the fulfilled values.
     *
     * Returns an array that contains the value of each promise (in the same
     * order the promises were provided). An exception is thrown if any of the
     * promises are rejected.
     *
     * @param iterable<PromiseInterface> $promises Iterable of PromiseInterface objects to wait on.
     *
     * @throws \Throwable on error
     */
    public static function unwrap($promises): array
    {
        $results = [];
        foreach ($promises as $key => $promise) {
            $results[$key] = $promise->wait();
        }

        return $results;
    }

    /**
     * Given an array of promises, return a promise that is fulfilled when all
     * the items in the array are fulfilled.
     *
     * The promise's fulfillment value is an array with fulfillment values at
     * respective positions to the original array. If any promise in the array
     * rejects, the returned promise is rejected with the rejection reason.
     *
     * @param mixed $promises  Promises or values.
     * @param bool  $recursive If true, resolves new promises that might have been added to the stack during its own resolution.
     */
    public static function all($promises, bool $recursive = false): PromiseInterface
    {
        $results = [];
        $promise = Each::of(
            $promises,
            function ($value, $idx) use (&$results): void {
                $results[$idx] = $value;
            },
            function ($reason, $idx, Promise $aggregate): void {
                $aggregate->reject($reason);
            }
        )->then(function () use (&$results) {
            ksort($results);

            return $results;
        });

        if (true === $recursive) {
            $promise = $promise->then(function ($results) use ($recursive, &$promises) {
                foreach ($promises as $promise) {
                    if (Is::pending($promise)) {
                        return self::all($promises, $recursive);
                    }
                }

                return $results;
            });
        }

        return $promise;
    }

    /**
     * Initiate a competitive race between multiple promises or values (values
     * will become immediately fulfilled promises).
     *
     * When count amount of promises have been fulfilled, the returned promise
     * is fulfilled with an array that contains the fulfillment values of the
     * winners in order of resolution.
     *
     * This promise is rejected with a {@see AggregateException} if the number
     * of fulfilled promises is less than the desired $count.
     *
     * @param int   $count    Total number of promises.
     * @param mixed $promises Promises or values.
     */
    public static function some(int $count, $promises): PromiseInterface
    {
        $results = [];
        $rejections = [];

        return Each::of(
            $promises,
            function ($value, $idx, PromiseInterface $p) use (&$results, $count): void {
                if (Is::settled($p)) {
                    return;
                }
                $results[$idx] = $value;
                if (count($results) >= $count) {
                    $p->resolve(null);
                }
            },
            function ($reason) use (&$rejections): void {
                $rejections[] = $reason;
            }
        )->then(
            function () use (&$results, &$rejections, $count) {
                if (count($results) !== $count) {
                    throw new AggregateException(
                        'Not enough promises to fulfill count',
                        $rejections
                    );
                }
                ksort($results);

                return array_values($results);
            }
        );
    }

    /**
     * Like some(), with 1 as count. However, if the promise fulfills, the
     * fulfillment value is not an array of 1 but the value directly.
     *
     * @param mixed $promises Promises or values.
     */
    public static function any($promises): PromiseInterface
    {
        return self::some(1, $promises)->then(function ($values) {
            return $values[0];
        });
    }

    /**
     * Returns a promise that is fulfilled when all of the provided promises have
     * been fulfilled or rejected.
     *
     * The returned promise is fulfilled with an array of inspection state arrays.
     *
     * @see inspect for the inspection state array format.
     *
     * @param mixed $promises Promises or values.
     */
    public static function settle($promises): PromiseInterface
    {
        $results = [];

        return Each::of(
            $promises,
            function ($value, $idx) use (&$results): void {
                $results[$idx] = ['state' => PromiseInterface::FULFILLED, 'value' => $value];
            },
            function ($reason, $idx) use (&$results): void {
                $results[$idx] = ['state' => PromiseInterface::REJECTED, 'reason' => $reason];
            }
        )->then(function () use (&$results) {
            ksort($results);

            return $results;
        });
    }
}
PK�c�\偢'��'guzzlehttp/promises/src/EachPromise.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise;

/**
 * Represents a promise that iterates over many promises and invokes
 * side-effect functions in the process.
 *
 * @final
 */
class EachPromise implements PromisorInterface
{
    private $pending = [];

    private $nextPendingIndex = 0;

    /** @var \Iterator|null */
    private $iterable;

    /** @var callable|int|null */
    private $concurrency;

    /** @var callable|null */
    private $onFulfilled;

    /** @var callable|null */
    private $onRejected;

    /** @var Promise|null */
    private $aggregate;

    /** @var bool|null */
    private $mutex;

    /**
     * Configuration hash can include the following key value pairs:
     *
     * - fulfilled: (callable) Invoked when a promise fulfills. The function
     *   is invoked with three arguments: the fulfillment value, the index
     *   position from the iterable list of the promise, and the aggregate
     *   promise that manages all of the promises. The aggregate promise may
     *   be resolved from within the callback to short-circuit the promise.
     * - rejected: (callable) Invoked when a promise is rejected. The
     *   function is invoked with three arguments: the rejection reason, the
     *   index position from the iterable list of the promise, and the
     *   aggregate promise that manages all of the promises. The aggregate
     *   promise may be resolved from within the callback to short-circuit
     *   the promise.
     * - concurrency: (integer) Pass this configuration option to limit the
     *   allowed number of outstanding concurrently executing promises,
     *   creating a capped pool of promises. There is no limit by default.
     *
     * @param mixed $iterable Promises or values to iterate.
     * @param array $config   Configuration options
     */
    public function __construct($iterable, array $config = [])
    {
        $this->iterable = Create::iterFor($iterable);

        if (isset($config['concurrency'])) {
            $this->concurrency = $config['concurrency'];
        }

        if (isset($config['fulfilled'])) {
            $this->onFulfilled = $config['fulfilled'];
        }

        if (isset($config['rejected'])) {
            $this->onRejected = $config['rejected'];
        }
    }

    /** @psalm-suppress InvalidNullableReturnType */
    public function promise(): PromiseInterface
    {
        if ($this->aggregate) {
            return $this->aggregate;
        }

        try {
            $this->createPromise();
            /** @psalm-assert Promise $this->aggregate */
            $this->iterable->rewind();
            $this->refillPending();
        } catch (\Throwable $e) {
            $this->aggregate->reject($e);
        }

        /**
         * @psalm-suppress NullableReturnStatement
         */
        return $this->aggregate;
    }

    private function createPromise(): void
    {
        $this->mutex = false;
        $this->aggregate = new Promise(function (): void {
            if ($this->checkIfFinished()) {
                return;
            }
            reset($this->pending);
            // Consume a potentially fluctuating list of promises while
            // ensuring that indexes are maintained (precluding array_shift).
            while ($promise = current($this->pending)) {
                next($this->pending);
                $promise->wait();
                if (Is::settled($this->aggregate)) {
                    return;
                }
            }
        });

        // Clear the references when the promise is resolved.
        $clearFn = function (): void {
            $this->iterable = $this->concurrency = $this->pending = null;
            $this->onFulfilled = $this->onRejected = null;
            $this->nextPendingIndex = 0;
        };

        $this->aggregate->then($clearFn, $clearFn);
    }

    private function refillPending(): void
    {
        if (!$this->concurrency) {
            // Add all pending promises.
            while ($this->addPending() && $this->advanceIterator()) {
            }

            return;
        }

        // Add only up to N pending promises.
        $concurrency = is_callable($this->concurrency)
            ? ($this->concurrency)(count($this->pending))
            : $this->concurrency;
        $concurrency = max($concurrency - count($this->pending), 0);
        // Concurrency may be set to 0 to disallow new promises.
        if (!$concurrency) {
            return;
        }
        // Add the first pending promise.
        $this->addPending();
        // Note this is special handling for concurrency=1 so that we do
        // not advance the iterator after adding the first promise. This
        // helps work around issues with generators that might not have the
        // next value to yield until promise callbacks are called.
        while (--$concurrency
            && $this->advanceIterator()
            && $this->addPending()) {
        }
    }

    private function addPending(): bool
    {
        if (!$this->iterable || !$this->iterable->valid()) {
            return false;
        }

        $promise = Create::promiseFor($this->iterable->current());
        $key = $this->iterable->key();

        // Iterable keys may not be unique, so we use a counter to
        // guarantee uniqueness
        $idx = $this->nextPendingIndex++;

        $this->pending[$idx] = $promise->then(
            function ($value) use ($idx, $key): void {
                if ($this->onFulfilled) {
                    ($this->onFulfilled)(
                        $value,
                        $key,
                        $this->aggregate
                    );
                }
                $this->step($idx);
            },
            function ($reason) use ($idx, $key): void {
                if ($this->onRejected) {
                    ($this->onRejected)(
                        $reason,
                        $key,
                        $this->aggregate
                    );
                }
                $this->step($idx);
            }
        );

        return true;
    }

    private function advanceIterator(): bool
    {
        // Place a lock on the iterator so that we ensure to not recurse,
        // preventing fatal generator errors.
        if ($this->mutex) {
            return false;
        }

        $this->mutex = true;

        try {
            $this->iterable->next();
            $this->mutex = false;

            return true;
        } catch (\Throwable $e) {
            $this->aggregate->reject($e);
            $this->mutex = false;

            return false;
        }
    }

    private function step(int $idx): void
    {
        // If the promise was already resolved, then ignore this step.
        if (Is::settled($this->aggregate)) {
            return;
        }

        unset($this->pending[$idx]);

        // Only refill pending promises if we are not locked, preventing the
        // EachPromise to recursively invoke the provided iterator, which
        // cause a fatal error: "Cannot resume an already running generator"
        if ($this->advanceIterator() && !$this->checkIfFinished()) {
            // Add more pending promises if possible.
            $this->refillPending();
        }
    }

    private function checkIfFinished(): bool
    {
        if (!$this->pending && !$this->iterable->valid()) {
            // Resolve the promise if there's nothing left to do.
            $this->aggregate->resolve(null);

            return true;
        }

        return false;
    }
}
PK�c�\R���"guzzlehttp/promises/src/Create.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise;

final class Create
{
    /**
     * Creates a promise for a value if the value is not a promise.
     *
     * @param mixed $value Promise or value.
     */
    public static function promiseFor($value): PromiseInterface
    {
        if ($value instanceof PromiseInterface) {
            return $value;
        }

        // Return a Guzzle promise that shadows the given promise.
        if (is_object($value) && method_exists($value, 'then')) {
            $wfn = method_exists($value, 'wait') ? [$value, 'wait'] : null;
            $cfn = method_exists($value, 'cancel') ? [$value, 'cancel'] : null;
            $promise = new Promise($wfn, $cfn);
            $value->then([$promise, 'resolve'], [$promise, 'reject']);

            return $promise;
        }

        return new FulfilledPromise($value);
    }

    /**
     * Creates a rejected promise for a reason if the reason is not a promise.
     * If the provided reason is a promise, then it is returned as-is.
     *
     * @param mixed $reason Promise or reason.
     */
    public static function rejectionFor($reason): PromiseInterface
    {
        if ($reason instanceof PromiseInterface) {
            return $reason;
        }

        return new RejectedPromise($reason);
    }

    /**
     * Create an exception for a rejected promise value.
     *
     * @param mixed $reason
     */
    public static function exceptionFor($reason): \Throwable
    {
        if ($reason instanceof \Throwable) {
            return $reason;
        }

        return new RejectionException($reason);
    }

    /**
     * Returns an iterator for the given value.
     *
     * @param mixed $value
     */
    public static function iterFor($value): \Iterator
    {
        if ($value instanceof \Iterator) {
            return $value;
        }

        if (is_array($value)) {
            return new \ArrayIterator($value);
        }

        return new \ArrayIterator([$value]);
    }
}
PK�c�\4g���%guzzlehttp/promises/src/TaskQueue.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise;

/**
 * A task queue that executes tasks in a FIFO order.
 *
 * This task queue class is used to settle promises asynchronously and
 * maintains a constant stack size. You can use the task queue asynchronously
 * by calling the `run()` function of the global task queue in an event loop.
 *
 *     GuzzleHttp\Promise\Utils::queue()->run();
 *
 * @final
 */
class TaskQueue implements TaskQueueInterface
{
    private $enableShutdown = true;
    private $queue = [];

    public function __construct(bool $withShutdown = true)
    {
        if ($withShutdown) {
            register_shutdown_function(function (): void {
                if ($this->enableShutdown) {
                    // Only run the tasks if an E_ERROR didn't occur.
                    $err = error_get_last();
                    if (!$err || ($err['type'] ^ E_ERROR)) {
                        $this->run();
                    }
                }
            });
        }
    }

    public function isEmpty(): bool
    {
        return !$this->queue;
    }

    public function add(callable $task): void
    {
        $this->queue[] = $task;
    }

    public function run(): void
    {
        while ($task = array_shift($this->queue)) {
            /** @var callable $task */
            $task();
        }
    }

    /**
     * The task queue will be run and exhausted by default when the process
     * exits IFF the exit is not the result of a PHP E_ERROR error.
     *
     * You can disable running the automatic shutdown of the queue by calling
     * this function. If you disable the task queue shutdown process, then you
     * MUST either run the task queue (as a result of running your event loop
     * or manually using the run() method) or wait on each outstanding promise.
     *
     * Note: This shutdown will occur before any destructors are triggered.
     */
    public function disableShutdown(): void
    {
        $this->enableShutdown = false;
    }
}
PK�c�\�b�{��.guzzlehttp/promises/src/AggregateException.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise;

/**
 * Exception thrown when too many errors occur in the some() or any() methods.
 */
class AggregateException extends RejectionException
{
    public function __construct(string $msg, array $reasons)
    {
        parent::__construct(
            $reasons,
            sprintf('%s; %d rejected promises', $msg, count($reasons))
        );
    }
}
PK�c�\NtD��"�"#guzzlehttp/promises/src/Promise.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise;

/**
 * Promises/A+ implementation that avoids recursion when possible.
 *
 * @see https://promisesaplus.com/
 *
 * @final
 */
class Promise implements PromiseInterface
{
    private $state = self::PENDING;
    private $result;
    private $cancelFn;
    private $waitFn;
    private $waitList;
    private $handlers = [];

    /**
     * @param callable $waitFn   Fn that when invoked resolves the promise.
     * @param callable $cancelFn Fn that when invoked cancels the promise.
     */
    public function __construct(
        callable $waitFn = null,
        callable $cancelFn = null
    ) {
        $this->waitFn = $waitFn;
        $this->cancelFn = $cancelFn;
    }

    public function then(
        callable $onFulfilled = null,
        callable $onRejected = null
    ): PromiseInterface {
        if ($this->state === self::PENDING) {
            $p = new Promise(null, [$this, 'cancel']);
            $this->handlers[] = [$p, $onFulfilled, $onRejected];
            $p->waitList = $this->waitList;
            $p->waitList[] = $this;

            return $p;
        }

        // Return a fulfilled promise and immediately invoke any callbacks.
        if ($this->state === self::FULFILLED) {
            $promise = Create::promiseFor($this->result);

            return $onFulfilled ? $promise->then($onFulfilled) : $promise;
        }

        // It's either cancelled or rejected, so return a rejected promise
        // and immediately invoke any callbacks.
        $rejection = Create::rejectionFor($this->result);

        return $onRejected ? $rejection->then(null, $onRejected) : $rejection;
    }

    public function otherwise(callable $onRejected): PromiseInterface
    {
        return $this->then(null, $onRejected);
    }

    public function wait(bool $unwrap = true)
    {
        $this->waitIfPending();

        if ($this->result instanceof PromiseInterface) {
            return $this->result->wait($unwrap);
        }
        if ($unwrap) {
            if ($this->state === self::FULFILLED) {
                return $this->result;
            }
            // It's rejected so "unwrap" and throw an exception.
            throw Create::exceptionFor($this->result);
        }
    }

    public function getState(): string
    {
        return $this->state;
    }

    public function cancel(): void
    {
        if ($this->state !== self::PENDING) {
            return;
        }

        $this->waitFn = $this->waitList = null;

        if ($this->cancelFn) {
            $fn = $this->cancelFn;
            $this->cancelFn = null;
            try {
                $fn();
            } catch (\Throwable $e) {
                $this->reject($e);
            }
        }

        // Reject the promise only if it wasn't rejected in a then callback.
        /** @psalm-suppress RedundantCondition */
        if ($this->state === self::PENDING) {
            $this->reject(new CancellationException('Promise has been cancelled'));
        }
    }

    public function resolve($value): void
    {
        $this->settle(self::FULFILLED, $value);
    }

    public function reject($reason): void
    {
        $this->settle(self::REJECTED, $reason);
    }

    private function settle(string $state, $value): void
    {
        if ($this->state !== self::PENDING) {
            // Ignore calls with the same resolution.
            if ($state === $this->state && $value === $this->result) {
                return;
            }
            throw $this->state === $state
                ? new \LogicException("The promise is already {$state}.")
                : new \LogicException("Cannot change a {$this->state} promise to {$state}");
        }

        if ($value === $this) {
            throw new \LogicException('Cannot fulfill or reject a promise with itself');
        }

        // Clear out the state of the promise but stash the handlers.
        $this->state = $state;
        $this->result = $value;
        $handlers = $this->handlers;
        $this->handlers = null;
        $this->waitList = $this->waitFn = null;
        $this->cancelFn = null;

        if (!$handlers) {
            return;
        }

        // If the value was not a settled promise or a thenable, then resolve
        // it in the task queue using the correct ID.
        if (!is_object($value) || !method_exists($value, 'then')) {
            $id = $state === self::FULFILLED ? 1 : 2;
            // It's a success, so resolve the handlers in the queue.
            Utils::queue()->add(static function () use ($id, $value, $handlers): void {
                foreach ($handlers as $handler) {
                    self::callHandler($id, $value, $handler);
                }
            });
        } elseif ($value instanceof Promise && Is::pending($value)) {
            // We can just merge our handlers onto the next promise.
            $value->handlers = array_merge($value->handlers, $handlers);
        } else {
            // Resolve the handlers when the forwarded promise is resolved.
            $value->then(
                static function ($value) use ($handlers): void {
                    foreach ($handlers as $handler) {
                        self::callHandler(1, $value, $handler);
                    }
                },
                static function ($reason) use ($handlers): void {
                    foreach ($handlers as $handler) {
                        self::callHandler(2, $reason, $handler);
                    }
                }
            );
        }
    }

    /**
     * Call a stack of handlers using a specific callback index and value.
     *
     * @param int   $index   1 (resolve) or 2 (reject).
     * @param mixed $value   Value to pass to the callback.
     * @param array $handler Array of handler data (promise and callbacks).
     */
    private static function callHandler(int $index, $value, array $handler): void
    {
        /** @var PromiseInterface $promise */
        $promise = $handler[0];

        // The promise may have been cancelled or resolved before placing
        // this thunk in the queue.
        if (Is::settled($promise)) {
            return;
        }

        try {
            if (isset($handler[$index])) {
                /*
                 * If $f throws an exception, then $handler will be in the exception
                 * stack trace. Since $handler contains a reference to the callable
                 * itself we get a circular reference. We clear the $handler
                 * here to avoid that memory leak.
                 */
                $f = $handler[$index];
                unset($handler);
                $promise->resolve($f($value));
            } elseif ($index === 1) {
                // Forward resolution values as-is.
                $promise->resolve($value);
            } else {
                // Forward rejections down the chain.
                $promise->reject($value);
            }
        } catch (\Throwable $reason) {
            $promise->reject($reason);
        }
    }

    private function waitIfPending(): void
    {
        if ($this->state !== self::PENDING) {
            return;
        } elseif ($this->waitFn) {
            $this->invokeWaitFn();
        } elseif ($this->waitList) {
            $this->invokeWaitList();
        } else {
            // If there's no wait function, then reject the promise.
            $this->reject('Cannot wait on a promise that has '
                .'no internal wait function. You must provide a wait '
                .'function when constructing the promise to be able to '
                .'wait on a promise.');
        }

        Utils::queue()->run();

        /** @psalm-suppress RedundantCondition */
        if ($this->state === self::PENDING) {
            $this->reject('Invoking the wait callback did not resolve the promise');
        }
    }

    private function invokeWaitFn(): void
    {
        try {
            $wfn = $this->waitFn;
            $this->waitFn = null;
            $wfn(true);
        } catch (\Throwable $reason) {
            if ($this->state === self::PENDING) {
                // The promise has not been resolved yet, so reject the promise
                // with the exception.
                $this->reject($reason);
            } else {
                // The promise was already resolved, so there's a problem in
                // the application.
                throw $reason;
            }
        }
    }

    private function invokeWaitList(): void
    {
        $waitList = $this->waitList;
        $this->waitList = null;

        foreach ($waitList as $result) {
            do {
                $result->waitIfPending();
                $result = $result->result;
            } while ($result instanceof Promise);

            if ($result instanceof PromiseInterface) {
                $result->wait(false);
            }
        }
    }
}
PK�c�\逿�,guzzlehttp/promises/src/PromiseInterface.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise;

/**
 * A promise represents the eventual result of an asynchronous operation.
 *
 * The primary way of interacting with a promise is through its then method,
 * which registers callbacks to receive either a promise’s eventual value or
 * the reason why the promise cannot be fulfilled.
 *
 * @see https://promisesaplus.com/
 */
interface PromiseInterface
{
    public const PENDING = 'pending';
    public const FULFILLED = 'fulfilled';
    public const REJECTED = 'rejected';

    /**
     * Appends fulfillment and rejection handlers to the promise, and returns
     * a new promise resolving to the return value of the called handler.
     *
     * @param callable $onFulfilled Invoked when the promise fulfills.
     * @param callable $onRejected  Invoked when the promise is rejected.
     */
    public function then(
        callable $onFulfilled = null,
        callable $onRejected = null
    ): PromiseInterface;

    /**
     * Appends a rejection handler callback to the promise, and returns a new
     * promise resolving to the return value of the callback if it is called,
     * or to its original fulfillment value if the promise is instead
     * fulfilled.
     *
     * @param callable $onRejected Invoked when the promise is rejected.
     */
    public function otherwise(callable $onRejected): PromiseInterface;

    /**
     * Get the state of the promise ("pending", "rejected", or "fulfilled").
     *
     * The three states can be checked against the constants defined on
     * PromiseInterface: PENDING, FULFILLED, and REJECTED.
     */
    public function getState(): string;

    /**
     * Resolve the promise with the given value.
     *
     * @param mixed $value
     *
     * @throws \RuntimeException if the promise is already resolved.
     */
    public function resolve($value): void;

    /**
     * Reject the promise with the given reason.
     *
     * @param mixed $reason
     *
     * @throws \RuntimeException if the promise is already resolved.
     */
    public function reject($reason): void;

    /**
     * Cancels the promise if possible.
     *
     * @see https://github.com/promises-aplus/cancellation-spec/issues/7
     */
    public function cancel(): void;

    /**
     * Waits until the promise completes if possible.
     *
     * Pass $unwrap as true to unwrap the result of the promise, either
     * returning the resolved value or throwing the rejected exception.
     *
     * If the promise cannot be waited on, then the promise will be rejected.
     *
     * @return mixed
     *
     * @throws \LogicException if the promise has no wait function or if the
     *                         promise does not settle after waiting.
     */
    public function wait(bool $unwrap = true);
}
PK�c�\��Lt��1guzzlehttp/promises/src/CancellationException.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise;

/**
 * Exception that is set as the reason for a promise that has been cancelled.
 */
class CancellationException extends RejectionException
{
}
PK�c�\G�y���.guzzlehttp/promises/src/RejectionException.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise;

/**
 * A special exception that is thrown when waiting on a rejected promise.
 *
 * The reason value is available via the getReason() method.
 */
class RejectionException extends \RuntimeException
{
    /** @var mixed Rejection reason. */
    private $reason;

    /**
     * @param mixed       $reason      Rejection reason.
     * @param string|null $description Optional description.
     */
    public function __construct($reason, string $description = null)
    {
        $this->reason = $reason;

        $message = 'The promise was rejected';

        if ($description) {
            $message .= ' with reason: '.$description;
        } elseif (is_string($reason)
            || (is_object($reason) && method_exists($reason, '__toString'))
        ) {
            $message .= ' with reason: '.$this->reason;
        } elseif ($reason instanceof \JsonSerializable) {
            $message .= ' with reason: '.json_encode($this->reason, JSON_PRETTY_PRINT);
        }

        parent::__construct($message);
    }

    /**
     * Returns the rejection reason.
     *
     * @return mixed
     */
    public function getReason()
    {
        return $this->reason;
    }
}
PK�c�\'�
c��,guzzlehttp/promises/src/FulfilledPromise.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise;

/**
 * A promise that has been fulfilled.
 *
 * Thenning off of this promise will invoke the onFulfilled callback
 * immediately and ignore other callbacks.
 *
 * @final
 */
class FulfilledPromise implements PromiseInterface
{
    private $value;

    /**
     * @param mixed $value
     */
    public function __construct($value)
    {
        if (is_object($value) && method_exists($value, 'then')) {
            throw new \InvalidArgumentException(
                'You cannot create a FulfilledPromise with a promise.'
            );
        }

        $this->value = $value;
    }

    public function then(
        callable $onFulfilled = null,
        callable $onRejected = null
    ): PromiseInterface {
        // Return itself if there is no onFulfilled function.
        if (!$onFulfilled) {
            return $this;
        }

        $queue = Utils::queue();
        $p = new Promise([$queue, 'run']);
        $value = $this->value;
        $queue->add(static function () use ($p, $value, $onFulfilled): void {
            if (Is::pending($p)) {
                try {
                    $p->resolve($onFulfilled($value));
                } catch (\Throwable $e) {
                    $p->reject($e);
                }
            }
        });

        return $p;
    }

    public function otherwise(callable $onRejected): PromiseInterface
    {
        return $this->then(null, $onRejected);
    }

    public function wait(bool $unwrap = true)
    {
        return $unwrap ? $this->value : null;
    }

    public function getState(): string
    {
        return self::FULFILLED;
    }

    public function resolve($value): void
    {
        if ($value !== $this->value) {
            throw new \LogicException('Cannot resolve a fulfilled promise');
        }
    }

    public function reject($reason): void
    {
        throw new \LogicException('Cannot reject a fulfilled promise');
    }

    public function cancel(): void
    {
        // pass
    }
}
PK�c�\0m���guzzlehttp/promises/src/Is.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise;

final class Is
{
    /**
     * Returns true if a promise is pending.
     */
    public static function pending(PromiseInterface $promise): bool
    {
        return $promise->getState() === PromiseInterface::PENDING;
    }

    /**
     * Returns true if a promise is fulfilled or rejected.
     */
    public static function settled(PromiseInterface $promise): bool
    {
        return $promise->getState() !== PromiseInterface::PENDING;
    }

    /**
     * Returns true if a promise is fulfilled.
     */
    public static function fulfilled(PromiseInterface $promise): bool
    {
        return $promise->getState() === PromiseInterface::FULFILLED;
    }

    /**
     * Returns true if a promise is rejected.
     */
    public static function rejected(PromiseInterface $promise): bool
    {
        return $promise->getState() === PromiseInterface::REJECTED;
    }
}
PK�c�\W��PAA%guzzlehttp/promises/src/Coroutine.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise;

use Generator;
use Throwable;

/**
 * Creates a promise that is resolved using a generator that yields values or
 * promises (somewhat similar to C#'s async keyword).
 *
 * When called, the Coroutine::of method will start an instance of the generator
 * and returns a promise that is fulfilled with its final yielded value.
 *
 * Control is returned back to the generator when the yielded promise settles.
 * This can lead to less verbose code when doing lots of sequential async calls
 * with minimal processing in between.
 *
 *     use GuzzleHttp\Promise;
 *
 *     function createPromise($value) {
 *         return new Promise\FulfilledPromise($value);
 *     }
 *
 *     $promise = Promise\Coroutine::of(function () {
 *         $value = (yield createPromise('a'));
 *         try {
 *             $value = (yield createPromise($value . 'b'));
 *         } catch (\Throwable $e) {
 *             // The promise was rejected.
 *         }
 *         yield $value . 'c';
 *     });
 *
 *     // Outputs "abc"
 *     $promise->then(function ($v) { echo $v; });
 *
 * @param callable $generatorFn Generator function to wrap into a promise.
 *
 * @return Promise
 *
 * @see https://github.com/petkaantonov/bluebird/blob/master/API.md#generators inspiration
 */
final class Coroutine implements PromiseInterface
{
    /**
     * @var PromiseInterface|null
     */
    private $currentPromise;

    /**
     * @var Generator
     */
    private $generator;

    /**
     * @var Promise
     */
    private $result;

    public function __construct(callable $generatorFn)
    {
        $this->generator = $generatorFn();
        $this->result = new Promise(function (): void {
            while (isset($this->currentPromise)) {
                $this->currentPromise->wait();
            }
        });
        try {
            $this->nextCoroutine($this->generator->current());
        } catch (Throwable $throwable) {
            $this->result->reject($throwable);
        }
    }

    /**
     * Create a new coroutine.
     */
    public static function of(callable $generatorFn): self
    {
        return new self($generatorFn);
    }

    public function then(
        callable $onFulfilled = null,
        callable $onRejected = null
    ): PromiseInterface {
        return $this->result->then($onFulfilled, $onRejected);
    }

    public function otherwise(callable $onRejected): PromiseInterface
    {
        return $this->result->otherwise($onRejected);
    }

    public function wait(bool $unwrap = true)
    {
        return $this->result->wait($unwrap);
    }

    public function getState(): string
    {
        return $this->result->getState();
    }

    public function resolve($value): void
    {
        $this->result->resolve($value);
    }

    public function reject($reason): void
    {
        $this->result->reject($reason);
    }

    public function cancel(): void
    {
        $this->currentPromise->cancel();
        $this->result->cancel();
    }

    private function nextCoroutine($yielded): void
    {
        $this->currentPromise = Create::promiseFor($yielded)
            ->then([$this, '_handleSuccess'], [$this, '_handleFailure']);
    }

    /**
     * @internal
     */
    public function _handleSuccess($value): void
    {
        unset($this->currentPromise);
        try {
            $next = $this->generator->send($value);
            if ($this->generator->valid()) {
                $this->nextCoroutine($next);
            } else {
                $this->result->resolve($value);
            }
        } catch (Throwable $throwable) {
            $this->result->reject($throwable);
        }
    }

    /**
     * @internal
     */
    public function _handleFailure($reason): void
    {
        unset($this->currentPromise);
        try {
            $nextYield = $this->generator->throw(Create::exceptionFor($reason));
            // The throw was caught, so keep iterating on the coroutine
            $this->nextCoroutine($nextYield);
        } catch (Throwable $throwable) {
            $this->result->reject($throwable);
        }
    }
}
PK�c�\V%�c
c
 guzzlehttp/promises/src/Each.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise;

final class Each
{
    /**
     * Given an iterator that yields promises or values, returns a promise that
     * is fulfilled with a null value when the iterator has been consumed or
     * the aggregate promise has been fulfilled or rejected.
     *
     * $onFulfilled is a function that accepts the fulfilled value, iterator
     * index, and the aggregate promise. The callback can invoke any necessary
     * side effects and choose to resolve or reject the aggregate if needed.
     *
     * $onRejected is a function that accepts the rejection reason, iterator
     * index, and the aggregate promise. The callback can invoke any necessary
     * side effects and choose to resolve or reject the aggregate if needed.
     *
     * @param mixed $iterable Iterator or array to iterate over.
     */
    public static function of(
        $iterable,
        callable $onFulfilled = null,
        callable $onRejected = null
    ): PromiseInterface {
        return (new EachPromise($iterable, [
            'fulfilled' => $onFulfilled,
            'rejected' => $onRejected,
        ]))->promise();
    }

    /**
     * Like of, but only allows a certain number of outstanding promises at any
     * given time.
     *
     * $concurrency may be an integer or a function that accepts the number of
     * pending promises and returns a numeric concurrency limit value to allow
     * for dynamic a concurrency size.
     *
     * @param mixed        $iterable
     * @param int|callable $concurrency
     */
    public static function ofLimit(
        $iterable,
        $concurrency,
        callable $onFulfilled = null,
        callable $onRejected = null
    ): PromiseInterface {
        return (new EachPromise($iterable, [
            'fulfilled' => $onFulfilled,
            'rejected' => $onRejected,
            'concurrency' => $concurrency,
        ]))->promise();
    }

    /**
     * Like limit, but ensures that no promise in the given $iterable argument
     * is rejected. If any promise is rejected, then the aggregate promise is
     * rejected with the encountered rejection.
     *
     * @param mixed        $iterable
     * @param int|callable $concurrency
     */
    public static function ofLimitAll(
        $iterable,
        $concurrency,
        callable $onFulfilled = null
    ): PromiseInterface {
        return self::ofLimit(
            $iterable,
            $concurrency,
            $onFulfilled,
            function ($reason, $idx, PromiseInterface $aggregate): void {
                $aggregate->reject($reason);
            }
        );
    }
}
PK�c�\��d���-guzzlehttp/promises/src/PromisorInterface.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise;

/**
 * Interface used with classes that return a promise.
 */
interface PromisorInterface
{
    /**
     * Returns a promise.
     */
    public function promise(): PromiseInterface;
}
PK�c�\�&��9guzzlehttp/promises/vendor-bin/php-cs-fixer/composer.jsonnu�[���{
    "require": {
        "php": "^7.4 || ^8.0",
        "friendsofphp/php-cs-fixer": "3.16.0"
    },
    "config": {
        "preferred-install": "dist"
    }
}
PK�c�\(��M��2guzzlehttp/promises/vendor-bin/psalm/composer.jsonnu�[���{
    "require": {
        "php": "^7.4 || ^8.0",
        "psalm/phar": "5.9.0"
    },
    "config": {
        "preferred-install": "dist"
    }
}
PK�c�\�&��4guzzlehttp/promises/vendor-bin/phpstan/composer.jsonnu�[���{
    "require": {
        "php": "^7.4 || ^8.0",
        "phpstan/phpstan": "1.10.11",
        "phpstan/phpstan-deprecation-rules": "1.1.3"
    },
    "config": {
        "preferred-install": "dist"
    }
}
PK�c�\:���!guzzlehttp/promises/composer.jsonnu�[���{
    "name": "guzzlehttp/promises",
    "description": "Guzzle promises library",
    "keywords": ["promise"],
    "license": "MIT",
    "authors": [
        {
            "name": "Graham Campbell",
            "email": "hello@gjcampbell.co.uk",
            "homepage": "https://github.com/GrahamCampbell"
        },
        {
            "name": "Michael Dowling",
            "email": "mtdowling@gmail.com",
            "homepage": "https://github.com/mtdowling"
        },
        {
            "name": "Tobias Nyholm",
            "email": "tobias.nyholm@gmail.com",
            "homepage": "https://github.com/Nyholm"
        },
        {
            "name": "Tobias Schultze",
            "email": "webmaster@tubo-world.de",
            "homepage": "https://github.com/Tobion"
        }
    ],
    "require": {
        "php": "^7.2.5 || ^8.0"
    },
    "require-dev": {
        "bamarni/composer-bin-plugin": "^1.8.2",
        "phpunit/phpunit": "^8.5.36 || ^9.6.15"
    },
    "autoload": {
        "psr-4": {
            "GuzzleHttp\\Promise\\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "GuzzleHttp\\Promise\\Tests\\": "tests/"
        }
    },
    "extra": {
        "bamarni-bin": {
            "bin-links": true,
            "forward-command": false
        }
    },
    "config": {
        "allow-plugins": {
            "bamarni/composer-bin-plugin": true
        },
        "preferred-install": "dist",
        "sort-packages": true
    }
}
PK�c�\{Z�y	y	 guzzlehttp/promises/CHANGELOG.mdnu�[���# CHANGELOG


## 2.0.2 - 2023-12-03

### Changed

- Replaced `call_user_func*` with native calls


## 2.0.1 - 2023-08-03

### Changed

- PHP 8.3 support


## 2.0.0 - 2023-05-21

### Added

- Added PHP 7 type hints

### Changed

- All previously non-final non-exception classes have been marked as soft-final

### Removed

- Dropped PHP < 7.2 support
- All functions in the `GuzzleHttp\Promise` namespace


## 1.5.3 - 2023-05-21

### Changed

- Removed remaining usage of deprecated functions


## 1.5.2 - 2022-08-07

### Changed

- Officially support PHP 8.2


## 1.5.1 - 2021-10-22

### Fixed

- Revert "Call handler when waiting on fulfilled/rejected Promise"
- Fix pool memory leak when empty array of promises provided


## 1.5.0 - 2021-10-07

### Changed

- Call handler when waiting on fulfilled/rejected Promise
- Officially support PHP 8.1

### Fixed

- Fix manually settle promises generated with `Utils::task`


## 1.4.1 - 2021-02-18

### Fixed

- Fixed `each_limit` skipping promises and failing


## 1.4.0 - 2020-09-30

### Added

- Support for PHP 8
- Optional `$recursive` flag to `all`
- Replaced functions by static methods

### Fixed

- Fix empty `each` processing
- Fix promise handling for Iterators of non-unique keys
- Fixed `method_exists` crashes on PHP 8
- Memory leak on exceptions


## 1.3.1 - 2016-12-20

### Fixed

- `wait()` foreign promise compatibility


## 1.3.0 - 2016-11-18

### Added

- Adds support for custom task queues.

### Fixed

- Fixed coroutine promise memory leak.


## 1.2.0 - 2016-05-18

### Changed

- Update to now catch `\Throwable` on PHP 7+


## 1.1.0 - 2016-03-07

### Changed

- Update EachPromise to prevent recurring on a iterator when advancing, as this
  could trigger fatal generator errors.
- Update Promise to allow recursive waiting without unwrapping exceptions.


## 1.0.3 - 2015-10-15

### Changed

- Update EachPromise to immediately resolve when the underlying promise iterator
  is empty. Previously, such a promise would throw an exception when its `wait`
  function was called.


## 1.0.2 - 2015-05-15

### Changed

- Conditionally require functions.php.


## 1.0.1 - 2015-06-24

### Changed

- Updating EachPromise to call next on the underlying promise iterator as late
  as possible to ensure that generators that generate new requests based on
  callbacks are not iterated until after callbacks are invoked.


## 1.0.0 - 2015-05-12

- Initial release
PK�c�\z�*/guzzlehttp/promises/LICENSEnu�[���The MIT License (MIT)

Copyright (c) 2015 Michael Dowling <mtdowling@gmail.com>
Copyright (c) 2015 Graham Campbell <hello@gjcampbell.co.uk>
Copyright (c) 2017 Tobias Schultze <webmaster@tubo-world.de>
Copyright (c) 2020 Tobias Nyholm <tobias.nyholm@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
PK�c�\e�'�D�Dguzzlehttp/promises/README.mdnu�[���# Guzzle Promises

[Promises/A+](https://promisesaplus.com/) implementation that handles promise
chaining and resolution iteratively, allowing for "infinite" promise chaining
while keeping the stack size constant. Read [this blog post](https://blog.domenic.me/youre-missing-the-point-of-promises/)
for a general introduction to promises.

- [Features](#features)
- [Quick start](#quick-start)
- [Synchronous wait](#synchronous-wait)
- [Cancellation](#cancellation)
- [API](#api)
  - [Promise](#promise)
  - [FulfilledPromise](#fulfilledpromise)
  - [RejectedPromise](#rejectedpromise)
- [Promise interop](#promise-interop)
- [Implementation notes](#implementation-notes)


## Features

- [Promises/A+](https://promisesaplus.com/) implementation.
- Promise resolution and chaining is handled iteratively, allowing for
  "infinite" promise chaining.
- Promises have a synchronous `wait` method.
- Promises can be cancelled.
- Works with any object that has a `then` function.
- C# style async/await coroutine promises using
  `GuzzleHttp\Promise\Coroutine::of()`.


## Installation

```shell
composer require guzzlehttp/promises
```


## Version Guidance

| Version | Status                 | PHP Version  |
|---------|------------------------|--------------|
| 1.x     | Bug and security fixes | >=5.5,<8.3   |
| 2.x     | Latest                 | >=7.2.5,<8.4 |


## Quick Start

A *promise* represents the eventual result of an asynchronous operation. The
primary way of interacting with a promise is through its `then` method, which
registers callbacks to receive either a promise's eventual value or the reason
why the promise cannot be fulfilled.

### Callbacks

Callbacks are registered with the `then` method by providing an optional 
`$onFulfilled` followed by an optional `$onRejected` function.


```php
use GuzzleHttp\Promise\Promise;

$promise = new Promise();
$promise->then(
    // $onFulfilled
    function ($value) {
        echo 'The promise was fulfilled.';
    },
    // $onRejected
    function ($reason) {
        echo 'The promise was rejected.';
    }
);
```

*Resolving* a promise means that you either fulfill a promise with a *value* or
reject a promise with a *reason*. Resolving a promise triggers callbacks
registered with the promise's `then` method. These callbacks are triggered
only once and in the order in which they were added.

### Resolving a Promise

Promises are fulfilled using the `resolve($value)` method. Resolving a promise
with any value other than a `GuzzleHttp\Promise\RejectedPromise` will trigger
all of the onFulfilled callbacks (resolving a promise with a rejected promise
will reject the promise and trigger the `$onRejected` callbacks).

```php
use GuzzleHttp\Promise\Promise;

$promise = new Promise();
$promise
    ->then(function ($value) {
        // Return a value and don't break the chain
        return "Hello, " . $value;
    })
    // This then is executed after the first then and receives the value
    // returned from the first then.
    ->then(function ($value) {
        echo $value;
    });

// Resolving the promise triggers the $onFulfilled callbacks and outputs
// "Hello, reader."
$promise->resolve('reader.');
```

### Promise Forwarding

Promises can be chained one after the other. Each then in the chain is a new
promise. The return value of a promise is what's forwarded to the next
promise in the chain. Returning a promise in a `then` callback will cause the
subsequent promises in the chain to only be fulfilled when the returned promise
has been fulfilled. The next promise in the chain will be invoked with the
resolved value of the promise.

```php
use GuzzleHttp\Promise\Promise;

$promise = new Promise();
$nextPromise = new Promise();

$promise
    ->then(function ($value) use ($nextPromise) {
        echo $value;
        return $nextPromise;
    })
    ->then(function ($value) {
        echo $value;
    });

// Triggers the first callback and outputs "A"
$promise->resolve('A');
// Triggers the second callback and outputs "B"
$nextPromise->resolve('B');
```

### Promise Rejection

When a promise is rejected, the `$onRejected` callbacks are invoked with the
rejection reason.

```php
use GuzzleHttp\Promise\Promise;

$promise = new Promise();
$promise->then(null, function ($reason) {
    echo $reason;
});

$promise->reject('Error!');
// Outputs "Error!"
```

### Rejection Forwarding

If an exception is thrown in an `$onRejected` callback, subsequent
`$onRejected` callbacks are invoked with the thrown exception as the reason.

```php
use GuzzleHttp\Promise\Promise;

$promise = new Promise();
$promise->then(null, function ($reason) {
    throw new Exception($reason);
})->then(null, function ($reason) {
    assert($reason->getMessage() === 'Error!');
});

$promise->reject('Error!');
```

You can also forward a rejection down the promise chain by returning a
`GuzzleHttp\Promise\RejectedPromise` in either an `$onFulfilled` or
`$onRejected` callback.

```php
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\RejectedPromise;

$promise = new Promise();
$promise->then(null, function ($reason) {
    return new RejectedPromise($reason);
})->then(null, function ($reason) {
    assert($reason === 'Error!');
});

$promise->reject('Error!');
```

If an exception is not thrown in a `$onRejected` callback and the callback
does not return a rejected promise, downstream `$onFulfilled` callbacks are
invoked using the value returned from the `$onRejected` callback.

```php
use GuzzleHttp\Promise\Promise;

$promise = new Promise();
$promise
    ->then(null, function ($reason) {
        return "It's ok";
    })
    ->then(function ($value) {
        assert($value === "It's ok");
    });

$promise->reject('Error!');
```


## Synchronous Wait

You can synchronously force promises to complete using a promise's `wait`
method. When creating a promise, you can provide a wait function that is used
to synchronously force a promise to complete. When a wait function is invoked
it is expected to deliver a value to the promise or reject the promise. If the
wait function does not deliver a value, then an exception is thrown. The wait
function provided to a promise constructor is invoked when the `wait` function
of the promise is called.

```php
$promise = new Promise(function () use (&$promise) {
    $promise->resolve('foo');
});

// Calling wait will return the value of the promise.
echo $promise->wait(); // outputs "foo"
```

If an exception is encountered while invoking the wait function of a promise,
the promise is rejected with the exception and the exception is thrown.

```php
$promise = new Promise(function () use (&$promise) {
    throw new Exception('foo');
});

$promise->wait(); // throws the exception.
```

Calling `wait` on a promise that has been fulfilled will not trigger the wait
function. It will simply return the previously resolved value.

```php
$promise = new Promise(function () { die('this is not called!'); });
$promise->resolve('foo');
echo $promise->wait(); // outputs "foo"
```

Calling `wait` on a promise that has been rejected will throw an exception. If
the rejection reason is an instance of `\Exception` the reason is thrown.
Otherwise, a `GuzzleHttp\Promise\RejectionException` is thrown and the reason
can be obtained by calling the `getReason` method of the exception.

```php
$promise = new Promise();
$promise->reject('foo');
$promise->wait();
```

> PHP Fatal error:  Uncaught exception 'GuzzleHttp\Promise\RejectionException' with message 'The promise was rejected with value: foo'

### Unwrapping a Promise

When synchronously waiting on a promise, you are joining the state of the
promise into the current state of execution (i.e., return the value of the
promise if it was fulfilled or throw an exception if it was rejected). This is
called "unwrapping" the promise. Waiting on a promise will by default unwrap
the promise state.

You can force a promise to resolve and *not* unwrap the state of the promise
by passing `false` to the first argument of the `wait` function:

```php
$promise = new Promise();
$promise->reject('foo');
// This will not throw an exception. It simply ensures the promise has
// been resolved.
$promise->wait(false);
```

When unwrapping a promise, the resolved value of the promise will be waited
upon until the unwrapped value is not a promise. This means that if you resolve
promise A with a promise B and unwrap promise A, the value returned by the
wait function will be the value delivered to promise B.

**Note**: when you do not unwrap the promise, no value is returned.


## Cancellation

You can cancel a promise that has not yet been fulfilled using the `cancel()`
method of a promise. When creating a promise you can provide an optional
cancel function that when invoked cancels the action of computing a resolution
of the promise.


## API

### Promise

When creating a promise object, you can provide an optional `$waitFn` and
`$cancelFn`. `$waitFn` is a function that is invoked with no arguments and is
expected to resolve the promise. `$cancelFn` is a function with no arguments
that is expected to cancel the computation of a promise. It is invoked when the
`cancel()` method of a promise is called.

```php
use GuzzleHttp\Promise\Promise;

$promise = new Promise(
    function () use (&$promise) {
        $promise->resolve('waited');
    },
    function () {
        // do something that will cancel the promise computation (e.g., close
        // a socket, cancel a database query, etc...)
    }
);

assert('waited' === $promise->wait());
```

A promise has the following methods:

- `then(callable $onFulfilled, callable $onRejected) : PromiseInterface`
  
  Appends fulfillment and rejection handlers to the promise, and returns a new promise resolving to the return value of the called handler.

- `otherwise(callable $onRejected) : PromiseInterface`
  
  Appends a rejection handler callback to the promise, and returns a new promise resolving to the return value of the callback if it is called, or to its original fulfillment value if the promise is instead fulfilled.

- `wait($unwrap = true) : mixed`

  Synchronously waits on the promise to complete.
  
  `$unwrap` controls whether or not the value of the promise is returned for a
  fulfilled promise or if an exception is thrown if the promise is rejected.
  This is set to `true` by default.

- `cancel()`

  Attempts to cancel the promise if possible. The promise being cancelled and
  the parent most ancestor that has not yet been resolved will also be
  cancelled. Any promises waiting on the cancelled promise to resolve will also
  be cancelled.

- `getState() : string`

  Returns the state of the promise. One of `pending`, `fulfilled`, or
  `rejected`.

- `resolve($value)`

  Fulfills the promise with the given `$value`.

- `reject($reason)`

  Rejects the promise with the given `$reason`.


### FulfilledPromise

A fulfilled promise can be created to represent a promise that has been
fulfilled.

```php
use GuzzleHttp\Promise\FulfilledPromise;

$promise = new FulfilledPromise('value');

// Fulfilled callbacks are immediately invoked.
$promise->then(function ($value) {
    echo $value;
});
```


### RejectedPromise

A rejected promise can be created to represent a promise that has been
rejected.

```php
use GuzzleHttp\Promise\RejectedPromise;

$promise = new RejectedPromise('Error');

// Rejected callbacks are immediately invoked.
$promise->then(null, function ($reason) {
    echo $reason;
});
```


## Promise Interoperability

This library works with foreign promises that have a `then` method. This means
you can use Guzzle promises with [React promises](https://github.com/reactphp/promise)
for example. When a foreign promise is returned inside of a then method
callback, promise resolution will occur recursively.

```php
// Create a React promise
$deferred = new React\Promise\Deferred();
$reactPromise = $deferred->promise();

// Create a Guzzle promise that is fulfilled with a React promise.
$guzzlePromise = new GuzzleHttp\Promise\Promise();
$guzzlePromise->then(function ($value) use ($reactPromise) {
    // Do something something with the value...
    // Return the React promise
    return $reactPromise;
});
```

Please note that wait and cancel chaining is no longer possible when forwarding
a foreign promise. You will need to wrap a third-party promise with a Guzzle
promise in order to utilize wait and cancel functions with foreign promises.


### Event Loop Integration

In order to keep the stack size constant, Guzzle promises are resolved
asynchronously using a task queue. When waiting on promises synchronously, the
task queue will be automatically run to ensure that the blocking promise and
any forwarded promises are resolved. When using promises asynchronously in an
event loop, you will need to run the task queue on each tick of the loop. If
you do not run the task queue, then promises will not be resolved.

You can run the task queue using the `run()` method of the global task queue
instance.

```php
// Get the global task queue
$queue = GuzzleHttp\Promise\Utils::queue();
$queue->run();
```

For example, you could use Guzzle promises with React using a periodic timer:

```php
$loop = React\EventLoop\Factory::create();
$loop->addPeriodicTimer(0, [$queue, 'run']);
```


## Implementation Notes

### Promise Resolution and Chaining is Handled Iteratively

By shuffling pending handlers from one owner to another, promises are
resolved iteratively, allowing for "infinite" then chaining.

```php
<?php
require 'vendor/autoload.php';

use GuzzleHttp\Promise\Promise;

$parent = new Promise();
$p = $parent;

for ($i = 0; $i < 1000; $i++) {
    $p = $p->then(function ($v) {
        // The stack size remains constant (a good thing)
        echo xdebug_get_stack_depth() . ', ';
        return $v + 1;
    });
}

$parent->resolve(0);
var_dump($p->wait()); // int(1000)

```

When a promise is fulfilled or rejected with a non-promise value, the promise
then takes ownership of the handlers of each child promise and delivers values
down the chain without using recursion.

When a promise is resolved with another promise, the original promise transfers
all of its pending handlers to the new promise. When the new promise is
eventually resolved, all of the pending handlers are delivered the forwarded
value.

### A Promise is the Deferred

Some promise libraries implement promises using a deferred object to represent
a computation and a promise object to represent the delivery of the result of
the computation. This is a nice separation of computation and delivery because
consumers of the promise cannot modify the value that will be eventually
delivered.

One side effect of being able to implement promise resolution and chaining
iteratively is that you need to be able for one promise to reach into the state
of another promise to shuffle around ownership of handlers. In order to achieve
this without making the handlers of a promise publicly mutable, a promise is
also the deferred value, allowing promises of the same parent class to reach
into and modify the private properties of promises of the same type. While this
does allow consumers of the value to modify the resolution or rejection of the
deferred, it is a small price to pay for keeping the stack size constant.

```php
$promise = new Promise();
$promise->then(function ($value) { echo $value; });
// The promise is the deferred value, so you can deliver a value to it.
$promise->resolve('foo');
// prints "foo"
```


## Upgrading from Function API

A static API was first introduced in 1.4.0, in order to mitigate problems with
functions conflicting between global and local copies of the package. The
function API was removed in 2.0.0. A migration table has been provided here for
your convenience:

| Original Function | Replacement Method |
|----------------|----------------|
| `queue` | `Utils::queue` |
| `task` | `Utils::task` |
| `promise_for` | `Create::promiseFor` |
| `rejection_for` | `Create::rejectionFor` |
| `exception_for` | `Create::exceptionFor` |
| `iter_for` | `Create::iterFor` |
| `inspect` | `Utils::inspect` |
| `inspect_all` | `Utils::inspectAll` |
| `unwrap` | `Utils::unwrap` |
| `all` | `Utils::all` |
| `some` | `Utils::some` |
| `any` | `Utils::any` |
| `settle` | `Utils::settle` |
| `each` | `Each::of` |
| `each_limit` | `Each::ofLimit` |
| `each_limit_all` | `Each::ofLimitAll` |
| `!is_fulfilled` | `Is::pending` |
| `is_fulfilled` | `Is::fulfilled` |
| `is_rejected` | `Is::rejected` |
| `is_settled` | `Is::settled` |
| `coroutine` | `Coroutine::of` |


## Security

If you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see [Security Policy](https://github.com/guzzle/promises/security/policy) for more information.


## License

Guzzle is made available under the MIT License (MIT). Please see [License File](LICENSE) for more information.


## For Enterprise

Available as part of the Tidelift Subscription

The maintainers of Guzzle and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-guzzlehttp-promises?utm_source=packagist-guzzlehttp-promises&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)
PK�c�\���** guzzlehttp/psr7/src/Response.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Psr7;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;

/**
 * PSR-7 response implementation.
 */
class Response implements ResponseInterface
{
    use MessageTrait;

    /** Map of standard HTTP status code/reason phrases */
    private const PHRASES = [
        100 => 'Continue',
        101 => 'Switching Protocols',
        102 => 'Processing',
        200 => 'OK',
        201 => 'Created',
        202 => 'Accepted',
        203 => 'Non-Authoritative Information',
        204 => 'No Content',
        205 => 'Reset Content',
        206 => 'Partial Content',
        207 => 'Multi-status',
        208 => 'Already Reported',
        300 => 'Multiple Choices',
        301 => 'Moved Permanently',
        302 => 'Found',
        303 => 'See Other',
        304 => 'Not Modified',
        305 => 'Use Proxy',
        306 => 'Switch Proxy',
        307 => 'Temporary Redirect',
        308 => 'Permanent Redirect',
        400 => 'Bad Request',
        401 => 'Unauthorized',
        402 => 'Payment Required',
        403 => 'Forbidden',
        404 => 'Not Found',
        405 => 'Method Not Allowed',
        406 => 'Not Acceptable',
        407 => 'Proxy Authentication Required',
        408 => 'Request Time-out',
        409 => 'Conflict',
        410 => 'Gone',
        411 => 'Length Required',
        412 => 'Precondition Failed',
        413 => 'Request Entity Too Large',
        414 => 'Request-URI Too Large',
        415 => 'Unsupported Media Type',
        416 => 'Requested range not satisfiable',
        417 => 'Expectation Failed',
        418 => 'I\'m a teapot',
        422 => 'Unprocessable Entity',
        423 => 'Locked',
        424 => 'Failed Dependency',
        425 => 'Unordered Collection',
        426 => 'Upgrade Required',
        428 => 'Precondition Required',
        429 => 'Too Many Requests',
        431 => 'Request Header Fields Too Large',
        451 => 'Unavailable For Legal Reasons',
        500 => 'Internal Server Error',
        501 => 'Not Implemented',
        502 => 'Bad Gateway',
        503 => 'Service Unavailable',
        504 => 'Gateway Time-out',
        505 => 'HTTP Version not supported',
        506 => 'Variant Also Negotiates',
        507 => 'Insufficient Storage',
        508 => 'Loop Detected',
        510 => 'Not Extended',
        511 => 'Network Authentication Required',
    ];

    /** @var string */
    private $reasonPhrase;

    /** @var int */
    private $statusCode;

    /**
     * @param int                                  $status  Status code
     * @param (string|string[])[]                  $headers Response headers
     * @param string|resource|StreamInterface|null $body    Response body
     * @param string                               $version Protocol version
     * @param string|null                          $reason  Reason phrase (when empty a default will be used based on the status code)
     */
    public function __construct(
        int $status = 200,
        array $headers = [],
        $body = null,
        string $version = '1.1',
        string $reason = null
    ) {
        $this->assertStatusCodeRange($status);

        $this->statusCode = $status;

        if ($body !== '' && $body !== null) {
            $this->stream = Utils::streamFor($body);
        }

        $this->setHeaders($headers);
        if ($reason == '' && isset(self::PHRASES[$this->statusCode])) {
            $this->reasonPhrase = self::PHRASES[$this->statusCode];
        } else {
            $this->reasonPhrase = (string) $reason;
        }

        $this->protocol = $version;
    }

    public function getStatusCode(): int
    {
        return $this->statusCode;
    }

    public function getReasonPhrase(): string
    {
        return $this->reasonPhrase;
    }

    public function withStatus($code, $reasonPhrase = ''): ResponseInterface
    {
        $this->assertStatusCodeIsInteger($code);
        $code = (int) $code;
        $this->assertStatusCodeRange($code);

        $new = clone $this;
        $new->statusCode = $code;
        if ($reasonPhrase == '' && isset(self::PHRASES[$new->statusCode])) {
            $reasonPhrase = self::PHRASES[$new->statusCode];
        }
        $new->reasonPhrase = (string) $reasonPhrase;

        return $new;
    }

    /**
     * @param mixed $statusCode
     */
    private function assertStatusCodeIsInteger($statusCode): void
    {
        if (filter_var($statusCode, FILTER_VALIDATE_INT) === false) {
            throw new \InvalidArgumentException('Status code must be an integer value.');
        }
    }

    private function assertStatusCodeRange(int $statusCode): void
    {
        if ($statusCode < 100 || $statusCode >= 600) {
            throw new \InvalidArgumentException('Status code must be an integer value between 1xx and 5xx.');
        }
    }
}
PK�c�\��'�;;$guzzlehttp/psr7/src/AppendStream.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Reads from multiple streams, one after the other.
 *
 * This is a read-only stream decorator.
 */
final class AppendStream implements StreamInterface
{
    /** @var StreamInterface[] Streams being decorated */
    private $streams = [];

    /** @var bool */
    private $seekable = true;

    /** @var int */
    private $current = 0;

    /** @var int */
    private $pos = 0;

    /**
     * @param StreamInterface[] $streams Streams to decorate. Each stream must
     *                                   be readable.
     */
    public function __construct(array $streams = [])
    {
        foreach ($streams as $stream) {
            $this->addStream($stream);
        }
    }

    public function __toString(): string
    {
        try {
            $this->rewind();

            return $this->getContents();
        } catch (\Throwable $e) {
            if (\PHP_VERSION_ID >= 70400) {
                throw $e;
            }
            trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR);

            return '';
        }
    }

    /**
     * Add a stream to the AppendStream
     *
     * @param StreamInterface $stream Stream to append. Must be readable.
     *
     * @throws \InvalidArgumentException if the stream is not readable
     */
    public function addStream(StreamInterface $stream): void
    {
        if (!$stream->isReadable()) {
            throw new \InvalidArgumentException('Each stream must be readable');
        }

        // The stream is only seekable if all streams are seekable
        if (!$stream->isSeekable()) {
            $this->seekable = false;
        }

        $this->streams[] = $stream;
    }

    public function getContents(): string
    {
        return Utils::copyToString($this);
    }

    /**
     * Closes each attached stream.
     */
    public function close(): void
    {
        $this->pos = $this->current = 0;
        $this->seekable = true;

        foreach ($this->streams as $stream) {
            $stream->close();
        }

        $this->streams = [];
    }

    /**
     * Detaches each attached stream.
     *
     * Returns null as it's not clear which underlying stream resource to return.
     */
    public function detach()
    {
        $this->pos = $this->current = 0;
        $this->seekable = true;

        foreach ($this->streams as $stream) {
            $stream->detach();
        }

        $this->streams = [];

        return null;
    }

    public function tell(): int
    {
        return $this->pos;
    }

    /**
     * Tries to calculate the size by adding the size of each stream.
     *
     * If any of the streams do not return a valid number, then the size of the
     * append stream cannot be determined and null is returned.
     */
    public function getSize(): ?int
    {
        $size = 0;

        foreach ($this->streams as $stream) {
            $s = $stream->getSize();
            if ($s === null) {
                return null;
            }
            $size += $s;
        }

        return $size;
    }

    public function eof(): bool
    {
        return !$this->streams
            || ($this->current >= count($this->streams) - 1
             && $this->streams[$this->current]->eof());
    }

    public function rewind(): void
    {
        $this->seek(0);
    }

    /**
     * Attempts to seek to the given position. Only supports SEEK_SET.
     */
    public function seek($offset, $whence = SEEK_SET): void
    {
        if (!$this->seekable) {
            throw new \RuntimeException('This AppendStream is not seekable');
        } elseif ($whence !== SEEK_SET) {
            throw new \RuntimeException('The AppendStream can only seek with SEEK_SET');
        }

        $this->pos = $this->current = 0;

        // Rewind each stream
        foreach ($this->streams as $i => $stream) {
            try {
                $stream->rewind();
            } catch (\Exception $e) {
                throw new \RuntimeException('Unable to seek stream '
                    .$i.' of the AppendStream', 0, $e);
            }
        }

        // Seek to the actual position by reading from each stream
        while ($this->pos < $offset && !$this->eof()) {
            $result = $this->read(min(8096, $offset - $this->pos));
            if ($result === '') {
                break;
            }
        }
    }

    /**
     * Reads from all of the appended streams until the length is met or EOF.
     */
    public function read($length): string
    {
        $buffer = '';
        $total = count($this->streams) - 1;
        $remaining = $length;
        $progressToNext = false;

        while ($remaining > 0) {
            // Progress to the next stream if needed.
            if ($progressToNext || $this->streams[$this->current]->eof()) {
                $progressToNext = false;
                if ($this->current === $total) {
                    break;
                }
                ++$this->current;
            }

            $result = $this->streams[$this->current]->read($remaining);

            if ($result === '') {
                $progressToNext = true;
                continue;
            }

            $buffer .= $result;
            $remaining = $length - strlen($buffer);
        }

        $this->pos += strlen($buffer);

        return $buffer;
    }

    public function isReadable(): bool
    {
        return true;
    }

    public function isWritable(): bool
    {
        return false;
    }

    public function isSeekable(): bool
    {
        return $this->seekable;
    }

    public function write($string): int
    {
        throw new \RuntimeException('Cannot write to an AppendStream');
    }

    /**
     * @return mixed
     */
    public function getMetadata($key = null)
    {
        return $key ? null : [];
    }
}
PK�c�\�$guzzlehttp/psr7/src/NoSeekStream.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Stream decorator that prevents a stream from being seeked.
 */
final class NoSeekStream implements StreamInterface
{
    use StreamDecoratorTrait;

    /** @var StreamInterface */
    private $stream;

    public function seek($offset, $whence = SEEK_SET): void
    {
        throw new \RuntimeException('Cannot seek a NoSeekStream');
    }

    public function isSeekable(): bool
    {
        return false;
    }
}
PK�c�\�Ӡ$guzzlehttp/psr7/src/UploadedFile.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Psr7;

use InvalidArgumentException;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UploadedFileInterface;
use RuntimeException;

class UploadedFile implements UploadedFileInterface
{
    private const ERRORS = [
        UPLOAD_ERR_OK,
        UPLOAD_ERR_INI_SIZE,
        UPLOAD_ERR_FORM_SIZE,
        UPLOAD_ERR_PARTIAL,
        UPLOAD_ERR_NO_FILE,
        UPLOAD_ERR_NO_TMP_DIR,
        UPLOAD_ERR_CANT_WRITE,
        UPLOAD_ERR_EXTENSION,
    ];

    /**
     * @var string|null
     */
    private $clientFilename;

    /**
     * @var string|null
     */
    private $clientMediaType;

    /**
     * @var int
     */
    private $error;

    /**
     * @var string|null
     */
    private $file;

    /**
     * @var bool
     */
    private $moved = false;

    /**
     * @var int|null
     */
    private $size;

    /**
     * @var StreamInterface|null
     */
    private $stream;

    /**
     * @param StreamInterface|string|resource $streamOrFile
     */
    public function __construct(
        $streamOrFile,
        ?int $size,
        int $errorStatus,
        string $clientFilename = null,
        string $clientMediaType = null
    ) {
        $this->setError($errorStatus);
        $this->size = $size;
        $this->clientFilename = $clientFilename;
        $this->clientMediaType = $clientMediaType;

        if ($this->isOk()) {
            $this->setStreamOrFile($streamOrFile);
        }
    }

    /**
     * Depending on the value set file or stream variable
     *
     * @param StreamInterface|string|resource $streamOrFile
     *
     * @throws InvalidArgumentException
     */
    private function setStreamOrFile($streamOrFile): void
    {
        if (is_string($streamOrFile)) {
            $this->file = $streamOrFile;
        } elseif (is_resource($streamOrFile)) {
            $this->stream = new Stream($streamOrFile);
        } elseif ($streamOrFile instanceof StreamInterface) {
            $this->stream = $streamOrFile;
        } else {
            throw new InvalidArgumentException(
                'Invalid stream or file provided for UploadedFile'
            );
        }
    }

    /**
     * @throws InvalidArgumentException
     */
    private function setError(int $error): void
    {
        if (false === in_array($error, UploadedFile::ERRORS, true)) {
            throw new InvalidArgumentException(
                'Invalid error status for UploadedFile'
            );
        }

        $this->error = $error;
    }

    private static function isStringNotEmpty($param): bool
    {
        return is_string($param) && false === empty($param);
    }

    /**
     * Return true if there is no upload error
     */
    private function isOk(): bool
    {
        return $this->error === UPLOAD_ERR_OK;
    }

    public function isMoved(): bool
    {
        return $this->moved;
    }

    /**
     * @throws RuntimeException if is moved or not ok
     */
    private function validateActive(): void
    {
        if (false === $this->isOk()) {
            throw new RuntimeException('Cannot retrieve stream due to upload error');
        }

        if ($this->isMoved()) {
            throw new RuntimeException('Cannot retrieve stream after it has already been moved');
        }
    }

    public function getStream(): StreamInterface
    {
        $this->validateActive();

        if ($this->stream instanceof StreamInterface) {
            return $this->stream;
        }

        /** @var string $file */
        $file = $this->file;

        return new LazyOpenStream($file, 'r+');
    }

    public function moveTo($targetPath): void
    {
        $this->validateActive();

        if (false === self::isStringNotEmpty($targetPath)) {
            throw new InvalidArgumentException(
                'Invalid path provided for move operation; must be a non-empty string'
            );
        }

        if ($this->file) {
            $this->moved = PHP_SAPI === 'cli'
                ? rename($this->file, $targetPath)
                : move_uploaded_file($this->file, $targetPath);
        } else {
            Utils::copyToStream(
                $this->getStream(),
                new LazyOpenStream($targetPath, 'w')
            );

            $this->moved = true;
        }

        if (false === $this->moved) {
            throw new RuntimeException(
                sprintf('Uploaded file could not be moved to %s', $targetPath)
            );
        }
    }

    public function getSize(): ?int
    {
        return $this->size;
    }

    public function getError(): int
    {
        return $this->error;
    }

    public function getClientFilename(): ?string
    {
        return $this->clientFilename;
    }

    public function getClientMediaType(): ?string
    {
        return $this->clientMediaType;
    }
}
PK�c�\EGO���#guzzlehttp/psr7/src/LimitStream.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Decorator used to return only a subset of a stream.
 */
final class LimitStream implements StreamInterface
{
    use StreamDecoratorTrait;

    /** @var int Offset to start reading from */
    private $offset;

    /** @var int Limit the number of bytes that can be read */
    private $limit;

    /** @var StreamInterface */
    private $stream;

    /**
     * @param StreamInterface $stream Stream to wrap
     * @param int             $limit  Total number of bytes to allow to be read
     *                                from the stream. Pass -1 for no limit.
     * @param int             $offset Position to seek to before reading (only
     *                                works on seekable streams).
     */
    public function __construct(
        StreamInterface $stream,
        int $limit = -1,
        int $offset = 0
    ) {
        $this->stream = $stream;
        $this->setLimit($limit);
        $this->setOffset($offset);
    }

    public function eof(): bool
    {
        // Always return true if the underlying stream is EOF
        if ($this->stream->eof()) {
            return true;
        }

        // No limit and the underlying stream is not at EOF
        if ($this->limit === -1) {
            return false;
        }

        return $this->stream->tell() >= $this->offset + $this->limit;
    }

    /**
     * Returns the size of the limited subset of data
     */
    public function getSize(): ?int
    {
        if (null === ($length = $this->stream->getSize())) {
            return null;
        } elseif ($this->limit === -1) {
            return $length - $this->offset;
        } else {
            return min($this->limit, $length - $this->offset);
        }
    }

    /**
     * Allow for a bounded seek on the read limited stream
     */
    public function seek($offset, $whence = SEEK_SET): void
    {
        if ($whence !== SEEK_SET || $offset < 0) {
            throw new \RuntimeException(sprintf(
                'Cannot seek to offset %s with whence %s',
                $offset,
                $whence
            ));
        }

        $offset += $this->offset;

        if ($this->limit !== -1) {
            if ($offset > $this->offset + $this->limit) {
                $offset = $this->offset + $this->limit;
            }
        }

        $this->stream->seek($offset);
    }

    /**
     * Give a relative tell()
     */
    public function tell(): int
    {
        return $this->stream->tell() - $this->offset;
    }

    /**
     * Set the offset to start limiting from
     *
     * @param int $offset Offset to seek to and begin byte limiting from
     *
     * @throws \RuntimeException if the stream cannot be seeked.
     */
    public function setOffset(int $offset): void
    {
        $current = $this->stream->tell();

        if ($current !== $offset) {
            // If the stream cannot seek to the offset position, then read to it
            if ($this->stream->isSeekable()) {
                $this->stream->seek($offset);
            } elseif ($current > $offset) {
                throw new \RuntimeException("Could not seek to stream offset $offset");
            } else {
                $this->stream->read($offset - $current);
            }
        }

        $this->offset = $offset;
    }

    /**
     * Set the limit of bytes that the decorator allows to be read from the
     * stream.
     *
     * @param int $limit Number of bytes to allow to be read from the stream.
     *                   Use -1 for no limit.
     */
    public function setLimit(int $limit): void
    {
        $this->limit = $limit;
    }

    public function read($length): string
    {
        if ($this->limit === -1) {
            return $this->stream->read($length);
        }

        // Check if the current position is less than the total allowed
        // bytes + original offset
        $remaining = ($this->offset + $this->limit) - $this->stream->tell();
        if ($remaining > 0) {
            // Only return the amount of requested data, ensuring that the byte
            // limit is not exceeded
            return $this->stream->read(min($remaining, $length));
        }

        return '';
    }
}
PK�c�\�P���<�<guzzlehttp/psr7/src/Utils.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Psr7;

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriInterface;

final class Utils
{
    /**
     * Remove the items given by the keys, case insensitively from the data.
     *
     * @param (string|int)[] $keys
     */
    public static function caselessRemove(array $keys, array $data): array
    {
        $result = [];

        foreach ($keys as &$key) {
            $key = strtolower((string) $key);
        }

        foreach ($data as $k => $v) {
            if (!in_array(strtolower((string) $k), $keys)) {
                $result[$k] = $v;
            }
        }

        return $result;
    }

    /**
     * Copy the contents of a stream into another stream until the given number
     * of bytes have been read.
     *
     * @param StreamInterface $source Stream to read from
     * @param StreamInterface $dest   Stream to write to
     * @param int             $maxLen Maximum number of bytes to read. Pass -1
     *                                to read the entire stream.
     *
     * @throws \RuntimeException on error.
     */
    public static function copyToStream(StreamInterface $source, StreamInterface $dest, int $maxLen = -1): void
    {
        $bufferSize = 8192;

        if ($maxLen === -1) {
            while (!$source->eof()) {
                if (!$dest->write($source->read($bufferSize))) {
                    break;
                }
            }
        } else {
            $remaining = $maxLen;
            while ($remaining > 0 && !$source->eof()) {
                $buf = $source->read(min($bufferSize, $remaining));
                $len = strlen($buf);
                if (!$len) {
                    break;
                }
                $remaining -= $len;
                $dest->write($buf);
            }
        }
    }

    /**
     * Copy the contents of a stream into a string until the given number of
     * bytes have been read.
     *
     * @param StreamInterface $stream Stream to read
     * @param int             $maxLen Maximum number of bytes to read. Pass -1
     *                                to read the entire stream.
     *
     * @throws \RuntimeException on error.
     */
    public static function copyToString(StreamInterface $stream, int $maxLen = -1): string
    {
        $buffer = '';

        if ($maxLen === -1) {
            while (!$stream->eof()) {
                $buf = $stream->read(1048576);
                if ($buf === '') {
                    break;
                }
                $buffer .= $buf;
            }

            return $buffer;
        }

        $len = 0;
        while (!$stream->eof() && $len < $maxLen) {
            $buf = $stream->read($maxLen - $len);
            if ($buf === '') {
                break;
            }
            $buffer .= $buf;
            $len = strlen($buffer);
        }

        return $buffer;
    }

    /**
     * Calculate a hash of a stream.
     *
     * This method reads the entire stream to calculate a rolling hash, based
     * on PHP's `hash_init` functions.
     *
     * @param StreamInterface $stream    Stream to calculate the hash for
     * @param string          $algo      Hash algorithm (e.g. md5, crc32, etc)
     * @param bool            $rawOutput Whether or not to use raw output
     *
     * @throws \RuntimeException on error.
     */
    public static function hash(StreamInterface $stream, string $algo, bool $rawOutput = false): string
    {
        $pos = $stream->tell();

        if ($pos > 0) {
            $stream->rewind();
        }

        $ctx = hash_init($algo);
        while (!$stream->eof()) {
            hash_update($ctx, $stream->read(1048576));
        }

        $out = hash_final($ctx, $rawOutput);
        $stream->seek($pos);

        return $out;
    }

    /**
     * Clone and modify a request with the given changes.
     *
     * This method is useful for reducing the number of clones needed to mutate
     * a message.
     *
     * The changes can be one of:
     * - method: (string) Changes the HTTP method.
     * - set_headers: (array) Sets the given headers.
     * - remove_headers: (array) Remove the given headers.
     * - body: (mixed) Sets the given body.
     * - uri: (UriInterface) Set the URI.
     * - query: (string) Set the query string value of the URI.
     * - version: (string) Set the protocol version.
     *
     * @param RequestInterface $request Request to clone and modify.
     * @param array            $changes Changes to apply.
     */
    public static function modifyRequest(RequestInterface $request, array $changes): RequestInterface
    {
        if (!$changes) {
            return $request;
        }

        $headers = $request->getHeaders();

        if (!isset($changes['uri'])) {
            $uri = $request->getUri();
        } else {
            // Remove the host header if one is on the URI
            if ($host = $changes['uri']->getHost()) {
                $changes['set_headers']['Host'] = $host;

                if ($port = $changes['uri']->getPort()) {
                    $standardPorts = ['http' => 80, 'https' => 443];
                    $scheme = $changes['uri']->getScheme();
                    if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) {
                        $changes['set_headers']['Host'] .= ':'.$port;
                    }
                }
            }
            $uri = $changes['uri'];
        }

        if (!empty($changes['remove_headers'])) {
            $headers = self::caselessRemove($changes['remove_headers'], $headers);
        }

        if (!empty($changes['set_headers'])) {
            $headers = self::caselessRemove(array_keys($changes['set_headers']), $headers);
            $headers = $changes['set_headers'] + $headers;
        }

        if (isset($changes['query'])) {
            $uri = $uri->withQuery($changes['query']);
        }

        if ($request instanceof ServerRequestInterface) {
            $new = (new ServerRequest(
                $changes['method'] ?? $request->getMethod(),
                $uri,
                $headers,
                $changes['body'] ?? $request->getBody(),
                $changes['version'] ?? $request->getProtocolVersion(),
                $request->getServerParams()
            ))
            ->withParsedBody($request->getParsedBody())
            ->withQueryParams($request->getQueryParams())
            ->withCookieParams($request->getCookieParams())
            ->withUploadedFiles($request->getUploadedFiles());

            foreach ($request->getAttributes() as $key => $value) {
                $new = $new->withAttribute($key, $value);
            }

            return $new;
        }

        return new Request(
            $changes['method'] ?? $request->getMethod(),
            $uri,
            $headers,
            $changes['body'] ?? $request->getBody(),
            $changes['version'] ?? $request->getProtocolVersion()
        );
    }

    /**
     * Read a line from the stream up to the maximum allowed buffer length.
     *
     * @param StreamInterface $stream    Stream to read from
     * @param int|null        $maxLength Maximum buffer length
     */
    public static function readLine(StreamInterface $stream, int $maxLength = null): string
    {
        $buffer = '';
        $size = 0;

        while (!$stream->eof()) {
            if ('' === ($byte = $stream->read(1))) {
                return $buffer;
            }
            $buffer .= $byte;
            // Break when a new line is found or the max length - 1 is reached
            if ($byte === "\n" || ++$size === $maxLength - 1) {
                break;
            }
        }

        return $buffer;
    }

    /**
     * Create a new stream based on the input type.
     *
     * Options is an associative array that can contain the following keys:
     * - metadata: Array of custom metadata.
     * - size: Size of the stream.
     *
     * This method accepts the following `$resource` types:
     * - `Psr\Http\Message\StreamInterface`: Returns the value as-is.
     * - `string`: Creates a stream object that uses the given string as the contents.
     * - `resource`: Creates a stream object that wraps the given PHP stream resource.
     * - `Iterator`: If the provided value implements `Iterator`, then a read-only
     *   stream object will be created that wraps the given iterable. Each time the
     *   stream is read from, data from the iterator will fill a buffer and will be
     *   continuously called until the buffer is equal to the requested read size.
     *   Subsequent read calls will first read from the buffer and then call `next`
     *   on the underlying iterator until it is exhausted.
     * - `object` with `__toString()`: If the object has the `__toString()` method,
     *   the object will be cast to a string and then a stream will be returned that
     *   uses the string value.
     * - `NULL`: When `null` is passed, an empty stream object is returned.
     * - `callable` When a callable is passed, a read-only stream object will be
     *   created that invokes the given callable. The callable is invoked with the
     *   number of suggested bytes to read. The callable can return any number of
     *   bytes, but MUST return `false` when there is no more data to return. The
     *   stream object that wraps the callable will invoke the callable until the
     *   number of requested bytes are available. Any additional bytes will be
     *   buffered and used in subsequent reads.
     *
     * @param resource|string|int|float|bool|StreamInterface|callable|\Iterator|null $resource Entity body data
     * @param array{size?: int, metadata?: array}                                    $options  Additional options
     *
     * @throws \InvalidArgumentException if the $resource arg is not valid.
     */
    public static function streamFor($resource = '', array $options = []): StreamInterface
    {
        if (is_scalar($resource)) {
            $stream = self::tryFopen('php://temp', 'r+');
            if ($resource !== '') {
                fwrite($stream, (string) $resource);
                fseek($stream, 0);
            }

            return new Stream($stream, $options);
        }

        switch (gettype($resource)) {
            case 'resource':
                /*
                 * The 'php://input' is a special stream with quirks and inconsistencies.
                 * We avoid using that stream by reading it into php://temp
                 */

                /** @var resource $resource */
                if ((\stream_get_meta_data($resource)['uri'] ?? '') === 'php://input') {
                    $stream = self::tryFopen('php://temp', 'w+');
                    stream_copy_to_stream($resource, $stream);
                    fseek($stream, 0);
                    $resource = $stream;
                }

                return new Stream($resource, $options);
            case 'object':
                /** @var object $resource */
                if ($resource instanceof StreamInterface) {
                    return $resource;
                } elseif ($resource instanceof \Iterator) {
                    return new PumpStream(function () use ($resource) {
                        if (!$resource->valid()) {
                            return false;
                        }
                        $result = $resource->current();
                        $resource->next();

                        return $result;
                    }, $options);
                } elseif (method_exists($resource, '__toString')) {
                    return self::streamFor((string) $resource, $options);
                }
                break;
            case 'NULL':
                return new Stream(self::tryFopen('php://temp', 'r+'), $options);
        }

        if (is_callable($resource)) {
            return new PumpStream($resource, $options);
        }

        throw new \InvalidArgumentException('Invalid resource type: '.gettype($resource));
    }

    /**
     * Safely opens a PHP stream resource using a filename.
     *
     * When fopen fails, PHP normally raises a warning. This function adds an
     * error handler that checks for errors and throws an exception instead.
     *
     * @param string $filename File to open
     * @param string $mode     Mode used to open the file
     *
     * @return resource
     *
     * @throws \RuntimeException if the file cannot be opened
     */
    public static function tryFopen(string $filename, string $mode)
    {
        $ex = null;
        set_error_handler(static function (int $errno, string $errstr) use ($filename, $mode, &$ex): bool {
            $ex = new \RuntimeException(sprintf(
                'Unable to open "%s" using mode "%s": %s',
                $filename,
                $mode,
                $errstr
            ));

            return true;
        });

        try {
            /** @var resource $handle */
            $handle = fopen($filename, $mode);
        } catch (\Throwable $e) {
            $ex = new \RuntimeException(sprintf(
                'Unable to open "%s" using mode "%s": %s',
                $filename,
                $mode,
                $e->getMessage()
            ), 0, $e);
        }

        restore_error_handler();

        if ($ex) {
            /** @var $ex \RuntimeException */
            throw $ex;
        }

        return $handle;
    }

    /**
     * Safely gets the contents of a given stream.
     *
     * When stream_get_contents fails, PHP normally raises a warning. This
     * function adds an error handler that checks for errors and throws an
     * exception instead.
     *
     * @param resource $stream
     *
     * @throws \RuntimeException if the stream cannot be read
     */
    public static function tryGetContents($stream): string
    {
        $ex = null;
        set_error_handler(static function (int $errno, string $errstr) use (&$ex): bool {
            $ex = new \RuntimeException(sprintf(
                'Unable to read stream contents: %s',
                $errstr
            ));

            return true;
        });

        try {
            /** @var string|false $contents */
            $contents = stream_get_contents($stream);

            if ($contents === false) {
                $ex = new \RuntimeException('Unable to read stream contents');
            }
        } catch (\Throwable $e) {
            $ex = new \RuntimeException(sprintf(
                'Unable to read stream contents: %s',
                $e->getMessage()
            ), 0, $e);
        }

        restore_error_handler();

        if ($ex) {
            /** @var $ex \RuntimeException */
            throw $ex;
        }

        return $contents;
    }

    /**
     * Returns a UriInterface for the given value.
     *
     * This function accepts a string or UriInterface and returns a
     * UriInterface for the given value. If the value is already a
     * UriInterface, it is returned as-is.
     *
     * @param string|UriInterface $uri
     *
     * @throws \InvalidArgumentException
     */
    public static function uriFor($uri): UriInterface
    {
        if ($uri instanceof UriInterface) {
            return $uri;
        }

        if (is_string($uri)) {
            return new Uri($uri);
        }

        throw new \InvalidArgumentException('URI must be a string or UriInterface');
    }
}
PK�c�\Ŝ
�~~%guzzlehttp/psr7/src/UriComparator.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Psr7;

use Psr\Http\Message\UriInterface;

/**
 * Provides methods to determine if a modified URL should be considered cross-origin.
 *
 * @author Graham Campbell
 */
final class UriComparator
{
    /**
     * Determines if a modified URL should be considered cross-origin with
     * respect to an original URL.
     */
    public static function isCrossOrigin(UriInterface $original, UriInterface $modified): bool
    {
        if (\strcasecmp($original->getHost(), $modified->getHost()) !== 0) {
            return true;
        }

        if ($original->getScheme() !== $modified->getScheme()) {
            return true;
        }

        if (self::computePort($original) !== self::computePort($modified)) {
            return true;
        }

        return false;
    }

    private static function computePort(UriInterface $uri): int
    {
        $port = $uri->getPort();

        if (null !== $port) {
            return $port;
        }

        return 'https' === $uri->getScheme() ? 443 : 80;
    }

    private function __construct()
    {
        // cannot be instantiated
    }
}
PK�c�\џ�U��guzzlehttp/psr7/src/Rfc7230.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Psr7;

/**
 * @internal
 */
final class Rfc7230
{
    /**
     * Header related regular expressions (based on amphp/http package)
     *
     * Note: header delimiter (\r\n) is modified to \r?\n to accept line feed only delimiters for BC reasons.
     *
     * @see https://github.com/amphp/http/blob/v1.0.1/src/Rfc7230.php#L12-L15
     *
     * @license https://github.com/amphp/http/blob/v1.0.1/LICENSE
     */
    public const HEADER_REGEX = "(^([^()<>@,;:\\\"/[\]?={}\x01-\x20\x7F]++):[ \t]*+((?:[ \t]*+[\x21-\x7E\x80-\xFF]++)*+)[ \t]*+\r?\n)m";
    public const HEADER_FOLD_REGEX = "(\r?\n[ \t]++)";
}
PK�c�\��eeguzzlehttp/psr7/src/Header.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Psr7;

final class Header
{
    /**
     * Parse an array of header values containing ";" separated data into an
     * array of associative arrays representing the header key value pair data
     * of the header. When a parameter does not contain a value, but just
     * contains a key, this function will inject a key with a '' string value.
     *
     * @param string|array $header Header to parse into components.
     */
    public static function parse($header): array
    {
        static $trimmed = "\"'  \n\t\r";
        $params = $matches = [];

        foreach ((array) $header as $value) {
            foreach (self::splitList($value) as $val) {
                $part = [];
                foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) ?: [] as $kvp) {
                    if (preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) {
                        $m = $matches[0];
                        if (isset($m[1])) {
                            $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed);
                        } else {
                            $part[] = trim($m[0], $trimmed);
                        }
                    }
                }
                if ($part) {
                    $params[] = $part;
                }
            }
        }

        return $params;
    }

    /**
     * Converts an array of header values that may contain comma separated
     * headers into an array of headers with no comma separated values.
     *
     * @param string|array $header Header to normalize.
     *
     * @deprecated Use self::splitList() instead.
     */
    public static function normalize($header): array
    {
        $result = [];
        foreach ((array) $header as $value) {
            foreach (self::splitList($value) as $parsed) {
                $result[] = $parsed;
            }
        }

        return $result;
    }

    /**
     * Splits a HTTP header defined to contain a comma-separated list into
     * each individual value. Empty values will be removed.
     *
     * Example headers include 'accept', 'cache-control' and 'if-none-match'.
     *
     * This method must not be used to parse headers that are not defined as
     * a list, such as 'user-agent' or 'set-cookie'.
     *
     * @param string|string[] $values Header value as returned by MessageInterface::getHeader()
     *
     * @return string[]
     */
    public static function splitList($values): array
    {
        if (!\is_array($values)) {
            $values = [$values];
        }

        $result = [];
        foreach ($values as $value) {
            if (!\is_string($value)) {
                throw new \TypeError('$header must either be a string or an array containing strings.');
            }

            $v = '';
            $isQuoted = false;
            $isEscaped = false;
            for ($i = 0, $max = \strlen($value); $i < $max; ++$i) {
                if ($isEscaped) {
                    $v .= $value[$i];
                    $isEscaped = false;

                    continue;
                }

                if (!$isQuoted && $value[$i] === ',') {
                    $v = \trim($v);
                    if ($v !== '') {
                        $result[] = $v;
                    }

                    $v = '';
                    continue;
                }

                if ($isQuoted && $value[$i] === '\\') {
                    $isEscaped = true;
                    $v .= $value[$i];

                    continue;
                }
                if ($value[$i] === '"') {
                    $isQuoted = !$isQuoted;
                    $v .= $value[$i];

                    continue;
                }

                $v .= $value[$i];
            }

            $v = \trim($v);
            if ($v !== '') {
                $result[] = $v;
            }
        }

        return $result;
    }
}
PK�c�\=a���� guzzlehttp/psr7/src/FnStream.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Compose stream implementations based on a hash of functions.
 *
 * Allows for easy testing and extension of a provided stream without needing
 * to create a concrete class for a simple extension point.
 */
#[\AllowDynamicProperties]
final class FnStream implements StreamInterface
{
    private const SLOTS = [
        '__toString', 'close', 'detach', 'rewind',
        'getSize', 'tell', 'eof', 'isSeekable', 'seek', 'isWritable', 'write',
        'isReadable', 'read', 'getContents', 'getMetadata',
    ];

    /** @var array<string, callable> */
    private $methods;

    /**
     * @param array<string, callable> $methods Hash of method name to a callable.
     */
    public function __construct(array $methods)
    {
        $this->methods = $methods;

        // Create the functions on the class
        foreach ($methods as $name => $fn) {
            $this->{'_fn_'.$name} = $fn;
        }
    }

    /**
     * Lazily determine which methods are not implemented.
     *
     * @throws \BadMethodCallException
     */
    public function __get(string $name): void
    {
        throw new \BadMethodCallException(str_replace('_fn_', '', $name)
            .'() is not implemented in the FnStream');
    }

    /**
     * The close method is called on the underlying stream only if possible.
     */
    public function __destruct()
    {
        if (isset($this->_fn_close)) {
            ($this->_fn_close)();
        }
    }

    /**
     * An unserialize would allow the __destruct to run when the unserialized value goes out of scope.
     *
     * @throws \LogicException
     */
    public function __wakeup(): void
    {
        throw new \LogicException('FnStream should never be unserialized');
    }

    /**
     * Adds custom functionality to an underlying stream by intercepting
     * specific method calls.
     *
     * @param StreamInterface         $stream  Stream to decorate
     * @param array<string, callable> $methods Hash of method name to a closure
     *
     * @return FnStream
     */
    public static function decorate(StreamInterface $stream, array $methods)
    {
        // If any of the required methods were not provided, then simply
        // proxy to the decorated stream.
        foreach (array_diff(self::SLOTS, array_keys($methods)) as $diff) {
            /** @var callable $callable */
            $callable = [$stream, $diff];
            $methods[$diff] = $callable;
        }

        return new self($methods);
    }

    public function __toString(): string
    {
        try {
            /** @var string */
            return ($this->_fn___toString)();
        } catch (\Throwable $e) {
            if (\PHP_VERSION_ID >= 70400) {
                throw $e;
            }
            trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR);

            return '';
        }
    }

    public function close(): void
    {
        ($this->_fn_close)();
    }

    public function detach()
    {
        return ($this->_fn_detach)();
    }

    public function getSize(): ?int
    {
        return ($this->_fn_getSize)();
    }

    public function tell(): int
    {
        return ($this->_fn_tell)();
    }

    public function eof(): bool
    {
        return ($this->_fn_eof)();
    }

    public function isSeekable(): bool
    {
        return ($this->_fn_isSeekable)();
    }

    public function rewind(): void
    {
        ($this->_fn_rewind)();
    }

    public function seek($offset, $whence = SEEK_SET): void
    {
        ($this->_fn_seek)($offset, $whence);
    }

    public function isWritable(): bool
    {
        return ($this->_fn_isWritable)();
    }

    public function write($string): int
    {
        return ($this->_fn_write)($string);
    }

    public function isReadable(): bool
    {
        return ($this->_fn_isReadable)();
    }

    public function read($length): string
    {
        return ($this->_fn_read)($length);
    }

    public function getContents(): string
    {
        return ($this->_fn_getContents)();
    }

    /**
     * @return mixed
     */
    public function getMetadata($key = null)
    {
        return ($this->_fn_getMetadata)($key);
    }
}
PK�c�\��N|��7guzzlehttp/psr7/src/Exception/MalformedUriException.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Psr7\Exception;

use InvalidArgumentException;

/**
 * Exception thrown if a URI cannot be parsed because it's malformed.
 */
class MalformedUriException extends InvalidArgumentException
{
}
PK�c�\J��� � guzzlehttp/psr7/src/Message.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Psr7;

use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

final class Message
{
    /**
     * Returns the string representation of an HTTP message.
     *
     * @param MessageInterface $message Message to convert to a string.
     */
    public static function toString(MessageInterface $message): string
    {
        if ($message instanceof RequestInterface) {
            $msg = trim($message->getMethod().' '
                    .$message->getRequestTarget())
                .' HTTP/'.$message->getProtocolVersion();
            if (!$message->hasHeader('host')) {
                $msg .= "\r\nHost: ".$message->getUri()->getHost();
            }
        } elseif ($message instanceof ResponseInterface) {
            $msg = 'HTTP/'.$message->getProtocolVersion().' '
                .$message->getStatusCode().' '
                .$message->getReasonPhrase();
        } else {
            throw new \InvalidArgumentException('Unknown message type');
        }

        foreach ($message->getHeaders() as $name => $values) {
            if (is_string($name) && strtolower($name) === 'set-cookie') {
                foreach ($values as $value) {
                    $msg .= "\r\n{$name}: ".$value;
                }
            } else {
                $msg .= "\r\n{$name}: ".implode(', ', $values);
            }
        }

        return "{$msg}\r\n\r\n".$message->getBody();
    }

    /**
     * Get a short summary of the message body.
     *
     * Will return `null` if the response is not printable.
     *
     * @param MessageInterface $message    The message to get the body summary
     * @param int              $truncateAt The maximum allowed size of the summary
     */
    public static function bodySummary(MessageInterface $message, int $truncateAt = 120): ?string
    {
        $body = $message->getBody();

        if (!$body->isSeekable() || !$body->isReadable()) {
            return null;
        }

        $size = $body->getSize();

        if ($size === 0) {
            return null;
        }

        $body->rewind();
        $summary = $body->read($truncateAt);
        $body->rewind();

        if ($size > $truncateAt) {
            $summary .= ' (truncated...)';
        }

        // Matches any printable character, including unicode characters:
        // letters, marks, numbers, punctuation, spacing, and separators.
        if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/u', $summary) !== 0) {
            return null;
        }

        return $summary;
    }

    /**
     * Attempts to rewind a message body and throws an exception on failure.
     *
     * The body of the message will only be rewound if a call to `tell()`
     * returns a value other than `0`.
     *
     * @param MessageInterface $message Message to rewind
     *
     * @throws \RuntimeException
     */
    public static function rewindBody(MessageInterface $message): void
    {
        $body = $message->getBody();

        if ($body->tell()) {
            $body->rewind();
        }
    }

    /**
     * Parses an HTTP message into an associative array.
     *
     * The array contains the "start-line" key containing the start line of
     * the message, "headers" key containing an associative array of header
     * array values, and a "body" key containing the body of the message.
     *
     * @param string $message HTTP request or response to parse.
     */
    public static function parseMessage(string $message): array
    {
        if (!$message) {
            throw new \InvalidArgumentException('Invalid message');
        }

        $message = ltrim($message, "\r\n");

        $messageParts = preg_split("/\r?\n\r?\n/", $message, 2);

        if ($messageParts === false || count($messageParts) !== 2) {
            throw new \InvalidArgumentException('Invalid message: Missing header delimiter');
        }

        [$rawHeaders, $body] = $messageParts;
        $rawHeaders .= "\r\n"; // Put back the delimiter we split previously
        $headerParts = preg_split("/\r?\n/", $rawHeaders, 2);

        if ($headerParts === false || count($headerParts) !== 2) {
            throw new \InvalidArgumentException('Invalid message: Missing status line');
        }

        [$startLine, $rawHeaders] = $headerParts;

        if (preg_match("/(?:^HTTP\/|^[A-Z]+ \S+ HTTP\/)(\d+(?:\.\d+)?)/i", $startLine, $matches) && $matches[1] === '1.0') {
            // Header folding is deprecated for HTTP/1.1, but allowed in HTTP/1.0
            $rawHeaders = preg_replace(Rfc7230::HEADER_FOLD_REGEX, ' ', $rawHeaders);
        }

        /** @var array[] $headerLines */
        $count = preg_match_all(Rfc7230::HEADER_REGEX, $rawHeaders, $headerLines, PREG_SET_ORDER);

        // If these aren't the same, then one line didn't match and there's an invalid header.
        if ($count !== substr_count($rawHeaders, "\n")) {
            // Folding is deprecated, see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.4
            if (preg_match(Rfc7230::HEADER_FOLD_REGEX, $rawHeaders)) {
                throw new \InvalidArgumentException('Invalid header syntax: Obsolete line folding');
            }

            throw new \InvalidArgumentException('Invalid header syntax');
        }

        $headers = [];

        foreach ($headerLines as $headerLine) {
            $headers[$headerLine[1]][] = $headerLine[2];
        }

        return [
            'start-line' => $startLine,
            'headers' => $headers,
            'body' => $body,
        ];
    }

    /**
     * Constructs a URI for an HTTP request message.
     *
     * @param string $path    Path from the start-line
     * @param array  $headers Array of headers (each value an array).
     */
    public static function parseRequestUri(string $path, array $headers): string
    {
        $hostKey = array_filter(array_keys($headers), function ($k) {
            // Numeric array keys are converted to int by PHP.
            $k = (string) $k;

            return strtolower($k) === 'host';
        });

        // If no host is found, then a full URI cannot be constructed.
        if (!$hostKey) {
            return $path;
        }

        $host = $headers[reset($hostKey)][0];
        $scheme = substr($host, -4) === ':443' ? 'https' : 'http';

        return $scheme.'://'.$host.'/'.ltrim($path, '/');
    }

    /**
     * Parses a request message string into a request object.
     *
     * @param string $message Request message string.
     */
    public static function parseRequest(string $message): RequestInterface
    {
        $data = self::parseMessage($message);
        $matches = [];
        if (!preg_match('/^[\S]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) {
            throw new \InvalidArgumentException('Invalid request string');
        }
        $parts = explode(' ', $data['start-line'], 3);
        $version = isset($parts[2]) ? explode('/', $parts[2])[1] : '1.1';

        $request = new Request(
            $parts[0],
            $matches[1] === '/' ? self::parseRequestUri($parts[1], $data['headers']) : $parts[1],
            $data['headers'],
            $data['body'],
            $version
        );

        return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]);
    }

    /**
     * Parses a response message string into a response object.
     *
     * @param string $message Response message string.
     */
    public static function parseResponse(string $message): ResponseInterface
    {
        $data = self::parseMessage($message);
        // According to https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2
        // the space between status-code and reason-phrase is required. But
        // browsers accept responses without space and reason as well.
        if (!preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/', $data['start-line'])) {
            throw new \InvalidArgumentException('Invalid response string: '.$data['start-line']);
        }
        $parts = explode(' ', $data['start-line'], 3);

        return new Response(
            (int) $parts[1],
            $data['headers'],
            $data['body'],
            explode('/', $parts[0])[1],
            $parts[2] ?? null
        );
    }
}
PK�c�\��)�DDguzzlehttp/psr7/src/Request.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Psr7;

use InvalidArgumentException;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriInterface;

/**
 * PSR-7 request implementation.
 */
class Request implements RequestInterface
{
    use MessageTrait;

    /** @var string */
    private $method;

    /** @var string|null */
    private $requestTarget;

    /** @var UriInterface */
    private $uri;

    /**
     * @param string                               $method  HTTP method
     * @param string|UriInterface                  $uri     URI
     * @param (string|string[])[]                  $headers Request headers
     * @param string|resource|StreamInterface|null $body    Request body
     * @param string                               $version Protocol version
     */
    public function __construct(
        string $method,
        $uri,
        array $headers = [],
        $body = null,
        string $version = '1.1'
    ) {
        $this->assertMethod($method);
        if (!($uri instanceof UriInterface)) {
            $uri = new Uri($uri);
        }

        $this->method = strtoupper($method);
        $this->uri = $uri;
        $this->setHeaders($headers);
        $this->protocol = $version;

        if (!isset($this->headerNames['host'])) {
            $this->updateHostFromUri();
        }

        if ($body !== '' && $body !== null) {
            $this->stream = Utils::streamFor($body);
        }
    }

    public function getRequestTarget(): string
    {
        if ($this->requestTarget !== null) {
            return $this->requestTarget;
        }

        $target = $this->uri->getPath();
        if ($target === '') {
            $target = '/';
        }
        if ($this->uri->getQuery() != '') {
            $target .= '?'.$this->uri->getQuery();
        }

        return $target;
    }

    public function withRequestTarget($requestTarget): RequestInterface
    {
        if (preg_match('#\s#', $requestTarget)) {
            throw new InvalidArgumentException(
                'Invalid request target provided; cannot contain whitespace'
            );
        }

        $new = clone $this;
        $new->requestTarget = $requestTarget;

        return $new;
    }

    public function getMethod(): string
    {
        return $this->method;
    }

    public function withMethod($method): RequestInterface
    {
        $this->assertMethod($method);
        $new = clone $this;
        $new->method = strtoupper($method);

        return $new;
    }

    public function getUri(): UriInterface
    {
        return $this->uri;
    }

    public function withUri(UriInterface $uri, $preserveHost = false): RequestInterface
    {
        if ($uri === $this->uri) {
            return $this;
        }

        $new = clone $this;
        $new->uri = $uri;

        if (!$preserveHost || !isset($this->headerNames['host'])) {
            $new->updateHostFromUri();
        }

        return $new;
    }

    private function updateHostFromUri(): void
    {
        $host = $this->uri->getHost();

        if ($host == '') {
            return;
        }

        if (($port = $this->uri->getPort()) !== null) {
            $host .= ':'.$port;
        }

        if (isset($this->headerNames['host'])) {
            $header = $this->headerNames['host'];
        } else {
            $header = 'Host';
            $this->headerNames['host'] = 'Host';
        }
        // Ensure Host is the first header.
        // See: https://datatracker.ietf.org/doc/html/rfc7230#section-5.4
        $this->headers = [$header => [$host]] + $this->headers;
    }

    /**
     * @param mixed $method
     */
    private function assertMethod($method): void
    {
        if (!is_string($method) || $method === '') {
            throw new InvalidArgumentException('Method must be a non-empty string.');
        }
    }
}
PK�c�\b���!�!#guzzlehttp/psr7/src/UriResolver.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Psr7;

use Psr\Http\Message\UriInterface;

/**
 * Resolves a URI reference in the context of a base URI and the opposite way.
 *
 * @author Tobias Schultze
 *
 * @see https://datatracker.ietf.org/doc/html/rfc3986#section-5
 */
final class UriResolver
{
    /**
     * Removes dot segments from a path and returns the new path.
     *
     * @see https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4
     */
    public static function removeDotSegments(string $path): string
    {
        if ($path === '' || $path === '/') {
            return $path;
        }

        $results = [];
        $segments = explode('/', $path);
        foreach ($segments as $segment) {
            if ($segment === '..') {
                array_pop($results);
            } elseif ($segment !== '.') {
                $results[] = $segment;
            }
        }

        $newPath = implode('/', $results);

        if ($path[0] === '/' && (!isset($newPath[0]) || $newPath[0] !== '/')) {
            // Re-add the leading slash if necessary for cases like "/.."
            $newPath = '/'.$newPath;
        } elseif ($newPath !== '' && ($segment === '.' || $segment === '..')) {
            // Add the trailing slash if necessary
            // If newPath is not empty, then $segment must be set and is the last segment from the foreach
            $newPath .= '/';
        }

        return $newPath;
    }

    /**
     * Converts the relative URI into a new URI that is resolved against the base URI.
     *
     * @see https://datatracker.ietf.org/doc/html/rfc3986#section-5.2
     */
    public static function resolve(UriInterface $base, UriInterface $rel): UriInterface
    {
        if ((string) $rel === '') {
            // we can simply return the same base URI instance for this same-document reference
            return $base;
        }

        if ($rel->getScheme() != '') {
            return $rel->withPath(self::removeDotSegments($rel->getPath()));
        }

        if ($rel->getAuthority() != '') {
            $targetAuthority = $rel->getAuthority();
            $targetPath = self::removeDotSegments($rel->getPath());
            $targetQuery = $rel->getQuery();
        } else {
            $targetAuthority = $base->getAuthority();
            if ($rel->getPath() === '') {
                $targetPath = $base->getPath();
                $targetQuery = $rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery();
            } else {
                if ($rel->getPath()[0] === '/') {
                    $targetPath = $rel->getPath();
                } else {
                    if ($targetAuthority != '' && $base->getPath() === '') {
                        $targetPath = '/'.$rel->getPath();
                    } else {
                        $lastSlashPos = strrpos($base->getPath(), '/');
                        if ($lastSlashPos === false) {
                            $targetPath = $rel->getPath();
                        } else {
                            $targetPath = substr($base->getPath(), 0, $lastSlashPos + 1).$rel->getPath();
                        }
                    }
                }
                $targetPath = self::removeDotSegments($targetPath);
                $targetQuery = $rel->getQuery();
            }
        }

        return new Uri(Uri::composeComponents(
            $base->getScheme(),
            $targetAuthority,
            $targetPath,
            $targetQuery,
            $rel->getFragment()
        ));
    }

    /**
     * Returns the target URI as a relative reference from the base URI.
     *
     * This method is the counterpart to resolve():
     *
     *    (string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target))
     *
     * One use-case is to use the current request URI as base URI and then generate relative links in your documents
     * to reduce the document size or offer self-contained downloadable document archives.
     *
     *    $base = new Uri('http://example.com/a/b/');
     *    echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c'));  // prints 'c'.
     *    echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y'));  // prints '../x/y'.
     *    echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'.
     *    echo UriResolver::relativize($base, new Uri('http://example.org/a/b/'));   // prints '//example.org/a/b/'.
     *
     * This method also accepts a target that is already relative and will try to relativize it further. Only a
     * relative-path reference will be returned as-is.
     *
     *    echo UriResolver::relativize($base, new Uri('/a/b/c'));  // prints 'c' as well
     */
    public static function relativize(UriInterface $base, UriInterface $target): UriInterface
    {
        if ($target->getScheme() !== ''
            && ($base->getScheme() !== $target->getScheme() || $target->getAuthority() === '' && $base->getAuthority() !== '')
        ) {
            return $target;
        }

        if (Uri::isRelativePathReference($target)) {
            // As the target is already highly relative we return it as-is. It would be possible to resolve
            // the target with `$target = self::resolve($base, $target);` and then try make it more relative
            // by removing a duplicate query. But let's not do that automatically.
            return $target;
        }

        if ($target->getAuthority() !== '' && $base->getAuthority() !== $target->getAuthority()) {
            return $target->withScheme('');
        }

        // We must remove the path before removing the authority because if the path starts with two slashes, the URI
        // would turn invalid. And we also cannot set a relative path before removing the authority, as that is also
        // invalid.
        $emptyPathUri = $target->withScheme('')->withPath('')->withUserInfo('')->withPort(null)->withHost('');

        if ($base->getPath() !== $target->getPath()) {
            return $emptyPathUri->withPath(self::getRelativePath($base, $target));
        }

        if ($base->getQuery() === $target->getQuery()) {
            // Only the target fragment is left. And it must be returned even if base and target fragment are the same.
            return $emptyPathUri->withQuery('');
        }

        // If the base URI has a query but the target has none, we cannot return an empty path reference as it would
        // inherit the base query component when resolving.
        if ($target->getQuery() === '') {
            $segments = explode('/', $target->getPath());
            /** @var string $lastSegment */
            $lastSegment = end($segments);

            return $emptyPathUri->withPath($lastSegment === '' ? './' : $lastSegment);
        }

        return $emptyPathUri;
    }

    private static function getRelativePath(UriInterface $base, UriInterface $target): string
    {
        $sourceSegments = explode('/', $base->getPath());
        $targetSegments = explode('/', $target->getPath());
        array_pop($sourceSegments);
        $targetLastSegment = array_pop($targetSegments);
        foreach ($sourceSegments as $i => $segment) {
            if (isset($targetSegments[$i]) && $segment === $targetSegments[$i]) {
                unset($sourceSegments[$i], $targetSegments[$i]);
            } else {
                break;
            }
        }
        $targetSegments[] = $targetLastSegment;
        $relativePath = str_repeat('../', count($sourceSegments)).implode('/', $targetSegments);

        // A reference to am empty last segment or an empty first sub-segment must be prefixed with "./".
        // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used
        // as the first segment of a relative-path reference, as it would be mistaken for a scheme name.
        if ('' === $relativePath || false !== strpos(explode('/', $relativePath, 2)[0], ':')) {
            $relativePath = "./$relativePath";
        } elseif ('/' === $relativePath[0]) {
            if ($base->getAuthority() != '' && $base->getPath() === '') {
                // In this case an extra slash is added by resolve() automatically. So we must not add one here.
                $relativePath = ".$relativePath";
            } else {
                $relativePath = "./$relativePath";
            }
        }

        return $relativePath;
    }

    private function __construct()
    {
        // cannot be instantiated
    }
}
PK�c�\0 N_��%guzzlehttp/psr7/src/StreamWrapper.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Converts Guzzle streams into PHP stream resources.
 *
 * @see https://www.php.net/streamwrapper
 */
final class StreamWrapper
{
    /** @var resource */
    public $context;

    /** @var StreamInterface */
    private $stream;

    /** @var string r, r+, or w */
    private $mode;

    /**
     * Returns a resource representing the stream.
     *
     * @param StreamInterface $stream The stream to get a resource for
     *
     * @return resource
     *
     * @throws \InvalidArgumentException if stream is not readable or writable
     */
    public static function getResource(StreamInterface $stream)
    {
        self::register();

        if ($stream->isReadable()) {
            $mode = $stream->isWritable() ? 'r+' : 'r';
        } elseif ($stream->isWritable()) {
            $mode = 'w';
        } else {
            throw new \InvalidArgumentException('The stream must be readable, '
                .'writable, or both.');
        }

        return fopen('guzzle://stream', $mode, false, self::createStreamContext($stream));
    }

    /**
     * Creates a stream context that can be used to open a stream as a php stream resource.
     *
     * @return resource
     */
    public static function createStreamContext(StreamInterface $stream)
    {
        return stream_context_create([
            'guzzle' => ['stream' => $stream],
        ]);
    }

    /**
     * Registers the stream wrapper if needed
     */
    public static function register(): void
    {
        if (!in_array('guzzle', stream_get_wrappers())) {
            stream_wrapper_register('guzzle', __CLASS__);
        }
    }

    public function stream_open(string $path, string $mode, int $options, string &$opened_path = null): bool
    {
        $options = stream_context_get_options($this->context);

        if (!isset($options['guzzle']['stream'])) {
            return false;
        }

        $this->mode = $mode;
        $this->stream = $options['guzzle']['stream'];

        return true;
    }

    public function stream_read(int $count): string
    {
        return $this->stream->read($count);
    }

    public function stream_write(string $data): int
    {
        return $this->stream->write($data);
    }

    public function stream_tell(): int
    {
        return $this->stream->tell();
    }

    public function stream_eof(): bool
    {
        return $this->stream->eof();
    }

    public function stream_seek(int $offset, int $whence): bool
    {
        $this->stream->seek($offset, $whence);

        return true;
    }

    /**
     * @return resource|false
     */
    public function stream_cast(int $cast_as)
    {
        $stream = clone $this->stream;
        $resource = $stream->detach();

        return $resource ?? false;
    }

    /**
     * @return array{
     *   dev: int,
     *   ino: int,
     *   mode: int,
     *   nlink: int,
     *   uid: int,
     *   gid: int,
     *   rdev: int,
     *   size: int,
     *   atime: int,
     *   mtime: int,
     *   ctime: int,
     *   blksize: int,
     *   blocks: int
     * }
     */
    public function stream_stat(): array
    {
        static $modeMap = [
            'r' => 33060,
            'rb' => 33060,
            'r+' => 33206,
            'w' => 33188,
            'wb' => 33188,
        ];

        return [
            'dev' => 0,
            'ino' => 0,
            'mode' => $modeMap[$this->mode],
            'nlink' => 0,
            'uid' => 0,
            'gid' => 0,
            'rdev' => 0,
            'size' => $this->stream->getSize() ?: 0,
            'atime' => 0,
            'mtime' => 0,
            'ctime' => 0,
            'blksize' => 0,
            'blocks' => 0,
        ];
    }

    /**
     * @return array{
     *   dev: int,
     *   ino: int,
     *   mode: int,
     *   nlink: int,
     *   uid: int,
     *   gid: int,
     *   rdev: int,
     *   size: int,
     *   atime: int,
     *   mtime: int,
     *   ctime: int,
     *   blksize: int,
     *   blocks: int
     * }
     */
    public function url_stat(string $path, int $flags): array
    {
        return [
            'dev' => 0,
            'ino' => 0,
            'mode' => 0,
            'nlink' => 0,
            'uid' => 0,
            'gid' => 0,
            'rdev' => 0,
            'size' => 0,
            'atime' => 0,
            'mtime' => 0,
            'ctime' => 0,
            'blksize' => 0,
            'blocks' => 0,
        ];
    }
}
PK�c�\{(���� guzzlehttp/psr7/src/MimeType.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Psr7;

final class MimeType
{
    private const MIME_TYPES = [
        '1km' => 'application/vnd.1000minds.decision-model+xml',
        '3dml' => 'text/vnd.in3d.3dml',
        '3ds' => 'image/x-3ds',
        '3g2' => 'video/3gpp2',
        '3gp' => 'video/3gp',
        '3gpp' => 'video/3gpp',
        '3mf' => 'model/3mf',
        '7z' => 'application/x-7z-compressed',
        '7zip' => 'application/x-7z-compressed',
        '123' => 'application/vnd.lotus-1-2-3',
        'aab' => 'application/x-authorware-bin',
        'aac' => 'audio/aac',
        'aam' => 'application/x-authorware-map',
        'aas' => 'application/x-authorware-seg',
        'abw' => 'application/x-abiword',
        'ac' => 'application/vnd.nokia.n-gage.ac+xml',
        'ac3' => 'audio/ac3',
        'acc' => 'application/vnd.americandynamics.acc',
        'ace' => 'application/x-ace-compressed',
        'acu' => 'application/vnd.acucobol',
        'acutc' => 'application/vnd.acucorp',
        'adp' => 'audio/adpcm',
        'adts' => 'audio/aac',
        'aep' => 'application/vnd.audiograph',
        'afm' => 'application/x-font-type1',
        'afp' => 'application/vnd.ibm.modcap',
        'age' => 'application/vnd.age',
        'ahead' => 'application/vnd.ahead.space',
        'ai' => 'application/pdf',
        'aif' => 'audio/x-aiff',
        'aifc' => 'audio/x-aiff',
        'aiff' => 'audio/x-aiff',
        'air' => 'application/vnd.adobe.air-application-installer-package+zip',
        'ait' => 'application/vnd.dvb.ait',
        'ami' => 'application/vnd.amiga.ami',
        'aml' => 'application/automationml-aml+xml',
        'amlx' => 'application/automationml-amlx+zip',
        'amr' => 'audio/amr',
        'apk' => 'application/vnd.android.package-archive',
        'apng' => 'image/apng',
        'appcache' => 'text/cache-manifest',
        'appinstaller' => 'application/appinstaller',
        'application' => 'application/x-ms-application',
        'appx' => 'application/appx',
        'appxbundle' => 'application/appxbundle',
        'apr' => 'application/vnd.lotus-approach',
        'arc' => 'application/x-freearc',
        'arj' => 'application/x-arj',
        'asc' => 'application/pgp-signature',
        'asf' => 'video/x-ms-asf',
        'asm' => 'text/x-asm',
        'aso' => 'application/vnd.accpac.simply.aso',
        'asx' => 'video/x-ms-asf',
        'atc' => 'application/vnd.acucorp',
        'atom' => 'application/atom+xml',
        'atomcat' => 'application/atomcat+xml',
        'atomdeleted' => 'application/atomdeleted+xml',
        'atomsvc' => 'application/atomsvc+xml',
        'atx' => 'application/vnd.antix.game-component',
        'au' => 'audio/x-au',
        'avci' => 'image/avci',
        'avcs' => 'image/avcs',
        'avi' => 'video/x-msvideo',
        'avif' => 'image/avif',
        'aw' => 'application/applixware',
        'azf' => 'application/vnd.airzip.filesecure.azf',
        'azs' => 'application/vnd.airzip.filesecure.azs',
        'azv' => 'image/vnd.airzip.accelerator.azv',
        'azw' => 'application/vnd.amazon.ebook',
        'b16' => 'image/vnd.pco.b16',
        'bat' => 'application/x-msdownload',
        'bcpio' => 'application/x-bcpio',
        'bdf' => 'application/x-font-bdf',
        'bdm' => 'application/vnd.syncml.dm+wbxml',
        'bdoc' => 'application/x-bdoc',
        'bed' => 'application/vnd.realvnc.bed',
        'bh2' => 'application/vnd.fujitsu.oasysprs',
        'bin' => 'application/octet-stream',
        'blb' => 'application/x-blorb',
        'blorb' => 'application/x-blorb',
        'bmi' => 'application/vnd.bmi',
        'bmml' => 'application/vnd.balsamiq.bmml+xml',
        'bmp' => 'image/bmp',
        'book' => 'application/vnd.framemaker',
        'box' => 'application/vnd.previewsystems.box',
        'boz' => 'application/x-bzip2',
        'bpk' => 'application/octet-stream',
        'bpmn' => 'application/octet-stream',
        'bsp' => 'model/vnd.valve.source.compiled-map',
        'btf' => 'image/prs.btif',
        'btif' => 'image/prs.btif',
        'buffer' => 'application/octet-stream',
        'bz' => 'application/x-bzip',
        'bz2' => 'application/x-bzip2',
        'c' => 'text/x-c',
        'c4d' => 'application/vnd.clonk.c4group',
        'c4f' => 'application/vnd.clonk.c4group',
        'c4g' => 'application/vnd.clonk.c4group',
        'c4p' => 'application/vnd.clonk.c4group',
        'c4u' => 'application/vnd.clonk.c4group',
        'c11amc' => 'application/vnd.cluetrust.cartomobile-config',
        'c11amz' => 'application/vnd.cluetrust.cartomobile-config-pkg',
        'cab' => 'application/vnd.ms-cab-compressed',
        'caf' => 'audio/x-caf',
        'cap' => 'application/vnd.tcpdump.pcap',
        'car' => 'application/vnd.curl.car',
        'cat' => 'application/vnd.ms-pki.seccat',
        'cb7' => 'application/x-cbr',
        'cba' => 'application/x-cbr',
        'cbr' => 'application/x-cbr',
        'cbt' => 'application/x-cbr',
        'cbz' => 'application/x-cbr',
        'cc' => 'text/x-c',
        'cco' => 'application/x-cocoa',
        'cct' => 'application/x-director',
        'ccxml' => 'application/ccxml+xml',
        'cdbcmsg' => 'application/vnd.contact.cmsg',
        'cdf' => 'application/x-netcdf',
        'cdfx' => 'application/cdfx+xml',
        'cdkey' => 'application/vnd.mediastation.cdkey',
        'cdmia' => 'application/cdmi-capability',
        'cdmic' => 'application/cdmi-container',
        'cdmid' => 'application/cdmi-domain',
        'cdmio' => 'application/cdmi-object',
        'cdmiq' => 'application/cdmi-queue',
        'cdr' => 'application/cdr',
        'cdx' => 'chemical/x-cdx',
        'cdxml' => 'application/vnd.chemdraw+xml',
        'cdy' => 'application/vnd.cinderella',
        'cer' => 'application/pkix-cert',
        'cfs' => 'application/x-cfs-compressed',
        'cgm' => 'image/cgm',
        'chat' => 'application/x-chat',
        'chm' => 'application/vnd.ms-htmlhelp',
        'chrt' => 'application/vnd.kde.kchart',
        'cif' => 'chemical/x-cif',
        'cii' => 'application/vnd.anser-web-certificate-issue-initiation',
        'cil' => 'application/vnd.ms-artgalry',
        'cjs' => 'application/node',
        'cla' => 'application/vnd.claymore',
        'class' => 'application/octet-stream',
        'cld' => 'model/vnd.cld',
        'clkk' => 'application/vnd.crick.clicker.keyboard',
        'clkp' => 'application/vnd.crick.clicker.palette',
        'clkt' => 'application/vnd.crick.clicker.template',
        'clkw' => 'application/vnd.crick.clicker.wordbank',
        'clkx' => 'application/vnd.crick.clicker',
        'clp' => 'application/x-msclip',
        'cmc' => 'application/vnd.cosmocaller',
        'cmdf' => 'chemical/x-cmdf',
        'cml' => 'chemical/x-cml',
        'cmp' => 'application/vnd.yellowriver-custom-menu',
        'cmx' => 'image/x-cmx',
        'cod' => 'application/vnd.rim.cod',
        'coffee' => 'text/coffeescript',
        'com' => 'application/x-msdownload',
        'conf' => 'text/plain',
        'cpio' => 'application/x-cpio',
        'cpl' => 'application/cpl+xml',
        'cpp' => 'text/x-c',
        'cpt' => 'application/mac-compactpro',
        'crd' => 'application/x-mscardfile',
        'crl' => 'application/pkix-crl',
        'crt' => 'application/x-x509-ca-cert',
        'crx' => 'application/x-chrome-extension',
        'cryptonote' => 'application/vnd.rig.cryptonote',
        'csh' => 'application/x-csh',
        'csl' => 'application/vnd.citationstyles.style+xml',
        'csml' => 'chemical/x-csml',
        'csp' => 'application/vnd.commonspace',
        'csr' => 'application/octet-stream',
        'css' => 'text/css',
        'cst' => 'application/x-director',
        'csv' => 'text/csv',
        'cu' => 'application/cu-seeme',
        'curl' => 'text/vnd.curl',
        'cwl' => 'application/cwl',
        'cww' => 'application/prs.cww',
        'cxt' => 'application/x-director',
        'cxx' => 'text/x-c',
        'dae' => 'model/vnd.collada+xml',
        'daf' => 'application/vnd.mobius.daf',
        'dart' => 'application/vnd.dart',
        'dataless' => 'application/vnd.fdsn.seed',
        'davmount' => 'application/davmount+xml',
        'dbf' => 'application/vnd.dbf',
        'dbk' => 'application/docbook+xml',
        'dcr' => 'application/x-director',
        'dcurl' => 'text/vnd.curl.dcurl',
        'dd2' => 'application/vnd.oma.dd2+xml',
        'ddd' => 'application/vnd.fujixerox.ddd',
        'ddf' => 'application/vnd.syncml.dmddf+xml',
        'dds' => 'image/vnd.ms-dds',
        'deb' => 'application/x-debian-package',
        'def' => 'text/plain',
        'deploy' => 'application/octet-stream',
        'der' => 'application/x-x509-ca-cert',
        'dfac' => 'application/vnd.dreamfactory',
        'dgc' => 'application/x-dgc-compressed',
        'dib' => 'image/bmp',
        'dic' => 'text/x-c',
        'dir' => 'application/x-director',
        'dis' => 'application/vnd.mobius.dis',
        'disposition-notification' => 'message/disposition-notification',
        'dist' => 'application/octet-stream',
        'distz' => 'application/octet-stream',
        'djv' => 'image/vnd.djvu',
        'djvu' => 'image/vnd.djvu',
        'dll' => 'application/octet-stream',
        'dmg' => 'application/x-apple-diskimage',
        'dmn' => 'application/octet-stream',
        'dmp' => 'application/vnd.tcpdump.pcap',
        'dms' => 'application/octet-stream',
        'dna' => 'application/vnd.dna',
        'doc' => 'application/msword',
        'docm' => 'application/vnd.ms-word.template.macroEnabled.12',
        'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
        'dot' => 'application/msword',
        'dotm' => 'application/vnd.ms-word.template.macroEnabled.12',
        'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
        'dp' => 'application/vnd.osgi.dp',
        'dpg' => 'application/vnd.dpgraph',
        'dpx' => 'image/dpx',
        'dra' => 'audio/vnd.dra',
        'drle' => 'image/dicom-rle',
        'dsc' => 'text/prs.lines.tag',
        'dssc' => 'application/dssc+der',
        'dtb' => 'application/x-dtbook+xml',
        'dtd' => 'application/xml-dtd',
        'dts' => 'audio/vnd.dts',
        'dtshd' => 'audio/vnd.dts.hd',
        'dump' => 'application/octet-stream',
        'dvb' => 'video/vnd.dvb.file',
        'dvi' => 'application/x-dvi',
        'dwd' => 'application/atsc-dwd+xml',
        'dwf' => 'model/vnd.dwf',
        'dwg' => 'image/vnd.dwg',
        'dxf' => 'image/vnd.dxf',
        'dxp' => 'application/vnd.spotfire.dxp',
        'dxr' => 'application/x-director',
        'ear' => 'application/java-archive',
        'ecelp4800' => 'audio/vnd.nuera.ecelp4800',
        'ecelp7470' => 'audio/vnd.nuera.ecelp7470',
        'ecelp9600' => 'audio/vnd.nuera.ecelp9600',
        'ecma' => 'application/ecmascript',
        'edm' => 'application/vnd.novadigm.edm',
        'edx' => 'application/vnd.novadigm.edx',
        'efif' => 'application/vnd.picsel',
        'ei6' => 'application/vnd.pg.osasli',
        'elc' => 'application/octet-stream',
        'emf' => 'image/emf',
        'eml' => 'message/rfc822',
        'emma' => 'application/emma+xml',
        'emotionml' => 'application/emotionml+xml',
        'emz' => 'application/x-msmetafile',
        'eol' => 'audio/vnd.digital-winds',
        'eot' => 'application/vnd.ms-fontobject',
        'eps' => 'application/postscript',
        'epub' => 'application/epub+zip',
        'es3' => 'application/vnd.eszigno3+xml',
        'esa' => 'application/vnd.osgi.subsystem',
        'esf' => 'application/vnd.epson.esf',
        'et3' => 'application/vnd.eszigno3+xml',
        'etx' => 'text/x-setext',
        'eva' => 'application/x-eva',
        'evy' => 'application/x-envoy',
        'exe' => 'application/octet-stream',
        'exi' => 'application/exi',
        'exp' => 'application/express',
        'exr' => 'image/aces',
        'ext' => 'application/vnd.novadigm.ext',
        'ez' => 'application/andrew-inset',
        'ez2' => 'application/vnd.ezpix-album',
        'ez3' => 'application/vnd.ezpix-package',
        'f' => 'text/x-fortran',
        'f4v' => 'video/mp4',
        'f77' => 'text/x-fortran',
        'f90' => 'text/x-fortran',
        'fbs' => 'image/vnd.fastbidsheet',
        'fcdt' => 'application/vnd.adobe.formscentral.fcdt',
        'fcs' => 'application/vnd.isac.fcs',
        'fdf' => 'application/vnd.fdf',
        'fdt' => 'application/fdt+xml',
        'fe_launch' => 'application/vnd.denovo.fcselayout-link',
        'fg5' => 'application/vnd.fujitsu.oasysgp',
        'fgd' => 'application/x-director',
        'fh' => 'image/x-freehand',
        'fh4' => 'image/x-freehand',
        'fh5' => 'image/x-freehand',
        'fh7' => 'image/x-freehand',
        'fhc' => 'image/x-freehand',
        'fig' => 'application/x-xfig',
        'fits' => 'image/fits',
        'flac' => 'audio/x-flac',
        'fli' => 'video/x-fli',
        'flo' => 'application/vnd.micrografx.flo',
        'flv' => 'video/x-flv',
        'flw' => 'application/vnd.kde.kivio',
        'flx' => 'text/vnd.fmi.flexstor',
        'fly' => 'text/vnd.fly',
        'fm' => 'application/vnd.framemaker',
        'fnc' => 'application/vnd.frogans.fnc',
        'fo' => 'application/vnd.software602.filler.form+xml',
        'for' => 'text/x-fortran',
        'fpx' => 'image/vnd.fpx',
        'frame' => 'application/vnd.framemaker',
        'fsc' => 'application/vnd.fsc.weblaunch',
        'fst' => 'image/vnd.fst',
        'ftc' => 'application/vnd.fluxtime.clip',
        'fti' => 'application/vnd.anser-web-funds-transfer-initiation',
        'fvt' => 'video/vnd.fvt',
        'fxp' => 'application/vnd.adobe.fxp',
        'fxpl' => 'application/vnd.adobe.fxp',
        'fzs' => 'application/vnd.fuzzysheet',
        'g2w' => 'application/vnd.geoplan',
        'g3' => 'image/g3fax',
        'g3w' => 'application/vnd.geospace',
        'gac' => 'application/vnd.groove-account',
        'gam' => 'application/x-tads',
        'gbr' => 'application/rpki-ghostbusters',
        'gca' => 'application/x-gca-compressed',
        'gdl' => 'model/vnd.gdl',
        'gdoc' => 'application/vnd.google-apps.document',
        'ged' => 'text/vnd.familysearch.gedcom',
        'geo' => 'application/vnd.dynageo',
        'geojson' => 'application/geo+json',
        'gex' => 'application/vnd.geometry-explorer',
        'ggb' => 'application/vnd.geogebra.file',
        'ggt' => 'application/vnd.geogebra.tool',
        'ghf' => 'application/vnd.groove-help',
        'gif' => 'image/gif',
        'gim' => 'application/vnd.groove-identity-message',
        'glb' => 'model/gltf-binary',
        'gltf' => 'model/gltf+json',
        'gml' => 'application/gml+xml',
        'gmx' => 'application/vnd.gmx',
        'gnumeric' => 'application/x-gnumeric',
        'gpg' => 'application/gpg-keys',
        'gph' => 'application/vnd.flographit',
        'gpx' => 'application/gpx+xml',
        'gqf' => 'application/vnd.grafeq',
        'gqs' => 'application/vnd.grafeq',
        'gram' => 'application/srgs',
        'gramps' => 'application/x-gramps-xml',
        'gre' => 'application/vnd.geometry-explorer',
        'grv' => 'application/vnd.groove-injector',
        'grxml' => 'application/srgs+xml',
        'gsf' => 'application/x-font-ghostscript',
        'gsheet' => 'application/vnd.google-apps.spreadsheet',
        'gslides' => 'application/vnd.google-apps.presentation',
        'gtar' => 'application/x-gtar',
        'gtm' => 'application/vnd.groove-tool-message',
        'gtw' => 'model/vnd.gtw',
        'gv' => 'text/vnd.graphviz',
        'gxf' => 'application/gxf',
        'gxt' => 'application/vnd.geonext',
        'gz' => 'application/gzip',
        'gzip' => 'application/gzip',
        'h' => 'text/x-c',
        'h261' => 'video/h261',
        'h263' => 'video/h263',
        'h264' => 'video/h264',
        'hal' => 'application/vnd.hal+xml',
        'hbci' => 'application/vnd.hbci',
        'hbs' => 'text/x-handlebars-template',
        'hdd' => 'application/x-virtualbox-hdd',
        'hdf' => 'application/x-hdf',
        'heic' => 'image/heic',
        'heics' => 'image/heic-sequence',
        'heif' => 'image/heif',
        'heifs' => 'image/heif-sequence',
        'hej2' => 'image/hej2k',
        'held' => 'application/atsc-held+xml',
        'hh' => 'text/x-c',
        'hjson' => 'application/hjson',
        'hlp' => 'application/winhlp',
        'hpgl' => 'application/vnd.hp-hpgl',
        'hpid' => 'application/vnd.hp-hpid',
        'hps' => 'application/vnd.hp-hps',
        'hqx' => 'application/mac-binhex40',
        'hsj2' => 'image/hsj2',
        'htc' => 'text/x-component',
        'htke' => 'application/vnd.kenameaapp',
        'htm' => 'text/html',
        'html' => 'text/html',
        'hvd' => 'application/vnd.yamaha.hv-dic',
        'hvp' => 'application/vnd.yamaha.hv-voice',
        'hvs' => 'application/vnd.yamaha.hv-script',
        'i2g' => 'application/vnd.intergeo',
        'icc' => 'application/vnd.iccprofile',
        'ice' => 'x-conference/x-cooltalk',
        'icm' => 'application/vnd.iccprofile',
        'ico' => 'image/x-icon',
        'ics' => 'text/calendar',
        'ief' => 'image/ief',
        'ifb' => 'text/calendar',
        'ifm' => 'application/vnd.shana.informed.formdata',
        'iges' => 'model/iges',
        'igl' => 'application/vnd.igloader',
        'igm' => 'application/vnd.insors.igm',
        'igs' => 'model/iges',
        'igx' => 'application/vnd.micrografx.igx',
        'iif' => 'application/vnd.shana.informed.interchange',
        'img' => 'application/octet-stream',
        'imp' => 'application/vnd.accpac.simply.imp',
        'ims' => 'application/vnd.ms-ims',
        'in' => 'text/plain',
        'ini' => 'text/plain',
        'ink' => 'application/inkml+xml',
        'inkml' => 'application/inkml+xml',
        'install' => 'application/x-install-instructions',
        'iota' => 'application/vnd.astraea-software.iota',
        'ipfix' => 'application/ipfix',
        'ipk' => 'application/vnd.shana.informed.package',
        'irm' => 'application/vnd.ibm.rights-management',
        'irp' => 'application/vnd.irepository.package+xml',
        'iso' => 'application/x-iso9660-image',
        'itp' => 'application/vnd.shana.informed.formtemplate',
        'its' => 'application/its+xml',
        'ivp' => 'application/vnd.immervision-ivp',
        'ivu' => 'application/vnd.immervision-ivu',
        'jad' => 'text/vnd.sun.j2me.app-descriptor',
        'jade' => 'text/jade',
        'jam' => 'application/vnd.jam',
        'jar' => 'application/java-archive',
        'jardiff' => 'application/x-java-archive-diff',
        'java' => 'text/x-java-source',
        'jhc' => 'image/jphc',
        'jisp' => 'application/vnd.jisp',
        'jls' => 'image/jls',
        'jlt' => 'application/vnd.hp-jlyt',
        'jng' => 'image/x-jng',
        'jnlp' => 'application/x-java-jnlp-file',
        'joda' => 'application/vnd.joost.joda-archive',
        'jp2' => 'image/jp2',
        'jpe' => 'image/jpeg',
        'jpeg' => 'image/jpeg',
        'jpf' => 'image/jpx',
        'jpg' => 'image/jpeg',
        'jpg2' => 'image/jp2',
        'jpgm' => 'video/jpm',
        'jpgv' => 'video/jpeg',
        'jph' => 'image/jph',
        'jpm' => 'video/jpm',
        'jpx' => 'image/jpx',
        'js' => 'application/javascript',
        'json' => 'application/json',
        'json5' => 'application/json5',
        'jsonld' => 'application/ld+json',
        'jsonml' => 'application/jsonml+json',
        'jsx' => 'text/jsx',
        'jt' => 'model/jt',
        'jxr' => 'image/jxr',
        'jxra' => 'image/jxra',
        'jxrs' => 'image/jxrs',
        'jxs' => 'image/jxs',
        'jxsc' => 'image/jxsc',
        'jxsi' => 'image/jxsi',
        'jxss' => 'image/jxss',
        'kar' => 'audio/midi',
        'karbon' => 'application/vnd.kde.karbon',
        'kdb' => 'application/octet-stream',
        'kdbx' => 'application/x-keepass2',
        'key' => 'application/x-iwork-keynote-sffkey',
        'kfo' => 'application/vnd.kde.kformula',
        'kia' => 'application/vnd.kidspiration',
        'kml' => 'application/vnd.google-earth.kml+xml',
        'kmz' => 'application/vnd.google-earth.kmz',
        'kne' => 'application/vnd.kinar',
        'knp' => 'application/vnd.kinar',
        'kon' => 'application/vnd.kde.kontour',
        'kpr' => 'application/vnd.kde.kpresenter',
        'kpt' => 'application/vnd.kde.kpresenter',
        'kpxx' => 'application/vnd.ds-keypoint',
        'ksp' => 'application/vnd.kde.kspread',
        'ktr' => 'application/vnd.kahootz',
        'ktx' => 'image/ktx',
        'ktx2' => 'image/ktx2',
        'ktz' => 'application/vnd.kahootz',
        'kwd' => 'application/vnd.kde.kword',
        'kwt' => 'application/vnd.kde.kword',
        'lasxml' => 'application/vnd.las.las+xml',
        'latex' => 'application/x-latex',
        'lbd' => 'application/vnd.llamagraphics.life-balance.desktop',
        'lbe' => 'application/vnd.llamagraphics.life-balance.exchange+xml',
        'les' => 'application/vnd.hhe.lesson-player',
        'less' => 'text/less',
        'lgr' => 'application/lgr+xml',
        'lha' => 'application/octet-stream',
        'link66' => 'application/vnd.route66.link66+xml',
        'list' => 'text/plain',
        'list3820' => 'application/vnd.ibm.modcap',
        'listafp' => 'application/vnd.ibm.modcap',
        'litcoffee' => 'text/coffeescript',
        'lnk' => 'application/x-ms-shortcut',
        'log' => 'text/plain',
        'lostxml' => 'application/lost+xml',
        'lrf' => 'application/octet-stream',
        'lrm' => 'application/vnd.ms-lrm',
        'ltf' => 'application/vnd.frogans.ltf',
        'lua' => 'text/x-lua',
        'luac' => 'application/x-lua-bytecode',
        'lvp' => 'audio/vnd.lucent.voice',
        'lwp' => 'application/vnd.lotus-wordpro',
        'lzh' => 'application/octet-stream',
        'm1v' => 'video/mpeg',
        'm2a' => 'audio/mpeg',
        'm2v' => 'video/mpeg',
        'm3a' => 'audio/mpeg',
        'm3u' => 'text/plain',
        'm3u8' => 'application/vnd.apple.mpegurl',
        'm4a' => 'audio/x-m4a',
        'm4p' => 'application/mp4',
        'm4s' => 'video/iso.segment',
        'm4u' => 'application/vnd.mpegurl',
        'm4v' => 'video/x-m4v',
        'm13' => 'application/x-msmediaview',
        'm14' => 'application/x-msmediaview',
        'm21' => 'application/mp21',
        'ma' => 'application/mathematica',
        'mads' => 'application/mads+xml',
        'maei' => 'application/mmt-aei+xml',
        'mag' => 'application/vnd.ecowin.chart',
        'maker' => 'application/vnd.framemaker',
        'man' => 'text/troff',
        'manifest' => 'text/cache-manifest',
        'map' => 'application/json',
        'mar' => 'application/octet-stream',
        'markdown' => 'text/markdown',
        'mathml' => 'application/mathml+xml',
        'mb' => 'application/mathematica',
        'mbk' => 'application/vnd.mobius.mbk',
        'mbox' => 'application/mbox',
        'mc1' => 'application/vnd.medcalcdata',
        'mcd' => 'application/vnd.mcd',
        'mcurl' => 'text/vnd.curl.mcurl',
        'md' => 'text/markdown',
        'mdb' => 'application/x-msaccess',
        'mdi' => 'image/vnd.ms-modi',
        'mdx' => 'text/mdx',
        'me' => 'text/troff',
        'mesh' => 'model/mesh',
        'meta4' => 'application/metalink4+xml',
        'metalink' => 'application/metalink+xml',
        'mets' => 'application/mets+xml',
        'mfm' => 'application/vnd.mfmp',
        'mft' => 'application/rpki-manifest',
        'mgp' => 'application/vnd.osgeo.mapguide.package',
        'mgz' => 'application/vnd.proteus.magazine',
        'mid' => 'audio/midi',
        'midi' => 'audio/midi',
        'mie' => 'application/x-mie',
        'mif' => 'application/vnd.mif',
        'mime' => 'message/rfc822',
        'mj2' => 'video/mj2',
        'mjp2' => 'video/mj2',
        'mjs' => 'text/javascript',
        'mk3d' => 'video/x-matroska',
        'mka' => 'audio/x-matroska',
        'mkd' => 'text/x-markdown',
        'mks' => 'video/x-matroska',
        'mkv' => 'video/x-matroska',
        'mlp' => 'application/vnd.dolby.mlp',
        'mmd' => 'application/vnd.chipnuts.karaoke-mmd',
        'mmf' => 'application/vnd.smaf',
        'mml' => 'text/mathml',
        'mmr' => 'image/vnd.fujixerox.edmics-mmr',
        'mng' => 'video/x-mng',
        'mny' => 'application/x-msmoney',
        'mobi' => 'application/x-mobipocket-ebook',
        'mods' => 'application/mods+xml',
        'mov' => 'video/quicktime',
        'movie' => 'video/x-sgi-movie',
        'mp2' => 'audio/mpeg',
        'mp2a' => 'audio/mpeg',
        'mp3' => 'audio/mpeg',
        'mp4' => 'video/mp4',
        'mp4a' => 'audio/mp4',
        'mp4s' => 'application/mp4',
        'mp4v' => 'video/mp4',
        'mp21' => 'application/mp21',
        'mpc' => 'application/vnd.mophun.certificate',
        'mpd' => 'application/dash+xml',
        'mpe' => 'video/mpeg',
        'mpeg' => 'video/mpeg',
        'mpf' => 'application/media-policy-dataset+xml',
        'mpg' => 'video/mpeg',
        'mpg4' => 'video/mp4',
        'mpga' => 'audio/mpeg',
        'mpkg' => 'application/vnd.apple.installer+xml',
        'mpm' => 'application/vnd.blueice.multipass',
        'mpn' => 'application/vnd.mophun.application',
        'mpp' => 'application/vnd.ms-project',
        'mpt' => 'application/vnd.ms-project',
        'mpy' => 'application/vnd.ibm.minipay',
        'mqy' => 'application/vnd.mobius.mqy',
        'mrc' => 'application/marc',
        'mrcx' => 'application/marcxml+xml',
        'ms' => 'text/troff',
        'mscml' => 'application/mediaservercontrol+xml',
        'mseed' => 'application/vnd.fdsn.mseed',
        'mseq' => 'application/vnd.mseq',
        'msf' => 'application/vnd.epson.msf',
        'msg' => 'application/vnd.ms-outlook',
        'msh' => 'model/mesh',
        'msi' => 'application/x-msdownload',
        'msix' => 'application/msix',
        'msixbundle' => 'application/msixbundle',
        'msl' => 'application/vnd.mobius.msl',
        'msm' => 'application/octet-stream',
        'msp' => 'application/octet-stream',
        'msty' => 'application/vnd.muvee.style',
        'mtl' => 'model/mtl',
        'mts' => 'model/vnd.mts',
        'mus' => 'application/vnd.musician',
        'musd' => 'application/mmt-usd+xml',
        'musicxml' => 'application/vnd.recordare.musicxml+xml',
        'mvb' => 'application/x-msmediaview',
        'mvt' => 'application/vnd.mapbox-vector-tile',
        'mwf' => 'application/vnd.mfer',
        'mxf' => 'application/mxf',
        'mxl' => 'application/vnd.recordare.musicxml',
        'mxmf' => 'audio/mobile-xmf',
        'mxml' => 'application/xv+xml',
        'mxs' => 'application/vnd.triscape.mxs',
        'mxu' => 'video/vnd.mpegurl',
        'n-gage' => 'application/vnd.nokia.n-gage.symbian.install',
        'n3' => 'text/n3',
        'nb' => 'application/mathematica',
        'nbp' => 'application/vnd.wolfram.player',
        'nc' => 'application/x-netcdf',
        'ncx' => 'application/x-dtbncx+xml',
        'nfo' => 'text/x-nfo',
        'ngdat' => 'application/vnd.nokia.n-gage.data',
        'nitf' => 'application/vnd.nitf',
        'nlu' => 'application/vnd.neurolanguage.nlu',
        'nml' => 'application/vnd.enliven',
        'nnd' => 'application/vnd.noblenet-directory',
        'nns' => 'application/vnd.noblenet-sealer',
        'nnw' => 'application/vnd.noblenet-web',
        'npx' => 'image/vnd.net-fpx',
        'nq' => 'application/n-quads',
        'nsc' => 'application/x-conference',
        'nsf' => 'application/vnd.lotus-notes',
        'nt' => 'application/n-triples',
        'ntf' => 'application/vnd.nitf',
        'numbers' => 'application/x-iwork-numbers-sffnumbers',
        'nzb' => 'application/x-nzb',
        'oa2' => 'application/vnd.fujitsu.oasys2',
        'oa3' => 'application/vnd.fujitsu.oasys3',
        'oas' => 'application/vnd.fujitsu.oasys',
        'obd' => 'application/x-msbinder',
        'obgx' => 'application/vnd.openblox.game+xml',
        'obj' => 'model/obj',
        'oda' => 'application/oda',
        'odb' => 'application/vnd.oasis.opendocument.database',
        'odc' => 'application/vnd.oasis.opendocument.chart',
        'odf' => 'application/vnd.oasis.opendocument.formula',
        'odft' => 'application/vnd.oasis.opendocument.formula-template',
        'odg' => 'application/vnd.oasis.opendocument.graphics',
        'odi' => 'application/vnd.oasis.opendocument.image',
        'odm' => 'application/vnd.oasis.opendocument.text-master',
        'odp' => 'application/vnd.oasis.opendocument.presentation',
        'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
        'odt' => 'application/vnd.oasis.opendocument.text',
        'oga' => 'audio/ogg',
        'ogex' => 'model/vnd.opengex',
        'ogg' => 'audio/ogg',
        'ogv' => 'video/ogg',
        'ogx' => 'application/ogg',
        'omdoc' => 'application/omdoc+xml',
        'onepkg' => 'application/onenote',
        'onetmp' => 'application/onenote',
        'onetoc' => 'application/onenote',
        'onetoc2' => 'application/onenote',
        'opf' => 'application/oebps-package+xml',
        'opml' => 'text/x-opml',
        'oprc' => 'application/vnd.palm',
        'opus' => 'audio/ogg',
        'org' => 'text/x-org',
        'osf' => 'application/vnd.yamaha.openscoreformat',
        'osfpvg' => 'application/vnd.yamaha.openscoreformat.osfpvg+xml',
        'osm' => 'application/vnd.openstreetmap.data+xml',
        'otc' => 'application/vnd.oasis.opendocument.chart-template',
        'otf' => 'font/otf',
        'otg' => 'application/vnd.oasis.opendocument.graphics-template',
        'oth' => 'application/vnd.oasis.opendocument.text-web',
        'oti' => 'application/vnd.oasis.opendocument.image-template',
        'otp' => 'application/vnd.oasis.opendocument.presentation-template',
        'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template',
        'ott' => 'application/vnd.oasis.opendocument.text-template',
        'ova' => 'application/x-virtualbox-ova',
        'ovf' => 'application/x-virtualbox-ovf',
        'owl' => 'application/rdf+xml',
        'oxps' => 'application/oxps',
        'oxt' => 'application/vnd.openofficeorg.extension',
        'p' => 'text/x-pascal',
        'p7a' => 'application/x-pkcs7-signature',
        'p7b' => 'application/x-pkcs7-certificates',
        'p7c' => 'application/pkcs7-mime',
        'p7m' => 'application/pkcs7-mime',
        'p7r' => 'application/x-pkcs7-certreqresp',
        'p7s' => 'application/pkcs7-signature',
        'p8' => 'application/pkcs8',
        'p10' => 'application/x-pkcs10',
        'p12' => 'application/x-pkcs12',
        'pac' => 'application/x-ns-proxy-autoconfig',
        'pages' => 'application/x-iwork-pages-sffpages',
        'pas' => 'text/x-pascal',
        'paw' => 'application/vnd.pawaafile',
        'pbd' => 'application/vnd.powerbuilder6',
        'pbm' => 'image/x-portable-bitmap',
        'pcap' => 'application/vnd.tcpdump.pcap',
        'pcf' => 'application/x-font-pcf',
        'pcl' => 'application/vnd.hp-pcl',
        'pclxl' => 'application/vnd.hp-pclxl',
        'pct' => 'image/x-pict',
        'pcurl' => 'application/vnd.curl.pcurl',
        'pcx' => 'image/x-pcx',
        'pdb' => 'application/x-pilot',
        'pde' => 'text/x-processing',
        'pdf' => 'application/pdf',
        'pem' => 'application/x-x509-user-cert',
        'pfa' => 'application/x-font-type1',
        'pfb' => 'application/x-font-type1',
        'pfm' => 'application/x-font-type1',
        'pfr' => 'application/font-tdpfr',
        'pfx' => 'application/x-pkcs12',
        'pgm' => 'image/x-portable-graymap',
        'pgn' => 'application/x-chess-pgn',
        'pgp' => 'application/pgp',
        'phar' => 'application/octet-stream',
        'php' => 'application/x-httpd-php',
        'php3' => 'application/x-httpd-php',
        'php4' => 'application/x-httpd-php',
        'phps' => 'application/x-httpd-php-source',
        'phtml' => 'application/x-httpd-php',
        'pic' => 'image/x-pict',
        'pkg' => 'application/octet-stream',
        'pki' => 'application/pkixcmp',
        'pkipath' => 'application/pkix-pkipath',
        'pkpass' => 'application/vnd.apple.pkpass',
        'pl' => 'application/x-perl',
        'plb' => 'application/vnd.3gpp.pic-bw-large',
        'plc' => 'application/vnd.mobius.plc',
        'plf' => 'application/vnd.pocketlearn',
        'pls' => 'application/pls+xml',
        'pm' => 'application/x-perl',
        'pml' => 'application/vnd.ctc-posml',
        'png' => 'image/png',
        'pnm' => 'image/x-portable-anymap',
        'portpkg' => 'application/vnd.macports.portpkg',
        'pot' => 'application/vnd.ms-powerpoint',
        'potm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
        'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
        'ppa' => 'application/vnd.ms-powerpoint',
        'ppam' => 'application/vnd.ms-powerpoint.addin.macroEnabled.12',
        'ppd' => 'application/vnd.cups-ppd',
        'ppm' => 'image/x-portable-pixmap',
        'pps' => 'application/vnd.ms-powerpoint',
        'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12',
        'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
        'ppt' => 'application/powerpoint',
        'pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
        'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
        'pqa' => 'application/vnd.palm',
        'prc' => 'model/prc',
        'pre' => 'application/vnd.lotus-freelance',
        'prf' => 'application/pics-rules',
        'provx' => 'application/provenance+xml',
        'ps' => 'application/postscript',
        'psb' => 'application/vnd.3gpp.pic-bw-small',
        'psd' => 'application/x-photoshop',
        'psf' => 'application/x-font-linux-psf',
        'pskcxml' => 'application/pskc+xml',
        'pti' => 'image/prs.pti',
        'ptid' => 'application/vnd.pvi.ptid1',
        'pub' => 'application/x-mspublisher',
        'pvb' => 'application/vnd.3gpp.pic-bw-var',
        'pwn' => 'application/vnd.3m.post-it-notes',
        'pya' => 'audio/vnd.ms-playready.media.pya',
        'pyo' => 'model/vnd.pytha.pyox',
        'pyox' => 'model/vnd.pytha.pyox',
        'pyv' => 'video/vnd.ms-playready.media.pyv',
        'qam' => 'application/vnd.epson.quickanime',
        'qbo' => 'application/vnd.intu.qbo',
        'qfx' => 'application/vnd.intu.qfx',
        'qps' => 'application/vnd.publishare-delta-tree',
        'qt' => 'video/quicktime',
        'qwd' => 'application/vnd.quark.quarkxpress',
        'qwt' => 'application/vnd.quark.quarkxpress',
        'qxb' => 'application/vnd.quark.quarkxpress',
        'qxd' => 'application/vnd.quark.quarkxpress',
        'qxl' => 'application/vnd.quark.quarkxpress',
        'qxt' => 'application/vnd.quark.quarkxpress',
        'ra' => 'audio/x-realaudio',
        'ram' => 'audio/x-pn-realaudio',
        'raml' => 'application/raml+yaml',
        'rapd' => 'application/route-apd+xml',
        'rar' => 'application/x-rar',
        'ras' => 'image/x-cmu-raster',
        'rcprofile' => 'application/vnd.ipunplugged.rcprofile',
        'rdf' => 'application/rdf+xml',
        'rdz' => 'application/vnd.data-vision.rdz',
        'relo' => 'application/p2p-overlay+xml',
        'rep' => 'application/vnd.businessobjects',
        'res' => 'application/x-dtbresource+xml',
        'rgb' => 'image/x-rgb',
        'rif' => 'application/reginfo+xml',
        'rip' => 'audio/vnd.rip',
        'ris' => 'application/x-research-info-systems',
        'rl' => 'application/resource-lists+xml',
        'rlc' => 'image/vnd.fujixerox.edmics-rlc',
        'rld' => 'application/resource-lists-diff+xml',
        'rm' => 'audio/x-pn-realaudio',
        'rmi' => 'audio/midi',
        'rmp' => 'audio/x-pn-realaudio-plugin',
        'rms' => 'application/vnd.jcp.javame.midlet-rms',
        'rmvb' => 'application/vnd.rn-realmedia-vbr',
        'rnc' => 'application/relax-ng-compact-syntax',
        'rng' => 'application/xml',
        'roa' => 'application/rpki-roa',
        'roff' => 'text/troff',
        'rp9' => 'application/vnd.cloanto.rp9',
        'rpm' => 'audio/x-pn-realaudio-plugin',
        'rpss' => 'application/vnd.nokia.radio-presets',
        'rpst' => 'application/vnd.nokia.radio-preset',
        'rq' => 'application/sparql-query',
        'rs' => 'application/rls-services+xml',
        'rsa' => 'application/x-pkcs7',
        'rsat' => 'application/atsc-rsat+xml',
        'rsd' => 'application/rsd+xml',
        'rsheet' => 'application/urc-ressheet+xml',
        'rss' => 'application/rss+xml',
        'rtf' => 'text/rtf',
        'rtx' => 'text/richtext',
        'run' => 'application/x-makeself',
        'rusd' => 'application/route-usd+xml',
        'rv' => 'video/vnd.rn-realvideo',
        's' => 'text/x-asm',
        's3m' => 'audio/s3m',
        'saf' => 'application/vnd.yamaha.smaf-audio',
        'sass' => 'text/x-sass',
        'sbml' => 'application/sbml+xml',
        'sc' => 'application/vnd.ibm.secure-container',
        'scd' => 'application/x-msschedule',
        'scm' => 'application/vnd.lotus-screencam',
        'scq' => 'application/scvp-cv-request',
        'scs' => 'application/scvp-cv-response',
        'scss' => 'text/x-scss',
        'scurl' => 'text/vnd.curl.scurl',
        'sda' => 'application/vnd.stardivision.draw',
        'sdc' => 'application/vnd.stardivision.calc',
        'sdd' => 'application/vnd.stardivision.impress',
        'sdkd' => 'application/vnd.solent.sdkm+xml',
        'sdkm' => 'application/vnd.solent.sdkm+xml',
        'sdp' => 'application/sdp',
        'sdw' => 'application/vnd.stardivision.writer',
        'sea' => 'application/octet-stream',
        'see' => 'application/vnd.seemail',
        'seed' => 'application/vnd.fdsn.seed',
        'sema' => 'application/vnd.sema',
        'semd' => 'application/vnd.semd',
        'semf' => 'application/vnd.semf',
        'senmlx' => 'application/senml+xml',
        'sensmlx' => 'application/sensml+xml',
        'ser' => 'application/java-serialized-object',
        'setpay' => 'application/set-payment-initiation',
        'setreg' => 'application/set-registration-initiation',
        'sfd-hdstx' => 'application/vnd.hydrostatix.sof-data',
        'sfs' => 'application/vnd.spotfire.sfs',
        'sfv' => 'text/x-sfv',
        'sgi' => 'image/sgi',
        'sgl' => 'application/vnd.stardivision.writer-global',
        'sgm' => 'text/sgml',
        'sgml' => 'text/sgml',
        'sh' => 'application/x-sh',
        'shar' => 'application/x-shar',
        'shex' => 'text/shex',
        'shf' => 'application/shf+xml',
        'shtml' => 'text/html',
        'sid' => 'image/x-mrsid-image',
        'sieve' => 'application/sieve',
        'sig' => 'application/pgp-signature',
        'sil' => 'audio/silk',
        'silo' => 'model/mesh',
        'sis' => 'application/vnd.symbian.install',
        'sisx' => 'application/vnd.symbian.install',
        'sit' => 'application/x-stuffit',
        'sitx' => 'application/x-stuffitx',
        'siv' => 'application/sieve',
        'skd' => 'application/vnd.koan',
        'skm' => 'application/vnd.koan',
        'skp' => 'application/vnd.koan',
        'skt' => 'application/vnd.koan',
        'sldm' => 'application/vnd.ms-powerpoint.slide.macroenabled.12',
        'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
        'slim' => 'text/slim',
        'slm' => 'text/slim',
        'sls' => 'application/route-s-tsid+xml',
        'slt' => 'application/vnd.epson.salt',
        'sm' => 'application/vnd.stepmania.stepchart',
        'smf' => 'application/vnd.stardivision.math',
        'smi' => 'application/smil',
        'smil' => 'application/smil',
        'smv' => 'video/x-smv',
        'smzip' => 'application/vnd.stepmania.package',
        'snd' => 'audio/basic',
        'snf' => 'application/x-font-snf',
        'so' => 'application/octet-stream',
        'spc' => 'application/x-pkcs7-certificates',
        'spdx' => 'text/spdx',
        'spf' => 'application/vnd.yamaha.smaf-phrase',
        'spl' => 'application/x-futuresplash',
        'spot' => 'text/vnd.in3d.spot',
        'spp' => 'application/scvp-vp-response',
        'spq' => 'application/scvp-vp-request',
        'spx' => 'audio/ogg',
        'sql' => 'application/x-sql',
        'src' => 'application/x-wais-source',
        'srt' => 'application/x-subrip',
        'sru' => 'application/sru+xml',
        'srx' => 'application/sparql-results+xml',
        'ssdl' => 'application/ssdl+xml',
        'sse' => 'application/vnd.kodak-descriptor',
        'ssf' => 'application/vnd.epson.ssf',
        'ssml' => 'application/ssml+xml',
        'sst' => 'application/octet-stream',
        'st' => 'application/vnd.sailingtracker.track',
        'stc' => 'application/vnd.sun.xml.calc.template',
        'std' => 'application/vnd.sun.xml.draw.template',
        'step' => 'application/STEP',
        'stf' => 'application/vnd.wt.stf',
        'sti' => 'application/vnd.sun.xml.impress.template',
        'stk' => 'application/hyperstudio',
        'stl' => 'model/stl',
        'stp' => 'application/STEP',
        'stpx' => 'model/step+xml',
        'stpxz' => 'model/step-xml+zip',
        'stpz' => 'model/step+zip',
        'str' => 'application/vnd.pg.format',
        'stw' => 'application/vnd.sun.xml.writer.template',
        'styl' => 'text/stylus',
        'stylus' => 'text/stylus',
        'sub' => 'text/vnd.dvb.subtitle',
        'sus' => 'application/vnd.sus-calendar',
        'susp' => 'application/vnd.sus-calendar',
        'sv4cpio' => 'application/x-sv4cpio',
        'sv4crc' => 'application/x-sv4crc',
        'svc' => 'application/vnd.dvb.service',
        'svd' => 'application/vnd.svd',
        'svg' => 'image/svg+xml',
        'svgz' => 'image/svg+xml',
        'swa' => 'application/x-director',
        'swf' => 'application/x-shockwave-flash',
        'swi' => 'application/vnd.aristanetworks.swi',
        'swidtag' => 'application/swid+xml',
        'sxc' => 'application/vnd.sun.xml.calc',
        'sxd' => 'application/vnd.sun.xml.draw',
        'sxg' => 'application/vnd.sun.xml.writer.global',
        'sxi' => 'application/vnd.sun.xml.impress',
        'sxm' => 'application/vnd.sun.xml.math',
        'sxw' => 'application/vnd.sun.xml.writer',
        't' => 'text/troff',
        't3' => 'application/x-t3vm-image',
        't38' => 'image/t38',
        'taglet' => 'application/vnd.mynfc',
        'tao' => 'application/vnd.tao.intent-module-archive',
        'tap' => 'image/vnd.tencent.tap',
        'tar' => 'application/x-tar',
        'tcap' => 'application/vnd.3gpp2.tcap',
        'tcl' => 'application/x-tcl',
        'td' => 'application/urc-targetdesc+xml',
        'teacher' => 'application/vnd.smart.teacher',
        'tei' => 'application/tei+xml',
        'teicorpus' => 'application/tei+xml',
        'tex' => 'application/x-tex',
        'texi' => 'application/x-texinfo',
        'texinfo' => 'application/x-texinfo',
        'text' => 'text/plain',
        'tfi' => 'application/thraud+xml',
        'tfm' => 'application/x-tex-tfm',
        'tfx' => 'image/tiff-fx',
        'tga' => 'image/x-tga',
        'tgz' => 'application/x-tar',
        'thmx' => 'application/vnd.ms-officetheme',
        'tif' => 'image/tiff',
        'tiff' => 'image/tiff',
        'tk' => 'application/x-tcl',
        'tmo' => 'application/vnd.tmobile-livetv',
        'toml' => 'application/toml',
        'torrent' => 'application/x-bittorrent',
        'tpl' => 'application/vnd.groove-tool-template',
        'tpt' => 'application/vnd.trid.tpt',
        'tr' => 'text/troff',
        'tra' => 'application/vnd.trueapp',
        'trig' => 'application/trig',
        'trm' => 'application/x-msterminal',
        'ts' => 'video/mp2t',
        'tsd' => 'application/timestamped-data',
        'tsv' => 'text/tab-separated-values',
        'ttc' => 'font/collection',
        'ttf' => 'font/ttf',
        'ttl' => 'text/turtle',
        'ttml' => 'application/ttml+xml',
        'twd' => 'application/vnd.simtech-mindmapper',
        'twds' => 'application/vnd.simtech-mindmapper',
        'txd' => 'application/vnd.genomatix.tuxedo',
        'txf' => 'application/vnd.mobius.txf',
        'txt' => 'text/plain',
        'u3d' => 'model/u3d',
        'u8dsn' => 'message/global-delivery-status',
        'u8hdr' => 'message/global-headers',
        'u8mdn' => 'message/global-disposition-notification',
        'u8msg' => 'message/global',
        'u32' => 'application/x-authorware-bin',
        'ubj' => 'application/ubjson',
        'udeb' => 'application/x-debian-package',
        'ufd' => 'application/vnd.ufdl',
        'ufdl' => 'application/vnd.ufdl',
        'ulx' => 'application/x-glulx',
        'umj' => 'application/vnd.umajin',
        'unityweb' => 'application/vnd.unity',
        'uo' => 'application/vnd.uoml+xml',
        'uoml' => 'application/vnd.uoml+xml',
        'uri' => 'text/uri-list',
        'uris' => 'text/uri-list',
        'urls' => 'text/uri-list',
        'usda' => 'model/vnd.usda',
        'usdz' => 'model/vnd.usdz+zip',
        'ustar' => 'application/x-ustar',
        'utz' => 'application/vnd.uiq.theme',
        'uu' => 'text/x-uuencode',
        'uva' => 'audio/vnd.dece.audio',
        'uvd' => 'application/vnd.dece.data',
        'uvf' => 'application/vnd.dece.data',
        'uvg' => 'image/vnd.dece.graphic',
        'uvh' => 'video/vnd.dece.hd',
        'uvi' => 'image/vnd.dece.graphic',
        'uvm' => 'video/vnd.dece.mobile',
        'uvp' => 'video/vnd.dece.pd',
        'uvs' => 'video/vnd.dece.sd',
        'uvt' => 'application/vnd.dece.ttml+xml',
        'uvu' => 'video/vnd.uvvu.mp4',
        'uvv' => 'video/vnd.dece.video',
        'uvva' => 'audio/vnd.dece.audio',
        'uvvd' => 'application/vnd.dece.data',
        'uvvf' => 'application/vnd.dece.data',
        'uvvg' => 'image/vnd.dece.graphic',
        'uvvh' => 'video/vnd.dece.hd',
        'uvvi' => 'image/vnd.dece.graphic',
        'uvvm' => 'video/vnd.dece.mobile',
        'uvvp' => 'video/vnd.dece.pd',
        'uvvs' => 'video/vnd.dece.sd',
        'uvvt' => 'application/vnd.dece.ttml+xml',
        'uvvu' => 'video/vnd.uvvu.mp4',
        'uvvv' => 'video/vnd.dece.video',
        'uvvx' => 'application/vnd.dece.unspecified',
        'uvvz' => 'application/vnd.dece.zip',
        'uvx' => 'application/vnd.dece.unspecified',
        'uvz' => 'application/vnd.dece.zip',
        'vbox' => 'application/x-virtualbox-vbox',
        'vbox-extpack' => 'application/x-virtualbox-vbox-extpack',
        'vcard' => 'text/vcard',
        'vcd' => 'application/x-cdlink',
        'vcf' => 'text/x-vcard',
        'vcg' => 'application/vnd.groove-vcard',
        'vcs' => 'text/x-vcalendar',
        'vcx' => 'application/vnd.vcx',
        'vdi' => 'application/x-virtualbox-vdi',
        'vds' => 'model/vnd.sap.vds',
        'vhd' => 'application/x-virtualbox-vhd',
        'vis' => 'application/vnd.visionary',
        'viv' => 'video/vnd.vivo',
        'vlc' => 'application/videolan',
        'vmdk' => 'application/x-virtualbox-vmdk',
        'vob' => 'video/x-ms-vob',
        'vor' => 'application/vnd.stardivision.writer',
        'vox' => 'application/x-authorware-bin',
        'vrml' => 'model/vrml',
        'vsd' => 'application/vnd.visio',
        'vsf' => 'application/vnd.vsf',
        'vss' => 'application/vnd.visio',
        'vst' => 'application/vnd.visio',
        'vsw' => 'application/vnd.visio',
        'vtf' => 'image/vnd.valve.source.texture',
        'vtt' => 'text/vtt',
        'vtu' => 'model/vnd.vtu',
        'vxml' => 'application/voicexml+xml',
        'w3d' => 'application/x-director',
        'wad' => 'application/x-doom',
        'wadl' => 'application/vnd.sun.wadl+xml',
        'war' => 'application/java-archive',
        'wasm' => 'application/wasm',
        'wav' => 'audio/x-wav',
        'wax' => 'audio/x-ms-wax',
        'wbmp' => 'image/vnd.wap.wbmp',
        'wbs' => 'application/vnd.criticaltools.wbs+xml',
        'wbxml' => 'application/wbxml',
        'wcm' => 'application/vnd.ms-works',
        'wdb' => 'application/vnd.ms-works',
        'wdp' => 'image/vnd.ms-photo',
        'weba' => 'audio/webm',
        'webapp' => 'application/x-web-app-manifest+json',
        'webm' => 'video/webm',
        'webmanifest' => 'application/manifest+json',
        'webp' => 'image/webp',
        'wg' => 'application/vnd.pmi.widget',
        'wgsl' => 'text/wgsl',
        'wgt' => 'application/widget',
        'wif' => 'application/watcherinfo+xml',
        'wks' => 'application/vnd.ms-works',
        'wm' => 'video/x-ms-wm',
        'wma' => 'audio/x-ms-wma',
        'wmd' => 'application/x-ms-wmd',
        'wmf' => 'image/wmf',
        'wml' => 'text/vnd.wap.wml',
        'wmlc' => 'application/wmlc',
        'wmls' => 'text/vnd.wap.wmlscript',
        'wmlsc' => 'application/vnd.wap.wmlscriptc',
        'wmv' => 'video/x-ms-wmv',
        'wmx' => 'video/x-ms-wmx',
        'wmz' => 'application/x-msmetafile',
        'woff' => 'font/woff',
        'woff2' => 'font/woff2',
        'word' => 'application/msword',
        'wpd' => 'application/vnd.wordperfect',
        'wpl' => 'application/vnd.ms-wpl',
        'wps' => 'application/vnd.ms-works',
        'wqd' => 'application/vnd.wqd',
        'wri' => 'application/x-mswrite',
        'wrl' => 'model/vrml',
        'wsc' => 'message/vnd.wfa.wsc',
        'wsdl' => 'application/wsdl+xml',
        'wspolicy' => 'application/wspolicy+xml',
        'wtb' => 'application/vnd.webturbo',
        'wvx' => 'video/x-ms-wvx',
        'x3d' => 'model/x3d+xml',
        'x3db' => 'model/x3d+fastinfoset',
        'x3dbz' => 'model/x3d+binary',
        'x3dv' => 'model/x3d-vrml',
        'x3dvz' => 'model/x3d+vrml',
        'x3dz' => 'model/x3d+xml',
        'x32' => 'application/x-authorware-bin',
        'x_b' => 'model/vnd.parasolid.transmit.binary',
        'x_t' => 'model/vnd.parasolid.transmit.text',
        'xaml' => 'application/xaml+xml',
        'xap' => 'application/x-silverlight-app',
        'xar' => 'application/vnd.xara',
        'xav' => 'application/xcap-att+xml',
        'xbap' => 'application/x-ms-xbap',
        'xbd' => 'application/vnd.fujixerox.docuworks.binder',
        'xbm' => 'image/x-xbitmap',
        'xca' => 'application/xcap-caps+xml',
        'xcs' => 'application/calendar+xml',
        'xdf' => 'application/xcap-diff+xml',
        'xdm' => 'application/vnd.syncml.dm+xml',
        'xdp' => 'application/vnd.adobe.xdp+xml',
        'xdssc' => 'application/dssc+xml',
        'xdw' => 'application/vnd.fujixerox.docuworks',
        'xel' => 'application/xcap-el+xml',
        'xenc' => 'application/xenc+xml',
        'xer' => 'application/patch-ops-error+xml',
        'xfdf' => 'application/xfdf',
        'xfdl' => 'application/vnd.xfdl',
        'xht' => 'application/xhtml+xml',
        'xhtm' => 'application/vnd.pwg-xhtml-print+xml',
        'xhtml' => 'application/xhtml+xml',
        'xhvml' => 'application/xv+xml',
        'xif' => 'image/vnd.xiff',
        'xl' => 'application/excel',
        'xla' => 'application/vnd.ms-excel',
        'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
        'xlc' => 'application/vnd.ms-excel',
        'xlf' => 'application/xliff+xml',
        'xlm' => 'application/vnd.ms-excel',
        'xls' => 'application/vnd.ms-excel',
        'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
        'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12',
        'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
        'xlt' => 'application/vnd.ms-excel',
        'xltm' => 'application/vnd.ms-excel.template.macroEnabled.12',
        'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
        'xlw' => 'application/vnd.ms-excel',
        'xm' => 'audio/xm',
        'xml' => 'application/xml',
        'xns' => 'application/xcap-ns+xml',
        'xo' => 'application/vnd.olpc-sugar',
        'xop' => 'application/xop+xml',
        'xpi' => 'application/x-xpinstall',
        'xpl' => 'application/xproc+xml',
        'xpm' => 'image/x-xpixmap',
        'xpr' => 'application/vnd.is-xpr',
        'xps' => 'application/vnd.ms-xpsdocument',
        'xpw' => 'application/vnd.intercon.formnet',
        'xpx' => 'application/vnd.intercon.formnet',
        'xsd' => 'application/xml',
        'xsf' => 'application/prs.xsf+xml',
        'xsl' => 'application/xml',
        'xslt' => 'application/xslt+xml',
        'xsm' => 'application/vnd.syncml+xml',
        'xspf' => 'application/xspf+xml',
        'xul' => 'application/vnd.mozilla.xul+xml',
        'xvm' => 'application/xv+xml',
        'xvml' => 'application/xv+xml',
        'xwd' => 'image/x-xwindowdump',
        'xyz' => 'chemical/x-xyz',
        'xz' => 'application/x-xz',
        'yaml' => 'text/yaml',
        'yang' => 'application/yang',
        'yin' => 'application/yin+xml',
        'yml' => 'text/yaml',
        'ymp' => 'text/x-suse-ymp',
        'z' => 'application/x-compress',
        'z1' => 'application/x-zmachine',
        'z2' => 'application/x-zmachine',
        'z3' => 'application/x-zmachine',
        'z4' => 'application/x-zmachine',
        'z5' => 'application/x-zmachine',
        'z6' => 'application/x-zmachine',
        'z7' => 'application/x-zmachine',
        'z8' => 'application/x-zmachine',
        'zaz' => 'application/vnd.zzazz.deck+xml',
        'zip' => 'application/zip',
        'zir' => 'application/vnd.zul',
        'zirz' => 'application/vnd.zul',
        'zmm' => 'application/vnd.handheld-entertainment+xml',
        'zsh' => 'text/x-scriptzsh',
    ];

    /**
     * Determines the mimetype of a file by looking at its extension.
     *
     * @see https://raw.githubusercontent.com/jshttp/mime-db/master/db.json
     */
    public static function fromFilename(string $filename): ?string
    {
        return self::fromExtension(pathinfo($filename, PATHINFO_EXTENSION));
    }

    /**
     * Maps a file extensions to a mimetype.
     *
     * @see https://raw.githubusercontent.com/jshttp/mime-db/master/db.json
     */
    public static function fromExtension(string $extension): ?string
    {
        return self::MIME_TYPES[strtolower($extension)] ?? null;
    }
}
PK�c�\�hiD#guzzlehttp/psr7/src/HttpFactory.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Psr7;

use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestFactoryInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UploadedFileFactoryInterface;
use Psr\Http\Message\UploadedFileInterface;
use Psr\Http\Message\UriFactoryInterface;
use Psr\Http\Message\UriInterface;

/**
 * Implements all of the PSR-17 interfaces.
 *
 * Note: in consuming code it is recommended to require the implemented interfaces
 * and inject the instance of this class multiple times.
 */
final class HttpFactory implements RequestFactoryInterface, ResponseFactoryInterface, ServerRequestFactoryInterface, StreamFactoryInterface, UploadedFileFactoryInterface, UriFactoryInterface
{
    public function createUploadedFile(
        StreamInterface $stream,
        int $size = null,
        int $error = \UPLOAD_ERR_OK,
        string $clientFilename = null,
        string $clientMediaType = null
    ): UploadedFileInterface {
        if ($size === null) {
            $size = $stream->getSize();
        }

        return new UploadedFile($stream, $size, $error, $clientFilename, $clientMediaType);
    }

    public function createStream(string $content = ''): StreamInterface
    {
        return Utils::streamFor($content);
    }

    public function createStreamFromFile(string $file, string $mode = 'r'): StreamInterface
    {
        try {
            $resource = Utils::tryFopen($file, $mode);
        } catch (\RuntimeException $e) {
            if ('' === $mode || false === \in_array($mode[0], ['r', 'w', 'a', 'x', 'c'], true)) {
                throw new \InvalidArgumentException(sprintf('Invalid file opening mode "%s"', $mode), 0, $e);
            }

            throw $e;
        }

        return Utils::streamFor($resource);
    }

    public function createStreamFromResource($resource): StreamInterface
    {
        return Utils::streamFor($resource);
    }

    public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface
    {
        if (empty($method)) {
            if (!empty($serverParams['REQUEST_METHOD'])) {
                $method = $serverParams['REQUEST_METHOD'];
            } else {
                throw new \InvalidArgumentException('Cannot determine HTTP method');
            }
        }

        return new ServerRequest($method, $uri, [], null, '1.1', $serverParams);
    }

    public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface
    {
        return new Response($code, [], null, '1.1', $reasonPhrase);
    }

    public function createRequest(string $method, $uri): RequestInterface
    {
        return new Request($method, $uri);
    }

    public function createUri(string $uri = ''): UriInterface
    {
        return new Uri($uri);
    }
}
PK�c�\�Dy��$guzzlehttp/psr7/src/BufferStream.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Provides a buffer stream that can be written to to fill a buffer, and read
 * from to remove bytes from the buffer.
 *
 * This stream returns a "hwm" metadata value that tells upstream consumers
 * what the configured high water mark of the stream is, or the maximum
 * preferred size of the buffer.
 */
final class BufferStream implements StreamInterface
{
    /** @var int */
    private $hwm;

    /** @var string */
    private $buffer = '';

    /**
     * @param int $hwm High water mark, representing the preferred maximum
     *                 buffer size. If the size of the buffer exceeds the high
     *                 water mark, then calls to write will continue to succeed
     *                 but will return 0 to inform writers to slow down
     *                 until the buffer has been drained by reading from it.
     */
    public function __construct(int $hwm = 16384)
    {
        $this->hwm = $hwm;
    }

    public function __toString(): string
    {
        return $this->getContents();
    }

    public function getContents(): string
    {
        $buffer = $this->buffer;
        $this->buffer = '';

        return $buffer;
    }

    public function close(): void
    {
        $this->buffer = '';
    }

    public function detach()
    {
        $this->close();

        return null;
    }

    public function getSize(): ?int
    {
        return strlen($this->buffer);
    }

    public function isReadable(): bool
    {
        return true;
    }

    public function isWritable(): bool
    {
        return true;
    }

    public function isSeekable(): bool
    {
        return false;
    }

    public function rewind(): void
    {
        $this->seek(0);
    }

    public function seek($offset, $whence = SEEK_SET): void
    {
        throw new \RuntimeException('Cannot seek a BufferStream');
    }

    public function eof(): bool
    {
        return strlen($this->buffer) === 0;
    }

    public function tell(): int
    {
        throw new \RuntimeException('Cannot determine the position of a BufferStream');
    }

    /**
     * Reads data from the buffer.
     */
    public function read($length): string
    {
        $currentLength = strlen($this->buffer);

        if ($length >= $currentLength) {
            // No need to slice the buffer because we don't have enough data.
            $result = $this->buffer;
            $this->buffer = '';
        } else {
            // Slice up the result to provide a subset of the buffer.
            $result = substr($this->buffer, 0, $length);
            $this->buffer = substr($this->buffer, $length);
        }

        return $result;
    }

    /**
     * Writes data to the buffer.
     */
    public function write($string): int
    {
        $this->buffer .= $string;

        if (strlen($this->buffer) >= $this->hwm) {
            return 0;
        }

        return strlen($string);
    }

    /**
     * @return mixed
     */
    public function getMetadata($key = null)
    {
        if ($key === 'hwm') {
            return $this->hwm;
        }

        return $key ? null : [];
    }
}
PK�c�\�-�M%M%%guzzlehttp/psr7/src/ServerRequest.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Psr7;

use InvalidArgumentException;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UploadedFileInterface;
use Psr\Http\Message\UriInterface;

/**
 * Server-side HTTP request
 *
 * Extends the Request definition to add methods for accessing incoming data,
 * specifically server parameters, cookies, matched path parameters, query
 * string arguments, body parameters, and upload file information.
 *
 * "Attributes" are discovered via decomposing the request (and usually
 * specifically the URI path), and typically will be injected by the application.
 *
 * Requests are considered immutable; all methods that might change state are
 * implemented such that they retain the internal state of the current
 * message and return a new instance that contains the changed state.
 */
class ServerRequest extends Request implements ServerRequestInterface
{
    /**
     * @var array
     */
    private $attributes = [];

    /**
     * @var array
     */
    private $cookieParams = [];

    /**
     * @var array|object|null
     */
    private $parsedBody;

    /**
     * @var array
     */
    private $queryParams = [];

    /**
     * @var array
     */
    private $serverParams;

    /**
     * @var array
     */
    private $uploadedFiles = [];

    /**
     * @param string                               $method       HTTP method
     * @param string|UriInterface                  $uri          URI
     * @param (string|string[])[]                  $headers      Request headers
     * @param string|resource|StreamInterface|null $body         Request body
     * @param string                               $version      Protocol version
     * @param array                                $serverParams Typically the $_SERVER superglobal
     */
    public function __construct(
        string $method,
        $uri,
        array $headers = [],
        $body = null,
        string $version = '1.1',
        array $serverParams = []
    ) {
        $this->serverParams = $serverParams;

        parent::__construct($method, $uri, $headers, $body, $version);
    }

    /**
     * Return an UploadedFile instance array.
     *
     * @param array $files An array which respect $_FILES structure
     *
     * @throws InvalidArgumentException for unrecognized values
     */
    public static function normalizeFiles(array $files): array
    {
        $normalized = [];

        foreach ($files as $key => $value) {
            if ($value instanceof UploadedFileInterface) {
                $normalized[$key] = $value;
            } elseif (is_array($value) && isset($value['tmp_name'])) {
                $normalized[$key] = self::createUploadedFileFromSpec($value);
            } elseif (is_array($value)) {
                $normalized[$key] = self::normalizeFiles($value);
                continue;
            } else {
                throw new InvalidArgumentException('Invalid value in files specification');
            }
        }

        return $normalized;
    }

    /**
     * Create and return an UploadedFile instance from a $_FILES specification.
     *
     * If the specification represents an array of values, this method will
     * delegate to normalizeNestedFileSpec() and return that return value.
     *
     * @param array $value $_FILES struct
     *
     * @return UploadedFileInterface|UploadedFileInterface[]
     */
    private static function createUploadedFileFromSpec(array $value)
    {
        if (is_array($value['tmp_name'])) {
            return self::normalizeNestedFileSpec($value);
        }

        return new UploadedFile(
            $value['tmp_name'],
            (int) $value['size'],
            (int) $value['error'],
            $value['name'],
            $value['type']
        );
    }

    /**
     * Normalize an array of file specifications.
     *
     * Loops through all nested files and returns a normalized array of
     * UploadedFileInterface instances.
     *
     * @return UploadedFileInterface[]
     */
    private static function normalizeNestedFileSpec(array $files = []): array
    {
        $normalizedFiles = [];

        foreach (array_keys($files['tmp_name']) as $key) {
            $spec = [
                'tmp_name' => $files['tmp_name'][$key],
                'size' => $files['size'][$key] ?? null,
                'error' => $files['error'][$key] ?? null,
                'name' => $files['name'][$key] ?? null,
                'type' => $files['type'][$key] ?? null,
            ];
            $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec);
        }

        return $normalizedFiles;
    }

    /**
     * Return a ServerRequest populated with superglobals:
     * $_GET
     * $_POST
     * $_COOKIE
     * $_FILES
     * $_SERVER
     */
    public static function fromGlobals(): ServerRequestInterface
    {
        $method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
        $headers = getallheaders();
        $uri = self::getUriFromGlobals();
        $body = new CachingStream(new LazyOpenStream('php://input', 'r+'));
        $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? str_replace('HTTP/', '', $_SERVER['SERVER_PROTOCOL']) : '1.1';

        $serverRequest = new ServerRequest($method, $uri, $headers, $body, $protocol, $_SERVER);

        return $serverRequest
            ->withCookieParams($_COOKIE)
            ->withQueryParams($_GET)
            ->withParsedBody($_POST)
            ->withUploadedFiles(self::normalizeFiles($_FILES));
    }

    private static function extractHostAndPortFromAuthority(string $authority): array
    {
        $uri = 'http://'.$authority;
        $parts = parse_url($uri);
        if (false === $parts) {
            return [null, null];
        }

        $host = $parts['host'] ?? null;
        $port = $parts['port'] ?? null;

        return [$host, $port];
    }

    /**
     * Get a Uri populated with values from $_SERVER.
     */
    public static function getUriFromGlobals(): UriInterface
    {
        $uri = new Uri('');

        $uri = $uri->withScheme(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http');

        $hasPort = false;
        if (isset($_SERVER['HTTP_HOST'])) {
            [$host, $port] = self::extractHostAndPortFromAuthority($_SERVER['HTTP_HOST']);
            if ($host !== null) {
                $uri = $uri->withHost($host);
            }

            if ($port !== null) {
                $hasPort = true;
                $uri = $uri->withPort($port);
            }
        } elseif (isset($_SERVER['SERVER_NAME'])) {
            $uri = $uri->withHost($_SERVER['SERVER_NAME']);
        } elseif (isset($_SERVER['SERVER_ADDR'])) {
            $uri = $uri->withHost($_SERVER['SERVER_ADDR']);
        }

        if (!$hasPort && isset($_SERVER['SERVER_PORT'])) {
            $uri = $uri->withPort($_SERVER['SERVER_PORT']);
        }

        $hasQuery = false;
        if (isset($_SERVER['REQUEST_URI'])) {
            $requestUriParts = explode('?', $_SERVER['REQUEST_URI'], 2);
            $uri = $uri->withPath($requestUriParts[0]);
            if (isset($requestUriParts[1])) {
                $hasQuery = true;
                $uri = $uri->withQuery($requestUriParts[1]);
            }
        }

        if (!$hasQuery && isset($_SERVER['QUERY_STRING'])) {
            $uri = $uri->withQuery($_SERVER['QUERY_STRING']);
        }

        return $uri;
    }

    public function getServerParams(): array
    {
        return $this->serverParams;
    }

    public function getUploadedFiles(): array
    {
        return $this->uploadedFiles;
    }

    public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface
    {
        $new = clone $this;
        $new->uploadedFiles = $uploadedFiles;

        return $new;
    }

    public function getCookieParams(): array
    {
        return $this->cookieParams;
    }

    public function withCookieParams(array $cookies): ServerRequestInterface
    {
        $new = clone $this;
        $new->cookieParams = $cookies;

        return $new;
    }

    public function getQueryParams(): array
    {
        return $this->queryParams;
    }

    public function withQueryParams(array $query): ServerRequestInterface
    {
        $new = clone $this;
        $new->queryParams = $query;

        return $new;
    }

    /**
     * @return array|object|null
     */
    public function getParsedBody()
    {
        return $this->parsedBody;
    }

    public function withParsedBody($data): ServerRequestInterface
    {
        $new = clone $this;
        $new->parsedBody = $data;

        return $new;
    }

    public function getAttributes(): array
    {
        return $this->attributes;
    }

    /**
     * @return mixed
     */
    public function getAttribute($attribute, $default = null)
    {
        if (false === array_key_exists($attribute, $this->attributes)) {
            return $default;
        }

        return $this->attributes[$attribute];
    }

    public function withAttribute($attribute, $value): ServerRequestInterface
    {
        $new = clone $this;
        $new->attributes[$attribute] = $value;

        return $new;
    }

    public function withoutAttribute($attribute): ServerRequestInterface
    {
        if (false === array_key_exists($attribute, $this->attributes)) {
            return $this;
        }

        $new = clone $this;
        unset($new->attributes[$attribute]);

        return $new;
    }
}
PK�c�\������%guzzlehttp/psr7/src/CachingStream.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Stream decorator that can cache previously read bytes from a sequentially
 * read stream.
 */
final class CachingStream implements StreamInterface
{
    use StreamDecoratorTrait;

    /** @var StreamInterface Stream being wrapped */
    private $remoteStream;

    /** @var int Number of bytes to skip reading due to a write on the buffer */
    private $skipReadBytes = 0;

    /**
     * @var StreamInterface
     */
    private $stream;

    /**
     * We will treat the buffer object as the body of the stream
     *
     * @param StreamInterface $stream Stream to cache. The cursor is assumed to be at the beginning of the stream.
     * @param StreamInterface $target Optionally specify where data is cached
     */
    public function __construct(
        StreamInterface $stream,
        StreamInterface $target = null
    ) {
        $this->remoteStream = $stream;
        $this->stream = $target ?: new Stream(Utils::tryFopen('php://temp', 'r+'));
    }

    public function getSize(): ?int
    {
        $remoteSize = $this->remoteStream->getSize();

        if (null === $remoteSize) {
            return null;
        }

        return max($this->stream->getSize(), $remoteSize);
    }

    public function rewind(): void
    {
        $this->seek(0);
    }

    public function seek($offset, $whence = SEEK_SET): void
    {
        if ($whence === SEEK_SET) {
            $byte = $offset;
        } elseif ($whence === SEEK_CUR) {
            $byte = $offset + $this->tell();
        } elseif ($whence === SEEK_END) {
            $size = $this->remoteStream->getSize();
            if ($size === null) {
                $size = $this->cacheEntireStream();
            }
            $byte = $size + $offset;
        } else {
            throw new \InvalidArgumentException('Invalid whence');
        }

        $diff = $byte - $this->stream->getSize();

        if ($diff > 0) {
            // Read the remoteStream until we have read in at least the amount
            // of bytes requested, or we reach the end of the file.
            while ($diff > 0 && !$this->remoteStream->eof()) {
                $this->read($diff);
                $diff = $byte - $this->stream->getSize();
            }
        } else {
            // We can just do a normal seek since we've already seen this byte.
            $this->stream->seek($byte);
        }
    }

    public function read($length): string
    {
        // Perform a regular read on any previously read data from the buffer
        $data = $this->stream->read($length);
        $remaining = $length - strlen($data);

        // More data was requested so read from the remote stream
        if ($remaining) {
            // If data was written to the buffer in a position that would have
            // been filled from the remote stream, then we must skip bytes on
            // the remote stream to emulate overwriting bytes from that
            // position. This mimics the behavior of other PHP stream wrappers.
            $remoteData = $this->remoteStream->read(
                $remaining + $this->skipReadBytes
            );

            if ($this->skipReadBytes) {
                $len = strlen($remoteData);
                $remoteData = substr($remoteData, $this->skipReadBytes);
                $this->skipReadBytes = max(0, $this->skipReadBytes - $len);
            }

            $data .= $remoteData;
            $this->stream->write($remoteData);
        }

        return $data;
    }

    public function write($string): int
    {
        // When appending to the end of the currently read stream, you'll want
        // to skip bytes from being read from the remote stream to emulate
        // other stream wrappers. Basically replacing bytes of data of a fixed
        // length.
        $overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell();
        if ($overflow > 0) {
            $this->skipReadBytes += $overflow;
        }

        return $this->stream->write($string);
    }

    public function eof(): bool
    {
        return $this->stream->eof() && $this->remoteStream->eof();
    }

    /**
     * Close both the remote stream and buffer stream
     */
    public function close(): void
    {
        $this->remoteStream->close();
        $this->stream->close();
    }

    private function cacheEntireStream(): int
    {
        $target = new FnStream(['write' => 'strlen']);
        Utils::copyToStream($this, $target);

        return $this->tell();
    }
}
PK�c�\@@$guzzlehttp/psr7/src/MessageTrait.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Psr7;

use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\StreamInterface;

/**
 * Trait implementing functionality common to requests and responses.
 */
trait MessageTrait
{
    /** @var string[][] Map of all registered headers, as original name => array of values */
    private $headers = [];

    /** @var string[] Map of lowercase header name => original name at registration */
    private $headerNames = [];

    /** @var string */
    private $protocol = '1.1';

    /** @var StreamInterface|null */
    private $stream;

    public function getProtocolVersion(): string
    {
        return $this->protocol;
    }

    public function withProtocolVersion($version): MessageInterface
    {
        if ($this->protocol === $version) {
            return $this;
        }

        $new = clone $this;
        $new->protocol = $version;

        return $new;
    }

    public function getHeaders(): array
    {
        return $this->headers;
    }

    public function hasHeader($header): bool
    {
        return isset($this->headerNames[strtolower($header)]);
    }

    public function getHeader($header): array
    {
        $header = strtolower($header);

        if (!isset($this->headerNames[$header])) {
            return [];
        }

        $header = $this->headerNames[$header];

        return $this->headers[$header];
    }

    public function getHeaderLine($header): string
    {
        return implode(', ', $this->getHeader($header));
    }

    public function withHeader($header, $value): MessageInterface
    {
        $this->assertHeader($header);
        $value = $this->normalizeHeaderValue($value);
        $normalized = strtolower($header);

        $new = clone $this;
        if (isset($new->headerNames[$normalized])) {
            unset($new->headers[$new->headerNames[$normalized]]);
        }
        $new->headerNames[$normalized] = $header;
        $new->headers[$header] = $value;

        return $new;
    }

    public function withAddedHeader($header, $value): MessageInterface
    {
        $this->assertHeader($header);
        $value = $this->normalizeHeaderValue($value);
        $normalized = strtolower($header);

        $new = clone $this;
        if (isset($new->headerNames[$normalized])) {
            $header = $this->headerNames[$normalized];
            $new->headers[$header] = array_merge($this->headers[$header], $value);
        } else {
            $new->headerNames[$normalized] = $header;
            $new->headers[$header] = $value;
        }

        return $new;
    }

    public function withoutHeader($header): MessageInterface
    {
        $normalized = strtolower($header);

        if (!isset($this->headerNames[$normalized])) {
            return $this;
        }

        $header = $this->headerNames[$normalized];

        $new = clone $this;
        unset($new->headers[$header], $new->headerNames[$normalized]);

        return $new;
    }

    public function getBody(): StreamInterface
    {
        if (!$this->stream) {
            $this->stream = Utils::streamFor('');
        }

        return $this->stream;
    }

    public function withBody(StreamInterface $body): MessageInterface
    {
        if ($body === $this->stream) {
            return $this;
        }

        $new = clone $this;
        $new->stream = $body;

        return $new;
    }

    /**
     * @param (string|string[])[] $headers
     */
    private function setHeaders(array $headers): void
    {
        $this->headerNames = $this->headers = [];
        foreach ($headers as $header => $value) {
            // Numeric array keys are converted to int by PHP.
            $header = (string) $header;

            $this->assertHeader($header);
            $value = $this->normalizeHeaderValue($value);
            $normalized = strtolower($header);
            if (isset($this->headerNames[$normalized])) {
                $header = $this->headerNames[$normalized];
                $this->headers[$header] = array_merge($this->headers[$header], $value);
            } else {
                $this->headerNames[$normalized] = $header;
                $this->headers[$header] = $value;
            }
        }
    }

    /**
     * @param mixed $value
     *
     * @return string[]
     */
    private function normalizeHeaderValue($value): array
    {
        if (!is_array($value)) {
            return $this->trimAndValidateHeaderValues([$value]);
        }

        if (count($value) === 0) {
            throw new \InvalidArgumentException('Header value can not be an empty array.');
        }

        return $this->trimAndValidateHeaderValues($value);
    }

    /**
     * Trims whitespace from the header values.
     *
     * Spaces and tabs ought to be excluded by parsers when extracting the field value from a header field.
     *
     * header-field = field-name ":" OWS field-value OWS
     * OWS          = *( SP / HTAB )
     *
     * @param mixed[] $values Header values
     *
     * @return string[] Trimmed header values
     *
     * @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.4
     */
    private function trimAndValidateHeaderValues(array $values): array
    {
        return array_map(function ($value) {
            if (!is_scalar($value) && null !== $value) {
                throw new \InvalidArgumentException(sprintf(
                    'Header value must be scalar or null but %s provided.',
                    is_object($value) ? get_class($value) : gettype($value)
                ));
            }

            $trimmed = trim((string) $value, " \t");
            $this->assertValue($trimmed);

            return $trimmed;
        }, array_values($values));
    }

    /**
     * @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2
     *
     * @param mixed $header
     */
    private function assertHeader($header): void
    {
        if (!is_string($header)) {
            throw new \InvalidArgumentException(sprintf(
                'Header name must be a string but %s provided.',
                is_object($header) ? get_class($header) : gettype($header)
            ));
        }

        if (!preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/D', $header)) {
            throw new \InvalidArgumentException(
                sprintf('"%s" is not valid header name.', $header)
            );
        }
    }

    /**
     * @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2
     *
     * field-value    = *( field-content / obs-fold )
     * field-content  = field-vchar [ 1*( SP / HTAB ) field-vchar ]
     * field-vchar    = VCHAR / obs-text
     * VCHAR          = %x21-7E
     * obs-text       = %x80-FF
     * obs-fold       = CRLF 1*( SP / HTAB )
     */
    private function assertValue(string $value): void
    {
        // The regular expression intentionally does not support the obs-fold production, because as
        // per RFC 7230#3.2.4:
        //
        // A sender MUST NOT generate a message that includes
        // line folding (i.e., that has any field-value that contains a match to
        // the obs-fold rule) unless the message is intended for packaging
        // within the message/http media type.
        //
        // Clients must not send a request with line folding and a server sending folded headers is
        // likely very rare. Line folding is a fairly obscure feature of HTTP/1.1 and thus not accepting
        // folding is not likely to break any legitimate use case.
        if (!preg_match('/^[\x20\x09\x21-\x7E\x80-\xFF]*$/D', $value)) {
            throw new \InvalidArgumentException(
                sprintf('"%s" is not valid header value.', $value)
            );
        }
    }
}
PK�c�\!.��??guzzlehttp/psr7/src/Query.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Psr7;

final class Query
{
    /**
     * Parse a query string into an associative array.
     *
     * If multiple values are found for the same key, the value of that key
     * value pair will become an array. This function does not parse nested
     * PHP style arrays into an associative array (e.g., `foo[a]=1&foo[b]=2`
     * will be parsed into `['foo[a]' => '1', 'foo[b]' => '2'])`.
     *
     * @param string   $str         Query string to parse
     * @param int|bool $urlEncoding How the query string is encoded
     */
    public static function parse(string $str, $urlEncoding = true): array
    {
        $result = [];

        if ($str === '') {
            return $result;
        }

        if ($urlEncoding === true) {
            $decoder = function ($value) {
                return rawurldecode(str_replace('+', ' ', (string) $value));
            };
        } elseif ($urlEncoding === PHP_QUERY_RFC3986) {
            $decoder = 'rawurldecode';
        } elseif ($urlEncoding === PHP_QUERY_RFC1738) {
            $decoder = 'urldecode';
        } else {
            $decoder = function ($str) {
                return $str;
            };
        }

        foreach (explode('&', $str) as $kvp) {
            $parts = explode('=', $kvp, 2);
            $key = $decoder($parts[0]);
            $value = isset($parts[1]) ? $decoder($parts[1]) : null;
            if (!array_key_exists($key, $result)) {
                $result[$key] = $value;
            } else {
                if (!is_array($result[$key])) {
                    $result[$key] = [$result[$key]];
                }
                $result[$key][] = $value;
            }
        }

        return $result;
    }

    /**
     * Build a query string from an array of key value pairs.
     *
     * This function can use the return value of `parse()` to build a query
     * string. This function does not modify the provided keys when an array is
     * encountered (like `http_build_query()` would).
     *
     * @param array     $params   Query string parameters.
     * @param int|false $encoding Set to false to not encode, PHP_QUERY_RFC3986
     *                            to encode using RFC3986, or PHP_QUERY_RFC1738
     *                            to encode using RFC1738.
     */
    public static function build(array $params, $encoding = PHP_QUERY_RFC3986): string
    {
        if (!$params) {
            return '';
        }

        if ($encoding === false) {
            $encoder = function (string $str): string {
                return $str;
            };
        } elseif ($encoding === PHP_QUERY_RFC3986) {
            $encoder = 'rawurlencode';
        } elseif ($encoding === PHP_QUERY_RFC1738) {
            $encoder = 'urlencode';
        } else {
            throw new \InvalidArgumentException('Invalid type');
        }

        $qs = '';
        foreach ($params as $k => $v) {
            $k = $encoder((string) $k);
            if (!is_array($v)) {
                $qs .= $k;
                $v = is_bool($v) ? (int) $v : $v;
                if ($v !== null) {
                    $qs .= '='.$encoder((string) $v);
                }
                $qs .= '&';
            } else {
                foreach ($v as $vv) {
                    $qs .= $k;
                    $vv = is_bool($vv) ? (int) $vv : $vv;
                    if ($vv !== null) {
                        $qs .= '='.$encoder((string) $vv);
                    }
                    $qs .= '&';
                }
            }
        }

        return $qs ? (string) substr($qs, 0, -1) : '';
    }
}
PK�c�\��k��%guzzlehttp/psr7/src/InflateStream.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Uses PHP's zlib.inflate filter to inflate zlib (HTTP deflate, RFC1950) or gzipped (RFC1952) content.
 *
 * This stream decorator converts the provided stream to a PHP stream resource,
 * then appends the zlib.inflate filter. The stream is then converted back
 * to a Guzzle stream resource to be used as a Guzzle stream.
 *
 * @see https://datatracker.ietf.org/doc/html/rfc1950
 * @see https://datatracker.ietf.org/doc/html/rfc1952
 * @see https://www.php.net/manual/en/filters.compression.php
 */
final class InflateStream implements StreamInterface
{
    use StreamDecoratorTrait;

    /** @var StreamInterface */
    private $stream;

    public function __construct(StreamInterface $stream)
    {
        $resource = StreamWrapper::getResource($stream);
        // Specify window=15+32, so zlib will use header detection to both gzip (with header) and zlib data
        // See https://www.zlib.net/manual.html#Advanced definition of inflateInit2
        // "Add 32 to windowBits to enable zlib and gzip decoding with automatic header detection"
        // Default window size is 15.
        stream_filter_append($resource, 'zlib.inflate', STREAM_FILTER_READ, ['window' => 15 + 32]);
        $this->stream = $stream->isSeekable() ? new Stream($resource) : new NoSeekStream(new Stream($resource));
    }
}
PK�c�\q:
t��&guzzlehttp/psr7/src/DroppingStream.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Stream decorator that begins dropping data once the size of the underlying
 * stream becomes too full.
 */
final class DroppingStream implements StreamInterface
{
    use StreamDecoratorTrait;

    /** @var int */
    private $maxLength;

    /** @var StreamInterface */
    private $stream;

    /**
     * @param StreamInterface $stream    Underlying stream to decorate.
     * @param int             $maxLength Maximum size before dropping data.
     */
    public function __construct(StreamInterface $stream, int $maxLength)
    {
        $this->stream = $stream;
        $this->maxLength = $maxLength;
    }

    public function write($string): int
    {
        $diff = $this->maxLength - $this->stream->getSize();

        // Begin returning 0 when the underlying stream is too large.
        if ($diff <= 0) {
            return 0;
        }

        // Write the stream or a subset of the stream if needed.
        if (strlen($string) < $diff) {
            return $this->stream->write($string);
        }

        return $this->stream->write(substr($string, 0, $diff));
    }
}
PK�c�\�=����"guzzlehttp/psr7/src/PumpStream.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Provides a read only stream that pumps data from a PHP callable.
 *
 * When invoking the provided callable, the PumpStream will pass the amount of
 * data requested to read to the callable. The callable can choose to ignore
 * this value and return fewer or more bytes than requested. Any extra data
 * returned by the provided callable is buffered internally until drained using
 * the read() function of the PumpStream. The provided callable MUST return
 * false when there is no more data to read.
 */
final class PumpStream implements StreamInterface
{
    /** @var callable(int): (string|false|null)|null */
    private $source;

    /** @var int|null */
    private $size;

    /** @var int */
    private $tellPos = 0;

    /** @var array */
    private $metadata;

    /** @var BufferStream */
    private $buffer;

    /**
     * @param callable(int): (string|false|null)  $source  Source of the stream data. The callable MAY
     *                                                     accept an integer argument used to control the
     *                                                     amount of data to return. The callable MUST
     *                                                     return a string when called, or false|null on error
     *                                                     or EOF.
     * @param array{size?: int, metadata?: array} $options Stream options:
     *                                                     - metadata: Hash of metadata to use with stream.
     *                                                     - size: Size of the stream, if known.
     */
    public function __construct(callable $source, array $options = [])
    {
        $this->source = $source;
        $this->size = $options['size'] ?? null;
        $this->metadata = $options['metadata'] ?? [];
        $this->buffer = new BufferStream();
    }

    public function __toString(): string
    {
        try {
            return Utils::copyToString($this);
        } catch (\Throwable $e) {
            if (\PHP_VERSION_ID >= 70400) {
                throw $e;
            }
            trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR);

            return '';
        }
    }

    public function close(): void
    {
        $this->detach();
    }

    public function detach()
    {
        $this->tellPos = 0;
        $this->source = null;

        return null;
    }

    public function getSize(): ?int
    {
        return $this->size;
    }

    public function tell(): int
    {
        return $this->tellPos;
    }

    public function eof(): bool
    {
        return $this->source === null;
    }

    public function isSeekable(): bool
    {
        return false;
    }

    public function rewind(): void
    {
        $this->seek(0);
    }

    public function seek($offset, $whence = SEEK_SET): void
    {
        throw new \RuntimeException('Cannot seek a PumpStream');
    }

    public function isWritable(): bool
    {
        return false;
    }

    public function write($string): int
    {
        throw new \RuntimeException('Cannot write to a PumpStream');
    }

    public function isReadable(): bool
    {
        return true;
    }

    public function read($length): string
    {
        $data = $this->buffer->read($length);
        $readLen = strlen($data);
        $this->tellPos += $readLen;
        $remaining = $length - $readLen;

        if ($remaining) {
            $this->pump($remaining);
            $data .= $this->buffer->read($remaining);
            $this->tellPos += strlen($data) - $readLen;
        }

        return $data;
    }

    public function getContents(): string
    {
        $result = '';
        while (!$this->eof()) {
            $result .= $this->read(1000000);
        }

        return $result;
    }

    /**
     * @return mixed
     */
    public function getMetadata($key = null)
    {
        if (!$key) {
            return $this->metadata;
        }

        return $this->metadata[$key] ?? null;
    }

    private function pump(int $length): void
    {
        if ($this->source !== null) {
            do {
                $data = ($this->source)($length);
                if ($data === false || $data === null) {
                    $this->source = null;

                    return;
                }
                $this->buffer->write($data);
                $length -= strlen($data);
            } while ($length > 0);
        }
    }
}
PK�c�\��u@@&guzzlehttp/psr7/src/LazyOpenStream.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Lazily reads or writes to a file that is opened only after an IO operation
 * take place on the stream.
 */
final class LazyOpenStream implements StreamInterface
{
    use StreamDecoratorTrait;

    /** @var string */
    private $filename;

    /** @var string */
    private $mode;

    /**
     * @var StreamInterface
     */
    private $stream;

    /**
     * @param string $filename File to lazily open
     * @param string $mode     fopen mode to use when opening the stream
     */
    public function __construct(string $filename, string $mode)
    {
        $this->filename = $filename;
        $this->mode = $mode;

        // unsetting the property forces the first access to go through
        // __get().
        unset($this->stream);
    }

    /**
     * Creates the underlying stream lazily when required.
     */
    protected function createStream(): StreamInterface
    {
        return Utils::streamFor(Utils::tryFopen($this->filename, $this->mode));
    }
}
PK�c�\j�g��,guzzlehttp/psr7/src/StreamDecoratorTrait.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Stream decorator trait
 *
 * @property StreamInterface $stream
 */
trait StreamDecoratorTrait
{
    /**
     * @param StreamInterface $stream Stream to decorate
     */
    public function __construct(StreamInterface $stream)
    {
        $this->stream = $stream;
    }

    /**
     * Magic method used to create a new stream if streams are not added in
     * the constructor of a decorator (e.g., LazyOpenStream).
     *
     * @return StreamInterface
     */
    public function __get(string $name)
    {
        if ($name === 'stream') {
            $this->stream = $this->createStream();

            return $this->stream;
        }

        throw new \UnexpectedValueException("$name not found on class");
    }

    public function __toString(): string
    {
        try {
            if ($this->isSeekable()) {
                $this->seek(0);
            }

            return $this->getContents();
        } catch (\Throwable $e) {
            if (\PHP_VERSION_ID >= 70400) {
                throw $e;
            }
            trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR);

            return '';
        }
    }

    public function getContents(): string
    {
        return Utils::copyToString($this);
    }

    /**
     * Allow decorators to implement custom methods
     *
     * @return mixed
     */
    public function __call(string $method, array $args)
    {
        /** @var callable $callable */
        $callable = [$this->stream, $method];
        $result = ($callable)(...$args);

        // Always return the wrapped object if the result is a return $this
        return $result === $this->stream ? $this : $result;
    }

    public function close(): void
    {
        $this->stream->close();
    }

    /**
     * @return mixed
     */
    public function getMetadata($key = null)
    {
        return $this->stream->getMetadata($key);
    }

    public function detach()
    {
        return $this->stream->detach();
    }

    public function getSize(): ?int
    {
        return $this->stream->getSize();
    }

    public function eof(): bool
    {
        return $this->stream->eof();
    }

    public function tell(): int
    {
        return $this->stream->tell();
    }

    public function isReadable(): bool
    {
        return $this->stream->isReadable();
    }

    public function isWritable(): bool
    {
        return $this->stream->isWritable();
    }

    public function isSeekable(): bool
    {
        return $this->stream->isSeekable();
    }

    public function rewind(): void
    {
        $this->seek(0);
    }

    public function seek($offset, $whence = SEEK_SET): void
    {
        $this->stream->seek($offset, $whence);
    }

    public function read($length): string
    {
        return $this->stream->read($length);
    }

    public function write($string): int
    {
        return $this->stream->write($string);
    }

    /**
     * Implement in subclasses to dynamically create streams when requested.
     *
     * @throws \BadMethodCallException
     */
    protected function createStream(): StreamInterface
    {
        throw new \BadMethodCallException('Not implemented');
    }
}
PK�c�\����!!%guzzlehttp/psr7/src/UriNormalizer.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Psr7;

use Psr\Http\Message\UriInterface;

/**
 * Provides methods to normalize and compare URIs.
 *
 * @author Tobias Schultze
 *
 * @see https://datatracker.ietf.org/doc/html/rfc3986#section-6
 */
final class UriNormalizer
{
    /**
     * Default normalizations which only include the ones that preserve semantics.
     */
    public const PRESERVING_NORMALIZATIONS =
        self::CAPITALIZE_PERCENT_ENCODING |
        self::DECODE_UNRESERVED_CHARACTERS |
        self::CONVERT_EMPTY_PATH |
        self::REMOVE_DEFAULT_HOST |
        self::REMOVE_DEFAULT_PORT |
        self::REMOVE_DOT_SEGMENTS;

    /**
     * All letters within a percent-encoding triplet (e.g., "%3A") are case-insensitive, and should be capitalized.
     *
     * Example: http://example.org/a%c2%b1b → http://example.org/a%C2%B1b
     */
    public const CAPITALIZE_PERCENT_ENCODING = 1;

    /**
     * Decodes percent-encoded octets of unreserved characters.
     *
     * For consistency, percent-encoded octets in the ranges of ALPHA (%41–%5A and %61–%7A), DIGIT (%30–%39),
     * hyphen (%2D), period (%2E), underscore (%5F), or tilde (%7E) should not be created by URI producers and,
     * when found in a URI, should be decoded to their corresponding unreserved characters by URI normalizers.
     *
     * Example: http://example.org/%7Eusern%61me/ → http://example.org/~username/
     */
    public const DECODE_UNRESERVED_CHARACTERS = 2;

    /**
     * Converts the empty path to "/" for http and https URIs.
     *
     * Example: http://example.org → http://example.org/
     */
    public const CONVERT_EMPTY_PATH = 4;

    /**
     * Removes the default host of the given URI scheme from the URI.
     *
     * Only the "file" scheme defines the default host "localhost".
     * All of `file:/myfile`, `file:///myfile`, and `file://localhost/myfile`
     * are equivalent according to RFC 3986. The first format is not accepted
     * by PHPs stream functions and thus already normalized implicitly to the
     * second format in the Uri class. See `GuzzleHttp\Psr7\Uri::composeComponents`.
     *
     * Example: file://localhost/myfile → file:///myfile
     */
    public const REMOVE_DEFAULT_HOST = 8;

    /**
     * Removes the default port of the given URI scheme from the URI.
     *
     * Example: http://example.org:80/ → http://example.org/
     */
    public const REMOVE_DEFAULT_PORT = 16;

    /**
     * Removes unnecessary dot-segments.
     *
     * Dot-segments in relative-path references are not removed as it would
     * change the semantics of the URI reference.
     *
     * Example: http://example.org/../a/b/../c/./d.html → http://example.org/a/c/d.html
     */
    public const REMOVE_DOT_SEGMENTS = 32;

    /**
     * Paths which include two or more adjacent slashes are converted to one.
     *
     * Webservers usually ignore duplicate slashes and treat those URIs equivalent.
     * But in theory those URIs do not need to be equivalent. So this normalization
     * may change the semantics. Encoded slashes (%2F) are not removed.
     *
     * Example: http://example.org//foo///bar.html → http://example.org/foo/bar.html
     */
    public const REMOVE_DUPLICATE_SLASHES = 64;

    /**
     * Sort query parameters with their values in alphabetical order.
     *
     * However, the order of parameters in a URI may be significant (this is not defined by the standard).
     * So this normalization is not safe and may change the semantics of the URI.
     *
     * Example: ?lang=en&article=fred → ?article=fred&lang=en
     *
     * Note: The sorting is neither locale nor Unicode aware (the URI query does not get decoded at all) as the
     * purpose is to be able to compare URIs in a reproducible way, not to have the params sorted perfectly.
     */
    public const SORT_QUERY_PARAMETERS = 128;

    /**
     * Returns a normalized URI.
     *
     * The scheme and host component are already normalized to lowercase per PSR-7 UriInterface.
     * This methods adds additional normalizations that can be configured with the $flags parameter.
     *
     * PSR-7 UriInterface cannot distinguish between an empty component and a missing component as
     * getQuery(), getFragment() etc. always return a string. This means the URIs "/?#" and "/" are
     * treated equivalent which is not necessarily true according to RFC 3986. But that difference
     * is highly uncommon in reality. So this potential normalization is implied in PSR-7 as well.
     *
     * @param UriInterface $uri   The URI to normalize
     * @param int          $flags A bitmask of normalizations to apply, see constants
     *
     * @see https://datatracker.ietf.org/doc/html/rfc3986#section-6.2
     */
    public static function normalize(UriInterface $uri, int $flags = self::PRESERVING_NORMALIZATIONS): UriInterface
    {
        if ($flags & self::CAPITALIZE_PERCENT_ENCODING) {
            $uri = self::capitalizePercentEncoding($uri);
        }

        if ($flags & self::DECODE_UNRESERVED_CHARACTERS) {
            $uri = self::decodeUnreservedCharacters($uri);
        }

        if ($flags & self::CONVERT_EMPTY_PATH && $uri->getPath() === ''
            && ($uri->getScheme() === 'http' || $uri->getScheme() === 'https')
        ) {
            $uri = $uri->withPath('/');
        }

        if ($flags & self::REMOVE_DEFAULT_HOST && $uri->getScheme() === 'file' && $uri->getHost() === 'localhost') {
            $uri = $uri->withHost('');
        }

        if ($flags & self::REMOVE_DEFAULT_PORT && $uri->getPort() !== null && Uri::isDefaultPort($uri)) {
            $uri = $uri->withPort(null);
        }

        if ($flags & self::REMOVE_DOT_SEGMENTS && !Uri::isRelativePathReference($uri)) {
            $uri = $uri->withPath(UriResolver::removeDotSegments($uri->getPath()));
        }

        if ($flags & self::REMOVE_DUPLICATE_SLASHES) {
            $uri = $uri->withPath(preg_replace('#//++#', '/', $uri->getPath()));
        }

        if ($flags & self::SORT_QUERY_PARAMETERS && $uri->getQuery() !== '') {
            $queryKeyValues = explode('&', $uri->getQuery());
            sort($queryKeyValues);
            $uri = $uri->withQuery(implode('&', $queryKeyValues));
        }

        return $uri;
    }

    /**
     * Whether two URIs can be considered equivalent.
     *
     * Both URIs are normalized automatically before comparison with the given $normalizations bitmask. The method also
     * accepts relative URI references and returns true when they are equivalent. This of course assumes they will be
     * resolved against the same base URI. If this is not the case, determination of equivalence or difference of
     * relative references does not mean anything.
     *
     * @param UriInterface $uri1           An URI to compare
     * @param UriInterface $uri2           An URI to compare
     * @param int          $normalizations A bitmask of normalizations to apply, see constants
     *
     * @see https://datatracker.ietf.org/doc/html/rfc3986#section-6.1
     */
    public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, int $normalizations = self::PRESERVING_NORMALIZATIONS): bool
    {
        return (string) self::normalize($uri1, $normalizations) === (string) self::normalize($uri2, $normalizations);
    }

    private static function capitalizePercentEncoding(UriInterface $uri): UriInterface
    {
        $regex = '/(?:%[A-Fa-f0-9]{2})++/';

        $callback = function (array $match): string {
            return strtoupper($match[0]);
        };

        return
            $uri->withPath(
                preg_replace_callback($regex, $callback, $uri->getPath())
            )->withQuery(
                preg_replace_callback($regex, $callback, $uri->getQuery())
            );
    }

    private static function decodeUnreservedCharacters(UriInterface $uri): UriInterface
    {
        $regex = '/%(?:2D|2E|5F|7E|3[0-9]|[46][1-9A-F]|[57][0-9A])/i';

        $callback = function (array $match): string {
            return rawurldecode($match[0]);
        };

        return
            $uri->withPath(
                preg_replace_callback($regex, $callback, $uri->getPath())
            )->withQuery(
                preg_replace_callback($regex, $callback, $uri->getQuery())
            );
    }

    private function __construct()
    {
        // cannot be instantiated
    }
}
PK�c�\z����guzzlehttp/psr7/src/Stream.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * PHP stream implementation.
 */
class Stream implements StreamInterface
{
    /**
     * @see https://www.php.net/manual/en/function.fopen.php
     * @see https://www.php.net/manual/en/function.gzopen.php
     */
    private const READABLE_MODES = '/r|a\+|ab\+|w\+|wb\+|x\+|xb\+|c\+|cb\+/';
    private const WRITABLE_MODES = '/a|w|r\+|rb\+|rw|x|c/';

    /** @var resource */
    private $stream;
    /** @var int|null */
    private $size;
    /** @var bool */
    private $seekable;
    /** @var bool */
    private $readable;
    /** @var bool */
    private $writable;
    /** @var string|null */
    private $uri;
    /** @var mixed[] */
    private $customMetadata;

    /**
     * This constructor accepts an associative array of options.
     *
     * - size: (int) If a read stream would otherwise have an indeterminate
     *   size, but the size is known due to foreknowledge, then you can
     *   provide that size, in bytes.
     * - metadata: (array) Any additional metadata to return when the metadata
     *   of the stream is accessed.
     *
     * @param resource                            $stream  Stream resource to wrap.
     * @param array{size?: int, metadata?: array} $options Associative array of options.
     *
     * @throws \InvalidArgumentException if the stream is not a stream resource
     */
    public function __construct($stream, array $options = [])
    {
        if (!is_resource($stream)) {
            throw new \InvalidArgumentException('Stream must be a resource');
        }

        if (isset($options['size'])) {
            $this->size = $options['size'];
        }

        $this->customMetadata = $options['metadata'] ?? [];
        $this->stream = $stream;
        $meta = stream_get_meta_data($this->stream);
        $this->seekable = $meta['seekable'];
        $this->readable = (bool) preg_match(self::READABLE_MODES, $meta['mode']);
        $this->writable = (bool) preg_match(self::WRITABLE_MODES, $meta['mode']);
        $this->uri = $this->getMetadata('uri');
    }

    /**
     * Closes the stream when the destructed
     */
    public function __destruct()
    {
        $this->close();
    }

    public function __toString(): string
    {
        try {
            if ($this->isSeekable()) {
                $this->seek(0);
            }

            return $this->getContents();
        } catch (\Throwable $e) {
            if (\PHP_VERSION_ID >= 70400) {
                throw $e;
            }
            trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR);

            return '';
        }
    }

    public function getContents(): string
    {
        if (!isset($this->stream)) {
            throw new \RuntimeException('Stream is detached');
        }

        if (!$this->readable) {
            throw new \RuntimeException('Cannot read from non-readable stream');
        }

        return Utils::tryGetContents($this->stream);
    }

    public function close(): void
    {
        if (isset($this->stream)) {
            if (is_resource($this->stream)) {
                fclose($this->stream);
            }
            $this->detach();
        }
    }

    public function detach()
    {
        if (!isset($this->stream)) {
            return null;
        }

        $result = $this->stream;
        unset($this->stream);
        $this->size = $this->uri = null;
        $this->readable = $this->writable = $this->seekable = false;

        return $result;
    }

    public function getSize(): ?int
    {
        if ($this->size !== null) {
            return $this->size;
        }

        if (!isset($this->stream)) {
            return null;
        }

        // Clear the stat cache if the stream has a URI
        if ($this->uri) {
            clearstatcache(true, $this->uri);
        }

        $stats = fstat($this->stream);
        if (is_array($stats) && isset($stats['size'])) {
            $this->size = $stats['size'];

            return $this->size;
        }

        return null;
    }

    public function isReadable(): bool
    {
        return $this->readable;
    }

    public function isWritable(): bool
    {
        return $this->writable;
    }

    public function isSeekable(): bool
    {
        return $this->seekable;
    }

    public function eof(): bool
    {
        if (!isset($this->stream)) {
            throw new \RuntimeException('Stream is detached');
        }

        return feof($this->stream);
    }

    public function tell(): int
    {
        if (!isset($this->stream)) {
            throw new \RuntimeException('Stream is detached');
        }

        $result = ftell($this->stream);

        if ($result === false) {
            throw new \RuntimeException('Unable to determine stream position');
        }

        return $result;
    }

    public function rewind(): void
    {
        $this->seek(0);
    }

    public function seek($offset, $whence = SEEK_SET): void
    {
        $whence = (int) $whence;

        if (!isset($this->stream)) {
            throw new \RuntimeException('Stream is detached');
        }
        if (!$this->seekable) {
            throw new \RuntimeException('Stream is not seekable');
        }
        if (fseek($this->stream, $offset, $whence) === -1) {
            throw new \RuntimeException('Unable to seek to stream position '
                .$offset.' with whence '.var_export($whence, true));
        }
    }

    public function read($length): string
    {
        if (!isset($this->stream)) {
            throw new \RuntimeException('Stream is detached');
        }
        if (!$this->readable) {
            throw new \RuntimeException('Cannot read from non-readable stream');
        }
        if ($length < 0) {
            throw new \RuntimeException('Length parameter cannot be negative');
        }

        if (0 === $length) {
            return '';
        }

        try {
            $string = fread($this->stream, $length);
        } catch (\Exception $e) {
            throw new \RuntimeException('Unable to read from stream', 0, $e);
        }

        if (false === $string) {
            throw new \RuntimeException('Unable to read from stream');
        }

        return $string;
    }

    public function write($string): int
    {
        if (!isset($this->stream)) {
            throw new \RuntimeException('Stream is detached');
        }
        if (!$this->writable) {
            throw new \RuntimeException('Cannot write to a non-writable stream');
        }

        // We can't know the size after writing anything
        $this->size = null;
        $result = fwrite($this->stream, $string);

        if ($result === false) {
            throw new \RuntimeException('Unable to write to stream');
        }

        return $result;
    }

    /**
     * @return mixed
     */
    public function getMetadata($key = null)
    {
        if (!isset($this->stream)) {
            return $key ? null : [];
        } elseif (!$key) {
            return $this->customMetadata + stream_get_meta_data($this->stream);
        } elseif (isset($this->customMetadata[$key])) {
            return $this->customMetadata[$key];
        }

        $meta = stream_get_meta_data($this->stream);

        return $meta[$key] ?? null;
    }
}
PK�c�\�u��U�Uguzzlehttp/psr7/src/Uri.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Psr7;

use GuzzleHttp\Psr7\Exception\MalformedUriException;
use Psr\Http\Message\UriInterface;

/**
 * PSR-7 URI implementation.
 *
 * @author Michael Dowling
 * @author Tobias Schultze
 * @author Matthew Weier O'Phinney
 */
class Uri implements UriInterface, \JsonSerializable
{
    /**
     * Absolute http and https URIs require a host per RFC 7230 Section 2.7
     * but in generic URIs the host can be empty. So for http(s) URIs
     * we apply this default host when no host is given yet to form a
     * valid URI.
     */
    private const HTTP_DEFAULT_HOST = 'localhost';

    private const DEFAULT_PORTS = [
        'http' => 80,
        'https' => 443,
        'ftp' => 21,
        'gopher' => 70,
        'nntp' => 119,
        'news' => 119,
        'telnet' => 23,
        'tn3270' => 23,
        'imap' => 143,
        'pop' => 110,
        'ldap' => 389,
    ];

    /**
     * Unreserved characters for use in a regex.
     *
     * @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.3
     */
    private const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~';

    /**
     * Sub-delims for use in a regex.
     *
     * @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.2
     */
    private const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;=';
    private const QUERY_SEPARATORS_REPLACEMENT = ['=' => '%3D', '&' => '%26'];

    /** @var string Uri scheme. */
    private $scheme = '';

    /** @var string Uri user info. */
    private $userInfo = '';

    /** @var string Uri host. */
    private $host = '';

    /** @var int|null Uri port. */
    private $port;

    /** @var string Uri path. */
    private $path = '';

    /** @var string Uri query string. */
    private $query = '';

    /** @var string Uri fragment. */
    private $fragment = '';

    /** @var string|null String representation */
    private $composedComponents;

    public function __construct(string $uri = '')
    {
        if ($uri !== '') {
            $parts = self::parse($uri);
            if ($parts === false) {
                throw new MalformedUriException("Unable to parse URI: $uri");
            }
            $this->applyParts($parts);
        }
    }

    /**
     * UTF-8 aware \parse_url() replacement.
     *
     * The internal function produces broken output for non ASCII domain names
     * (IDN) when used with locales other than "C".
     *
     * On the other hand, cURL understands IDN correctly only when UTF-8 locale
     * is configured ("C.UTF-8", "en_US.UTF-8", etc.).
     *
     * @see https://bugs.php.net/bug.php?id=52923
     * @see https://www.php.net/manual/en/function.parse-url.php#114817
     * @see https://curl.haxx.se/libcurl/c/CURLOPT_URL.html#ENCODING
     *
     * @return array|false
     */
    private static function parse(string $url)
    {
        // If IPv6
        $prefix = '';
        if (preg_match('%^(.*://\[[0-9:a-f]+\])(.*?)$%', $url, $matches)) {
            /** @var array{0:string, 1:string, 2:string} $matches */
            $prefix = $matches[1];
            $url = $matches[2];
        }

        /** @var string */
        $encodedUrl = preg_replace_callback(
            '%[^:/@?&=#]+%usD',
            static function ($matches) {
                return urlencode($matches[0]);
            },
            $url
        );

        $result = parse_url($prefix.$encodedUrl);

        if ($result === false) {
            return false;
        }

        return array_map('urldecode', $result);
    }

    public function __toString(): string
    {
        if ($this->composedComponents === null) {
            $this->composedComponents = self::composeComponents(
                $this->scheme,
                $this->getAuthority(),
                $this->path,
                $this->query,
                $this->fragment
            );
        }

        return $this->composedComponents;
    }

    /**
     * Composes a URI reference string from its various components.
     *
     * Usually this method does not need to be called manually but instead is used indirectly via
     * `Psr\Http\Message\UriInterface::__toString`.
     *
     * PSR-7 UriInterface treats an empty component the same as a missing component as
     * getQuery(), getFragment() etc. always return a string. This explains the slight
     * difference to RFC 3986 Section 5.3.
     *
     * Another adjustment is that the authority separator is added even when the authority is missing/empty
     * for the "file" scheme. This is because PHP stream functions like `file_get_contents` only work with
     * `file:///myfile` but not with `file:/myfile` although they are equivalent according to RFC 3986. But
     * `file:///` is the more common syntax for the file scheme anyway (Chrome for example redirects to
     * that format).
     *
     * @see https://datatracker.ietf.org/doc/html/rfc3986#section-5.3
     */
    public static function composeComponents(?string $scheme, ?string $authority, string $path, ?string $query, ?string $fragment): string
    {
        $uri = '';

        // weak type checks to also accept null until we can add scalar type hints
        if ($scheme != '') {
            $uri .= $scheme.':';
        }

        if ($authority != '' || $scheme === 'file') {
            $uri .= '//'.$authority;
        }

        if ($authority != '' && $path != '' && $path[0] != '/') {
            $path = '/'.$path;
        }

        $uri .= $path;

        if ($query != '') {
            $uri .= '?'.$query;
        }

        if ($fragment != '') {
            $uri .= '#'.$fragment;
        }

        return $uri;
    }

    /**
     * Whether the URI has the default port of the current scheme.
     *
     * `Psr\Http\Message\UriInterface::getPort` may return null or the standard port. This method can be used
     * independently of the implementation.
     */
    public static function isDefaultPort(UriInterface $uri): bool
    {
        return $uri->getPort() === null
            || (isset(self::DEFAULT_PORTS[$uri->getScheme()]) && $uri->getPort() === self::DEFAULT_PORTS[$uri->getScheme()]);
    }

    /**
     * Whether the URI is absolute, i.e. it has a scheme.
     *
     * An instance of UriInterface can either be an absolute URI or a relative reference. This method returns true
     * if it is the former. An absolute URI has a scheme. A relative reference is used to express a URI relative
     * to another URI, the base URI. Relative references can be divided into several forms:
     * - network-path references, e.g. '//example.com/path'
     * - absolute-path references, e.g. '/path'
     * - relative-path references, e.g. 'subpath'
     *
     * @see Uri::isNetworkPathReference
     * @see Uri::isAbsolutePathReference
     * @see Uri::isRelativePathReference
     * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4
     */
    public static function isAbsolute(UriInterface $uri): bool
    {
        return $uri->getScheme() !== '';
    }

    /**
     * Whether the URI is a network-path reference.
     *
     * A relative reference that begins with two slash characters is termed an network-path reference.
     *
     * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2
     */
    public static function isNetworkPathReference(UriInterface $uri): bool
    {
        return $uri->getScheme() === '' && $uri->getAuthority() !== '';
    }

    /**
     * Whether the URI is a absolute-path reference.
     *
     * A relative reference that begins with a single slash character is termed an absolute-path reference.
     *
     * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2
     */
    public static function isAbsolutePathReference(UriInterface $uri): bool
    {
        return $uri->getScheme() === ''
            && $uri->getAuthority() === ''
            && isset($uri->getPath()[0])
            && $uri->getPath()[0] === '/';
    }

    /**
     * Whether the URI is a relative-path reference.
     *
     * A relative reference that does not begin with a slash character is termed a relative-path reference.
     *
     * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2
     */
    public static function isRelativePathReference(UriInterface $uri): bool
    {
        return $uri->getScheme() === ''
            && $uri->getAuthority() === ''
            && (!isset($uri->getPath()[0]) || $uri->getPath()[0] !== '/');
    }

    /**
     * Whether the URI is a same-document reference.
     *
     * A same-document reference refers to a URI that is, aside from its fragment
     * component, identical to the base URI. When no base URI is given, only an empty
     * URI reference (apart from its fragment) is considered a same-document reference.
     *
     * @param UriInterface      $uri  The URI to check
     * @param UriInterface|null $base An optional base URI to compare against
     *
     * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.4
     */
    public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null): bool
    {
        if ($base !== null) {
            $uri = UriResolver::resolve($base, $uri);

            return ($uri->getScheme() === $base->getScheme())
                && ($uri->getAuthority() === $base->getAuthority())
                && ($uri->getPath() === $base->getPath())
                && ($uri->getQuery() === $base->getQuery());
        }

        return $uri->getScheme() === '' && $uri->getAuthority() === '' && $uri->getPath() === '' && $uri->getQuery() === '';
    }

    /**
     * Creates a new URI with a specific query string value removed.
     *
     * Any existing query string values that exactly match the provided key are
     * removed.
     *
     * @param UriInterface $uri URI to use as a base.
     * @param string       $key Query string key to remove.
     */
    public static function withoutQueryValue(UriInterface $uri, string $key): UriInterface
    {
        $result = self::getFilteredQueryString($uri, [$key]);

        return $uri->withQuery(implode('&', $result));
    }

    /**
     * Creates a new URI with a specific query string value.
     *
     * Any existing query string values that exactly match the provided key are
     * removed and replaced with the given key value pair.
     *
     * A value of null will set the query string key without a value, e.g. "key"
     * instead of "key=value".
     *
     * @param UriInterface $uri   URI to use as a base.
     * @param string       $key   Key to set.
     * @param string|null  $value Value to set
     */
    public static function withQueryValue(UriInterface $uri, string $key, ?string $value): UriInterface
    {
        $result = self::getFilteredQueryString($uri, [$key]);

        $result[] = self::generateQueryString($key, $value);

        return $uri->withQuery(implode('&', $result));
    }

    /**
     * Creates a new URI with multiple specific query string values.
     *
     * It has the same behavior as withQueryValue() but for an associative array of key => value.
     *
     * @param UriInterface    $uri           URI to use as a base.
     * @param (string|null)[] $keyValueArray Associative array of key and values
     */
    public static function withQueryValues(UriInterface $uri, array $keyValueArray): UriInterface
    {
        $result = self::getFilteredQueryString($uri, array_keys($keyValueArray));

        foreach ($keyValueArray as $key => $value) {
            $result[] = self::generateQueryString((string) $key, $value !== null ? (string) $value : null);
        }

        return $uri->withQuery(implode('&', $result));
    }

    /**
     * Creates a URI from a hash of `parse_url` components.
     *
     * @see https://www.php.net/manual/en/function.parse-url.php
     *
     * @throws MalformedUriException If the components do not form a valid URI.
     */
    public static function fromParts(array $parts): UriInterface
    {
        $uri = new self();
        $uri->applyParts($parts);
        $uri->validateState();

        return $uri;
    }

    public function getScheme(): string
    {
        return $this->scheme;
    }

    public function getAuthority(): string
    {
        $authority = $this->host;
        if ($this->userInfo !== '') {
            $authority = $this->userInfo.'@'.$authority;
        }

        if ($this->port !== null) {
            $authority .= ':'.$this->port;
        }

        return $authority;
    }

    public function getUserInfo(): string
    {
        return $this->userInfo;
    }

    public function getHost(): string
    {
        return $this->host;
    }

    public function getPort(): ?int
    {
        return $this->port;
    }

    public function getPath(): string
    {
        return $this->path;
    }

    public function getQuery(): string
    {
        return $this->query;
    }

    public function getFragment(): string
    {
        return $this->fragment;
    }

    public function withScheme($scheme): UriInterface
    {
        $scheme = $this->filterScheme($scheme);

        if ($this->scheme === $scheme) {
            return $this;
        }

        $new = clone $this;
        $new->scheme = $scheme;
        $new->composedComponents = null;
        $new->removeDefaultPort();
        $new->validateState();

        return $new;
    }

    public function withUserInfo($user, $password = null): UriInterface
    {
        $info = $this->filterUserInfoComponent($user);
        if ($password !== null) {
            $info .= ':'.$this->filterUserInfoComponent($password);
        }

        if ($this->userInfo === $info) {
            return $this;
        }

        $new = clone $this;
        $new->userInfo = $info;
        $new->composedComponents = null;
        $new->validateState();

        return $new;
    }

    public function withHost($host): UriInterface
    {
        $host = $this->filterHost($host);

        if ($this->host === $host) {
            return $this;
        }

        $new = clone $this;
        $new->host = $host;
        $new->composedComponents = null;
        $new->validateState();

        return $new;
    }

    public function withPort($port): UriInterface
    {
        $port = $this->filterPort($port);

        if ($this->port === $port) {
            return $this;
        }

        $new = clone $this;
        $new->port = $port;
        $new->composedComponents = null;
        $new->removeDefaultPort();
        $new->validateState();

        return $new;
    }

    public function withPath($path): UriInterface
    {
        $path = $this->filterPath($path);

        if ($this->path === $path) {
            return $this;
        }

        $new = clone $this;
        $new->path = $path;
        $new->composedComponents = null;
        $new->validateState();

        return $new;
    }

    public function withQuery($query): UriInterface
    {
        $query = $this->filterQueryAndFragment($query);

        if ($this->query === $query) {
            return $this;
        }

        $new = clone $this;
        $new->query = $query;
        $new->composedComponents = null;

        return $new;
    }

    public function withFragment($fragment): UriInterface
    {
        $fragment = $this->filterQueryAndFragment($fragment);

        if ($this->fragment === $fragment) {
            return $this;
        }

        $new = clone $this;
        $new->fragment = $fragment;
        $new->composedComponents = null;

        return $new;
    }

    public function jsonSerialize(): string
    {
        return $this->__toString();
    }

    /**
     * Apply parse_url parts to a URI.
     *
     * @param array $parts Array of parse_url parts to apply.
     */
    private function applyParts(array $parts): void
    {
        $this->scheme = isset($parts['scheme'])
            ? $this->filterScheme($parts['scheme'])
            : '';
        $this->userInfo = isset($parts['user'])
            ? $this->filterUserInfoComponent($parts['user'])
            : '';
        $this->host = isset($parts['host'])
            ? $this->filterHost($parts['host'])
            : '';
        $this->port = isset($parts['port'])
            ? $this->filterPort($parts['port'])
            : null;
        $this->path = isset($parts['path'])
            ? $this->filterPath($parts['path'])
            : '';
        $this->query = isset($parts['query'])
            ? $this->filterQueryAndFragment($parts['query'])
            : '';
        $this->fragment = isset($parts['fragment'])
            ? $this->filterQueryAndFragment($parts['fragment'])
            : '';
        if (isset($parts['pass'])) {
            $this->userInfo .= ':'.$this->filterUserInfoComponent($parts['pass']);
        }

        $this->removeDefaultPort();
    }

    /**
     * @param mixed $scheme
     *
     * @throws \InvalidArgumentException If the scheme is invalid.
     */
    private function filterScheme($scheme): string
    {
        if (!is_string($scheme)) {
            throw new \InvalidArgumentException('Scheme must be a string');
        }

        return \strtr($scheme, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
    }

    /**
     * @param mixed $component
     *
     * @throws \InvalidArgumentException If the user info is invalid.
     */
    private function filterUserInfoComponent($component): string
    {
        if (!is_string($component)) {
            throw new \InvalidArgumentException('User info must be a string');
        }

        return preg_replace_callback(
            '/(?:[^%'.self::CHAR_UNRESERVED.self::CHAR_SUB_DELIMS.']+|%(?![A-Fa-f0-9]{2}))/',
            [$this, 'rawurlencodeMatchZero'],
            $component
        );
    }

    /**
     * @param mixed $host
     *
     * @throws \InvalidArgumentException If the host is invalid.
     */
    private function filterHost($host): string
    {
        if (!is_string($host)) {
            throw new \InvalidArgumentException('Host must be a string');
        }

        return \strtr($host, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
    }

    /**
     * @param mixed $port
     *
     * @throws \InvalidArgumentException If the port is invalid.
     */
    private function filterPort($port): ?int
    {
        if ($port === null) {
            return null;
        }

        $port = (int) $port;
        if (0 > $port || 0xFFFF < $port) {
            throw new \InvalidArgumentException(
                sprintf('Invalid port: %d. Must be between 0 and 65535', $port)
            );
        }

        return $port;
    }

    /**
     * @param (string|int)[] $keys
     *
     * @return string[]
     */
    private static function getFilteredQueryString(UriInterface $uri, array $keys): array
    {
        $current = $uri->getQuery();

        if ($current === '') {
            return [];
        }

        $decodedKeys = array_map(function ($k): string {
            return rawurldecode((string) $k);
        }, $keys);

        return array_filter(explode('&', $current), function ($part) use ($decodedKeys) {
            return !in_array(rawurldecode(explode('=', $part)[0]), $decodedKeys, true);
        });
    }

    private static function generateQueryString(string $key, ?string $value): string
    {
        // Query string separators ("=", "&") within the key or value need to be encoded
        // (while preventing double-encoding) before setting the query string. All other
        // chars that need percent-encoding will be encoded by withQuery().
        $queryString = strtr($key, self::QUERY_SEPARATORS_REPLACEMENT);

        if ($value !== null) {
            $queryString .= '='.strtr($value, self::QUERY_SEPARATORS_REPLACEMENT);
        }

        return $queryString;
    }

    private function removeDefaultPort(): void
    {
        if ($this->port !== null && self::isDefaultPort($this)) {
            $this->port = null;
        }
    }

    /**
     * Filters the path of a URI
     *
     * @param mixed $path
     *
     * @throws \InvalidArgumentException If the path is invalid.
     */
    private function filterPath($path): string
    {
        if (!is_string($path)) {
            throw new \InvalidArgumentException('Path must be a string');
        }

        return preg_replace_callback(
            '/(?:[^'.self::CHAR_UNRESERVED.self::CHAR_SUB_DELIMS.'%:@\/]++|%(?![A-Fa-f0-9]{2}))/',
            [$this, 'rawurlencodeMatchZero'],
            $path
        );
    }

    /**
     * Filters the query string or fragment of a URI.
     *
     * @param mixed $str
     *
     * @throws \InvalidArgumentException If the query or fragment is invalid.
     */
    private function filterQueryAndFragment($str): string
    {
        if (!is_string($str)) {
            throw new \InvalidArgumentException('Query and fragment must be a string');
        }

        return preg_replace_callback(
            '/(?:[^'.self::CHAR_UNRESERVED.self::CHAR_SUB_DELIMS.'%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/',
            [$this, 'rawurlencodeMatchZero'],
            $str
        );
    }

    private function rawurlencodeMatchZero(array $match): string
    {
        return rawurlencode($match[0]);
    }

    private function validateState(): void
    {
        if ($this->host === '' && ($this->scheme === 'http' || $this->scheme === 'https')) {
            $this->host = self::HTTP_DEFAULT_HOST;
        }

        if ($this->getAuthority() === '') {
            if (0 === strpos($this->path, '//')) {
                throw new MalformedUriException('The path of a URI without an authority must not start with two slashes "//"');
            }
            if ($this->scheme === '' && false !== strpos(explode('/', $this->path, 2)[0], ':')) {
                throw new MalformedUriException('A relative URI must not have a path beginning with a segment containing a colon');
            }
        }
    }
}
PK�c�\&�_88'guzzlehttp/psr7/src/MultipartStream.phpnu�[���<?php

declare(strict_types=1);

namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Stream that when read returns bytes for a streaming multipart or
 * multipart/form-data stream.
 */
final class MultipartStream implements StreamInterface
{
    use StreamDecoratorTrait;

    /** @var string */
    private $boundary;

    /** @var StreamInterface */
    private $stream;

    /**
     * @param array  $elements Array of associative arrays, each containing a
     *                         required "name" key mapping to the form field,
     *                         name, a required "contents" key mapping to a
     *                         StreamInterface/resource/string, an optional
     *                         "headers" associative array of custom headers,
     *                         and an optional "filename" key mapping to a
     *                         string to send as the filename in the part.
     * @param string $boundary You can optionally provide a specific boundary
     *
     * @throws \InvalidArgumentException
     */
    public function __construct(array $elements = [], string $boundary = null)
    {
        $this->boundary = $boundary ?: bin2hex(random_bytes(20));
        $this->stream = $this->createStream($elements);
    }

    public function getBoundary(): string
    {
        return $this->boundary;
    }

    public function isWritable(): bool
    {
        return false;
    }

    /**
     * Get the headers needed before transferring the content of a POST file
     *
     * @param string[] $headers
     */
    private function getHeaders(array $headers): string
    {
        $str = '';
        foreach ($headers as $key => $value) {
            $str .= "{$key}: {$value}\r\n";
        }

        return "--{$this->boundary}\r\n".trim($str)."\r\n\r\n";
    }

    /**
     * Create the aggregate stream that will be used to upload the POST data
     */
    protected function createStream(array $elements = []): StreamInterface
    {
        $stream = new AppendStream();

        foreach ($elements as $element) {
            if (!is_array($element)) {
                throw new \UnexpectedValueException('An array is expected');
            }
            $this->addElement($stream, $element);
        }

        // Add the trailing boundary with CRLF
        $stream->addStream(Utils::streamFor("--{$this->boundary}--\r\n"));

        return $stream;
    }

    private function addElement(AppendStream $stream, array $element): void
    {
        foreach (['contents', 'name'] as $key) {
            if (!array_key_exists($key, $element)) {
                throw new \InvalidArgumentException("A '{$key}' key is required");
            }
        }

        $element['contents'] = Utils::streamFor($element['contents']);

        if (empty($element['filename'])) {
            $uri = $element['contents']->getMetadata('uri');
            if ($uri && \is_string($uri) && \substr($uri, 0, 6) !== 'php://' && \substr($uri, 0, 7) !== 'data://') {
                $element['filename'] = $uri;
            }
        }

        [$body, $headers] = $this->createElement(
            $element['name'],
            $element['contents'],
            $element['filename'] ?? null,
            $element['headers'] ?? []
        );

        $stream->addStream(Utils::streamFor($this->getHeaders($headers)));
        $stream->addStream($body);
        $stream->addStream(Utils::streamFor("\r\n"));
    }

    /**
     * @param string[] $headers
     *
     * @return array{0: StreamInterface, 1: string[]}
     */
    private function createElement(string $name, StreamInterface $stream, ?string $filename, array $headers): array
    {
        // Set a default content-disposition header if one was no provided
        $disposition = self::getHeader($headers, 'content-disposition');
        if (!$disposition) {
            $headers['Content-Disposition'] = ($filename === '0' || $filename)
                ? sprintf(
                    'form-data; name="%s"; filename="%s"',
                    $name,
                    basename($filename)
                )
                : "form-data; name=\"{$name}\"";
        }

        // Set a default content-length header if one was no provided
        $length = self::getHeader($headers, 'content-length');
        if (!$length) {
            if ($length = $stream->getSize()) {
                $headers['Content-Length'] = (string) $length;
            }
        }

        // Set a default Content-Type if one was not supplied
        $type = self::getHeader($headers, 'content-type');
        if (!$type && ($filename === '0' || $filename)) {
            $headers['Content-Type'] = MimeType::fromFilename($filename) ?? 'application/octet-stream';
        }

        return [$stream, $headers];
    }

    /**
     * @param string[] $headers
     */
    private static function getHeader(array $headers, string $key): ?string
    {
        $lowercaseHeader = strtolower($key);
        foreach ($headers as $k => $v) {
            if (strtolower((string) $k) === $lowercaseHeader) {
                return $v;
            }
        }

        return null;
    }
}
PK�c�\X����	�	guzzlehttp/psr7/composer.jsonnu�[���{
    "name": "guzzlehttp/psr7",
    "description": "PSR-7 message implementation that also provides common utility methods",
    "keywords": [
        "request",
        "response",
        "message",
        "stream",
        "http",
        "uri",
        "url",
        "psr-7"
    ],
    "license": "MIT",
    "authors": [
        {
            "name": "Graham Campbell",
            "email": "hello@gjcampbell.co.uk",
            "homepage": "https://github.com/GrahamCampbell"
        },
        {
            "name": "Michael Dowling",
            "email": "mtdowling@gmail.com",
            "homepage": "https://github.com/mtdowling"
        },
        {
            "name": "George Mponos",
            "email": "gmponos@gmail.com",
            "homepage": "https://github.com/gmponos"
        },
        {
            "name": "Tobias Nyholm",
            "email": "tobias.nyholm@gmail.com",
            "homepage": "https://github.com/Nyholm"
        },
        {
            "name": "Márk Sági-Kazár",
            "email": "mark.sagikazar@gmail.com",
            "homepage": "https://github.com/sagikazarmark"
        },
        {
            "name": "Tobias Schultze",
            "email": "webmaster@tubo-world.de",
            "homepage": "https://github.com/Tobion"
        },
        {
            "name": "Márk Sági-Kazár",
            "email": "mark.sagikazar@gmail.com",
            "homepage": "https://sagikazarmark.hu"
        }
    ],
    "require": {
        "php": "^7.2.5 || ^8.0",
        "psr/http-factory": "^1.0",
        "psr/http-message": "^1.1 || ^2.0",
        "ralouphie/getallheaders": "^3.0"
    },
    "provide": {
        "psr/http-factory-implementation": "1.0",
        "psr/http-message-implementation": "1.0"
    },
    "require-dev": {
        "bamarni/composer-bin-plugin": "^1.8.2",
        "http-interop/http-factory-tests": "^0.9",
        "phpunit/phpunit": "^8.5.36 || ^9.6.15"
    },
    "suggest": {
        "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
    },
    "autoload": {
        "psr-4": {
            "GuzzleHttp\\Psr7\\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "GuzzleHttp\\Tests\\Psr7\\": "tests/"
        }
    },
    "extra": {
        "bamarni-bin": {
            "bin-links": true,
            "forward-command": false
        }
    },
    "config": {
        "allow-plugins": {
            "bamarni/composer-bin-plugin": true
        },
        "preferred-install": "dist",
        "sort-packages": true
    }
}
PK�c�\Aa���+�+guzzlehttp/psr7/CHANGELOG.mdnu�[���# Change Log

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## 2.6.2 - 2023-12-03

### Fixed

- Fixed another issue with the fact that PHP transforms numeric strings in array keys to ints

### Changed

- Updated links in docs to their canonical versions
- Replaced `call_user_func*` with native calls

## 2.6.1 - 2023-08-27

### Fixed

- Properly handle the fact that PHP transforms numeric strings in array keys to ints

## 2.6.0 - 2023-08-03

### Changed

- Updated the mime type map to add some new entries, fix a couple of invalid entries, and remove an invalid entry
- Fallback to `application/octet-stream` if we are unable to guess the content type for a multipart file upload

## 2.5.1 - 2023-08-03

### Fixed

- Corrected mime type for `.acc` files to `audio/aac`

### Changed

- PHP 8.3 support

## 2.5.0 - 2023-04-17

### Changed

- Adjusted `psr/http-message` version constraint to `^1.1 || ^2.0`

## 2.4.5 - 2023-04-17

### Fixed

- Prevent possible warnings on unset variables in `ServerRequest::normalizeNestedFileSpec`
- Fixed `Message::bodySummary` when `preg_match` fails
- Fixed header validation issue

## 2.4.4 - 2023-03-09

### Changed

- Removed the need for `AllowDynamicProperties` in `LazyOpenStream`

## 2.4.3 - 2022-10-26

### Changed

- Replaced `sha1(uniqid())` by `bin2hex(random_bytes(20))`

## 2.4.2 - 2022-10-25

### Fixed

- Fixed erroneous behaviour when combining host and relative path

## 2.4.1 - 2022-08-28

### Fixed

- Rewind body before reading in `Message::bodySummary`

## 2.4.0 - 2022-06-20

### Added

- Added provisional PHP 8.2 support
- Added `UriComparator::isCrossOrigin` method

## 2.3.0 - 2022-06-09

### Fixed

- Added `Header::splitList` method
- Added `Utils::tryGetContents` method
- Improved `Stream::getContents` method
- Updated mimetype mappings

## 2.2.2 - 2022-06-08

### Fixed

- Fix `Message::parseRequestUri` for numeric headers
- Re-wrap exceptions thrown in `fread` into runtime exceptions
- Throw an exception when multipart options is misformatted

## 2.2.1 - 2022-03-20

### Fixed

- Correct header value validation

## 2.2.0 - 2022-03-20

### Added

- A more compressive list of mime types
- Add JsonSerializable to Uri
- Missing return types

### Fixed

- Bug MultipartStream no `uri` metadata
- Bug MultipartStream with filename for `data://` streams
- Fixed new line handling in MultipartStream
- Reduced RAM usage when copying streams
- Updated parsing in `Header::normalize()`

## 2.1.1 - 2022-03-20

### Fixed

- Validate header values properly

## 2.1.0 - 2021-10-06

### Changed

- Attempting to create a `Uri` object from a malformed URI will no longer throw a generic
  `InvalidArgumentException`, but rather a `MalformedUriException`, which inherits from the former
  for backwards compatibility. Callers relying on the exception being thrown to detect invalid
  URIs should catch the new exception.

### Fixed

- Return `null` in caching stream size if remote size is `null`

## 2.0.0 - 2021-06-30

Identical to the RC release.

## 2.0.0@RC-1 - 2021-04-29

### Fixed

- Handle possibly unset `url` in `stream_get_meta_data`

## 2.0.0@beta-1 - 2021-03-21

### Added

- PSR-17 factories
- Made classes final
- PHP7 type hints

### Changed

- When building a query string, booleans are represented as 1 and 0.

### Removed

- PHP < 7.2 support
- All functions in the `GuzzleHttp\Psr7` namespace

## 1.8.1 - 2021-03-21

### Fixed

- Issue parsing IPv6 URLs
- Issue modifying ServerRequest lost all its attributes

## 1.8.0 - 2021-03-21

### Added

- Locale independent URL parsing
- Most classes got a `@final` annotation to prepare for 2.0

### Fixed

- Issue when creating stream from `php://input` and curl-ext is not installed
- Broken `Utils::tryFopen()` on PHP 8

## 1.7.0 - 2020-09-30

### Added

- Replaced functions by static methods

### Fixed

- Converting a non-seekable stream to a string
- Handle multiple Set-Cookie correctly
- Ignore array keys in header values when merging
- Allow multibyte characters to be parsed in `Message:bodySummary()`

### Changed

- Restored partial HHVM 3 support


## [1.6.1] - 2019-07-02

### Fixed

- Accept null and bool header values again


## [1.6.0] - 2019-06-30

### Added

- Allowed version `^3.0` of `ralouphie/getallheaders` dependency (#244)
- Added MIME type for WEBP image format (#246)
- Added more validation of values according to PSR-7 and RFC standards, e.g. status code range (#250, #272)

### Changed

- Tests don't pass with HHVM 4.0, so HHVM support got dropped. Other libraries like composer have done the same. (#262)
- Accept port number 0 to be valid (#270)

### Fixed

- Fixed subsequent reads from `php://input` in ServerRequest (#247)
- Fixed readable/writable detection for certain stream modes (#248)
- Fixed encoding of special characters in the `userInfo` component of an URI (#253)


## [1.5.2] - 2018-12-04

### Fixed

- Check body size when getting the message summary


## [1.5.1] - 2018-12-04

### Fixed

- Get the summary of a body only if it is readable


## [1.5.0] - 2018-12-03

### Added

- Response first-line to response string exception (fixes #145)
- A test for #129 behavior
- `get_message_body_summary` function in order to get the message summary
- `3gp` and `mkv` mime types

### Changed

- Clarify exception message when stream is detached

### Deprecated

- Deprecated parsing folded header lines as per RFC 7230

### Fixed

- Fix `AppendStream::detach` to not close streams
- `InflateStream` preserves `isSeekable` attribute of the underlying stream
- `ServerRequest::getUriFromGlobals` to support URLs in query parameters


Several other fixes and improvements.


## [1.4.2] - 2017-03-20

### Fixed

- Reverted BC break to `Uri::resolve` and `Uri::removeDotSegments` by removing
  calls to `trigger_error` when deprecated methods are invoked.


## [1.4.1] - 2017-02-27

### Added

- Rriggering of silenced deprecation warnings.

### Fixed

- Reverted BC break by reintroducing behavior to automagically fix a URI with a
  relative path and an authority by adding a leading slash to the path. It's only
  deprecated now.


## [1.4.0] - 2017-02-21

### Added

- Added common URI utility methods based on RFC 3986 (see documentation in the readme):
  - `Uri::isDefaultPort`
  - `Uri::isAbsolute`
  - `Uri::isNetworkPathReference`
  - `Uri::isAbsolutePathReference`
  - `Uri::isRelativePathReference`
  - `Uri::isSameDocumentReference`
  - `Uri::composeComponents`
  - `UriNormalizer::normalize`
  - `UriNormalizer::isEquivalent`
  - `UriResolver::relativize`

### Changed

- Ensure `ServerRequest::getUriFromGlobals` returns a URI in absolute form.
- Allow `parse_response` to parse a response without delimiting space and reason.
- Ensure each URI modification results in a valid URI according to PSR-7 discussions.
  Invalid modifications will throw an exception instead of returning a wrong URI or
  doing some magic.
  - `(new Uri)->withPath('foo')->withHost('example.com')` will throw an exception
    because the path of a URI with an authority must start with a slash "/" or be empty
  - `(new Uri())->withScheme('http')` will return `'http://localhost'`

### Deprecated

- `Uri::resolve` in favor of `UriResolver::resolve`
- `Uri::removeDotSegments` in favor of `UriResolver::removeDotSegments`

### Fixed

- `Stream::read` when length parameter <= 0.
- `copy_to_stream` reads bytes in chunks instead of `maxLen` into memory.
- `ServerRequest::getUriFromGlobals` when `Host` header contains port.
- Compatibility of URIs with `file` scheme and empty host.


## [1.3.1] - 2016-06-25

### Fixed

- `Uri::__toString` for network path references, e.g. `//example.org`.
- Missing lowercase normalization for host.
- Handling of URI components in case they are `'0'` in a lot of places,
  e.g. as a user info password.
- `Uri::withAddedHeader` to correctly merge headers with different case.
- Trimming of header values in `Uri::withAddedHeader`. Header values may
  be surrounded by whitespace which should be ignored according to RFC 7230
  Section 3.2.4. This does not apply to header names.
- `Uri::withAddedHeader` with an array of header values.
- `Uri::resolve` when base path has no slash and handling of fragment.
- Handling of encoding in `Uri::with(out)QueryValue` so one can pass the
  key/value both in encoded as well as decoded form to those methods. This is
  consistent with withPath, withQuery etc.
- `ServerRequest::withoutAttribute` when attribute value is null.


## [1.3.0] - 2016-04-13

### Added

- Remaining interfaces needed for full PSR7 compatibility
  (ServerRequestInterface, UploadedFileInterface, etc.).
- Support for stream_for from scalars.

### Changed

- Can now extend Uri.

### Fixed
- A bug in validating request methods by making it more permissive.


## [1.2.3] - 2016-02-18

### Fixed

- Support in `GuzzleHttp\Psr7\CachingStream` for seeking forward on remote
  streams, which can sometimes return fewer bytes than requested with `fread`.
- Handling of gzipped responses with FNAME headers.


## [1.2.2] - 2016-01-22

### Added

- Support for URIs without any authority.
- Support for HTTP 451 'Unavailable For Legal Reasons.'
- Support for using '0' as a filename.
- Support for including non-standard ports in Host headers.


## [1.2.1] - 2015-11-02

### Changes

- Now supporting negative offsets when seeking to SEEK_END.


## [1.2.0] - 2015-08-15

### Changed

- Body as `"0"` is now properly added to a response.
- Now allowing forward seeking in CachingStream.
- Now properly parsing HTTP requests that contain proxy targets in
  `parse_request`.
- functions.php is now conditionally required.
- user-info is no longer dropped when resolving URIs.


## [1.1.0] - 2015-06-24

### Changed

- URIs can now be relative.
- `multipart/form-data` headers are now overridden case-insensitively.
- URI paths no longer encode the following characters because they are allowed
  in URIs: "(", ")", "*", "!", "'"
- A port is no longer added to a URI when the scheme is missing and no port is
  present.


## 1.0.0 - 2015-05-19

Initial release.

Currently unsupported:

- `Psr\Http\Message\ServerRequestInterface`
- `Psr\Http\Message\UploadedFileInterface`



[1.6.0]: https://github.com/guzzle/psr7/compare/1.5.2...1.6.0
[1.5.2]: https://github.com/guzzle/psr7/compare/1.5.1...1.5.2
[1.5.1]: https://github.com/guzzle/psr7/compare/1.5.0...1.5.1
[1.5.0]: https://github.com/guzzle/psr7/compare/1.4.2...1.5.0
[1.4.2]: https://github.com/guzzle/psr7/compare/1.4.1...1.4.2
[1.4.1]: https://github.com/guzzle/psr7/compare/1.4.0...1.4.1
[1.4.0]: https://github.com/guzzle/psr7/compare/1.3.1...1.4.0
[1.3.1]: https://github.com/guzzle/psr7/compare/1.3.0...1.3.1
[1.3.0]: https://github.com/guzzle/psr7/compare/1.2.3...1.3.0
[1.2.3]: https://github.com/guzzle/psr7/compare/1.2.2...1.2.3
[1.2.2]: https://github.com/guzzle/psr7/compare/1.2.1...1.2.2
[1.2.1]: https://github.com/guzzle/psr7/compare/1.2.0...1.2.1
[1.2.0]: https://github.com/guzzle/psr7/compare/1.1.0...1.2.0
[1.1.0]: https://github.com/guzzle/psr7/compare/1.0.0...1.1.0
PK�c�\^�pLzzguzzlehttp/psr7/LICENSEnu�[���The MIT License (MIT)

Copyright (c) 2015 Michael Dowling <mtdowling@gmail.com>
Copyright (c) 2015 Márk Sági-Kazár <mark.sagikazar@gmail.com>
Copyright (c) 2015 Graham Campbell <hello@gjcampbell.co.uk>
Copyright (c) 2016 Tobias Schultze <webmaster@tubo-world.de>
Copyright (c) 2016 George Mponos <gmponos@gmail.com>
Copyright (c) 2018 Tobias Nyholm <tobias.nyholm@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
PK�c�\�DBZjrjrguzzlehttp/psr7/README.mdnu�[���# PSR-7 Message Implementation

This repository contains a full [PSR-7](https://www.php-fig.org/psr/psr-7/)
message implementation, several stream decorators, and some helpful
functionality like query string parsing.

![CI](https://github.com/guzzle/psr7/workflows/CI/badge.svg)
![Static analysis](https://github.com/guzzle/psr7/workflows/Static%20analysis/badge.svg)


## Features

This package comes with a number of stream implementations and stream
decorators.


## Installation

```shell
composer require guzzlehttp/psr7
```

## Version Guidance

| Version | Status              | PHP Version  |
|---------|---------------------|--------------|
| 1.x     | Security fixes only | >=5.4,<8.1   |
| 2.x     | Latest              | >=7.2.5,<8.4 |


## AppendStream

`GuzzleHttp\Psr7\AppendStream`

Reads from multiple streams, one after the other.

```php
use GuzzleHttp\Psr7;

$a = Psr7\Utils::streamFor('abc, ');
$b = Psr7\Utils::streamFor('123.');
$composed = new Psr7\AppendStream([$a, $b]);

$composed->addStream(Psr7\Utils::streamFor(' Above all listen to me'));

echo $composed; // abc, 123. Above all listen to me.
```


## BufferStream

`GuzzleHttp\Psr7\BufferStream`

Provides a buffer stream that can be written to fill a buffer, and read
from to remove bytes from the buffer.

This stream returns a "hwm" metadata value that tells upstream consumers
what the configured high water mark of the stream is, or the maximum
preferred size of the buffer.

```php
use GuzzleHttp\Psr7;

// When more than 1024 bytes are in the buffer, it will begin returning
// false to writes. This is an indication that writers should slow down.
$buffer = new Psr7\BufferStream(1024);
```


## CachingStream

The CachingStream is used to allow seeking over previously read bytes on
non-seekable streams. This can be useful when transferring a non-seekable
entity body fails due to needing to rewind the stream (for example, resulting
from a redirect). Data that is read from the remote stream will be buffered in
a PHP temp stream so that previously read bytes are cached first in memory,
then on disk.

```php
use GuzzleHttp\Psr7;

$original = Psr7\Utils::streamFor(fopen('http://www.google.com', 'r'));
$stream = new Psr7\CachingStream($original);

$stream->read(1024);
echo $stream->tell();
// 1024

$stream->seek(0);
echo $stream->tell();
// 0
```


## DroppingStream

`GuzzleHttp\Psr7\DroppingStream`

Stream decorator that begins dropping data once the size of the underlying
stream becomes too full.

```php
use GuzzleHttp\Psr7;

// Create an empty stream
$stream = Psr7\Utils::streamFor();

// Start dropping data when the stream has more than 10 bytes
$dropping = new Psr7\DroppingStream($stream, 10);

$dropping->write('01234567890123456789');
echo $stream; // 0123456789
```


## FnStream

`GuzzleHttp\Psr7\FnStream`

Compose stream implementations based on a hash of functions.

Allows for easy testing and extension of a provided stream without needing
to create a concrete class for a simple extension point.

```php

use GuzzleHttp\Psr7;

$stream = Psr7\Utils::streamFor('hi');
$fnStream = Psr7\FnStream::decorate($stream, [
    'rewind' => function () use ($stream) {
        echo 'About to rewind - ';
        $stream->rewind();
        echo 'rewound!';
    }
]);

$fnStream->rewind();
// Outputs: About to rewind - rewound!
```


## InflateStream

`GuzzleHttp\Psr7\InflateStream`

Uses PHP's zlib.inflate filter to inflate zlib (HTTP deflate, RFC1950) or gzipped (RFC1952) content.

This stream decorator converts the provided stream to a PHP stream resource,
then appends the zlib.inflate filter. The stream is then converted back
to a Guzzle stream resource to be used as a Guzzle stream.


## LazyOpenStream

`GuzzleHttp\Psr7\LazyOpenStream`

Lazily reads or writes to a file that is opened only after an IO operation
take place on the stream.

```php
use GuzzleHttp\Psr7;

$stream = new Psr7\LazyOpenStream('/path/to/file', 'r');
// The file has not yet been opened...

echo $stream->read(10);
// The file is opened and read from only when needed.
```


## LimitStream

`GuzzleHttp\Psr7\LimitStream`

LimitStream can be used to read a subset or slice of an existing stream object.
This can be useful for breaking a large file into smaller pieces to be sent in
chunks (e.g. Amazon S3's multipart upload API).

```php
use GuzzleHttp\Psr7;

$original = Psr7\Utils::streamFor(fopen('/tmp/test.txt', 'r+'));
echo $original->getSize();
// >>> 1048576

// Limit the size of the body to 1024 bytes and start reading from byte 2048
$stream = new Psr7\LimitStream($original, 1024, 2048);
echo $stream->getSize();
// >>> 1024
echo $stream->tell();
// >>> 0
```


## MultipartStream

`GuzzleHttp\Psr7\MultipartStream`

Stream that when read returns bytes for a streaming multipart or
multipart/form-data stream.


## NoSeekStream

`GuzzleHttp\Psr7\NoSeekStream`

NoSeekStream wraps a stream and does not allow seeking.

```php
use GuzzleHttp\Psr7;

$original = Psr7\Utils::streamFor('foo');
$noSeek = new Psr7\NoSeekStream($original);

echo $noSeek->read(3);
// foo
var_export($noSeek->isSeekable());
// false
$noSeek->seek(0);
var_export($noSeek->read(3));
// NULL
```


## PumpStream

`GuzzleHttp\Psr7\PumpStream`

Provides a read only stream that pumps data from a PHP callable.

When invoking the provided callable, the PumpStream will pass the amount of
data requested to read to the callable. The callable can choose to ignore
this value and return fewer or more bytes than requested. Any extra data
returned by the provided callable is buffered internally until drained using
the read() function of the PumpStream. The provided callable MUST return
false when there is no more data to read.


## Implementing stream decorators

Creating a stream decorator is very easy thanks to the
`GuzzleHttp\Psr7\StreamDecoratorTrait`. This trait provides methods that
implement `Psr\Http\Message\StreamInterface` by proxying to an underlying
stream. Just `use` the `StreamDecoratorTrait` and implement your custom
methods.

For example, let's say we wanted to call a specific function each time the last
byte is read from a stream. This could be implemented by overriding the
`read()` method.

```php
use Psr\Http\Message\StreamInterface;
use GuzzleHttp\Psr7\StreamDecoratorTrait;

class EofCallbackStream implements StreamInterface
{
    use StreamDecoratorTrait;

    private $callback;

    private $stream;

    public function __construct(StreamInterface $stream, callable $cb)
    {
        $this->stream = $stream;
        $this->callback = $cb;
    }

    public function read($length)
    {
        $result = $this->stream->read($length);

        // Invoke the callback when EOF is hit.
        if ($this->eof()) {
            ($this->callback)();
        }

        return $result;
    }
}
```

This decorator could be added to any existing stream and used like so:

```php
use GuzzleHttp\Psr7;

$original = Psr7\Utils::streamFor('foo');

$eofStream = new EofCallbackStream($original, function () {
    echo 'EOF!';
});

$eofStream->read(2);
$eofStream->read(1);
// echoes "EOF!"
$eofStream->seek(0);
$eofStream->read(3);
// echoes "EOF!"
```


## PHP StreamWrapper

You can use the `GuzzleHttp\Psr7\StreamWrapper` class if you need to use a
PSR-7 stream as a PHP stream resource.

Use the `GuzzleHttp\Psr7\StreamWrapper::getResource()` method to create a PHP
stream from a PSR-7 stream.

```php
use GuzzleHttp\Psr7\StreamWrapper;

$stream = GuzzleHttp\Psr7\Utils::streamFor('hello!');
$resource = StreamWrapper::getResource($stream);
echo fread($resource, 6); // outputs hello!
```


# Static API

There are various static methods available under the `GuzzleHttp\Psr7` namespace.


## `GuzzleHttp\Psr7\Message::toString`

`public static function toString(MessageInterface $message): string`

Returns the string representation of an HTTP message.

```php
$request = new GuzzleHttp\Psr7\Request('GET', 'http://example.com');
echo GuzzleHttp\Psr7\Message::toString($request);
```


## `GuzzleHttp\Psr7\Message::bodySummary`

`public static function bodySummary(MessageInterface $message, int $truncateAt = 120): string|null`

Get a short summary of the message body.

Will return `null` if the response is not printable.


## `GuzzleHttp\Psr7\Message::rewindBody`

`public static function rewindBody(MessageInterface $message): void`

Attempts to rewind a message body and throws an exception on failure.

The body of the message will only be rewound if a call to `tell()`
returns a value other than `0`.


## `GuzzleHttp\Psr7\Message::parseMessage`

`public static function parseMessage(string $message): array`

Parses an HTTP message into an associative array.

The array contains the "start-line" key containing the start line of
the message, "headers" key containing an associative array of header
array values, and a "body" key containing the body of the message.


## `GuzzleHttp\Psr7\Message::parseRequestUri`

`public static function parseRequestUri(string $path, array $headers): string`

Constructs a URI for an HTTP request message.


## `GuzzleHttp\Psr7\Message::parseRequest`

`public static function parseRequest(string $message): Request`

Parses a request message string into a request object.


## `GuzzleHttp\Psr7\Message::parseResponse`

`public static function parseResponse(string $message): Response`

Parses a response message string into a response object.


## `GuzzleHttp\Psr7\Header::parse`

`public static function parse(string|array $header): array`

Parse an array of header values containing ";" separated data into an
array of associative arrays representing the header key value pair data
of the header. When a parameter does not contain a value, but just
contains a key, this function will inject a key with a '' string value.


## `GuzzleHttp\Psr7\Header::splitList`

`public static function splitList(string|string[] $header): string[]`

Splits a HTTP header defined to contain a comma-separated list into
each individual value:

```
$knownEtags = Header::splitList($request->getHeader('if-none-match'));
```

Example headers include `accept`, `cache-control` and `if-none-match`.


## `GuzzleHttp\Psr7\Header::normalize` (deprecated)

`public static function normalize(string|array $header): array`

`Header::normalize()` is deprecated in favor of [`Header::splitList()`](README.md#guzzlehttppsr7headersplitlist)
which performs the same operation with a cleaned up API and improved
documentation.

Converts an array of header values that may contain comma separated
headers into an array of headers with no comma separated values.


## `GuzzleHttp\Psr7\Query::parse`

`public static function parse(string $str, int|bool $urlEncoding = true): array`

Parse a query string into an associative array.

If multiple values are found for the same key, the value of that key
value pair will become an array. This function does not parse nested
PHP style arrays into an associative array (e.g., `foo[a]=1&foo[b]=2`
will be parsed into `['foo[a]' => '1', 'foo[b]' => '2'])`.


## `GuzzleHttp\Psr7\Query::build`

`public static function build(array $params, int|false $encoding = PHP_QUERY_RFC3986): string`

Build a query string from an array of key value pairs.

This function can use the return value of `parse()` to build a query
string. This function does not modify the provided keys when an array is
encountered (like `http_build_query()` would).


## `GuzzleHttp\Psr7\Utils::caselessRemove`

`public static function caselessRemove(iterable<string> $keys, $keys, array $data): array`

Remove the items given by the keys, case insensitively from the data.


## `GuzzleHttp\Psr7\Utils::copyToStream`

`public static function copyToStream(StreamInterface $source, StreamInterface $dest, int $maxLen = -1): void`

Copy the contents of a stream into another stream until the given number
of bytes have been read.


## `GuzzleHttp\Psr7\Utils::copyToString`

`public static function copyToString(StreamInterface $stream, int $maxLen = -1): string`

Copy the contents of a stream into a string until the given number of
bytes have been read.


## `GuzzleHttp\Psr7\Utils::hash`

`public static function hash(StreamInterface $stream, string $algo, bool $rawOutput = false): string`

Calculate a hash of a stream.

This method reads the entire stream to calculate a rolling hash, based on
PHP's `hash_init` functions.


## `GuzzleHttp\Psr7\Utils::modifyRequest`

`public static function modifyRequest(RequestInterface $request, array $changes): RequestInterface`

Clone and modify a request with the given changes.

This method is useful for reducing the number of clones needed to mutate
a message.

- method: (string) Changes the HTTP method.
- set_headers: (array) Sets the given headers.
- remove_headers: (array) Remove the given headers.
- body: (mixed) Sets the given body.
- uri: (UriInterface) Set the URI.
- query: (string) Set the query string value of the URI.
- version: (string) Set the protocol version.


## `GuzzleHttp\Psr7\Utils::readLine`

`public static function readLine(StreamInterface $stream, int $maxLength = null): string`

Read a line from the stream up to the maximum allowed buffer length.


## `GuzzleHttp\Psr7\Utils::streamFor`

`public static function streamFor(resource|string|null|int|float|bool|StreamInterface|callable|\Iterator $resource = '', array $options = []): StreamInterface`

Create a new stream based on the input type.

Options is an associative array that can contain the following keys:

- metadata: Array of custom metadata.
- size: Size of the stream.

This method accepts the following `$resource` types:

- `Psr\Http\Message\StreamInterface`: Returns the value as-is.
- `string`: Creates a stream object that uses the given string as the contents.
- `resource`: Creates a stream object that wraps the given PHP stream resource.
- `Iterator`: If the provided value implements `Iterator`, then a read-only
  stream object will be created that wraps the given iterable. Each time the
  stream is read from, data from the iterator will fill a buffer and will be
  continuously called until the buffer is equal to the requested read size.
  Subsequent read calls will first read from the buffer and then call `next`
  on the underlying iterator until it is exhausted.
- `object` with `__toString()`: If the object has the `__toString()` method,
  the object will be cast to a string and then a stream will be returned that
  uses the string value.
- `NULL`: When `null` is passed, an empty stream object is returned.
- `callable` When a callable is passed, a read-only stream object will be
  created that invokes the given callable. The callable is invoked with the
  number of suggested bytes to read. The callable can return any number of
  bytes, but MUST return `false` when there is no more data to return. The
  stream object that wraps the callable will invoke the callable until the
  number of requested bytes are available. Any additional bytes will be
  buffered and used in subsequent reads.

```php
$stream = GuzzleHttp\Psr7\Utils::streamFor('foo');
$stream = GuzzleHttp\Psr7\Utils::streamFor(fopen('/path/to/file', 'r'));

$generator = function ($bytes) {
    for ($i = 0; $i < $bytes; $i++) {
        yield ' ';
    }
}

$stream = GuzzleHttp\Psr7\Utils::streamFor($generator(100));
```


## `GuzzleHttp\Psr7\Utils::tryFopen`

`public static function tryFopen(string $filename, string $mode): resource`

Safely opens a PHP stream resource using a filename.

When fopen fails, PHP normally raises a warning. This function adds an
error handler that checks for errors and throws an exception instead.


## `GuzzleHttp\Psr7\Utils::tryGetContents`

`public static function tryGetContents(resource $stream): string`

Safely gets the contents of a given stream.

When stream_get_contents fails, PHP normally raises a warning. This
function adds an error handler that checks for errors and throws an
exception instead.


## `GuzzleHttp\Psr7\Utils::uriFor`

`public static function uriFor(string|UriInterface $uri): UriInterface`

Returns a UriInterface for the given value.

This function accepts a string or UriInterface and returns a
UriInterface for the given value. If the value is already a
UriInterface, it is returned as-is.


## `GuzzleHttp\Psr7\MimeType::fromFilename`

`public static function fromFilename(string $filename): string|null`

Determines the mimetype of a file by looking at its extension.


## `GuzzleHttp\Psr7\MimeType::fromExtension`

`public static function fromExtension(string $extension): string|null`

Maps a file extensions to a mimetype.


## Upgrading from Function API

The static API was first introduced in 1.7.0, in order to mitigate problems with functions conflicting between global and local copies of the package. The function API was removed in 2.0.0. A migration table has been provided here for your convenience:

| Original Function | Replacement Method |
|----------------|----------------|
| `str` | `Message::toString` |
| `uri_for` | `Utils::uriFor` |
| `stream_for` | `Utils::streamFor` |
| `parse_header` | `Header::parse` |
| `normalize_header` | `Header::normalize` |
| `modify_request` | `Utils::modifyRequest` |
| `rewind_body` | `Message::rewindBody` |
| `try_fopen` | `Utils::tryFopen` |
| `copy_to_string` | `Utils::copyToString` |
| `copy_to_stream` | `Utils::copyToStream` |
| `hash` | `Utils::hash` |
| `readline` | `Utils::readLine` |
| `parse_request` | `Message::parseRequest` |
| `parse_response` | `Message::parseResponse` |
| `parse_query` | `Query::parse` |
| `build_query` | `Query::build` |
| `mimetype_from_filename` | `MimeType::fromFilename` |
| `mimetype_from_extension` | `MimeType::fromExtension` |
| `_parse_message` | `Message::parseMessage` |
| `_parse_request_uri` | `Message::parseRequestUri` |
| `get_message_body_summary` | `Message::bodySummary` |
| `_caseless_remove` | `Utils::caselessRemove` |


# Additional URI Methods

Aside from the standard `Psr\Http\Message\UriInterface` implementation in form of the `GuzzleHttp\Psr7\Uri` class,
this library also provides additional functionality when working with URIs as static methods.

## URI Types

An instance of `Psr\Http\Message\UriInterface` can either be an absolute URI or a relative reference.
An absolute URI has a scheme. A relative reference is used to express a URI relative to another URI,
the base URI. Relative references can be divided into several forms according to
[RFC 3986 Section 4.2](https://datatracker.ietf.org/doc/html/rfc3986#section-4.2):

- network-path references, e.g. `//example.com/path`
- absolute-path references, e.g. `/path`
- relative-path references, e.g. `subpath`

The following methods can be used to identify the type of the URI.

### `GuzzleHttp\Psr7\Uri::isAbsolute`

`public static function isAbsolute(UriInterface $uri): bool`

Whether the URI is absolute, i.e. it has a scheme.

### `GuzzleHttp\Psr7\Uri::isNetworkPathReference`

`public static function isNetworkPathReference(UriInterface $uri): bool`

Whether the URI is a network-path reference. A relative reference that begins with two slash characters is
termed an network-path reference.

### `GuzzleHttp\Psr7\Uri::isAbsolutePathReference`

`public static function isAbsolutePathReference(UriInterface $uri): bool`

Whether the URI is a absolute-path reference. A relative reference that begins with a single slash character is
termed an absolute-path reference.

### `GuzzleHttp\Psr7\Uri::isRelativePathReference`

`public static function isRelativePathReference(UriInterface $uri): bool`

Whether the URI is a relative-path reference. A relative reference that does not begin with a slash character is
termed a relative-path reference.

### `GuzzleHttp\Psr7\Uri::isSameDocumentReference`

`public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null): bool`

Whether the URI is a same-document reference. A same-document reference refers to a URI that is, aside from its
fragment component, identical to the base URI. When no base URI is given, only an empty URI reference
(apart from its fragment) is considered a same-document reference.

## URI Components

Additional methods to work with URI components.

### `GuzzleHttp\Psr7\Uri::isDefaultPort`

`public static function isDefaultPort(UriInterface $uri): bool`

Whether the URI has the default port of the current scheme. `Psr\Http\Message\UriInterface::getPort` may return null
or the standard port. This method can be used independently of the implementation.

### `GuzzleHttp\Psr7\Uri::composeComponents`

`public static function composeComponents($scheme, $authority, $path, $query, $fragment): string`

Composes a URI reference string from its various components according to
[RFC 3986 Section 5.3](https://datatracker.ietf.org/doc/html/rfc3986#section-5.3). Usually this method does not need
to be called manually but instead is used indirectly via `Psr\Http\Message\UriInterface::__toString`.

### `GuzzleHttp\Psr7\Uri::fromParts`

`public static function fromParts(array $parts): UriInterface`

Creates a URI from a hash of [`parse_url`](https://www.php.net/manual/en/function.parse-url.php) components.


### `GuzzleHttp\Psr7\Uri::withQueryValue`

`public static function withQueryValue(UriInterface $uri, $key, $value): UriInterface`

Creates a new URI with a specific query string value. Any existing query string values that exactly match the
provided key are removed and replaced with the given key value pair. A value of null will set the query string
key without a value, e.g. "key" instead of "key=value".

### `GuzzleHttp\Psr7\Uri::withQueryValues`

`public static function withQueryValues(UriInterface $uri, array $keyValueArray): UriInterface`

Creates a new URI with multiple query string values. It has the same behavior as `withQueryValue()` but for an
associative array of key => value.

### `GuzzleHttp\Psr7\Uri::withoutQueryValue`

`public static function withoutQueryValue(UriInterface $uri, $key): UriInterface`

Creates a new URI with a specific query string value removed. Any existing query string values that exactly match the
provided key are removed.

## Cross-Origin Detection

`GuzzleHttp\Psr7\UriComparator` provides methods to determine if a modified URL should be considered cross-origin.

### `GuzzleHttp\Psr7\UriComparator::isCrossOrigin`

`public static function isCrossOrigin(UriInterface $original, UriInterface $modified): bool`

Determines if a modified URL should be considered cross-origin with respect to an original URL.

## Reference Resolution

`GuzzleHttp\Psr7\UriResolver` provides methods to resolve a URI reference in the context of a base URI according
to [RFC 3986 Section 5](https://datatracker.ietf.org/doc/html/rfc3986#section-5). This is for example also what web
browsers do when resolving a link in a website based on the current request URI.

### `GuzzleHttp\Psr7\UriResolver::resolve`

`public static function resolve(UriInterface $base, UriInterface $rel): UriInterface`

Converts the relative URI into a new URI that is resolved against the base URI.

### `GuzzleHttp\Psr7\UriResolver::removeDotSegments`

`public static function removeDotSegments(string $path): string`

Removes dot segments from a path and returns the new path according to
[RFC 3986 Section 5.2.4](https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4).

### `GuzzleHttp\Psr7\UriResolver::relativize`

`public static function relativize(UriInterface $base, UriInterface $target): UriInterface`

Returns the target URI as a relative reference from the base URI. This method is the counterpart to resolve():

```php
(string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target))
```

One use-case is to use the current request URI as base URI and then generate relative links in your documents
to reduce the document size or offer self-contained downloadable document archives.

```php
$base = new Uri('http://example.com/a/b/');
echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c'));  // prints 'c'.
echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y'));  // prints '../x/y'.
echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'.
echo UriResolver::relativize($base, new Uri('http://example.org/a/b/'));   // prints '//example.org/a/b/'.
```

## Normalization and Comparison

`GuzzleHttp\Psr7\UriNormalizer` provides methods to normalize and compare URIs according to
[RFC 3986 Section 6](https://datatracker.ietf.org/doc/html/rfc3986#section-6).

### `GuzzleHttp\Psr7\UriNormalizer::normalize`

`public static function normalize(UriInterface $uri, $flags = self::PRESERVING_NORMALIZATIONS): UriInterface`

Returns a normalized URI. The scheme and host component are already normalized to lowercase per PSR-7 UriInterface.
This methods adds additional normalizations that can be configured with the `$flags` parameter which is a bitmask
of normalizations to apply. The following normalizations are available:

- `UriNormalizer::PRESERVING_NORMALIZATIONS`

    Default normalizations which only include the ones that preserve semantics.

- `UriNormalizer::CAPITALIZE_PERCENT_ENCODING`

    All letters within a percent-encoding triplet (e.g., "%3A") are case-insensitive, and should be capitalized.

    Example: `http://example.org/a%c2%b1b` → `http://example.org/a%C2%B1b`

- `UriNormalizer::DECODE_UNRESERVED_CHARACTERS`

    Decodes percent-encoded octets of unreserved characters. For consistency, percent-encoded octets in the ranges of
    ALPHA (%41–%5A and %61–%7A), DIGIT (%30–%39), hyphen (%2D), period (%2E), underscore (%5F), or tilde (%7E) should
    not be created by URI producers and, when found in a URI, should be decoded to their corresponding unreserved
    characters by URI normalizers.

    Example: `http://example.org/%7Eusern%61me/` → `http://example.org/~username/`

- `UriNormalizer::CONVERT_EMPTY_PATH`

    Converts the empty path to "/" for http and https URIs.

    Example: `http://example.org` → `http://example.org/`

- `UriNormalizer::REMOVE_DEFAULT_HOST`

    Removes the default host of the given URI scheme from the URI. Only the "file" scheme defines the default host
    "localhost". All of `file:/myfile`, `file:///myfile`, and `file://localhost/myfile` are equivalent according to
    RFC 3986.

    Example: `file://localhost/myfile` → `file:///myfile`

- `UriNormalizer::REMOVE_DEFAULT_PORT`

    Removes the default port of the given URI scheme from the URI.

    Example: `http://example.org:80/` → `http://example.org/`

- `UriNormalizer::REMOVE_DOT_SEGMENTS`

    Removes unnecessary dot-segments. Dot-segments in relative-path references are not removed as it would
    change the semantics of the URI reference.

    Example: `http://example.org/../a/b/../c/./d.html` → `http://example.org/a/c/d.html`

- `UriNormalizer::REMOVE_DUPLICATE_SLASHES`

    Paths which include two or more adjacent slashes are converted to one. Webservers usually ignore duplicate slashes
    and treat those URIs equivalent. But in theory those URIs do not need to be equivalent. So this normalization
    may change the semantics. Encoded slashes (%2F) are not removed.

    Example: `http://example.org//foo///bar.html` → `http://example.org/foo/bar.html`

- `UriNormalizer::SORT_QUERY_PARAMETERS`

    Sort query parameters with their values in alphabetical order. However, the order of parameters in a URI may be
    significant (this is not defined by the standard). So this normalization is not safe and may change the semantics
    of the URI.

    Example: `?lang=en&article=fred` → `?article=fred&lang=en`

### `GuzzleHttp\Psr7\UriNormalizer::isEquivalent`

`public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS): bool`

Whether two URIs can be considered equivalent. Both URIs are normalized automatically before comparison with the given
`$normalizations` bitmask. The method also accepts relative URI references and returns true when they are equivalent.
This of course assumes they will be resolved against the same base URI. If this is not the case, determination of
equivalence or difference of relative references does not mean anything.


## Security

If you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see [Security Policy](https://github.com/guzzle/psr7/security/policy) for more information.


## License

Guzzle is made available under the MIT License (MIT). Please see [License File](LICENSE) for more information.


## For Enterprise

Available as part of the Tidelift Subscription

The maintainers of Guzzle and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-guzzlehttp-psr7?utm_source=packagist-guzzlehttp-psr7&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)
PK�c�\y�q�6
6
bin/crypt-gpg-pinentrynu�[���#! /usr/bin/env php
<?php

/**
 * Proxy PHP file generated by Composer
 *
 * This file includes the referenced bin path (../pear/crypt_gpg/scripts/crypt-gpg-pinentry)
 * using a stream wrapper to prevent the shebang from being output on PHP<8
 *
 * @generated
 */

namespace Composer;

$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';

if (PHP_VERSION_ID < 80000) {
    if (!class_exists('Composer\BinProxyWrapper')) {
        /**
         * @internal
         */
        final class BinProxyWrapper
        {
            private $handle;
            private $position;
            private $realpath;

            public function stream_open($path, $mode, $options, &$opened_path)
            {
                // get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
                $opened_path = substr($path, 17);
                $this->realpath = realpath($opened_path) ?: $opened_path;
                $opened_path = $this->realpath;
                $this->handle = fopen($this->realpath, $mode);
                $this->position = 0;

                return (bool) $this->handle;
            }

            public function stream_read($count)
            {
                $data = fread($this->handle, $count);

                if ($this->position === 0) {
                    $data = preg_replace('{^#!.*\r?\n}', '', $data);
                }

                $this->position += strlen($data);

                return $data;
            }

            public function stream_cast($castAs)
            {
                return $this->handle;
            }

            public function stream_close()
            {
                fclose($this->handle);
            }

            public function stream_lock($operation)
            {
                return $operation ? flock($this->handle, $operation) : true;
            }

            public function stream_seek($offset, $whence)
            {
                if (0 === fseek($this->handle, $offset, $whence)) {
                    $this->position = ftell($this->handle);
                    return true;
                }

                return false;
            }

            public function stream_tell()
            {
                return $this->position;
            }

            public function stream_eof()
            {
                return feof($this->handle);
            }

            public function stream_stat()
            {
                return array();
            }

            public function stream_set_option($option, $arg1, $arg2)
            {
                return true;
            }

            public function url_stat($path, $flags)
            {
                $path = substr($path, 17);
                if (file_exists($path)) {
                    return stat($path);
                }

                return false;
            }
        }
    }

    if (
        (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
        || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
    ) {
        return include("phpvfscomposer://" . __DIR__ . '/..'.'/pear/crypt_gpg/scripts/crypt-gpg-pinentry');
    }
}

return include __DIR__ . '/..'.'/pear/crypt_gpg/scripts/crypt-gpg-pinentry';
PK�c�\zCo	++autoload.phpnu�[���<?php

// autoload.php @generated by Composer

if (PHP_VERSION_ID < 50600) {
    if (!headers_sent()) {
        header('HTTP/1.1 500 Internal Server Error');
    }
    $err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
    if (!ini_get('display_errors')) {
        if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
            fwrite(STDERR, $err);
        } elseif (!headers_sent()) {
            echo $err;
        }
    }
    trigger_error(
        $err,
        E_USER_ERROR
    );
}

require_once __DIR__ . '/composer/autoload_real.php';

return ComposerAutoloaderInitd30534b7ccef70d5be1f4ba9bc89dbe4::getLoader();
// generated by Roundcube install 1.6.7
PK�c�\,�H�II+symfony/deprecation-contracts/composer.jsonnu�[���{
    "name": "symfony/deprecation-contracts",
    "type": "library",
    "description": "A generic function and convention to trigger deprecation notices",
    "homepage": "https://symfony.com",
    "license": "MIT",
    "authors": [
        {
            "name": "Nicolas Grekas",
            "email": "p@tchwork.com"
        },
        {
            "name": "Symfony Community",
            "homepage": "https://symfony.com/contributors"
        }
    ],
    "require": {
        "php": ">=7.1"
    },
    "autoload": {
        "files": [
            "function.php"
        ]
    },
    "minimum-stability": "dev",
    "extra": {
        "branch-alias": {
            "dev-main": "2.5-dev"
        },
        "thanks": {
            "name": "symfony/contracts",
            "url": "https://github.com/symfony/contracts"
        }
    }
}
PK�c�\h{#��*symfony/deprecation-contracts/CHANGELOG.mdnu�[���CHANGELOG
=========

The changelog is maintained for all Symfony contracts at the following URL:
https://github.com/symfony/contracts/blob/main/CHANGELOG.md
PK�c�\K�,,%symfony/deprecation-contracts/LICENSEnu�[���Copyright (c) 2020-present Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
PK�c�\rg����*symfony/deprecation-contracts/function.phpnu�[���<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

if (!function_exists('trigger_deprecation')) {
    /**
     * Triggers a silenced deprecation notice.
     *
     * @param string $package The name of the Composer package that is triggering the deprecation
     * @param string $version The version of the package that introduced the deprecation
     * @param string $message The message of the deprecation
     * @param mixed  ...$args Values to insert in the message using printf() formatting
     *
     * @author Nicolas Grekas <p@tchwork.com>
     */
    function trigger_deprecation(string $package, string $version, string $message, ...$args): void
    {
        @trigger_error(($package || $version ? "Since $package $version: " : '').($args ? vsprintf($message, $args) : $message), \E_USER_DEPRECATED);
    }
}
PK�c�\��3��'symfony/deprecation-contracts/README.mdnu�[���Symfony Deprecation Contracts
=============================

A generic function and convention to trigger deprecation notices.

This package provides a single global function named `trigger_deprecation()` that triggers silenced deprecation notices.

By using a custom PHP error handler such as the one provided by the Symfony ErrorHandler component,
the triggered deprecations can be caught and logged for later discovery, both on dev and prod environments.

The function requires at least 3 arguments:
 - the name of the Composer package that is triggering the deprecation
 - the version of the package that introduced the deprecation
 - the message of the deprecation
 - more arguments can be provided: they will be inserted in the message using `printf()` formatting

Example:
```php
trigger_deprecation('symfony/blockchain', '8.9', 'Using "%s" is deprecated, use "%s" instead.', 'bitcoin', 'fabcoin');
```

This will generate the following message:
`Since symfony/blockchain 8.9: Using "bitcoin" is deprecated, use "fabcoin" instead.`

While not necessarily recommended, the deprecation notices can be completely ignored by declaring an empty
`function trigger_deprecation() {}` in your application.
PK�c�\�CI�

!dasprid/enum/src/AbstractEnum.phpnu�[���<?php
declare(strict_types = 1);

namespace DASPRiD\Enum;

use DASPRiD\Enum\Exception\CloneNotSupportedException;
use DASPRiD\Enum\Exception\IllegalArgumentException;
use DASPRiD\Enum\Exception\MismatchException;
use DASPRiD\Enum\Exception\SerializeNotSupportedException;
use DASPRiD\Enum\Exception\UnserializeNotSupportedException;
use ReflectionClass;

abstract class AbstractEnum
{
    /**
     * @var string
     */
    private $name;

    /**
     * @var int
     */
    private $ordinal;

    /**
     * @var array<string, array<string, static>>
     */
    private static $values = [];

    /**
     * @var array<string, bool>
     */
    private static $allValuesLoaded = [];

    /**
     * @var array<string, array>
     */
    private static $constants = [];

    /**
     * The constructor is private by default to avoid arbitrary enum creation.
     *
     * When creating your own constructor for a parameterized enum, make sure to declare it as protected, so that
     * the static methods are able to construct it. Avoid making it public, as that would allow creation of
     * non-singleton enum instances.
     */
    private function __construct()
    {
    }

    /**
     * Magic getter which forwards all calls to {@see self::valueOf()}.
     *
     * @return static
     */
    final public static function __callStatic(string $name, array $arguments) : self
    {
        return static::valueOf($name);
    }

    /**
     * Returns an enum with the specified name.
     *
     * The name must match exactly an identifier used to declare an enum in this type (extraneous whitespace characters
     * are not permitted).
     *
     * @return static
     * @throws IllegalArgumentException if the enum has no constant with the specified name
     */
    final public static function valueOf(string $name) : self
    {
        if (isset(self::$values[static::class][$name])) {
            return self::$values[static::class][$name];
        }

        $constants = self::constants();

        if (array_key_exists($name, $constants)) {
            return self::createValue($name, $constants[$name][0], $constants[$name][1]);
        }

        throw new IllegalArgumentException(sprintf('No enum constant %s::%s', static::class, $name));
    }

    /**
     * @return static
     */
    private static function createValue(string $name, int $ordinal, array $arguments) : self
    {
        $instance = new static(...$arguments);
        $instance->name = $name;
        $instance->ordinal = $ordinal;
        self::$values[static::class][$name] = $instance;
        return $instance;
    }

    /**
     * Obtains all possible types defined by this enum.
     *
     * @return static[]
     */
    final public static function values() : array
    {
        if (isset(self::$allValuesLoaded[static::class])) {
            return self::$values[static::class];
        }

        if (! isset(self::$values[static::class])) {
            self::$values[static::class] = [];
        }

        foreach (self::constants() as $name => $constant) {
            if (array_key_exists($name, self::$values[static::class])) {
                continue;
            }

            static::createValue($name, $constant[0], $constant[1]);
        }

        uasort(self::$values[static::class], function (self $a, self $b) {
            return $a->ordinal() <=> $b->ordinal();
        });

        self::$allValuesLoaded[static::class] = true;
        return self::$values[static::class];
    }

    private static function constants() : array
    {
        if (isset(self::$constants[static::class])) {
            return self::$constants[static::class];
        }

        self::$constants[static::class] = [];
        $reflectionClass = new ReflectionClass(static::class);
        $ordinal = -1;

        foreach ($reflectionClass->getReflectionConstants() as $reflectionConstant) {
            if (! $reflectionConstant->isProtected()) {
                continue;
            }

            $value = $reflectionConstant->getValue();

            self::$constants[static::class][$reflectionConstant->name] = [
                ++$ordinal,
                is_array($value) ? $value : []
            ];
        }

        return self::$constants[static::class];
    }

    /**
     * Returns the name of this enum constant, exactly as declared in its enum declaration.
     *
     * Most programmers should use the {@see self::__toString()} method in preference to this one, as the toString
     * method may return a more user-friendly name. This method is designed primarily for use in specialized situations
     * where correctness depends on getting the exact name, which will not vary from release to release.
     */
    final public function name() : string
    {
        return $this->name;
    }

    /**
     * Returns the ordinal of this enumeration constant (its position in its enum declaration, where the initial
     * constant is assigned an ordinal of zero).
     *
     * Most programmers will have no use for this method. It is designed for use by sophisticated enum-based data
     * structures.
     */
    final public function ordinal() : int
    {
        return $this->ordinal;
    }

    /**
     * Compares this enum with the specified object for order.
     *
     * Returns negative integer, zero or positive integer as this object is less than, equal to or greater than the
     * specified object.
     *
     * Enums are only comparable to other enums of the same type. The natural order implemented by this method is the
     * order in which the constants are declared.
     *
     * @throws MismatchException if the passed enum is not of the same type
     */
    final public function compareTo(self $other) : int
    {
        if (! $other instanceof static) {
            throw new MismatchException(sprintf(
                'The passed enum %s is not of the same type as %s',
                get_class($other),
                static::class
            ));
        }

        return $this->ordinal - $other->ordinal;
    }

    /**
     * Forbid cloning enums.
     *
     * @throws CloneNotSupportedException
     */
    final public function __clone()
    {
        throw new CloneNotSupportedException();
    }

    /**
     * Forbid serializing enums.
     *
     * @throws SerializeNotSupportedException
     */
    final public function __sleep() : array
    {
        throw new SerializeNotSupportedException();
    }

    /**
     * Forbid unserializing enums.
     *
     * @throws UnserializeNotSupportedException
     */
    final public function __wakeup() : void
    {
        throw new UnserializeNotSupportedException();
    }

    /**
     * Turns the enum into a string representation.
     *
     * You may override this method to give a more user-friendly version.
     */
    public function __toString() : string
    {
        return $this->name;
    }
}
PK�c�\��ʴ�=dasprid/enum/src/Exception/SerializeNotSupportedException.phpnu�[���<?php
declare(strict_types = 1);

namespace DASPRiD\Enum\Exception;

use Exception;

final class SerializeNotSupportedException extends Exception implements ExceptionInterface
{
}
PK�c�\1����3dasprid/enum/src/Exception/ExpectationException.phpnu�[���<?php
declare(strict_types = 1);

namespace DASPRiD\Enum\Exception;

use Exception;

final class ExpectationException extends Exception implements ExceptionInterface
{
}
PK�c�\�m�Y��7dasprid/enum/src/Exception/IllegalArgumentException.phpnu�[���<?php
declare(strict_types = 1);

namespace DASPRiD\Enum\Exception;

use Exception;

final class IllegalArgumentException extends Exception implements ExceptionInterface
{
}
PK�c�\N͐{��0dasprid/enum/src/Exception/MismatchException.phpnu�[���<?php
declare(strict_types = 1);

namespace DASPRiD\Enum\Exception;

use Exception;

final class MismatchException extends Exception implements ExceptionInterface
{
}
PK�c�\|�6��1dasprid/enum/src/Exception/ExceptionInterface.phpnu�[���<?php
declare(strict_types = 1);

namespace DASPRiD\Enum\Exception;

use Throwable;

interface ExceptionInterface extends Throwable
{
}
PK�c�\���ް�9dasprid/enum/src/Exception/CloneNotSupportedException.phpnu�[���<?php
declare(strict_types = 1);

namespace DASPRiD\Enum\Exception;

use Exception;

final class CloneNotSupportedException extends Exception implements ExceptionInterface
{
}
PK�c�\�L�A��?dasprid/enum/src/Exception/UnserializeNotSupportedException.phpnu�[���<?php
declare(strict_types = 1);

namespace DASPRiD\Enum\Exception;

use Exception;

final class UnserializeNotSupportedException extends Exception implements ExceptionInterface
{
}
PK�c�\c�YYdasprid/enum/src/NullValue.phpnu�[���<?php
declare(strict_types = 1);

namespace DASPRiD\Enum;

use DASPRiD\Enum\Exception\CloneNotSupportedException;
use DASPRiD\Enum\Exception\SerializeNotSupportedException;
use DASPRiD\Enum\Exception\UnserializeNotSupportedException;

final class NullValue
{
    /**
     * @var self
     */
    private static $instance;

    private function __construct()
    {
    }

    public static function instance() : self
    {
        return self::$instance ?: self::$instance = new self();
    }

    /**
     * Forbid cloning enums.
     *
     * @throws CloneNotSupportedException
     */
    final public function __clone()
    {
        throw new CloneNotSupportedException();
    }

    /**
     * Forbid serializing enums.
     *
     * @throws SerializeNotSupportedException
     */
    final public function __sleep() : array
    {
        throw new SerializeNotSupportedException();
    }

    /**
     * Forbid unserializing enums.
     *
     * @throws UnserializeNotSupportedException
     */
    final public function __wakeup() : void
    {
        throw new UnserializeNotSupportedException();
    }
}
PK�c�\ d�͟,�,dasprid/enum/src/EnumMap.phpnu�[���<?php
declare(strict_types = 1);

namespace DASPRiD\Enum;

use DASPRiD\Enum\Exception\ExpectationException;
use DASPRiD\Enum\Exception\IllegalArgumentException;
use IteratorAggregate;
use Serializable;
use Traversable;

/**
 * A specialized map implementation for use with enum type keys.
 *
 * All of the keys in an enum map must come from a single enum type that is specified, when the map is created. Enum
 * maps are represented internally as arrays. This representation is extremely compact and efficient.
 *
 * Enum maps are maintained in the natural order of their keys (the order in which the enum constants are declared).
 * This is reflected in the iterators returned by the collection views {@see self::getIterator()} and
 * {@see self::values()}.
 *
 * Iterators returned by the collection views are not consistent: They may or may not show the effects of modifications
 * to the map that occur while the iteration is in progress.
 */
final class EnumMap implements Serializable, IteratorAggregate
{
    /**
     * The class name of the key.
     *
     * @var string
     */
    private $keyType;

    /**
     * The type of the value.
     *
     * @var string
     */
    private $valueType;

    /**
     * @var bool
     */
    private $allowNullValues;

    /**
     * All of the constants comprising the enum, cached for performance.
     *
     * @var array<int, AbstractEnum>
     */
    private $keyUniverse;

    /**
     * Array representation of this map. The ith element is the value to which universe[i] is currently mapped, or null
     * if it isn't mapped to anything, or NullValue if it's mapped to null.
     *
     * @var array<int, mixed>
     */
    private $values;

    /**
     * @var int
     */
    private $size = 0;

    /**
     * Creates a new enum map.
     *
     * @param string $keyType the type of the keys, must extend AbstractEnum
     * @param string $valueType the type of the values
     * @param bool $allowNullValues whether to allow null values
     * @throws IllegalArgumentException when key type does not extend AbstractEnum
     */
    public function __construct(string $keyType, string $valueType, bool $allowNullValues)
    {
        if (! is_subclass_of($keyType, AbstractEnum::class)) {
            throw new IllegalArgumentException(sprintf(
                'Class %s does not extend %s',
                $keyType,
                AbstractEnum::class
            ));
        }

        $this->keyType = $keyType;
        $this->valueType = $valueType;
        $this->allowNullValues = $allowNullValues;
        $this->keyUniverse = $keyType::values();
        $this->values = array_fill(0, count($this->keyUniverse), null);
    }

    public function __serialize(): array
    {
        $values = [];

        foreach ($this->values as $ordinal => $value) {
            if (null === $value) {
                continue;
            }

            $values[$ordinal] = $this->unmaskNull($value);
        }

        return [
            'keyType' => $this->keyType,
            'valueType' => $this->valueType,
            'allowNullValues' => $this->allowNullValues,
            'values' => $values,
        ];
    }

    public function __unserialize(array $data): void
    {
        $this->unserialize(serialize($data));
    }

    /**
     * Checks whether the map types match the supplied ones.
     *
     * You should call this method when an EnumMap is passed to you and you want to ensure that it's made up of the
     * correct types.
     *
     * @throws ExpectationException when supplied key type mismatches local key type
     * @throws ExpectationException when supplied value type mismatches local value type
     * @throws ExpectationException when the supplied map allows null values, abut should not
     */
    public function expect(string $keyType, string $valueType, bool $allowNullValues) : void
    {
        if ($keyType !== $this->keyType) {
            throw new ExpectationException(sprintf(
                'Callee expected an EnumMap with key type %s, but got %s',
                $keyType,
                $this->keyType
            ));
        }

        if ($valueType !== $this->valueType) {
            throw new ExpectationException(sprintf(
                'Callee expected an EnumMap with value type %s, but got %s',
                $keyType,
                $this->keyType
            ));
        }

        if ($allowNullValues !== $this->allowNullValues) {
            throw new ExpectationException(sprintf(
                'Callee expected an EnumMap with nullable flag %s, but got %s',
                ($allowNullValues ? 'true' : 'false'),
                ($this->allowNullValues ? 'true' : 'false')
            ));
        }
    }

    /**
     * Returns the number of key-value mappings in this map.
     */
    public function size() : int
    {
        return $this->size;
    }

    /**
     * Returns true if this map maps one or more keys to the specified value.
     */
    public function containsValue($value) : bool
    {
        return in_array($this->maskNull($value), $this->values, true);
    }

    /**
     * Returns true if this map contains a mapping for the specified key.
     */
    public function containsKey(AbstractEnum $key) : bool
    {
        $this->checkKeyType($key);
        return null !== $this->values[$key->ordinal()];
    }

    /**
     * Returns the value to which the specified key is mapped, or null if this map contains no mapping for the key.
     *
     * More formally, if this map contains a mapping from a key to a value, then this method returns the value;
     * otherwise it returns null (there can be at most one such mapping).
     *
     * A return value of null does not necessarily indicate that the map contains no mapping for the key; it's also
     * possible that hte map explicitly maps the key to null. The {@see self::containsKey()} operation may be used to
     * distinguish these two cases.
     *
     * @return mixed
     */
    public function get(AbstractEnum $key)
    {
        $this->checkKeyType($key);
        return $this->unmaskNull($this->values[$key->ordinal()]);
    }

    /**
     * Associates the specified value with the specified key in this map.
     *
     * If the map previously contained a mapping for this key, the old value is replaced.
     *
     * @return mixed the previous value associated with the specified key, or null if there was no mapping for the key.
     *               (a null return can also indicate that the map previously associated null with the specified key.)
     * @throws IllegalArgumentException when the passed values does not match the internal value type
     */
    public function put(AbstractEnum $key, $value)
    {
        $this->checkKeyType($key);

        if (! $this->isValidValue($value)) {
            throw new IllegalArgumentException(sprintf('Value is not of type %s', $this->valueType));
        }

        $index = $key->ordinal();
        $oldValue = $this->values[$index];
        $this->values[$index] = $this->maskNull($value);

        if (null === $oldValue) {
            ++$this->size;
        }

        return $this->unmaskNull($oldValue);
    }

    /**
     * Removes the mapping for this key frm this map if present.
     *
     * @return mixed the previous value associated with the specified key, or null if there was no mapping for the key.
     *               (a null return can also indicate that the map previously associated null with the specified key.)
     */
    public function remove(AbstractEnum $key)
    {
        $this->checkKeyType($key);

        $index = $key->ordinal();
        $oldValue = $this->values[$index];
        $this->values[$index] = null;

        if (null !== $oldValue) {
            --$this->size;
        }

        return $this->unmaskNull($oldValue);
    }

    /**
     * Removes all mappings from this map.
     */
    public function clear() : void
    {
        $this->values = array_fill(0, count($this->keyUniverse), null);
        $this->size = 0;
    }

    /**
     * Compares the specified map with this map for quality.
     *
     * Returns true if the two maps represent the same mappings.
     */
    public function equals(self $other) : bool
    {
        if ($this === $other) {
            return true;
        }

        if ($this->size !== $other->size) {
            return false;
        }

        return $this->values === $other->values;
    }

    /**
     * Returns the values contained in this map.
     *
     * The array will contain the values in the order their corresponding keys appear in the map, which is their natural
     * order (the order in which the num constants are declared).
     */
    public function values() : array
    {
        return array_values(array_map(function ($value) {
            return $this->unmaskNull($value);
        }, array_filter($this->values, function ($value) : bool {
            return null !== $value;
        })));
    }

    public function serialize() : string
    {
        return serialize($this->__serialize());
    }

    public function unserialize($serialized) : void
    {
        $data = unserialize($serialized);
        $this->__construct($data['keyType'], $data['valueType'], $data['allowNullValues']);

        foreach ($this->keyUniverse as $key) {
            if (array_key_exists($key->ordinal(), $data['values'])) {
                $this->put($key, $data['values'][$key->ordinal()]);
            }
        }
    }

    public function getIterator() : Traversable
    {
        foreach ($this->keyUniverse as $key) {
            if (null === $this->values[$key->ordinal()]) {
                continue;
            }

            yield $key => $this->unmaskNull($this->values[$key->ordinal()]);
        }
    }

    private function maskNull($value)
    {
        if (null === $value) {
            return NullValue::instance();
        }

        return $value;
    }

    private function unmaskNull($value)
    {
        if ($value instanceof NullValue) {
            return null;
        }

        return $value;
    }

    /**
     * @throws IllegalArgumentException when the passed key does not match the internal key type
     */
    private function checkKeyType(AbstractEnum $key) : void
    {
        if (get_class($key) !== $this->keyType) {
            throw new IllegalArgumentException(sprintf(
                'Object of type %s is not the same type as %s',
                get_class($key),
                $this->keyType
            ));
        }
    }

    private function isValidValue($value) : bool
    {
        if (null === $value) {
            if ($this->allowNullValues) {
                return true;
            }

            return false;
        }

        switch ($this->valueType) {
            case 'mixed':
                return true;

            case 'bool':
            case 'boolean':
                return is_bool($value);

            case 'int':
            case 'integer':
                return is_int($value);

            case 'float':
            case 'double':
                return is_float($value);

            case 'string':
                return is_string($value);

            case 'object':
                return is_object($value);

            case 'array':
                return is_array($value);
        }

        return $value instanceof $this->valueType;
    }
}
PK�c�\�a&���dasprid/enum/composer.jsonnu�[���{
    "name": "dasprid/enum",
    "description": "PHP 7.1 enum implementation",
    "license": "BSD-2-Clause",
    "authors": [
        {
            "name": "Ben Scholzen 'DASPRiD'",
            "email": "mail@dasprids.de",
            "homepage": "https://dasprids.de/",
            "role": "Developer"
        }
    ],
    "keywords": [
        "enum",
        "map"
    ],
    "require": {
        "php": ">=7.1 <9.0"
    },
    "require-dev": {
        "phpunit/phpunit": "^7 | ^8 | ^9",
        "squizlabs/php_codesniffer": "*"
    },
    "autoload": {
        "psr-4": {
            "DASPRiD\\Enum\\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "DASPRiD\\EnumTest\\": "test/"
        }
    }
}
PK�c�\˷I�dasprid/enum/LICENSEnu�[���Copyright (c) 2017, Ben Scholzen 'DASPRiD'
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
PK�c�\5#�ttdasprid/enum/README.mdnu�[���# PHP 7.1 enums

[![Build Status](https://github.com/DASPRiD/Enum/actions/workflows/tests.yml/badge.svg)](https://github.com/DASPRiD/Enum/actions?query=workflow%3Atests)
[![Coverage Status](https://coveralls.io/repos/github/DASPRiD/Enum/badge.svg?branch=master)](https://coveralls.io/github/DASPRiD/Enum?branch=master)
[![Latest Stable Version](https://poser.pugx.org/dasprid/enum/v/stable)](https://packagist.org/packages/dasprid/enum)
[![Total Downloads](https://poser.pugx.org/dasprid/enum/downloads)](https://packagist.org/packages/dasprid/enum)
[![License](https://poser.pugx.org/dasprid/enum/license)](https://packagist.org/packages/dasprid/enum)

It is a well known fact that PHP is missing a basic enum type, ignoring the rather incomplete `SplEnum` implementation
which is only available as a PECL extension. There are also quite a few other userland enum implementations around,
but all of them have one or another compromise. This library tries to close that gap as far as PHP allows it to.

## Usage

### Basics

At its core, there is the `DASPRiD\Enum\AbstractEnum` class, which by default will work with constants like any other
enum implementation you might know. The first clear difference is that you should define all the constants as protected
(so nobody outside your class can read them but the `AbstractEnum` can still do so). The other even mightier difference
is that, for simple enums, the value of the constant doesn't matter at all. Let's have a look at a simple example:

```php
use DASPRiD\Enum\AbstractEnum;

/**
 * @method static self MONDAY()
 * @method static self TUESDAY()
 * @method static self WEDNESDAY()
 * @method static self THURSDAY()
 * @method static self FRIDAY()
 * @method static self SATURDAY()
 * @method static self SUNDAY()
 */
final class WeekDay extends AbstractEnum
{
    protected const MONDAY = null;
    protected const TUESDAY = null;
    protected const WEDNESDAY = null;
    protected const THURSDAY = null;
    protected const FRIDAY = null;
    protected const SATURDAY = null;
    protected const SUNDAY = null;
}
```

If you need to provide constants for either internal use or public use, you can mark them as either private or public,
in which case they will be ignored by the enum, which only considers protected constants as valid values. As you can
see, we specifically defined the generated magic methods in a class level doc block, so anyone using this class will
automatically have proper auto-completion in their IDE. Now since you have defined the enum, you can simply use it like
that:

```php
function tellItLikeItIs(WeekDay $weekDay)
{
    switch ($weekDay) {
        case WeekDay::MONDAY():
            echo 'Mondays are bad.';
            break;

        case WeekDay::FRIDAY():
            echo 'Fridays are better.';
            break;

        case WeekDay::SATURDAY():
        case WeekDay::SUNDAY():
            echo 'Weekends are best.';
            break;

        default:
            echo 'Midweek days are so-so.';
    }
}

tellItLikeItIs(WeekDay::MONDAY());
tellItLikeItIs(WeekDay::WEDNESDAY());
tellItLikeItIs(WeekDay::FRIDAY());
tellItLikeItIs(WeekDay::SATURDAY());
tellItLikeItIs(WeekDay::SUNDAY());
```

### More complex example

Of course, all enums are singletons, which are not cloneable or serializable. Thus you can be sure that there is always
just one instance of the same type. Of course, the values of constants are not completely useless, let's have a look at
a more complex example:

```php
use DASPRiD\Enum\AbstractEnum;

/**
 * @method static self MERCURY()
 * @method static self VENUS()
 * @method static self EARTH()
 * @method static self MARS()
 * @method static self JUPITER()
 * @method static self SATURN()
 * @method static self URANUS()
 * @method static self NEPTUNE()
 */
final class Planet extends AbstractEnum
{
    protected const MERCURY = [3.303e+23, 2.4397e6];
    protected const VENUS = [4.869e+24, 6.0518e6];
    protected const EARTH = [5.976e+24, 6.37814e6];
    protected const MARS = [6.421e+23, 3.3972e6];
    protected const JUPITER = [1.9e+27, 7.1492e7];
    protected const SATURN = [5.688e+26, 6.0268e7];
    protected const URANUS = [8.686e+25, 2.5559e7];
    protected const NEPTUNE = [1.024e+26, 2.4746e7];

    /**
     * Universal gravitational constant.
     *
     * @var float
     */
    private const G = 6.67300E-11;

    /**
     * Mass in kilograms.
     *
     * @var float
     */
    private $mass;

    /**
     * Radius in meters.
     *
     * @var float
     */
    private $radius;

    protected function __construct(float $mass, float $radius)
    {
        $this->mass = $mass;
        $this->radius = $radius;
    }

    public function mass() : float
    {
        return $this->mass;
    }

    public function radius() : float
    {
        return $this->radius;
    }

    public function surfaceGravity() : float
    {
        return self::G * $this->mass / ($this->radius * $this->radius);
    }

    public function surfaceWeight(float $otherMass) : float
    {
        return $otherMass * $this->surfaceGravity();
    }
}

$myMass = 80;

foreach (Planet::values() as $planet) {
    printf("Your weight on %s is %f\n", $planet, $planet->surfaceWeight($myMass));
}
```
PK�c�\"�7��1psr/http-client/src/NetworkExceptionInterface.phpnu�[���<?php

namespace Psr\Http\Client;

use Psr\Http\Message\RequestInterface;

/**
 * Thrown when the request cannot be completed because of network issues.
 *
 * There is no response object as this exception is thrown when no response has been received.
 *
 * Example: the target host name can not be resolved or the connection failed.
 */
interface NetworkExceptionInterface extends ClientExceptionInterface
{
    /**
     * Returns the request.
     *
     * The request object MAY be a different object from the one passed to ClientInterface::sendRequest()
     *
     * @return RequestInterface
     */
    public function getRequest(): RequestInterface;
}
PK�c�\��:���0psr/http-client/src/ClientExceptionInterface.phpnu�[���<?php

namespace Psr\Http\Client;

/**
 * Every HTTP client related exception MUST implement this interface.
 */
interface ClientExceptionInterface extends \Throwable
{
}
PK�c�\Ҟv��'psr/http-client/src/ClientInterface.phpnu�[���<?php

namespace Psr\Http\Client;

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

interface ClientInterface
{
    /**
     * Sends a PSR-7 request and returns a PSR-7 response.
     *
     * @param RequestInterface $request
     *
     * @return ResponseInterface
     *
     * @throws \Psr\Http\Client\ClientExceptionInterface If an error happens while processing the request.
     */
    public function sendRequest(RequestInterface $request): ResponseInterface;
}
PK�c�\�E�uJJ1psr/http-client/src/RequestExceptionInterface.phpnu�[���<?php

namespace Psr\Http\Client;

use Psr\Http\Message\RequestInterface;

/**
 * Exception for when a request failed.
 *
 * Examples:
 *      - Request is invalid (e.g. method is missing)
 *      - Runtime request errors (e.g. the body stream is not seekable)
 */
interface RequestExceptionInterface extends ClientExceptionInterface
{
    /**
     * Returns the request.
     *
     * The request object MAY be a different object from the one passed to ClientInterface::sendRequest()
     *
     * @return RequestInterface
     */
    public function getRequest(): RequestInterface;
}
PK�c�\����psr/http-client/composer.jsonnu�[���{
    "name": "psr/http-client",
    "description": "Common interface for HTTP clients",
    "keywords": ["psr", "psr-18", "http", "http-client"],
    "homepage": "https://github.com/php-fig/http-client",
    "license": "MIT",
    "authors": [
        {
            "name": "PHP-FIG",
            "homepage": "https://www.php-fig.org/"
        }
    ],
    "support": {
        "source": "https://github.com/php-fig/http-client"
    },
    "require": {
        "php": "^7.0 || ^8.0",
        "psr/http-message": "^1.0 || ^2.0"
    },
    "autoload": {
        "psr-4": {
            "Psr\\Http\\Client\\": "src/"
        }
    },
    "extra": {
        "branch-alias": {
            "dev-master": "1.0.x-dev"
        }
    }
}
PK�c�\z��psr/http-client/CHANGELOG.mdnu�[���# Changelog

All notable changes to this project will be documented in this file, in reverse chronological order by release.

## 1.0.3

Add `source` link in composer.json. No code changes.

## 1.0.2

Allow PSR-7 (psr/http-message) 2.0. No code changes.

## 1.0.1

Allow installation with PHP 8. No code changes.

## 1.0.0

First stable release. No changes since 0.3.0.

## 0.3.0

Added Interface suffix on exceptions
 
## 0.2.0 

All exceptions are in `Psr\Http\Client` namespace

## 0.1.0

First release
PK�c�\�S�==psr/http-client/LICENSEnu�[���Copyright (c) 2017 PHP Framework Interoperability Group

Permission is hereby granted, free of charge, to any person obtaining a copy 
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights 
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
copies of the Software, and to permit persons to whom the Software is 
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in 
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
PK�c�\�F�%%psr/http-client/README.mdnu�[���HTTP Client
===========

This repository holds all the common code related to [PSR-18 (HTTP Client)][psr-url].

Note that this is not a HTTP Client implementation of its own. It is merely abstractions that describe the components of a HTTP Client.

The installable [package][package-url] and [implementations][implementation-url] are listed on Packagist.

[psr-url]: https://www.php-fig.org/psr/psr-18
[package-url]: https://packagist.org/packages/psr/http-client
[implementation-url]: https://packagist.org/providers/psr/http-client-implementation
PK�c�\��DhEE,psr/http-factory/src/UriFactoryInterface.phpnu�[���<?php

namespace Psr\Http\Message;

interface UriFactoryInterface
{
    /**
     * Create a new URI.
     *
     * @param string $uri
     *
     * @return UriInterface
     *
     * @throws \InvalidArgumentException If the given URI cannot be parsed.
     */
    public function createUri(string $uri = ''): UriInterface;
}
PK�c�\Bj�hh5psr/http-factory/src/UploadedFileFactoryInterface.phpnu�[���<?php

namespace Psr\Http\Message;

interface UploadedFileFactoryInterface
{
    /**
     * Create a new uploaded file.
     *
     * If a size is not provided it will be determined by checking the size of
     * the file.
     *
     * @see http://php.net/manual/features.file-upload.post-method.php
     * @see http://php.net/manual/features.file-upload.errors.php
     *
     * @param StreamInterface $stream Underlying stream representing the
     *     uploaded file content.
     * @param int|null $size in bytes
     * @param int $error PHP file upload error
     * @param string|null $clientFilename Filename as provided by the client, if any.
     * @param string|null $clientMediaType Media type as provided by the client, if any.
     *
     * @return UploadedFileInterface
     *
     * @throws \InvalidArgumentException If the file resource is not readable.
     */
    public function createUploadedFile(
        StreamInterface $stream,
        ?int $size = null,
        int $error = \UPLOAD_ERR_OK,
        ?string $clientFilename = null,
        ?string $clientMediaType = null
    ): UploadedFileInterface;
}
PK�c�\X��""1psr/http-factory/src/ResponseFactoryInterface.phpnu�[���<?php

namespace Psr\Http\Message;

interface ResponseFactoryInterface
{
    /**
     * Create a new response.
     *
     * @param int $code HTTP status code; defaults to 200
     * @param string $reasonPhrase Reason phrase to associate with status code
     *     in generated response; if none is provided implementations MAY use
     *     the defaults as suggested in the HTTP specification.
     *
     * @return ResponseInterface
     */
    public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface;
}
PK�c�\�yۜ��/psr/http-factory/src/StreamFactoryInterface.phpnu�[���<?php

namespace Psr\Http\Message;

interface StreamFactoryInterface
{
    /**
     * Create a new stream from a string.
     *
     * The stream SHOULD be created with a temporary resource.
     *
     * @param string $content String content with which to populate the stream.
     *
     * @return StreamInterface
     */
    public function createStream(string $content = ''): StreamInterface;

    /**
     * Create a stream from an existing file.
     *
     * The file MUST be opened using the given mode, which may be any mode
     * supported by the `fopen` function.
     *
     * The `$filename` MAY be any string supported by `fopen()`.
     *
     * @param string $filename Filename or stream URI to use as basis of stream.
     * @param string $mode Mode with which to open the underlying filename/stream.
     *
     * @return StreamInterface
     * @throws \RuntimeException If the file cannot be opened.
     * @throws \InvalidArgumentException If the mode is invalid.
     */
    public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface;

    /**
     * Create a new stream from an existing resource.
     *
     * The stream MUST be readable and may be writable.
     *
     * @param resource $resource PHP resource to use as basis of stream.
     *
     * @return StreamInterface
     */
    public function createStreamFromResource($resource): StreamInterface;
}
PK�c�\rT�X��0psr/http-factory/src/RequestFactoryInterface.phpnu�[���<?php

namespace Psr\Http\Message;

interface RequestFactoryInterface
{
    /**
     * Create a new request.
     *
     * @param string $method The HTTP method associated with the request.
     * @param UriInterface|string $uri The URI associated with the request. If
     *     the value is a string, the factory MUST create a UriInterface
     *     instance based on it.
     *
     * @return RequestInterface
     */
    public function createRequest(string $method, $uri): RequestInterface;
}
PK�c�\BH�A��6psr/http-factory/src/ServerRequestFactoryInterface.phpnu�[���<?php

namespace Psr\Http\Message;

interface ServerRequestFactoryInterface
{
    /**
     * Create a new server request.
     *
     * Note that server-params are taken precisely as given - no parsing/processing
     * of the given values is performed, and, in particular, no attempt is made to
     * determine the HTTP method or URI, which must be provided explicitly.
     *
     * @param string $method The HTTP method associated with the request.
     * @param UriInterface|string $uri The URI associated with the request. If
     *     the value is a string, the factory MUST create a UriInterface
     *     instance based on it.
     * @param array $serverParams Array of SAPI parameters with which to seed
     *     the generated request instance.
     *
     * @return ServerRequestInterface
     */
    public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface;
}
PK�c�\��Opsr/http-factory/composer.jsonnu�[���{
    "name": "psr/http-factory",
    "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories",
    "keywords": [
        "psr",
        "psr-7",
        "psr-17",
        "http",
        "factory",
        "message",
        "request",
        "response"
    ],
    "license": "MIT",
    "authors": [
        {
            "name": "PHP-FIG",
            "homepage": "https://www.php-fig.org/"
        }
    ],
    "support": {
        "source": "https://github.com/php-fig/http-factory"
    },
    "require": {
        "php": ">=7.1",
        "psr/http-message": "^1.0 || ^2.0"
    },
    "autoload": {
        "psr-4": {
            "Psr\\Http\\Message\\": "src/"
        }
    },
    "extra": {
        "branch-alias": {
            "dev-master": "1.0.x-dev"
        }
    }
}
PK�c�\�}]�((psr/http-factory/LICENSEnu�[���MIT License

Copyright (c) 2018 PHP-FIG

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
PK�c�\z�wf,,psr/http-factory/README.mdnu�[���HTTP Factories
==============

This repository holds all interfaces related to [PSR-17 (HTTP Factories)][psr-url].

Note that this is not a HTTP Factory implementation of its own. It is merely interfaces that describe the components of a HTTP Factory.

The installable [package][package-url] and [implementations][implementation-url] are listed on Packagist.

[psr-url]: https://www.php-fig.org/psr/psr-17/
[package-url]: https://packagist.org/packages/psr/http-factory
[implementation-url]: https://packagist.org/providers/psr/http-factory-implementation
PK�c�\iP:^:(:(/psr/http-message/src/ServerRequestInterface.phpnu�[���<?php

namespace Psr\Http\Message;

/**
 * Representation of an incoming, server-side HTTP request.
 *
 * Per the HTTP specification, this interface includes properties for
 * each of the following:
 *
 * - Protocol version
 * - HTTP method
 * - URI
 * - Headers
 * - Message body
 *
 * Additionally, it encapsulates all data as it has arrived to the
 * application from the CGI and/or PHP environment, including:
 *
 * - The values represented in $_SERVER.
 * - Any cookies provided (generally via $_COOKIE)
 * - Query string arguments (generally via $_GET, or as parsed via parse_str())
 * - Upload files, if any (as represented by $_FILES)
 * - Deserialized body parameters (generally from $_POST)
 *
 * $_SERVER values MUST be treated as immutable, as they represent application
 * state at the time of request; as such, no methods are provided to allow
 * modification of those values. The other values provide such methods, as they
 * can be restored from $_SERVER or the request body, and may need treatment
 * during the application (e.g., body parameters may be deserialized based on
 * content type).
 *
 * Additionally, this interface recognizes the utility of introspecting a
 * request to derive and match additional parameters (e.g., via URI path
 * matching, decrypting cookie values, deserializing non-form-encoded body
 * content, matching authorization headers to users, etc). These parameters
 * are stored in an "attributes" property.
 *
 * Requests are considered immutable; all methods that might change state MUST
 * be implemented such that they retain the internal state of the current
 * message and return an instance that contains the changed state.
 */
interface ServerRequestInterface extends RequestInterface
{
    /**
     * Retrieve server parameters.
     *
     * Retrieves data related to the incoming request environment,
     * typically derived from PHP's $_SERVER superglobal. The data IS NOT
     * REQUIRED to originate from $_SERVER.
     *
     * @return array
     */
    public function getServerParams(): array;

    /**
     * Retrieve cookies.
     *
     * Retrieves cookies sent by the client to the server.
     *
     * The data MUST be compatible with the structure of the $_COOKIE
     * superglobal.
     *
     * @return array
     */
    public function getCookieParams(): array;

    /**
     * Return an instance with the specified cookies.
     *
     * The data IS NOT REQUIRED to come from the $_COOKIE superglobal, but MUST
     * be compatible with the structure of $_COOKIE. Typically, this data will
     * be injected at instantiation.
     *
     * This method MUST NOT update the related Cookie header of the request
     * instance, nor related values in the server params.
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * updated cookie values.
     *
     * @param array $cookies Array of key/value pairs representing cookies.
     * @return static
     */
    public function withCookieParams(array $cookies): ServerRequestInterface;

    /**
     * Retrieve query string arguments.
     *
     * Retrieves the deserialized query string arguments, if any.
     *
     * Note: the query params might not be in sync with the URI or server
     * params. If you need to ensure you are only getting the original
     * values, you may need to parse the query string from `getUri()->getQuery()`
     * or from the `QUERY_STRING` server param.
     *
     * @return array
     */
    public function getQueryParams(): array;

    /**
     * Return an instance with the specified query string arguments.
     *
     * These values SHOULD remain immutable over the course of the incoming
     * request. They MAY be injected during instantiation, such as from PHP's
     * $_GET superglobal, or MAY be derived from some other value such as the
     * URI. In cases where the arguments are parsed from the URI, the data
     * MUST be compatible with what PHP's parse_str() would return for
     * purposes of how duplicate query parameters are handled, and how nested
     * sets are handled.
     *
     * Setting query string arguments MUST NOT change the URI stored by the
     * request, nor the values in the server params.
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * updated query string arguments.
     *
     * @param array $query Array of query string arguments, typically from
     *     $_GET.
     * @return static
     */
    public function withQueryParams(array $query): ServerRequestInterface;

    /**
     * Retrieve normalized file upload data.
     *
     * This method returns upload metadata in a normalized tree, with each leaf
     * an instance of Psr\Http\Message\UploadedFileInterface.
     *
     * These values MAY be prepared from $_FILES or the message body during
     * instantiation, or MAY be injected via withUploadedFiles().
     *
     * @return array An array tree of UploadedFileInterface instances; an empty
     *     array MUST be returned if no data is present.
     */
    public function getUploadedFiles(): array;

    /**
     * Create a new instance with the specified uploaded files.
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * updated body parameters.
     *
     * @param array $uploadedFiles An array tree of UploadedFileInterface instances.
     * @return static
     * @throws \InvalidArgumentException if an invalid structure is provided.
     */
    public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface;

    /**
     * Retrieve any parameters provided in the request body.
     *
     * If the request Content-Type is either application/x-www-form-urlencoded
     * or multipart/form-data, and the request method is POST, this method MUST
     * return the contents of $_POST.
     *
     * Otherwise, this method may return any results of deserializing
     * the request body content; as parsing returns structured content, the
     * potential types MUST be arrays or objects only. A null value indicates
     * the absence of body content.
     *
     * @return null|array|object The deserialized body parameters, if any.
     *     These will typically be an array or object.
     */
    public function getParsedBody();

    /**
     * Return an instance with the specified body parameters.
     *
     * These MAY be injected during instantiation.
     *
     * If the request Content-Type is either application/x-www-form-urlencoded
     * or multipart/form-data, and the request method is POST, use this method
     * ONLY to inject the contents of $_POST.
     *
     * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of
     * deserializing the request body content. Deserialization/parsing returns
     * structured data, and, as such, this method ONLY accepts arrays or objects,
     * or a null value if nothing was available to parse.
     *
     * As an example, if content negotiation determines that the request data
     * is a JSON payload, this method could be used to create a request
     * instance with the deserialized parameters.
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * updated body parameters.
     *
     * @param null|array|object $data The deserialized body data. This will
     *     typically be in an array or object.
     * @return static
     * @throws \InvalidArgumentException if an unsupported argument type is
     *     provided.
     */
    public function withParsedBody($data): ServerRequestInterface;

    /**
     * Retrieve attributes derived from the request.
     *
     * The request "attributes" may be used to allow injection of any
     * parameters derived from the request: e.g., the results of path
     * match operations; the results of decrypting cookies; the results of
     * deserializing non-form-encoded message bodies; etc. Attributes
     * will be application and request specific, and CAN be mutable.
     *
     * @return array Attributes derived from the request.
     */
    public function getAttributes(): array;

    /**
     * Retrieve a single derived request attribute.
     *
     * Retrieves a single derived request attribute as described in
     * getAttributes(). If the attribute has not been previously set, returns
     * the default value as provided.
     *
     * This method obviates the need for a hasAttribute() method, as it allows
     * specifying a default value to return if the attribute is not found.
     *
     * @see getAttributes()
     * @param string $name The attribute name.
     * @param mixed $default Default value to return if the attribute does not exist.
     * @return mixed
     */
    public function getAttribute(string $name, $default = null);

    /**
     * Return an instance with the specified derived request attribute.
     *
     * This method allows setting a single derived request attribute as
     * described in getAttributes().
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * updated attribute.
     *
     * @see getAttributes()
     * @param string $name The attribute name.
     * @param mixed $value The value of the attribute.
     * @return static
     */
    public function withAttribute(string $name, $value): ServerRequestInterface;

    /**
     * Return an instance that removes the specified derived request attribute.
     *
     * This method allows removing a single derived request attribute as
     * described in getAttributes().
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that removes
     * the attribute.
     *
     * @see getAttributes()
     * @param string $name The attribute name.
     * @return static
     */
    public function withoutAttribute(string $name): ServerRequestInterface;
}
PK�c�\�_8�77)psr/http-message/src/RequestInterface.phpnu�[���<?php

namespace Psr\Http\Message;

/**
 * Representation of an outgoing, client-side request.
 *
 * Per the HTTP specification, this interface includes properties for
 * each of the following:
 *
 * - Protocol version
 * - HTTP method
 * - URI
 * - Headers
 * - Message body
 *
 * During construction, implementations MUST attempt to set the Host header from
 * a provided URI if no Host header is provided.
 *
 * Requests are considered immutable; all methods that might change state MUST
 * be implemented such that they retain the internal state of the current
 * message and return an instance that contains the changed state.
 */
interface RequestInterface extends MessageInterface
{
    /**
     * Retrieves the message's request target.
     *
     * Retrieves the message's request-target either as it will appear (for
     * clients), as it appeared at request (for servers), or as it was
     * specified for the instance (see withRequestTarget()).
     *
     * In most cases, this will be the origin-form of the composed URI,
     * unless a value was provided to the concrete implementation (see
     * withRequestTarget() below).
     *
     * If no URI is available, and no request-target has been specifically
     * provided, this method MUST return the string "/".
     *
     * @return string
     */
    public function getRequestTarget(): string;

    /**
     * Return an instance with the specific request-target.
     *
     * If the request needs a non-origin-form request-target — e.g., for
     * specifying an absolute-form, authority-form, or asterisk-form —
     * this method may be used to create an instance with the specified
     * request-target, verbatim.
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * changed request target.
     *
     * @link http://tools.ietf.org/html/rfc7230#section-5.3 (for the various
     *     request-target forms allowed in request messages)
     * @param string $requestTarget
     * @return static
     */
    public function withRequestTarget(string $requestTarget): RequestInterface;


    /**
     * Retrieves the HTTP method of the request.
     *
     * @return string Returns the request method.
     */
    public function getMethod(): string;

    /**
     * Return an instance with the provided HTTP method.
     *
     * While HTTP method names are typically all uppercase characters, HTTP
     * method names are case-sensitive and thus implementations SHOULD NOT
     * modify the given string.
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * changed request method.
     *
     * @param string $method Case-sensitive method.
     * @return static
     * @throws \InvalidArgumentException for invalid HTTP methods.
     */
    public function withMethod(string $method): RequestInterface;

    /**
     * Retrieves the URI instance.
     *
     * This method MUST return a UriInterface instance.
     *
     * @link http://tools.ietf.org/html/rfc3986#section-4.3
     * @return UriInterface Returns a UriInterface instance
     *     representing the URI of the request.
     */
    public function getUri(): UriInterface;

    /**
     * Returns an instance with the provided URI.
     *
     * This method MUST update the Host header of the returned request by
     * default if the URI contains a host component. If the URI does not
     * contain a host component, any pre-existing Host header MUST be carried
     * over to the returned request.
     *
     * You can opt-in to preserving the original state of the Host header by
     * setting `$preserveHost` to `true`. When `$preserveHost` is set to
     * `true`, this method interacts with the Host header in the following ways:
     *
     * - If the Host header is missing or empty, and the new URI contains
     *   a host component, this method MUST update the Host header in the returned
     *   request.
     * - If the Host header is missing or empty, and the new URI does not contain a
     *   host component, this method MUST NOT update the Host header in the returned
     *   request.
     * - If a Host header is present and non-empty, this method MUST NOT update
     *   the Host header in the returned request.
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * new UriInterface instance.
     *
     * @link http://tools.ietf.org/html/rfc3986#section-4.3
     * @param UriInterface $uri New request URI to use.
     * @param bool $preserveHost Preserve the original state of the Host header.
     * @return static
     */
    public function withUri(UriInterface $uri, bool $preserveHost = false): RequestInterface;
}
PK�c�\Z�݌�.psr/http-message/src/UploadedFileInterface.phpnu�[���<?php

namespace Psr\Http\Message;

/**
 * Value object representing a file uploaded through an HTTP request.
 *
 * Instances of this interface are considered immutable; all methods that
 * might change state MUST be implemented such that they retain the internal
 * state of the current instance and return an instance that contains the
 * changed state.
 */
interface UploadedFileInterface
{
    /**
     * Retrieve a stream representing the uploaded file.
     *
     * This method MUST return a StreamInterface instance, representing the
     * uploaded file. The purpose of this method is to allow utilizing native PHP
     * stream functionality to manipulate the file upload, such as
     * stream_copy_to_stream() (though the result will need to be decorated in a
     * native PHP stream wrapper to work with such functions).
     *
     * If the moveTo() method has been called previously, this method MUST raise
     * an exception.
     *
     * @return StreamInterface Stream representation of the uploaded file.
     * @throws \RuntimeException in cases when no stream is available or can be
     *     created.
     */
    public function getStream(): StreamInterface;

    /**
     * Move the uploaded file to a new location.
     *
     * Use this method as an alternative to move_uploaded_file(). This method is
     * guaranteed to work in both SAPI and non-SAPI environments.
     * Implementations must determine which environment they are in, and use the
     * appropriate method (move_uploaded_file(), rename(), or a stream
     * operation) to perform the operation.
     *
     * $targetPath may be an absolute path, or a relative path. If it is a
     * relative path, resolution should be the same as used by PHP's rename()
     * function.
     *
     * The original file or stream MUST be removed on completion.
     *
     * If this method is called more than once, any subsequent calls MUST raise
     * an exception.
     *
     * When used in an SAPI environment where $_FILES is populated, when writing
     * files via moveTo(), is_uploaded_file() and move_uploaded_file() SHOULD be
     * used to ensure permissions and upload status are verified correctly.
     *
     * If you wish to move to a stream, use getStream(), as SAPI operations
     * cannot guarantee writing to stream destinations.
     *
     * @see http://php.net/is_uploaded_file
     * @see http://php.net/move_uploaded_file
     * @param string $targetPath Path to which to move the uploaded file.
     * @throws \InvalidArgumentException if the $targetPath specified is invalid.
     * @throws \RuntimeException on any error during the move operation, or on
     *     the second or subsequent call to the method.
     */
    public function moveTo(string $targetPath): void;
    
    /**
     * Retrieve the file size.
     *
     * Implementations SHOULD return the value stored in the "size" key of
     * the file in the $_FILES array if available, as PHP calculates this based
     * on the actual size transmitted.
     *
     * @return int|null The file size in bytes or null if unknown.
     */
    public function getSize(): ?int;
    
    /**
     * Retrieve the error associated with the uploaded file.
     *
     * The return value MUST be one of PHP's UPLOAD_ERR_XXX constants.
     *
     * If the file was uploaded successfully, this method MUST return
     * UPLOAD_ERR_OK.
     *
     * Implementations SHOULD return the value stored in the "error" key of
     * the file in the $_FILES array.
     *
     * @see http://php.net/manual/en/features.file-upload.errors.php
     * @return int One of PHP's UPLOAD_ERR_XXX constants.
     */
    public function getError(): int;
    
    /**
     * Retrieve the filename sent by the client.
     *
     * Do not trust the value returned by this method. A client could send
     * a malicious filename with the intention to corrupt or hack your
     * application.
     *
     * Implementations SHOULD return the value stored in the "name" key of
     * the file in the $_FILES array.
     *
     * @return string|null The filename sent by the client or null if none
     *     was provided.
     */
    public function getClientFilename(): ?string;
    
    /**
     * Retrieve the media type sent by the client.
     *
     * Do not trust the value returned by this method. A client could send
     * a malicious media type with the intention to corrupt or hack your
     * application.
     *
     * Implementations SHOULD return the value stored in the "type" key of
     * the file in the $_FILES array.
     *
     * @return string|null The media type sent by the client or null if none
     *     was provided.
     */
    public function getClientMediaType(): ?string;
}
PK�c�\o��22%psr/http-message/src/UriInterface.phpnu�[���<?php

namespace Psr\Http\Message;

/**
 * Value object representing a URI.
 *
 * This interface is meant to represent URIs according to RFC 3986 and to
 * provide methods for most common operations. Additional functionality for
 * working with URIs can be provided on top of the interface or externally.
 * Its primary use is for HTTP requests, but may also be used in other
 * contexts.
 *
 * Instances of this interface are considered immutable; all methods that
 * might change state MUST be implemented such that they retain the internal
 * state of the current instance and return an instance that contains the
 * changed state.
 *
 * Typically the Host header will be also be present in the request message.
 * For server-side requests, the scheme will typically be discoverable in the
 * server parameters.
 *
 * @link http://tools.ietf.org/html/rfc3986 (the URI specification)
 */
interface UriInterface
{
    /**
     * Retrieve the scheme component of the URI.
     *
     * If no scheme is present, this method MUST return an empty string.
     *
     * The value returned MUST be normalized to lowercase, per RFC 3986
     * Section 3.1.
     *
     * The trailing ":" character is not part of the scheme and MUST NOT be
     * added.
     *
     * @see https://tools.ietf.org/html/rfc3986#section-3.1
     * @return string The URI scheme.
     */
    public function getScheme(): string;

    /**
     * Retrieve the authority component of the URI.
     *
     * If no authority information is present, this method MUST return an empty
     * string.
     *
     * The authority syntax of the URI is:
     *
     * <pre>
     * [user-info@]host[:port]
     * </pre>
     *
     * If the port component is not set or is the standard port for the current
     * scheme, it SHOULD NOT be included.
     *
     * @see https://tools.ietf.org/html/rfc3986#section-3.2
     * @return string The URI authority, in "[user-info@]host[:port]" format.
     */
    public function getAuthority(): string;

    /**
     * Retrieve the user information component of the URI.
     *
     * If no user information is present, this method MUST return an empty
     * string.
     *
     * If a user is present in the URI, this will return that value;
     * additionally, if the password is also present, it will be appended to the
     * user value, with a colon (":") separating the values.
     *
     * The trailing "@" character is not part of the user information and MUST
     * NOT be added.
     *
     * @return string The URI user information, in "username[:password]" format.
     */
    public function getUserInfo(): string;

    /**
     * Retrieve the host component of the URI.
     *
     * If no host is present, this method MUST return an empty string.
     *
     * The value returned MUST be normalized to lowercase, per RFC 3986
     * Section 3.2.2.
     *
     * @see http://tools.ietf.org/html/rfc3986#section-3.2.2
     * @return string The URI host.
     */
    public function getHost(): string;

    /**
     * Retrieve the port component of the URI.
     *
     * If a port is present, and it is non-standard for the current scheme,
     * this method MUST return it as an integer. If the port is the standard port
     * used with the current scheme, this method SHOULD return null.
     *
     * If no port is present, and no scheme is present, this method MUST return
     * a null value.
     *
     * If no port is present, but a scheme is present, this method MAY return
     * the standard port for that scheme, but SHOULD return null.
     *
     * @return null|int The URI port.
     */
    public function getPort(): ?int;

    /**
     * Retrieve the path component of the URI.
     *
     * The path can either be empty or absolute (starting with a slash) or
     * rootless (not starting with a slash). Implementations MUST support all
     * three syntaxes.
     *
     * Normally, the empty path "" and absolute path "/" are considered equal as
     * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically
     * do this normalization because in contexts with a trimmed base path, e.g.
     * the front controller, this difference becomes significant. It's the task
     * of the user to handle both "" and "/".
     *
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
     * any characters. To determine what characters to encode, please refer to
     * RFC 3986, Sections 2 and 3.3.
     *
     * As an example, if the value should include a slash ("/") not intended as
     * delimiter between path segments, that value MUST be passed in encoded
     * form (e.g., "%2F") to the instance.
     *
     * @see https://tools.ietf.org/html/rfc3986#section-2
     * @see https://tools.ietf.org/html/rfc3986#section-3.3
     * @return string The URI path.
     */
    public function getPath(): string;

    /**
     * Retrieve the query string of the URI.
     *
     * If no query string is present, this method MUST return an empty string.
     *
     * The leading "?" character is not part of the query and MUST NOT be
     * added.
     *
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
     * any characters. To determine what characters to encode, please refer to
     * RFC 3986, Sections 2 and 3.4.
     *
     * As an example, if a value in a key/value pair of the query string should
     * include an ampersand ("&") not intended as a delimiter between values,
     * that value MUST be passed in encoded form (e.g., "%26") to the instance.
     *
     * @see https://tools.ietf.org/html/rfc3986#section-2
     * @see https://tools.ietf.org/html/rfc3986#section-3.4
     * @return string The URI query string.
     */
    public function getQuery(): string;

    /**
     * Retrieve the fragment component of the URI.
     *
     * If no fragment is present, this method MUST return an empty string.
     *
     * The leading "#" character is not part of the fragment and MUST NOT be
     * added.
     *
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
     * any characters. To determine what characters to encode, please refer to
     * RFC 3986, Sections 2 and 3.5.
     *
     * @see https://tools.ietf.org/html/rfc3986#section-2
     * @see https://tools.ietf.org/html/rfc3986#section-3.5
     * @return string The URI fragment.
     */
    public function getFragment(): string;

    /**
     * Return an instance with the specified scheme.
     *
     * This method MUST retain the state of the current instance, and return
     * an instance that contains the specified scheme.
     *
     * Implementations MUST support the schemes "http" and "https" case
     * insensitively, and MAY accommodate other schemes if required.
     *
     * An empty scheme is equivalent to removing the scheme.
     *
     * @param string $scheme The scheme to use with the new instance.
     * @return static A new instance with the specified scheme.
     * @throws \InvalidArgumentException for invalid or unsupported schemes.
     */
    public function withScheme(string $scheme): UriInterface;

    /**
     * Return an instance with the specified user information.
     *
     * This method MUST retain the state of the current instance, and return
     * an instance that contains the specified user information.
     *
     * Password is optional, but the user information MUST include the
     * user; an empty string for the user is equivalent to removing user
     * information.
     *
     * @param string $user The user name to use for authority.
     * @param null|string $password The password associated with $user.
     * @return static A new instance with the specified user information.
     */
    public function withUserInfo(string $user, ?string $password = null): UriInterface;

    /**
     * Return an instance with the specified host.
     *
     * This method MUST retain the state of the current instance, and return
     * an instance that contains the specified host.
     *
     * An empty host value is equivalent to removing the host.
     *
     * @param string $host The hostname to use with the new instance.
     * @return static A new instance with the specified host.
     * @throws \InvalidArgumentException for invalid hostnames.
     */
    public function withHost(string $host): UriInterface;

    /**
     * Return an instance with the specified port.
     *
     * This method MUST retain the state of the current instance, and return
     * an instance that contains the specified port.
     *
     * Implementations MUST raise an exception for ports outside the
     * established TCP and UDP port ranges.
     *
     * A null value provided for the port is equivalent to removing the port
     * information.
     *
     * @param null|int $port The port to use with the new instance; a null value
     *     removes the port information.
     * @return static A new instance with the specified port.
     * @throws \InvalidArgumentException for invalid ports.
     */
    public function withPort(?int $port): UriInterface;

    /**
     * Return an instance with the specified path.
     *
     * This method MUST retain the state of the current instance, and return
     * an instance that contains the specified path.
     *
     * The path can either be empty or absolute (starting with a slash) or
     * rootless (not starting with a slash). Implementations MUST support all
     * three syntaxes.
     *
     * If the path is intended to be domain-relative rather than path relative then
     * it must begin with a slash ("/"). Paths not starting with a slash ("/")
     * are assumed to be relative to some base path known to the application or
     * consumer.
     *
     * Users can provide both encoded and decoded path characters.
     * Implementations ensure the correct encoding as outlined in getPath().
     *
     * @param string $path The path to use with the new instance.
     * @return static A new instance with the specified path.
     * @throws \InvalidArgumentException for invalid paths.
     */
    public function withPath(string $path): UriInterface;

    /**
     * Return an instance with the specified query string.
     *
     * This method MUST retain the state of the current instance, and return
     * an instance that contains the specified query string.
     *
     * Users can provide both encoded and decoded query characters.
     * Implementations ensure the correct encoding as outlined in getQuery().
     *
     * An empty query string value is equivalent to removing the query string.
     *
     * @param string $query The query string to use with the new instance.
     * @return static A new instance with the specified query string.
     * @throws \InvalidArgumentException for invalid query strings.
     */
    public function withQuery(string $query): UriInterface;

    /**
     * Return an instance with the specified URI fragment.
     *
     * This method MUST retain the state of the current instance, and return
     * an instance that contains the specified URI fragment.
     *
     * Users can provide both encoded and decoded fragment characters.
     * Implementations ensure the correct encoding as outlined in getFragment().
     *
     * An empty fragment value is equivalent to removing the fragment.
     *
     * @param string $fragment The fragment to use with the new instance.
     * @return static A new instance with the specified fragment.
     */
    public function withFragment(string $fragment): UriInterface;

    /**
     * Return the string representation as a URI reference.
     *
     * Depending on which components of the URI are present, the resulting
     * string is either a full URI or relative reference according to RFC 3986,
     * Section 4.1. The method concatenates the various components of the URI,
     * using the appropriate delimiters:
     *
     * - If a scheme is present, it MUST be suffixed by ":".
     * - If an authority is present, it MUST be prefixed by "//".
     * - The path can be concatenated without delimiters. But there are two
     *   cases where the path has to be adjusted to make the URI reference
     *   valid as PHP does not allow to throw an exception in __toString():
     *     - If the path is rootless and an authority is present, the path MUST
     *       be prefixed by "/".
     *     - If the path is starting with more than one "/" and no authority is
     *       present, the starting slashes MUST be reduced to one.
     * - If a query is present, it MUST be prefixed by "?".
     * - If a fragment is present, it MUST be prefixed by "#".
     *
     * @see http://tools.ietf.org/html/rfc3986#section-4.1
     * @return string
     */
    public function __toString(): string;
}
PK�c�\?����)psr/http-message/src/MessageInterface.phpnu�[���<?php

namespace Psr\Http\Message;

/**
 * HTTP messages consist of requests from a client to a server and responses
 * from a server to a client. This interface defines the methods common to
 * each.
 *
 * Messages are considered immutable; all methods that might change state MUST
 * be implemented such that they retain the internal state of the current
 * message and return an instance that contains the changed state.
 *
 * @link http://www.ietf.org/rfc/rfc7230.txt
 * @link http://www.ietf.org/rfc/rfc7231.txt
 */
interface MessageInterface
{
    /**
     * Retrieves the HTTP protocol version as a string.
     *
     * The string MUST contain only the HTTP version number (e.g., "1.1", "1.0").
     *
     * @return string HTTP protocol version.
     */
    public function getProtocolVersion(): string;

    /**
     * Return an instance with the specified HTTP protocol version.
     *
     * The version string MUST contain only the HTTP version number (e.g.,
     * "1.1", "1.0").
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * new protocol version.
     *
     * @param string $version HTTP protocol version
     * @return static
     */
    public function withProtocolVersion(string $version): MessageInterface;

    /**
     * Retrieves all message header values.
     *
     * The keys represent the header name as it will be sent over the wire, and
     * each value is an array of strings associated with the header.
     *
     *     // Represent the headers as a string
     *     foreach ($message->getHeaders() as $name => $values) {
     *         echo $name . ": " . implode(", ", $values);
     *     }
     *
     *     // Emit headers iteratively:
     *     foreach ($message->getHeaders() as $name => $values) {
     *         foreach ($values as $value) {
     *             header(sprintf('%s: %s', $name, $value), false);
     *         }
     *     }
     *
     * While header names are not case-sensitive, getHeaders() will preserve the
     * exact case in which headers were originally specified.
     *
     * @return string[][] Returns an associative array of the message's headers. Each
     *     key MUST be a header name, and each value MUST be an array of strings
     *     for that header.
     */
    public function getHeaders(): array;

    /**
     * Checks if a header exists by the given case-insensitive name.
     *
     * @param string $name Case-insensitive header field name.
     * @return bool Returns true if any header names match the given header
     *     name using a case-insensitive string comparison. Returns false if
     *     no matching header name is found in the message.
     */
    public function hasHeader(string $name): bool;

    /**
     * Retrieves a message header value by the given case-insensitive name.
     *
     * This method returns an array of all the header values of the given
     * case-insensitive header name.
     *
     * If the header does not appear in the message, this method MUST return an
     * empty array.
     *
     * @param string $name Case-insensitive header field name.
     * @return string[] An array of string values as provided for the given
     *    header. If the header does not appear in the message, this method MUST
     *    return an empty array.
     */
    public function getHeader(string $name): array;

    /**
     * Retrieves a comma-separated string of the values for a single header.
     *
     * This method returns all of the header values of the given
     * case-insensitive header name as a string concatenated together using
     * a comma.
     *
     * NOTE: Not all header values may be appropriately represented using
     * comma concatenation. For such headers, use getHeader() instead
     * and supply your own delimiter when concatenating.
     *
     * If the header does not appear in the message, this method MUST return
     * an empty string.
     *
     * @param string $name Case-insensitive header field name.
     * @return string A string of values as provided for the given header
     *    concatenated together using a comma. If the header does not appear in
     *    the message, this method MUST return an empty string.
     */
    public function getHeaderLine(string $name): string;

    /**
     * Return an instance with the provided value replacing the specified header.
     *
     * While header names are case-insensitive, the casing of the header will
     * be preserved by this function, and returned from getHeaders().
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * new and/or updated header and value.
     *
     * @param string $name Case-insensitive header field name.
     * @param string|string[] $value Header value(s).
     * @return static
     * @throws \InvalidArgumentException for invalid header names or values.
     */
    public function withHeader(string $name, $value): MessageInterface;

    /**
     * Return an instance with the specified header appended with the given value.
     *
     * Existing values for the specified header will be maintained. The new
     * value(s) will be appended to the existing list. If the header did not
     * exist previously, it will be added.
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * new header and/or value.
     *
     * @param string $name Case-insensitive header field name to add.
     * @param string|string[] $value Header value(s).
     * @return static
     * @throws \InvalidArgumentException for invalid header names or values.
     */
    public function withAddedHeader(string $name, $value): MessageInterface;

    /**
     * Return an instance without the specified header.
     *
     * Header resolution MUST be done without case-sensitivity.
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that removes
     * the named header.
     *
     * @param string $name Case-insensitive header field name to remove.
     * @return static
     */
    public function withoutHeader(string $name): MessageInterface;

    /**
     * Gets the body of the message.
     *
     * @return StreamInterface Returns the body as a stream.
     */
    public function getBody(): StreamInterface;

    /**
     * Return an instance with the specified message body.
     *
     * The body MUST be a StreamInterface object.
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return a new instance that has the
     * new body stream.
     *
     * @param StreamInterface $body Body.
     * @return static
     * @throws \InvalidArgumentException When the body is not valid.
     */
    public function withBody(StreamInterface $body): MessageInterface;
}
PK�c�\bY6J
J
*psr/http-message/src/ResponseInterface.phpnu�[���<?php

namespace Psr\Http\Message;

/**
 * Representation of an outgoing, server-side response.
 *
 * Per the HTTP specification, this interface includes properties for
 * each of the following:
 *
 * - Protocol version
 * - Status code and reason phrase
 * - Headers
 * - Message body
 *
 * Responses are considered immutable; all methods that might change state MUST
 * be implemented such that they retain the internal state of the current
 * message and return an instance that contains the changed state.
 */
interface ResponseInterface extends MessageInterface
{
    /**
     * Gets the response status code.
     *
     * The status code is a 3-digit integer result code of the server's attempt
     * to understand and satisfy the request.
     *
     * @return int Status code.
     */
    public function getStatusCode(): int;

    /**
     * Return an instance with the specified status code and, optionally, reason phrase.
     *
     * If no reason phrase is specified, implementations MAY choose to default
     * to the RFC 7231 or IANA recommended reason phrase for the response's
     * status code.
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * updated status and reason phrase.
     *
     * @link http://tools.ietf.org/html/rfc7231#section-6
     * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
     * @param int $code The 3-digit integer result code to set.
     * @param string $reasonPhrase The reason phrase to use with the
     *     provided status code; if none is provided, implementations MAY
     *     use the defaults as suggested in the HTTP specification.
     * @return static
     * @throws \InvalidArgumentException For invalid status code arguments.
     */
    public function withStatus(int $code, string $reasonPhrase = ''): ResponseInterface;

    /**
     * Gets the response reason phrase associated with the status code.
     *
     * Because a reason phrase is not a required element in a response
     * status line, the reason phrase value MAY be null. Implementations MAY
     * choose to return the default RFC 7231 recommended reason phrase (or those
     * listed in the IANA HTTP Status Code Registry) for the response's
     * status code.
     *
     * @link http://tools.ietf.org/html/rfc7231#section-6
     * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
     * @return string Reason phrase; must return an empty string if none present.
     */
    public function getReasonPhrase(): string;
}
PK�c�\жJ���(psr/http-message/src/StreamInterface.phpnu�[���<?php

namespace Psr\Http\Message;

/**
 * Describes a data stream.
 *
 * Typically, an instance will wrap a PHP stream; this interface provides
 * a wrapper around the most common operations, including serialization of
 * the entire stream to a string.
 */
interface StreamInterface
{
    /**
     * Reads all data from the stream into a string, from the beginning to end.
     *
     * This method MUST attempt to seek to the beginning of the stream before
     * reading data and read the stream until the end is reached.
     *
     * Warning: This could attempt to load a large amount of data into memory.
     *
     * This method MUST NOT raise an exception in order to conform with PHP's
     * string casting operations.
     *
     * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring
     * @return string
     */
    public function __toString(): string;

    /**
     * Closes the stream and any underlying resources.
     *
     * @return void
     */
    public function close(): void;

    /**
     * Separates any underlying resources from the stream.
     *
     * After the stream has been detached, the stream is in an unusable state.
     *
     * @return resource|null Underlying PHP stream, if any
     */
    public function detach();

    /**
     * Get the size of the stream if known.
     *
     * @return int|null Returns the size in bytes if known, or null if unknown.
     */
    public function getSize(): ?int;

    /**
     * Returns the current position of the file read/write pointer
     *
     * @return int Position of the file pointer
     * @throws \RuntimeException on error.
     */
    public function tell(): int;

    /**
     * Returns true if the stream is at the end of the stream.
     *
     * @return bool
     */
    public function eof(): bool;

    /**
     * Returns whether or not the stream is seekable.
     *
     * @return bool
     */
    public function isSeekable(): bool;

    /**
     * Seek to a position in the stream.
     *
     * @link http://www.php.net/manual/en/function.fseek.php
     * @param int $offset Stream offset
     * @param int $whence Specifies how the cursor position will be calculated
     *     based on the seek offset. Valid values are identical to the built-in
     *     PHP $whence values for `fseek()`.  SEEK_SET: Set position equal to
     *     offset bytes SEEK_CUR: Set position to current location plus offset
     *     SEEK_END: Set position to end-of-stream plus offset.
     * @throws \RuntimeException on failure.
     */
    public function seek(int $offset, int $whence = SEEK_SET): void;

    /**
     * Seek to the beginning of the stream.
     *
     * If the stream is not seekable, this method will raise an exception;
     * otherwise, it will perform a seek(0).
     *
     * @see seek()
     * @link http://www.php.net/manual/en/function.fseek.php
     * @throws \RuntimeException on failure.
     */
    public function rewind(): void;

    /**
     * Returns whether or not the stream is writable.
     *
     * @return bool
     */
    public function isWritable(): bool;

    /**
     * Write data to the stream.
     *
     * @param string $string The string that is to be written.
     * @return int Returns the number of bytes written to the stream.
     * @throws \RuntimeException on failure.
     */
    public function write(string $string): int;

    /**
     * Returns whether or not the stream is readable.
     *
     * @return bool
     */
    public function isReadable(): bool;

    /**
     * Read data from the stream.
     *
     * @param int $length Read up to $length bytes from the object and return
     *     them. Fewer than $length bytes may be returned if underlying stream
     *     call returns fewer bytes.
     * @return string Returns the data read from the stream, or an empty string
     *     if no bytes are available.
     * @throws \RuntimeException if an error occurs.
     */
    public function read(int $length): string;

    /**
     * Returns the remaining contents in a string
     *
     * @return string
     * @throws \RuntimeException if unable to read or an error occurs while
     *     reading.
     */
    public function getContents(): string;

    /**
     * Get stream metadata as an associative array or retrieve a specific key.
     *
     * The keys returned are identical to the keys returned from PHP's
     * stream_get_meta_data() function.
     *
     * @link http://php.net/manual/en/function.stream-get-meta-data.php
     * @param string|null $key Specific metadata to retrieve.
     * @return array|mixed|null Returns an associative array if no key is
     *     provided. Returns a specific key value if a key is provided and the
     *     value is found, or null if the key is not found.
     */
    public function getMetadata(?string $key = null);
}
PK�c�\�Lo$sspsr/http-message/composer.jsonnu�[���{
    "name": "psr/http-message",
    "description": "Common interface for HTTP messages",
    "keywords": ["psr", "psr-7", "http", "http-message", "request", "response"],
    "homepage": "https://github.com/php-fig/http-message",
    "license": "MIT",
    "authors": [
        {
            "name": "PHP-FIG",
            "homepage": "https://www.php-fig.org/"
        }
    ],
    "require": {
        "php": "^7.2 || ^8.0"
    },
    "autoload": {
        "psr-4": {
            "Psr\\Http\\Message\\": "src/"
        }
    },
    "extra": {
        "branch-alias": {
            "dev-master": "2.0.x-dev"
        }
    }
}
PK�c�\�6L%L%(psr/http-message/docs/PSR7-Interfaces.mdnu�[���# Interfaces

The purpose of this list is to help in finding the methods when working with PSR-7. This can be considered as a cheatsheet for PSR-7 interfaces.

The interfaces defined in PSR-7 are the following:

| Class Name | Description |
|---|---|
| [Psr\Http\Message\MessageInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessagemessageinterface) | Representation of a HTTP message |
| [Psr\Http\Message\RequestInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessagerequestinterface) | Representation of an outgoing, client-side request. |
| [Psr\Http\Message\ServerRequestInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessageserverrequestinterface) | Representation of an incoming, server-side HTTP request. | 
| [Psr\Http\Message\ResponseInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessageresponseinterface) | Representation of an outgoing, server-side response. |
| [Psr\Http\Message\StreamInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessagestreaminterface) | Describes a data stream |
| [Psr\Http\Message\UriInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessageuriinterface) | Value object representing a URI. |
| [Psr\Http\Message\UploadedFileInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessageuploadedfileinterface) | Value object representing a file uploaded through an HTTP request. |

## `Psr\Http\Message\MessageInterface` Methods

| Method Name                        | Description | Notes |
|------------------------------------| ----------- | ----- |
| `getProtocolVersion()`             | Retrieve HTTP protocol version          |  1.0 or 1.1 |
| `withProtocolVersion($version)`    | Returns new message instance with given HTTP protocol version          |      |
| `getHeaders()`                     | Retrieve all HTTP Headers               | [Request Header List](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields), [Response Header List](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Response_fields)      |
| `hasHeader($name)`                 | Checks if HTTP Header with given name exists  | |
| `getHeader($name)`                 | Retrieves a array with the values for a single header | |
| `getHeaderLine($name)`             | Retrieves a comma-separated string of the values for a single header |  |
| `withHeader($name, $value)`        | Returns new message instance with given HTTP Header | if the header existed in the original instance, replaces the header value from the original message with the value provided when creating the new instance. |
| `withAddedHeader($name, $value)`   | Returns new message instance with appended value to given header | If header already exists value will be appended, if not a new header will be created |
| `withoutHeader($name)`             | Removes HTTP Header with given name| |
| `getBody()`                        | Retrieves the HTTP Message Body | Returns object implementing `StreamInterface`|
| `withBody(StreamInterface $body)`  | Returns new message instance with given HTTP Message Body | |


## `Psr\Http\Message\RequestInterface` Methods

Same methods as `Psr\Http\Message\MessageInterface`  + the following methods:

| Method Name                        | Description | Notes |
|------------------------------------| ----------- | ----- |
| `getRequestTarget()`                | Retrieves the message's request target              | origin-form, absolute-form, authority-form, asterisk-form ([RFC7230](https://www.rfc-editor.org/rfc/rfc7230.txt)) |
| `withRequestTarget($requestTarget)` | Return a new message instance with the specific request-target |      |
| `getMethod()`                       | Retrieves the HTTP method of the request.  |  GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE (defined in [RFC7231](https://tools.ietf.org/html/rfc7231)), PATCH (defined in [RFC5789](https://tools.ietf.org/html/rfc5789)) |
| `withMethod($method)`               | Returns a new message instance with the provided HTTP method  | |
| `getUri()`                 | Retrieves the URI instance | |
| `withUri(UriInterface $uri, $preserveHost = false)` | Returns a new message instance with the provided URI |  |


## `Psr\Http\Message\ServerRequestInterface` Methods

Same methods as `Psr\Http\Message\RequestInterface`  + the following methods:

| Method Name                        | Description | Notes |
|------------------------------------| ----------- | ----- |
| `getServerParams() `               | Retrieve server parameters  | Typically derived from `$_SERVER`  |
| `getCookieParams()`                | Retrieves cookies sent by the client to the server. | Typically derived from `$_COOKIES` |
| `withCookieParams(array $cookies)` |  Returns a new request instance with the specified cookies      |   | 
| `withQueryParams(array $query)` | Returns a new request instance with the specified query string arguments  |  |
| `getUploadedFiles()` | Retrieve normalized file upload data  |  |
| `withUploadedFiles(array $uploadedFiles)` | Returns a new request instance with the specified uploaded files  |  |
| `getParsedBody()` | Retrieve any parameters provided in the request body  |  |
| `withParsedBody($data)` | Returns a new request instance with the specified body parameters  |  |
| `getAttributes()` | Retrieve attributes derived from the request  |  |
| `getAttribute($name, $default = null)` | Retrieve a single derived request attribute  |  |
| `withAttribute($name, $value)` | Returns a new request instance with the specified derived request attribute  |  |
| `withoutAttribute($name)` | Returns a new request instance that without the specified derived request attribute  |  |

## `Psr\Http\Message\ResponseInterface` Methods:

Same methods as `Psr\Http\Message\MessageInterface`  + the following methods:

| Method Name                        | Description | Notes |
|------------------------------------| ----------- | ----- |
| `getStatusCode()` | Gets the response status code. | |
| `withStatus($code, $reasonPhrase = '')` | Returns a new response instance with the specified status code and, optionally, reason phrase. | |
| `getReasonPhrase()` | Gets the response reason phrase associated with the status code. | |

##  `Psr\Http\Message\StreamInterface` Methods

| Method Name                        | Description | Notes |
|------------------------------------| ----------- | ----- |
| `__toString()` | Reads all data from the stream into a string, from the beginning to end. | |
| `close()` | Closes the stream and any underlying resources. | |
| `detach()` | Separates any underlying resources from the stream. | |
| `getSize()` | Get the size of the stream if known. | |
| `eof()` | Returns true if the stream is at the end of the stream.| |
| `isSeekable()` |  Returns whether or not the stream is seekable. | |
| `seek($offset, $whence = SEEK_SET)` | Seek to a position in the stream. | |
| `rewind()` | Seek to the beginning of the stream. | |
| `isWritable()` | Returns whether or not the stream is writable. | |
| `write($string)` | Write data to the stream. | |
| `isReadable()` | Returns whether or not the stream is readable. | |
| `read($length)` | Read data from the stream. | |
| `getContents()` | Returns the remaining contents in a string | |
| `getMetadata($key = null)()` | Get stream metadata as an associative array or retrieve a specific key. | |

## `Psr\Http\Message\UriInterface` Methods

| Method Name                        | Description | Notes |
|------------------------------------| ----------- | ----- |
| `getScheme()` | Retrieve the scheme component of the URI. | |
| `getAuthority()` | Retrieve the authority component of the URI. | |
| `getUserInfo()` | Retrieve the user information component of the URI. | |
| `getHost()` | Retrieve the host component of the URI. | |
| `getPort()` | Retrieve the port component of the URI. | |
| `getPath()` | Retrieve the path component of the URI. | |
| `getQuery()` | Retrieve the query string of the URI. | |
| `getFragment()` | Retrieve the fragment component of the URI. | |
| `withScheme($scheme)` | Return an instance with the specified scheme. | |
| `withUserInfo($user, $password = null)` | Return an instance with the specified user information. | |
| `withHost($host)` | Return an instance with the specified host. | |
| `withPort($port)` | Return an instance with the specified port. | |
| `withPath($path)` | Return an instance with the specified path. | |
| `withQuery($query)` | Return an instance with the specified query string. | |
| `withFragment($fragment)` | Return an instance with the specified URI fragment. | |
| `__toString()` | Return the string representation as a URI reference. | |

## `Psr\Http\Message\UploadedFileInterface` Methods

| Method Name                        | Description | Notes |
|------------------------------------| ----------- | ----- |
| `getStream()` | Retrieve a stream representing the uploaded file. | |
| `moveTo($targetPath)` | Move the uploaded file to a new location. | |
| `getSize()` | Retrieve the file size. | |
| `getError()` | Retrieve the error associated with the uploaded file. | |
| `getClientFilename()` | Retrieve the filename sent by the client. | |
| `getClientMediaType()` | Retrieve the media type sent by the client. | |

> `RequestInterface`, `ServerRequestInterface`, `ResponseInterface` extend `MessageInterface`  because the `Request` and the `Response` are `HTTP Messages`.
> When using `ServerRequestInterface`, both `RequestInterface` and `Psr\Http\Message\MessageInterface` methods are considered.

PK�c�\z �Xtt#psr/http-message/docs/PSR7-Usage.mdnu�[���### PSR-7 Usage

All PSR-7 applications comply with these interfaces 
They were created to establish a standard between middleware implementations.

> `RequestInterface`, `ServerRequestInterface`, `ResponseInterface` extend `MessageInterface`  because the `Request` and the `Response` are `HTTP Messages`.
> When using `ServerRequestInterface`, both `RequestInterface` and `Psr\Http\Message\MessageInterface` methods are considered.


The following examples will illustrate how basic operations are done in PSR-7.

##### Examples


For this examples to work (at least) a PSR-7 implementation package is required. (eg: zendframework/zend-diactoros, guzzlehttp/psr7, slim/slim, etc)
All PSR-7 implementations should have the same behaviour.

The following will be assumed: 
`$request` is an object of `Psr\Http\Message\RequestInterface` and

`$response` is an object implementing `Psr\Http\Message\RequestInterface`


### Working with HTTP Headers

#### Adding headers to response:

```php
$response->withHeader('My-Custom-Header', 'My Custom Message');
```

#### Appending values to headers

```php
$response->withAddedHeader('My-Custom-Header', 'The second message');
```

#### Checking if header exists:

```php
$request->hasHeader('My-Custom-Header'); // will return false
$response->hasHeader('My-Custom-Header'); // will return true
```

> Note: My-Custom-Header was only added in the Response

#### Getting comma-separated values from a header (also applies to request)

```php
// getting value from request headers
$request->getHeaderLine('Content-Type'); // will return: "text/html; charset=UTF-8"
// getting value from response headers
$response->getHeaderLine('My-Custom-Header'); // will return:  "My Custom Message; The second message"
```

#### Getting array of value from a header (also applies to request)
```php
// getting value from request headers
$request->getHeader('Content-Type'); // will return: ["text/html", "charset=UTF-8"]
// getting value from response headers
$response->getHeader('My-Custom-Header'); // will return:  ["My Custom Message",  "The second message"]
```

#### Removing headers from HTTP Messages
```php
// removing a header from Request, removing deprecated "Content-MD5" header
$request->withoutHeader('Content-MD5'); 

// removing a header from Response
// effect: the browser won't know the size of the stream
// the browser will download the stream till it ends
$response->withoutHeader('Content-Length');
```

### Working with HTTP Message Body

When working with the PSR-7 there are two methods of implementation:
#### 1. Getting the body separately

> This method makes the body handling easier to understand and is useful when repeatedly calling body methods. (You only call `getBody()` once). Using this method mistakes like `$response->write()` are also prevented.

```php
$body = $response->getBody();
// operations on body, eg. read, write, seek
// ...
// replacing the old body
$response->withBody($body); 
// this last statement is optional as we working with objects
// in this case the "new" body is same with the "old" one
// the $body variable has the same value as the one in $request, only the reference is passed
```

#### 2. Working directly on response

> This method is useful when only performing few operations as the `$request->getBody()` statement fragment is required

```php
$response->getBody()->write('hello');
```

### Getting the body contents

The following snippet gets the contents of a stream contents.
> Note: Streams must be rewinded, if content was written into streams, it will be ignored when calling `getContents()` because the stream pointer is set to the last character, which is `\0` - meaning end of stream.
```php 
$body = $response->getBody();
$body->rewind(); // or $body->seek(0);
$bodyText = $body->getContents();
```
> Note: If `$body->seek(1)` is called before `$body->getContents()`, the first character will be ommited as the starting pointer is set to `1`, not `0`. This is why using `$body->rewind()` is recommended.

### Append to body

```php
$response->getBody()->write('Hello'); // writing directly
$body = $request->getBody(); // which is a `StreamInterface`
$body->write('xxxxx');
```

### Prepend to body
Prepending is different when it comes to streams. The content must be copied before writing the content to be prepended.
The following example will explain the behaviour of streams.

```php
// assuming our response is initially empty
$body = $repsonse->getBody();
// writing the string "abcd"
$body->write('abcd');

// seeking to start of stream
$body->seek(0);
// writing 'ef'
$body->write('ef'); // at this point the stream contains "efcd"
```

#### Prepending by rewriting separately

```php
// assuming our response body stream only contains: "abcd"
$body = $response->getBody();
$body->rewind();
$contents = $body->getContents(); // abcd
// seeking the stream to beginning
$body->rewind();
$body->write('ef'); // stream contains "efcd"
$body->write($contents); // stream contains "efabcd"
```

> Note: `getContents()` seeks the stream while reading it, therefore if the second `rewind()` method call was not present the stream would have resulted in `abcdefabcd` because the `write()` method appends to stream if not preceeded by `rewind()` or `seek(0)`.

#### Prepending by using contents as a string
```php
$body = $response->getBody();
$body->rewind();
$contents = $body->getContents(); // efabcd
$contents = 'ef'.$contents;
$body->rewind();
$body->write($contents);
```
PK�c�\�:\Y33psr/http-message/CHANGELOG.mdnu�[���# Changelog

All notable changes to this project will be documented in this file, in reverse chronological order by release.

## 1.0.1 - 2016-08-06

### Added

- Nothing.

### Deprecated

- Nothing.

### Removed

- Nothing.

### Fixed

- Updated all `@return self` annotation references in interfaces to use
  `@return static`, which more closelly follows the semantics of the
  specification.
- Updated the `MessageInterface::getHeaders()` return annotation to use the
  value `string[][]`, indicating the format is a nested array of strings.
- Updated the `@link` annotation for `RequestInterface::withRequestTarget()`
  to point to the correct section of RFC 7230.
- Updated the `ServerRequestInterface::withUploadedFiles()` parameter annotation
  to add the parameter name (`$uploadedFiles`).
- Updated a `@throws` annotation for the `UploadedFileInterface::moveTo()`
  method to correctly reference the method parameter (it was referencing an
  incorrect parameter name previously).

## 1.0.0 - 2016-05-18

Initial stable release; reflects accepted PSR-7 specification.
PK�c�\���==psr/http-message/LICENSEnu�[���Copyright (c) 2014 PHP Framework Interoperability Group

Permission is hereby granted, free of charge, to any person obtaining a copy 
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights 
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
copies of the Software, and to permit persons to whom the Software is 
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in 
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
PK�c�\�����psr/http-message/README.mdnu�[���PSR Http Message
================

This repository holds all interfaces/classes/traits related to
[PSR-7](http://www.php-fig.org/psr/psr-7/).

Note that this is not a HTTP message implementation of its own. It is merely an
interface that describes a HTTP message. See the specification for more details.

Usage
-----

Before reading the usage guide we recommend reading the PSR-7 interfaces method list:

* [`PSR-7 Interfaces Method List`](docs/PSR7-Interfaces.md)
* [`PSR-7 Usage Guide`](docs/PSR7-Usage.md)PKyc�\QuQ�����composer/installed.jsonnu�[���PKyc�\3�MEE��composer/autoload_static.phpnu�[���PKyc�\��%���f	composer/platform_check.phpnu�[���PKzc�\ 2��??N
composer/InstalledVersions.phpnu�[���PKzc�\6�J���Lcomposer/autoload_classmap.phpnu�[���PKzc�\ �..�[composer/LICENSEnu�[���PKzc�\2@u�?�?`composer/ClassLoader.phpnu�[���PKzc�\�k���]�composer/autoload_files.phpnu�[���PKzc�\�)�)d�composer/installed.phpnu�[���PKzc�\�]p�

v�composer/include_paths.phpnu�[���PKzc�\���99��composer/autoload_real.phpnu�[���PK{c�\�3�2��P�composer/autoload_psr4.phpnu�[���PK{c�\*���� !�composer/autoload_namespaces.phpnu�[���PK{c�\U�@@"!�bacon/bacon-qr-code/src/Writer.phpnu�[���PK{c�\�����>��bacon/bacon-qr-code/src/Exception/UnexpectedValueException.phpnu�[���PK{c�\y&��:��bacon/bacon-qr-code/src/Exception/OutOfBoundsException.phpnu�[���PK{c�\6�����>��bacon/bacon-qr-code/src/Exception/InvalidArgumentException.phpnu�[���PK{c�\"�s���8��bacon/bacon-qr-code/src/Exception/ExceptionInterface.phpnu�[���PK{c�\3/f!��6��bacon/bacon-qr-code/src/Exception/RuntimeException.phpnu�[���PK{c�\��-��5��bacon/bacon-qr-code/src/Exception/WriterException.phpnu�[���PK{c�\JJ�o88,��bacon/bacon-qr-code/src/Common/BitMatrix.phpnu�[���PK{c�\}@v==7bacon/bacon-qr-code/src/Common/ErrorCorrectionLevel.phpnu�[���PK{c�\���.��2#bacon/bacon-qr-code/src/Common/CharacterSetEci.phpnu�[���PK{c�\�}n�pUpU*i#bacon/bacon-qr-code/src/Common/Version.phpnu�[���PK{c�\�"�9:9:33ybacon/bacon-qr-code/src/Common/ReedSolomonCodec.phpnu�[���PK{c�\׉�ʊ�+ϳbacon/bacon-qr-code/src/Common/BitUtils.phpnu�[���PK{c�\$� L??'��bacon/bacon-qr-code/src/Common/Mode.phpnu�[���PK{c�\ζ#բ�4J�bacon/bacon-qr-code/src/Common/FormatInformation.phpnu�[���PK{c�\E7����+P�bacon/bacon-qr-code/src/Common/EcBlocks.phpnu�[���PK{c�\�礹�"�"+5�bacon/bacon-qr-code/src/Common/BitArray.phpnu�[���PK{c�\20�`��*cbacon/bacon-qr-code/src/Common/EcBlock.phpnu�[���PK{c�\=@�3�A�A.�bacon/bacon-qr-code/src/Encoder/MatrixUtil.phpnu�[���PK{c�\4�q��U�U+�Gbacon/bacon-qr-code/src/Encoder/Encoder.phpnu�[���PK{c�\=f�.�
�
*7�bacon/bacon-qr-code/src/Encoder/QrCode.phpnu�[���PK{c�\��C�VV-e�bacon/bacon-qr-code/src/Encoder/BlockPair.phpnu�[���PK{c�\G��� � ,�bacon/bacon-qr-code/src/Encoder/MaskUtil.phpnu�[���PK{c�\m�����..�bacon/bacon-qr-code/src/Encoder/ByteMatrix.phpnu�[���PK{c�\�D�2c�bacon/bacon-qr-code/src/Renderer/ImageRenderer.phpnu�[���PK{c�\[䘑��:��bacon/bacon-qr-code/src/Renderer/RendererStyle/EyeFill.phpnu�[���PK{c�\v��117��bacon/bacon-qr-code/src/Renderer/RendererStyle/Fill.phpnu�[���PK{c�\;p+ݯ�@�bacon/bacon-qr-code/src/Renderer/RendererStyle/RendererStyle.phpnu�[���PK{c�\�g��ZZ;�bacon/bacon-qr-code/src/Renderer/RendererStyle/Gradient.phpnu�[���PK{c�\#[�I?xbacon/bacon-qr-code/src/Renderer/RendererStyle/GradientType.phpnu�[���PK{c�\5����6bacon/bacon-qr-code/src/Renderer/RendererInterface.phpnu�[���PK{c�\�`�s(s(>$bacon/bacon-qr-code/src/Renderer/Image/ImagickImageBackEnd.phpnu�[���PK{c�\<��1�.�.:=bacon/bacon-qr-code/src/Renderer/Image/EpsImageBackEnd.phpnu�[���PK{c�\ף`�T1T1:*lbacon/bacon-qr-code/src/Renderer/Image/SvgImageBackEnd.phpnu�[���PK{c�\�j]���?�bacon/bacon-qr-code/src/Renderer/Image/TransformationMatrix.phpnu�[���PK{c�\g^q�	�	@P�bacon/bacon-qr-code/src/Renderer/Image/ImageBackEndInterface.phpnu�[���PK{c�\=�3�
	
	/��bacon/bacon-qr-code/src/Renderer/Color/Cmyk.phpnu�[���PK{c�\���mm9��bacon/bacon-qr-code/src/Renderer/Color/ColorInterface.phpnu�[���PK{c�\B�m���.ӻbacon/bacon-qr-code/src/Renderer/Color/Rgb.phpnu�[���PK{c�\/�W:>>0��bacon/bacon-qr-code/src/Renderer/Color/Alpha.phpnu�[���PK|c�\va��/t�bacon/bacon-qr-code/src/Renderer/Color/Gray.phpnu�[���PK|c�\��^�SS5r�bacon/bacon-qr-code/src/Renderer/Path/EllipticArc.phpnu�[���PK|c�\3�����.*�bacon/bacon-qr-code/src/Renderer/Path/Line.phpnu�[���PK|c�\GO�9��/�bacon/bacon-qr-code/src/Renderer/Path/Curve.phpnu�[���PK|c�\��M��.4�bacon/bacon-qr-code/src/Renderer/Path/Move.phpnu�[���PK|c�\�۶k��/"�bacon/bacon-qr-code/src/Renderer/Path/Close.phpnu�[���PK|c�\�����<y�bacon/bacon-qr-code/src/Renderer/Path/OperationInterface.phpnu�[���PK|c�\��uǤ	�	.�bacon/bacon-qr-code/src/Renderer/Path/Path.phpnu�[���PK|c�\6�翎�6�bacon/bacon-qr-code/src/Renderer/PlainTextRenderer.phpnu�[���PK|c�\��=��8�
bacon/bacon-qr-code/src/Renderer/Eye/SimpleCircleEye.phpnu�[���PK|c�\_��
112�bacon/bacon-qr-code/src/Renderer/Eye/SquareEye.phpnu�[���PK|c�\�#�TT5bacon/bacon-qr-code/src/Renderer/Eye/EyeInterface.phpnu�[���PK|c�\C"랫�28bacon/bacon-qr-code/src/Renderer/Eye/ModuleEye.phpnu�[���PK|c�\7�$��5Ebacon/bacon-qr-code/src/Renderer/Eye/CompositeEye.phpnu�[���PK|c�\7A<y��;�bacon/bacon-qr-code/src/Renderer/Module/ModuleInterface.phpnu�[���PK|c�\F�݀��=�!bacon/bacon-qr-code/src/Renderer/Module/EdgeIterator/Edge.phpnu�[���PK|c�\%���iiE\*bacon/bacon-qr-code/src/Renderer/Module/EdgeIterator/EdgeIterator.phpnu�[���PK|c�\�xT}&&8:9bacon/bacon-qr-code/src/Renderer/Module/SquareModule.phpnu�[���PK|c�\:	��||;�=bacon/bacon-qr-code/src/Renderer/Module/RoundnessModule.phpnu�[���PK|c�\W�.!6�Obacon/bacon-qr-code/src/Renderer/Module/DotsModule.phpnu�[���PK|c�\^�W�UU!1Wbacon/bacon-qr-code/composer.jsonnu�[���PK|c�\˷I��[bacon/bacon-qr-code/LICENSEnu�[���PK|c�\.ڌӞ�>abacon/bacon-qr-code/README.mdnu�[���PK|c�\ty�33)hkolab/net_ldap3/.arcconfignu�[���PK|c�\�wF����hkolab/net_ldap3/composer.jsonnu�[���PK|c�\|�wfK�K��kkolab/net_ldap3/LICENSEnu�[���PK|c�\8*���!S�kolab/net_ldap3/lib/Net/LDAP3.phpnu�[���PK|c�\{�_�����pear/net_ldap2/composer.jsonnu�[���PK|c�\�������pear/net_ldap2/LICENSEnu�[���PK|c�\��������pear/net_ldap2/package.xmlnu�[���PK|c�\���=\\�\pear/net_ldap2/Net/LDAP2.phpnu�[���PK|c�\7�(1K1K#�o	pear/net_ldap2/Net/LDAP2/Search.phpnu�[���PK|c�\W�NN$�	pear/net_ldap2/Net/LDAP2/RootDSE.phpnu�[���PK|c�\�U/�2��	pear/net_ldap2/Net/LDAP2/SimpleFileSchemaCache.phpnu�[���PK|c�\
~��R�R#�	pear/net_ldap2/Net/LDAP2/Schema.phpnu�[���PK|c�\�r�B����!3
pear/net_ldap2/Net/LDAP2/LDIF.phpnu�[���PK|c�\s|��k�k#(�
pear/net_ldap2/Net/LDAP2/Filter.phpnu�[���PK|c�\�����"s%pear/net_ldap2/Net/LDAP2/Entry.phpnu�[���PK|c�\L��+��2ڷpear/net_ldap2/Net/LDAP2/SchemaCache.interface.phpnu�[���PK|c�\<Ά�a�a!�pear/net_ldap2/Net/LDAP2/Util.phpnu�[���PK|c�\Z�����a"pear/net_smtp/composer.jsonnu�[���PK|c�\����((�'pear/net_smtp/LICENSEnu�[���PK|c�\�SOܪ%�%-pear/net_smtp/README.rstnu�[���PK|c�\	�(�{�{�Spear/net_smtp/Net/SMTP.phpnu�[���PK|c�\s�='���
pear/net_socket/composer.jsonnu�[���PK|c�\�b��HH�
pear/net_socket/package.xmlnu�[���PK|c�\�W�OO�"
pear/net_socket/Net/Socket.phpnu�[���PK|c�\��0AC5C5&�q
pear/console_getopt/Console/Getopt.phpnu�[���PK|c�\+L�uu!p�
pear/console_getopt/composer.jsonnu�[���PK|c�\K��K6�
pear/console_getopt/LICENSEnu�[���PK|c�\ׁ�����
pear/console_getopt/README.rstnu�[���PK|c�\���S^^��
pear/console_getopt/package.xmlnu�[���PK|c�\DoPnn7�
pear/mail_mime/composer.jsonnu�[���PK|c�\��Zj��
pear/mail_mime/READMEnu�[���PK|c�\Z�8�g�g�E�
pear/mail_mime/Mail/mime.phpnu�[���PK|c�\|n��W�W� ��pear/mail_mime/Mail/mimePart.phpnu�[���PK|c�\�����fpear/net_sieve/composer.jsonnu�[���PK|c�\_�m���slpear/net_sieve/Sieve.phpnu�[���PK|c�\�]<--'d*pear/pear-core-minimal/src/OS/Guess.phpnu�[���PK|c�\7�?�����#�Wpear/pear-core-minimal/src/PEAR.phpnu�[���PK|c�\V���.��pear/pear-core-minimal/src/PEAR/ErrorStack.phpnu�[���PK|c�\�;_�CC)jpear/pear-core-minimal/src/PEAR/Error.phpnu�[���PK|c�\m��*�P�P%�kpear/pear-core-minimal/src/System.phpnu�[���PK|c�\-�y66$ܼpear/pear-core-minimal/composer.jsonnu�[���PK|c�\B����!f�pear/pear-core-minimal/README.rstnu�[���PK|c�\�3����!��pear/pear_exception/composer.jsonnu�[���PK|c�\�u������pear/pear_exception/LICENSEnu�[���PK|c�\��_�)<)<&��pear/pear_exception/PEAR/Exception.phpnu�[���PK|c�\�5oI��C	pear/auth_sasl/composer.jsonnu�[���PK|c�\cd��\\v
pear/auth_sasl/package.xmlnu�[���PK|c�\2r����#pear/auth_sasl/Auth/SASL.phpnu�[���PK|c�\����=="=8pear/auth_sasl/Auth/SASL/Plain.phpnu�[���PK|c�\�<� OO"�Dpear/auth_sasl/Auth/SASL/Login.phpnu�[���PK|c�\�J�}
}
&mQpear/auth_sasl/Auth/SASL/Anonymous.phpnu�[���PK|c�\%ׅtg!g!&@_pear/auth_sasl/Auth/SASL/DigestMD5.phpnu�[���PK|c�\�T�#��pear/auth_sasl/Auth/SASL/Common.phpnu�[���PK|c�\vR�r,,%f�pear/auth_sasl/Auth/SASL/External.phpnu�[���PK}c�\ќ��0�0"�pear/auth_sasl/Auth/SASL/SCRAM.phpnu�[���PK}c�\P(~�T
T
$4�pear/auth_sasl/Auth/SASL/CramMD5.phpnu�[���PK}c�\�G׭����pear/auth_sasl/README.mdnu�[���PK}c�\�
{����0��pear/console_commandline/Console/CommandLine.phpnu�[���PK}c�\}uuz��9ܕpear/console_commandline/Console/CommandLine/Renderer.phpnu�[���PK}c�\���Ɏ�F?�pear/console_commandline/Console/CommandLine/CustomMessageProvider.phpnu�[���PK}c�\o�R^#-#-7C�pear/console_commandline/Console/CommandLine/Option.phpnu�[���PK}c�\HSAޭ�@��pear/console_commandline/Console/CommandLine/MessageProvider.phpnu�[���PK}c�\�3����:��pear/console_commandline/Console/CommandLine/Exception.phpnu�[���PK}c�\둶���BR�pear/console_commandline/Console/CommandLine/Outputter/Default.phpnu�[���PK}c�\��f�GGA��pear/console_commandline/Console/CommandLine/Action/StoreTrue.phpnu�[���PK}c�\�o*��?>�pear/console_commandline/Console/CommandLine/Action/Version.phpnu�[���PK}c�\�{-�		B��pear/console_commandline/Console/CommandLine/Action/StoreFloat.phpnu�[���PK}c�\�?���<pear/console_commandline/Console/CommandLine/Action/Help.phpnu�[���PK}c�\��KKBfpear/console_commandline/Console/CommandLine/Action/StoreFalse.phpnu�[���PK}c�\��TO�	�	@#pear/console_commandline/Console/CommandLine/Action/Callback.phpnu�[���PK}c�\�b���
�
@i!pear/console_commandline/Console/CommandLine/Action/Password.phpnu�[���PK}c�\�#�		@�,pear/console_commandline/Console/CommandLine/Action/StoreInt.phpnu�[���PK}c�\W>���?46pear/console_commandline/Console/CommandLine/Action/Counter.phpnu�[���PK}c�\й6l�	�	<�?pear/console_commandline/Console/CommandLine/Action/List.phpnu�[���PK}c�\I �j��B�Ipear/console_commandline/Console/CommandLine/Action/StoreArray.phpnu�[���PK}c�\�7LC�Rpear/console_commandline/Console/CommandLine/Action/StoreString.phpnu�[���PK}c�\���qqH<Zpear/console_commandline/Console/CommandLine/MessageProvider/Default.phpnu�[���PK}c�\F��&&:%ppear/console_commandline/Console/CommandLine/XmlParser.phpnu�[���PK}c�\�л��:��pear/console_commandline/Console/CommandLine/Outputter.phpnu�[���PK}c�\�	�8��pear/console_commandline/Console/CommandLine/Command.phpnu�[���PK}c�\|ר��9!�pear/console_commandline/Console/CommandLine/Argument.phpnu�[���PK}c�\CE�7 �pear/console_commandline/Console/CommandLine/Action.phpnu�[���PK}c�\U�*��8��pear/console_commandline/Console/CommandLine/Element.phpnu�[���PK}c�\Rb�00A��pear/console_commandline/Console/CommandLine/Renderer/Default.phpnu�[���PK}c�\ęR�%%&pear/console_commandline/composer.jsonnu�[���PK}c�\H��-LL#pear/console_commandline/README.rstnu�[���PK}c�\DI�o��$
pear/console_commandline/phpunit.xmlnu�[���PK}c�\7^����+5pear/console_commandline/data/xmlschema.rngnu�[���PK}c�\`���[!pear/crypt_gpg/composer.jsonnu�[���PK}c�\��xRxR)L'pear/crypt_gpg/Crypt/GPG/KeyGenerator.phpnu�[���PK}c�\�J�ڠ#�##zpear/crypt_gpg/Crypt/GPG/UserId.phpnu�[���PK}c�\ֈ�m�� �pear/crypt_gpg/Crypt/GPG/Key.phpnu�[���PK}c�\4]r��#�pear/crypt_gpg/Crypt/GPG/Engine.phpnu�[���PK}c�\��;>W>W%�pear/crypt_gpg/Crypt/GPG/PinEntry.phpnu�[���PK}c�\w���,�,&xpear/crypt_gpg/Crypt/GPG/Signature.phpnu�[���PK}c�\C�7��4�4';pear/crypt_gpg/Crypt/GPG/Exceptions.phpnu�[���PK}c�\J�%�H�H#�ppear/crypt_gpg/Crypt/GPG/SubKey.phpnu�[���PK}c�\. ��+��pear/crypt_gpg/Crypt/GPG/ProcessControl.phpnu�[���PK}c�\ddžU�w�w+��pear/crypt_gpg/Crypt/GPG/ProcessHandler.phpnu�[���PK}c�\s�[&&|Apear/crypt_gpg/Crypt/GPG.phpnu�[���PK}c�\����=?=?$�gpear/crypt_gpg/Crypt/GPGAbstract.phpnu�[���PK}c�\X��j�g�gY�pear/crypt_gpg/LICENSEnu�[���PK}c�\�}�2��$-pear/crypt_gpg/data/pinentry-cli.xmlnu�[���PK}c�\��7�)"pear/crypt_gpg/scripts/crypt-gpg-pinentrynu�[���PK}c�\�C�k���pear/crypt_gpg/README.mdnu�[���PK}c�\���zhh-�ralouphie/getallheaders/src/getallheaders.phpnu�[���PK}c�\�G���%n"ralouphie/getallheaders/composer.jsonnu�[���PK}c�\Ka�88�$ralouphie/getallheaders/LICENSEnu�[���PK}c�\��\�@@!)ralouphie/getallheaders/README.mdnu�[���PK}c�\�(��b!b!�-masterminds/html5/src/HTML5.phpnu�[���PK}c�\{�7
7
9]Omasterminds/html5/src/HTML5/Serializer/RulesInterface.phpnu�[���PK}c�\)�%�@�@6�Ymasterminds/html5/src/HTML5/Serializer/OutputRules.phpnu�[���PK}c�\�l�E��4M�masterminds/html5/src/HTML5/Serializer/Traverser.phpnu�[���PK}c�\	�n���8~�masterminds/html5/src/HTML5/Serializer/HTML5Entities.phpnu�[���PK}c�\e%ա�0�Ymasterminds/html5/src/HTML5/Serializer/README.mdnu�[���PK}c�\��e��)�]masterminds/html5/src/HTML5/Exception.phpnu�[���PK}c�\P�k���4�^masterminds/html5/src/HTML5/InstructionProcessor.phpnu�[���PK}c�\�(#\M\M(fmasterminds/html5/src/HTML5/Elements.phpnu�[���PK}c�\ȍRx��(ѳmasterminds/html5/src/HTML5/Entities.phpnu�[���PK}c�\���̹���0<�masterminds/html5/src/HTML5/Parser/Tokenizer.phpnu�[���PK}c�\r5u��8U/masterminds/html5/src/HTML5/Parser/TreeBuildingRules.phpnu�[���PK�c�\���i[[0�<masterminds/html5/src/HTML5/Parser/UTF8Utils.phpnu�[���PK�c�\�w"YY5AZmasterminds/html5/src/HTML5/Parser/DOMTreeBuilder.phpnu�[���PK�c�\�w�[��6��masterminds/html5/src/HTML5/Parser/FileInputStream.phpnu�[���PK�c�\�����,�,.��masterminds/html5/src/HTML5/Parser/Scanner.phpnu�[���PK�c�\��ز:%:%8��masterminds/html5/src/HTML5/Parser/StringInputStream.phpnu�[���PK�c�\�:�X��1`masterminds/html5/src/HTML5/Parser/ParseError.phpnu�[���PK�c�\����	�	2Cmasterminds/html5/src/HTML5/Parser/InputStream.phpnu�[���PK�c�\	3sg\\3hmasterminds/html5/src/HTML5/Parser/EventHandler.phpnu�[���PK�c�\���G��9'%masterminds/html5/src/HTML5/Parser/CharacterReference.phpnu�[���PK�c�\�%Ʉ��,�+masterminds/html5/src/HTML5/Parser/README.mdnu�[���PK�c�\������2masterminds/html5/RELEASE.mdnu�[���PK�c�\��
����Fmasterminds/html5/LICENSE.txtnu�[���PK�c�\]�.�22�Rmasterminds/html5/composer.jsonnu�[���PK�c�\t��s��Wmasterminds/html5/CREDITSnu�[���PK�c�\�H~~"Zmasterminds/html5/bin/entities.phpnu�[���PK�c�\�)�����\masterminds/html5/UPGRADING.mdnu�[���PK�c�\��lk`&`&_masterminds/html5/README.mdnu�[���PK�c�\�&Q��2��roundcube/plugin-installer/src/PluginInstaller.phpnu�[���PK�c�\Kp��JJE�roundcube/plugin-installer/src/Roundcube/Composer/PluginInstaller.phpnu�[���PK�c�\�����Hϒroundcube/plugin-installer/src/Roundcube/Composer/RoundcubeInstaller.phpnu�[���PK�c�\�ULs�<�<H�roundcube/plugin-installer/src/Roundcube/Composer/ExtensionInstaller.phpnu�[���PK�c�\9���C3�roundcube/plugin-installer/src/Roundcube/Composer/SkinInstaller.phpnu�[���PK�c�\o
^��5��roundcube/plugin-installer/src/RoundcubeInstaller.phpnu�[���PK�c�\�@;�9B9B5��roundcube/plugin-installer/src/ExtensionInstaller.phpnu�[���PK�c�\	]��tt09!roundcube/plugin-installer/src/SkinInstaller.phpnu�[���PK�c�\�?�(
'roundcube/plugin-installer/composer.jsonnu�[���PK�c�\�R(�%%1v-roundcube/plugin-installer/.php-cs-fixer.dist.phpnu�[���PK�c�\�7����6�5roundcube/plugin-installer/test-composer/composer.jsonnu�[���PK�c�\O��##,S8roundcube/plugin-installer/phpstan.neon.distnu�[���PK�c�\���P�	�	$�:roundcube/plugin-installer/README.mdnu�[���PK�c�\��o�,�,'�Droundcube/rtf-html-php/src/Document.phpnu�[���PK�c�\<mN���*�qroundcube/rtf-html-php/src/ControlWord.phpnu�[���PK�c�\�ۗ>K
K
$�sroundcube/rtf-html-php/src/Group.phpnu�[���PK�c�\��wS�b�b1`~roundcube/rtf-html-php/src/Html/HtmlFormatter.phpnu�[���PK�c�\#�)�[[)v�roundcube/rtf-html-php/src/Html/Image.phpnu�[���PK�c�\aP�J��)*�roundcube/rtf-html-php/src/Html/State.phpnu�[���PK�c�\��P��(�roundcube/rtf-html-php/src/Html/Font.phpnu�[���PK�c�\�
��,��roundcube/rtf-html-php/src/ControlSymbol.phpnu�[���PK�c�\)%,M��& roundcube/rtf-html-php/src/Element.phpnu�[���PK�c�\1�J//#� roundcube/rtf-html-php/src/Text.phpnu�[���PK�c�\�!u��$v roundcube/rtf-html-php/composer.jsonnu�[���PK�c�\S����#� roundcube/rtf-html-php/CHANGELOG.mdnu�[���PK�c�\J��p�F�F�
 roundcube/rtf-html-php/LICENSEnu�[���PK�c�\�$�$�� �T roundcube/rtf-html-php/README.mdnu�[���PK�c�\�}�jj'�[ guzzlehttp/guzzle/src/TransferStats.phpnu�[���PK�c�\]�����1�h guzzlehttp/guzzle/src/BodySummarizerInterface.phpnu�[���PK�c�\����+�+$�i guzzlehttp/guzzle/src/Middleware.phpnu�[���PK�c�\G�Lss*�� guzzlehttp/guzzle/src/MessageFormatter.phpnu�[���PK�c�\N�33�� guzzlehttp/guzzle/src/Utils.phpnu�[���PK�c�\�^:ww(�� guzzlehttp/guzzle/src/BodySummarizer.phpnu�[���PK�c�\��Vx.#.#%�� guzzlehttp/guzzle/src/ClientTrait.phpnu�[���PK�c�\�F�j��3B!guzzlehttp/guzzle/src/Exception/ServerException.phpnu�[���PK�c�\�N�
��4H!guzzlehttp/guzzle/src/Exception/ConnectException.phpnu�[���PK�c�\5O\$��3,!guzzlehttp/guzzle/src/Exception/GuzzleException.phpnu�[���PK�c�\c�o���<%!guzzlehttp/guzzle/src/Exception/InvalidArgumentException.phpnu�[���PK�c�\��/�yy5!guzzlehttp/guzzle/src/Exception/TransferException.phpnu�[���PK�c�\0؜���4�!guzzlehttp/guzzle/src/Exception/RequestException.phpnu�[���PK�c�\j���3U+!guzzlehttp/guzzle/src/Exception/ClientException.phpnu�[���PK�c�\��Om��8[,!guzzlehttp/guzzle/src/Exception/BadResponseException.phpnu�[���PK�c�\$��ee=�0!guzzlehttp/guzzle/src/Exception/TooManyRedirectsException.phpnu�[���PK�c�\�t�//3j1!guzzlehttp/guzzle/src/MessageFormatterInterface.phpnu�[���PK�c�\�����1�3!guzzlehttp/guzzle/src/Cookie/SessionCookieJar.phpnu�[���PK�c�\(�>%>%*0<!guzzlehttp/guzzle/src/Cookie/CookieJar.phpnu�[���PK�c�\_���7�7*�a!guzzlehttp/guzzle/src/Cookie/SetCookie.phpnu�[���PK�c�\��Y�
�
.�!guzzlehttp/guzzle/src/Cookie/FileCookieJar.phpnu�[���PK�c�\U��

3
�!guzzlehttp/guzzle/src/Cookie/CookieJarInterface.phpnu�[���PK�c�\��.88#z�!guzzlehttp/guzzle/src/functions.phpnu�[���PK�c�\����HH �!guzzlehttp/guzzle/src/Client.phpnu�[���PK�c�\��P��6d"guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.phpnu�[���PK�c�\��af

-["guzzlehttp/guzzle/src/Handler/MockHandler.phpnu�[���PK�c�\�I]22-�+"guzzlehttp/guzzle/src/Handler/CurlHandler.phpnu�[���PK�c�\�ju$�`�`-T1"guzzlehttp/guzzle/src/Handler/CurlFactory.phpnu�[���PK�c�\k(��WW25�"guzzlehttp/guzzle/src/Handler/CurlMultiHandler.phpnu�[���PK�c�\�?�CTT,�"guzzlehttp/guzzle/src/Handler/EasyHandle.phpnu�[���PK�c�\7�{  1��"guzzlehttp/guzzle/src/Handler/HeaderProcessor.phpnu�[���PK�c�\��9���'�"guzzlehttp/guzzle/src/Handler/Proxy.phpnu�[���PK�c�\�b�VSVS/d�"guzzlehttp/guzzle/src/Handler/StreamHandler.phpnu�[���PK�c�\L�+ll#guzzlehttp/guzzle/src/Pool.phpnu�[���PK�c�\���TT)�1#guzzlehttp/guzzle/src/ClientInterface.phpnu�[���PK�c�\E�9��+�=#guzzlehttp/guzzle/src/functions_include.phpnu�[���PK�c�\DS��,{>#guzzlehttp/guzzle/src/RedirectMiddleware.phpnu�[���PK�c�\���)�^#guzzlehttp/guzzle/src/RetryMiddleware.phpnu�[���PK�c�\��HGMM/�l#guzzlehttp/guzzle/src/PrepareBodyMiddleware.phpnu�[���PK�c�\l���""&�y#guzzlehttp/guzzle/src/HandlerStack.phpnu�[���PK�c�\��n�*�*(�#guzzlehttp/guzzle/src/RequestOptions.phpnu�[���PK�c�\99f�uu6�#guzzlehttp/guzzle/composer.jsonnu�[���PK�c�\ژ3=aTaT��#guzzlehttp/guzzle/CHANGELOG.mdnu�[���PK�c�\�Շ����'%guzzlehttp/guzzle/LICENSEnu�[���PK�c�\��y�y��-%guzzlehttp/guzzle/UPGRADING.mdnu�[���PK�c�\�a���m�%guzzlehttp/guzzle/README.mdnu�[���PK�c�\�>=��.b&guzzlehttp/promises/src/TaskQueueInterface.phpnu�[���PK�c�\�d�1��+�&guzzlehttp/promises/src/RejectedPromise.phpnu�[���PK�c�\�� � !�&guzzlehttp/promises/src/Utils.phpnu�[���PK�c�\偢'��'�?&guzzlehttp/promises/src/EachPromise.phpnu�[���PK�c�\R���"�]&guzzlehttp/promises/src/Create.phpnu�[���PK�c�\4g���%�e&guzzlehttp/promises/src/TaskQueue.phpnu�[���PK�c�\�b�{��. n&guzzlehttp/promises/src/AggregateException.phpnu�[���PK�c�\NtD��"�"#p&guzzlehttp/promises/src/Promise.phpnu�[���PK�c�\逿�,k�&guzzlehttp/promises/src/PromiseInterface.phpnu�[���PK�c�\��Lt��1Ξ&guzzlehttp/promises/src/CancellationException.phpnu�[���PK�c�\G�y���.�&guzzlehttp/promises/src/RejectionException.phpnu�[���PK�c�\'�
c��,6�&guzzlehttp/promises/src/FulfilledPromise.phpnu�[���PK�c�\0m�����&guzzlehttp/promises/src/Is.phpnu�[���PK�c�\W��PAA%��&guzzlehttp/promises/src/Coroutine.phpnu�[���PK�c�\V%�c
c
 �&guzzlehttp/promises/src/Each.phpnu�[���PK�c�\��d���-��&guzzlehttp/promises/src/PromisorInterface.phpnu�[���PK�c�\�&��9'�&guzzlehttp/promises/vendor-bin/php-cs-fixer/composer.jsonnu�[���PK�c�\(��M��23�&guzzlehttp/promises/vendor-bin/psalm/composer.jsonnu�[���PK�c�\�&��4(�&guzzlehttp/promises/vendor-bin/phpstan/composer.jsonnu�[���PK�c�\:���!\�&guzzlehttp/promises/composer.jsonnu�[���PK�c�\{Z�y	y	 ��&guzzlehttp/promises/CHANGELOG.mdnu�[���PK�c�\z�*/K�&guzzlehttp/promises/LICENSEnu�[���PK�c�\e�'�D�D��&guzzlehttp/promises/README.mdnu�[���PK�c�\���** �+'guzzlehttp/psr7/src/Response.phpnu�[���PK�c�\��'�;;$S?'guzzlehttp/psr7/src/AppendStream.phpnu�[���PK�c�\×�$�V'guzzlehttp/psr7/src/NoSeekStream.phpnu�[���PK�c�\�Ӡ$BY'guzzlehttp/psr7/src/UploadedFile.phpnu�[���PK�c�\EGO���#�l'guzzlehttp/psr7/src/LimitStream.phpnu�[���PK�c�\�P���<�<�}'guzzlehttp/psr7/src/Utils.phpnu�[���PK�c�\Ŝ
�~~%Ժ'guzzlehttp/psr7/src/UriComparator.phpnu�[���PK�c�\џ�U����'guzzlehttp/psr7/src/Rfc7230.phpnu�[���PK�c�\��ee��'guzzlehttp/psr7/src/Header.phpnu�[���PK�c�\=a���� >�'guzzlehttp/psr7/src/FnStream.phpnu�[���PK�c�\��N|��7c�'guzzlehttp/psr7/src/Exception/MalformedUriException.phpnu�[���PK�c�\J��� � ��'guzzlehttp/psr7/src/Message.phpnu�[���PK�c�\��)�DD�(guzzlehttp/psr7/src/Request.phpnu�[���PK�c�\b���!�!#0(guzzlehttp/psr7/src/UriResolver.phpnu�[���PK�c�\0 N_��%7(guzzlehttp/psr7/src/StreamWrapper.phpnu�[���PK�c�\{(���� &I(guzzlehttp/psr7/src/MimeType.phpnu�[���PK�c�\�hiD#s )guzzlehttp/psr7/src/HttpFactory.phpnu�[���PK�c�\�Dy��$�,)guzzlehttp/psr7/src/BufferStream.phpnu�[���PK�c�\�-�M%M%%�9)guzzlehttp/psr7/src/ServerRequest.phpnu�[���PK�c�\������%L_)guzzlehttp/psr7/src/CachingStream.phpnu�[���PK�c�\@@$�q)guzzlehttp/psr7/src/MessageTrait.phpnu�[���PK�c�\!.��??%�)guzzlehttp/psr7/src/Query.phpnu�[���PK�c�\��k��%��)guzzlehttp/psr7/src/InflateStream.phpnu�[���PK�c�\q:
t��&��)guzzlehttp/psr7/src/DroppingStream.phpnu�[���PK�c�\�=����"��)guzzlehttp/psr7/src/PumpStream.phpnu�[���PK�c�\��u@@&׻)guzzlehttp/psr7/src/LazyOpenStream.phpnu�[���PK�c�\j�g��,m�)guzzlehttp/psr7/src/StreamDecoratorTrait.phpnu�[���PK�c�\����!!%��)guzzlehttp/psr7/src/UriNormalizer.phpnu�[���PK�c�\z����"�)guzzlehttp/psr7/src/Stream.phpnu�[���PK�c�\�u��U�UJ*guzzlehttp/psr7/src/Uri.phpnu�[���PK�c�\&�_88'Xb*guzzlehttp/psr7/src/MultipartStream.phpnu�[���PK�c�\X����	�	�v*guzzlehttp/psr7/composer.jsonnu�[���PK�c�\Aa���+�+ �*guzzlehttp/psr7/CHANGELOG.mdnu�[���PK�c�\^�pLzzT�*guzzlehttp/psr7/LICENSEnu�[���PK�c�\�DBZjrjr�*guzzlehttp/psr7/README.mdnu�[���PK�c�\y�q�6
6
�%+bin/crypt-gpg-pinentrynu�[���PK�c�\zCo	++D3+autoload.phpnu�[���PK�c�\,�H�II+�6+symfony/deprecation-contracts/composer.jsonnu�[���PK�c�\h{#��*O:+symfony/deprecation-contracts/CHANGELOG.mdnu�[���PK�c�\K�,,%F;+symfony/deprecation-contracts/LICENSEnu�[���PK�c�\rg����*�?+symfony/deprecation-contracts/function.phpnu�[���PK�c�\��3��'D+symfony/deprecation-contracts/README.mdnu�[���PK�c�\�CI�

!I+dasprid/enum/src/AbstractEnum.phpnu�[���PK�c�\��ʴ�=wd+dasprid/enum/src/Exception/SerializeNotSupportedException.phpnu�[���PK�c�\1����3�e+dasprid/enum/src/Exception/ExpectationException.phpnu�[���PK�c�\�m�Y��7�f+dasprid/enum/src/Exception/IllegalArgumentException.phpnu�[���PK�c�\N͐{��0�g+dasprid/enum/src/Exception/MismatchException.phpnu�[���PK�c�\|�6��1�h+dasprid/enum/src/Exception/ExceptionInterface.phpnu�[���PK�c�\���ް�9�i+dasprid/enum/src/Exception/CloneNotSupportedException.phpnu�[���PK�c�\�L�A��?�j+dasprid/enum/src/Exception/UnserializeNotSupportedException.phpnu�[���PK�c�\c�YY�k+dasprid/enum/src/NullValue.phpnu�[���PK�c�\ d�͟,�,�p+dasprid/enum/src/EnumMap.phpnu�[���PK�c�\�a&���z�+dasprid/enum/composer.jsonnu�[���PK�c�\˷I���+dasprid/enum/LICENSEnu�[���PK�c�\5#�tt�+dasprid/enum/README.mdnu�[���PK�c�\"�7��1��+psr/http-client/src/NetworkExceptionInterface.phpnu�[���PK�c�\��:���0��+psr/http-client/src/ClientExceptionInterface.phpnu�[���PK�c�\Ҟv��'��+psr/http-client/src/ClientInterface.phpnu�[���PK�c�\�E�uJJ1�+psr/http-client/src/RequestExceptionInterface.phpnu�[���PK�c�\������+psr/http-client/composer.jsonnu�[���PK�c�\z����+psr/http-client/CHANGELOG.mdnu�[���PK�c�\�S�==�+psr/http-client/LICENSEnu�[���PK�c�\�F�%%��+psr/http-client/README.mdnu�[���PK�c�\��DhEE,�+psr/http-factory/src/UriFactoryInterface.phpnu�[���PK�c�\Bj�hh5��+psr/http-factory/src/UploadedFileFactoryInterface.phpnu�[���PK�c�\X��""1z�+psr/http-factory/src/ResponseFactoryInterface.phpnu�[���PK�c�\�yۜ��/��+psr/http-factory/src/StreamFactoryInterface.phpnu�[���PK�c�\rT�X��0��+psr/http-factory/src/RequestFactoryInterface.phpnu�[���PK�c�\BH�A��69�+psr/http-factory/src/ServerRequestFactoryInterface.phpnu�[���PK�c�\��O>�+psr/http-factory/composer.jsonnu�[���PK�c�\�}]�((��+psr/http-factory/LICENSEnu�[���PK�c�\z�wf,,�+psr/http-factory/README.mdnu�[���PK�c�\iP:^:(:(/��+psr/http-message/src/ServerRequestInterface.phpnu�[���PK�c�\�_8�77)*,psr/http-message/src/RequestInterface.phpnu�[���PK�c�\Z�݌�.�+,psr/http-message/src/UploadedFileInterface.phpnu�[���PK�c�\o��22%�>,psr/http-message/src/UriInterface.phpnu�[���PK�c�\?����)q,psr/http-message/src/MessageInterface.phpnu�[���PK�c�\bY6J
J
*/�,psr/http-message/src/ResponseInterface.phpnu�[���PK�c�\жJ���(ӗ,psr/http-message/src/StreamInterface.phpnu�[���PK�c�\�Lo$ss'�,psr/http-message/composer.jsonnu�[���PK�c�\�6L%L%(�,psr/http-message/docs/PSR7-Interfaces.mdnu�[���PK�c�\z �Xtt#��,psr/http-message/docs/PSR7-Usage.mdnu�[���PK�c�\�:\Y33S�,psr/http-message/CHANGELOG.mdnu�[���PK�c�\���==��,psr/http-message/LICENSEnu�[���PK�c�\�����X�,psr/http-message/README.mdnu�[���PK��!���,